diff --git a/LICENSE b/LICENSE index eec15462..be3f7b28 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies @@ -7,17 +7,15 @@ Preamble - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to +our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. The precise terms and conditions for copying, distribution and modification follow. @@ -72,7 +60,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Use with the GNU Affero General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single +under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General +Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published +GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's +versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -631,44 +629,33 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - BBOT I/O is a (WIP) database for your BBOT scan data. - Copyright (C) 2024 BLS OPS LLC / TheTechromancer + + Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by + it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + GNU Affero General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see +For more information on this, and how to apply and follow the GNU AGPL, see . - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/README.md b/README.md index 5aec1add..8af195bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Image](https://github.com/user-attachments/assets/3cf3fb27-ded3-47b5-8eec-2f8358358ffd) -[![Python Version](https://img.shields.io/badge/python-3.9+-FF8400)](https://www.python.org) [![License](https://img.shields.io/badge/license-GPLv3-FF8400.svg)](https://github.com/blacklanternsecurity/bbot/blob/dev/LICENSE) [![PyPi Downloads](https://static.pepy.tech/personalized-badge/bbot-server?right_color=orange&left_color=grey)](https://pepy.tech/project/bbot-server) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![Tests](https://github.com/blacklanternsecurity/bbot-server/actions/workflows/tests.yml/badge.svg?branch=stable)](https://github.com/blacklanternsecurity/bbot-server/actions?query=workflow%3A"tests") [![Codecov](https://codecov.io/gh/blacklanternsecurity/bbot-server/branch/stable/graph/badge.svg?token=IR5AZBDM5K)](https://codecov.io/gh/blacklanternsecurity/bbot-server) [![Discord](https://img.shields.io/discord/859164869970362439)](https://discord.com/invite/PZqkgxu5SA) +[![Python Version](https://img.shields.io/badge/python-3.9+-FF8400)](https://www.python.org) [![License](https://img.shields.io/badge/license-AGPLv3-FF8400.svg)](https://github.com/blacklanternsecurity/bbot/blob/dev/LICENSE) [![PyPi Downloads](https://static.pepy.tech/personalized-badge/bbot-server?right_color=orange&left_color=grey)](https://pepy.tech/project/bbot-server) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![Tests](https://github.com/blacklanternsecurity/bbot-server/actions/workflows/tests.yml/badge.svg?branch=stable)](https://github.com/blacklanternsecurity/bbot-server/actions?query=workflow%3A"tests") [![Codecov](https://codecov.io/gh/blacklanternsecurity/bbot-server/branch/stable/graph/badge.svg?token=IR5AZBDM5K)](https://codecov.io/gh/blacklanternsecurity/bbot-server) [![Discord](https://img.shields.io/discord/859164869970362439)](https://discord.com/invite/PZqkgxu5SA) # BBOT Server [BETA] @@ -350,21 +350,30 @@ bbctl asset stats --domain evilcorp.com | jq BBOT Server supports chat-based AI interaction via MCP (Model Context Protocol). -The SSE server listens at `http://localhost:8807/v1/mcp/` +The SSE server listens at `http://localhost:8807/v1/mcp/`. -`mcp.json` (cursor / vs code): +`mcp.json` (Cursor / VS Code): ```json { - "mcpServers": { - "bbot": { - "url": "http://localhost:8807/v1/mcp/" - } + "mcpServers": { + "bbot": { + "url": "http://localhost:8807/v1/mcp/", + "headers": { + "x-api-key": "deadbeef-9b4d-4208-890c-4ce9ad3b4710" + } } + } } ``` After connecting your AI client to BBOT Server, you can ask it sensible questions like, "Use MCP to get all the bbot findings", "what are the top open ports?", "what else can you do with BBOT MCP?", etc. +**NOTE**: Authentication is [currently broken](https://github.com/blacklanternsecurity/bbot-server/issues/52) in Cursor, Cline, and it seems most other VS Code forks. A workaround is to disable authentication with `--no-authentication` when starting the server. Obviously, be careful with this and don't be a dumbass. + +```bash +bbctl server start --no-authentication +``` + ## As a Python Library You can interact fully with BBOT Server as a Python library. It supports either local or remote connections, and the interface to both is identical: diff --git a/bbot_server/applets/base.py b/bbot_server/applets/base.py index c214f358..1e9ad901 100644 --- a/bbot_server/applets/base.py +++ b/bbot_server/applets/base.py @@ -372,6 +372,11 @@ def make_activity(self, *args, **kwargs): return Activity(*args, **kwargs) async def emit_activity(self, *args, **kwargs): + """ + Emits an activity to the message queue. + + Accepts either an Activity object, or arguments to create a new Activity object. + """ if not kwargs and len(args) == 1 and isinstance(args[0], Activity): activity = args[0] else: diff --git a/bbot_server/models/asset_models.py b/bbot_server/models/asset_models.py index 6216cdab..c83397ba 100644 --- a/bbot_server/models/asset_models.py +++ b/bbot_server/models/asset_models.py @@ -10,23 +10,14 @@ host_split_regex = re.compile(r"[^a-z0-9]") -class BaseAssetFacet(BaseBBOTServerModel): +class BaseHostModel(BaseBBOTServerModel): """ - An "asset facet" is a database object that contains data about an asset. + A base model for all BBOT Server models that have a host, port, netloc, and url - Unlike the main asset model which contains a summary of all the data, - a facet contains a certain detail which is too big to be stored in the main asset model. - - For example, the main asset might contain a summary of all the technologies found on the asset, - but a facet might contain the specific technologies and details about their discovery. - - A facet typically corresponds to an applet. + Inherited by Asset and Activity models. """ - # unless overridden, all asset facets are stored in the asset store - __store_type__ = "asset" - __table_name__ = "assets" - + # TODO: why is id commented out? # id: Annotated[str, "indexed", "unique"] = Field(default_factory=lambda: str(uuid.uuid4())) type: Annotated[Optional[str], "indexed"] = None host: Annotated[str, "indexed"] @@ -37,10 +28,8 @@ class BaseAssetFacet(BaseBBOTServerModel): modified: Annotated[float, "indexed"] = Field(default_factory=utc_now) ignored: bool = False archived: bool = False - scope: Annotated[list[UUID], "indexed"] = [] def __init__(self, *args, **kwargs): - kwargs["type"] = self.__class__.__name__ event = kwargs.pop("event", None) super().__init__(*args, **kwargs) if self.host and self.port: @@ -68,28 +57,38 @@ def set_event(self, event): @computed_field @property def reverse_host(self) -> Annotated[str, "indexed"]: + if not self.host: + return "" return self.host[::-1] @computed_field @property def host_parts(self) -> Annotated[list[str], "indexed"]: + if not self.host: + return [] return host_split_regex.split(self.host) - # def _ingest_event(self, event) -> list[Activity]: - # self_before = self.__class__.model_validate(self) - # self.ingest_event(event) - # return self.diff(self_before) - # def ingest_event(self, event): - # """ - # Given a BBOT event, update the asset facet. +class BaseAssetFacet(BaseHostModel): + """ + An "asset facet" is a database object that contains data about an asset. - # E.g., given an OPEN_TCP_PORT event, update the open_ports field to include the new port. - # """ - # raise NotImplementedError(f"Must define ingest_event() in {self.__class__.__name__}") + Unlike the main asset model which contains a summary of all the data, + a facet contains a certain detail which is too big to be stored in the main asset model. + + For example, the main asset might contain a summary of all the technologies found on the asset, + but a facet might contain the specific technologies and details about their discovery. + + A facet typically corresponds to an applet. + """ + + # scope is an array of target IDs, which are dynamically maintained as new scan data arrives, or as targets are created/updated. + scope: Annotated[list[UUID], "indexed"] = [] + + # unless overridden, all asset facets are stored in the asset store + __store_type__ = "asset" + __table_name__ = "assets" - # def diff(self, other) -> list[Activity]: - # """ - # Given another facet (typically an older version of the same host), return a list of AssetActivities which describe the new changes. - # """ - # raise NotImplementedError(f"Must define diff() in {self.__class__.__name__}") + def __init__(self, *args, **kwargs): + kwargs["type"] = self.__class__.__name__ + super().__init__(*args, **kwargs) diff --git a/bbot_server/modules/activity/activity_models.py b/bbot_server/modules/activity/activity_models.py index 97ee977a..3d86b770 100644 --- a/bbot_server/modules/activity/activity_models.py +++ b/bbot_server/modules/activity/activity_models.py @@ -9,14 +9,14 @@ from bbot_server.utils.misc import utc_now from bbot_server.cli.themes import COLOR, DARK_COLOR -from bbot_server.models.base import BaseBBOTServerModel +from bbot_server.models.asset_models import BaseHostModel remove_rich_color_pattern = re.compile(r"\[([\w ]+)\](.*?)\[/\1\]") log = logging.getLogger(__name__) -class Activity(BaseBBOTServerModel): +class Activity(BaseHostModel): """ An Activity is BBOT server's equivalent of an event. @@ -25,23 +25,19 @@ class Activity(BaseBBOTServerModel): They are usually associated with an asset, and can be traced back to a specific BBOT event. """ - __table_name__ = "history" __store_type__ = "asset" + __table_name__ = "history" # id is a UUID id: Annotated[str, "indexed", "unique"] = Field(default_factory=lambda: str(uuid.uuid4())) - type: Annotated[str, "indexed"] timestamp: Annotated[float, "indexed"] created: Annotated[float, "indexed"] = Field(default_factory=utc_now) archived: Annotated[bool, "indexed"] = False description: Annotated[str, "indexed"] description_colored: str = Field(default="") detail: dict[str, Any] = {} - host: Annotated[Optional[str], "indexed"] = None - port: Annotated[Optional[int], "indexed"] = None - netloc: Annotated[Optional[str], "indexed"] = None - url: Annotated[Optional[str], "indexed"] = None module: Annotated[Optional[str], "indexed"] = None scan: Annotated[Optional[str], "indexed"] = None + host: Annotated[Optional[str], "indexed"] = None parent_event_uuid: Annotated[Optional[str], "indexed"] = None parent_event_id: Annotated[Optional[str], "indexed"] = None parent_scan_run_id: Annotated[Optional[str], "indexed"] = None diff --git a/bbot_server/modules/assets/assets_api.py b/bbot_server/modules/assets/assets_api.py index 5da1a709..6682862d 100644 --- a/bbot_server/modules/assets/assets_api.py +++ b/bbot_server/modules/assets/assets_api.py @@ -163,7 +163,7 @@ async def refresh_assets(self): for child_applet in self.all_child_applets(include_self=True): activities = await child_applet.refresh(asset, events_by_type) for activity in activities: - await self._emit_activity(activity) + await self.emit_activity(activity) # update the asset with any changes made by the child applets await self.update_asset(asset) diff --git a/bbot_server/modules/findings/findings_api.py b/bbot_server/modules/findings/findings_api.py index d2730977..b40c2535 100644 --- a/bbot_server/modules/findings/findings_api.py +++ b/bbot_server/modules/findings/findings_api.py @@ -306,8 +306,10 @@ async def _insert_or_update_finding(self, finding: Finding, asset, event=None): { "$set": { "modified": self.helpers.utc_now(), - "confidence": finding.confidence_score, + "severity": finding.severity, "severity_score": finding.severity_score, + "confidence": finding.confidence, + "confidence_score": finding.confidence_score, } }, ) diff --git a/bbot_server/modules/presets/presets_models.py b/bbot_server/modules/presets/presets_models.py index 0bd43e3b..ed58a359 100644 --- a/bbot_server/modules/presets/presets_models.py +++ b/bbot_server/modules/presets/presets_models.py @@ -18,7 +18,7 @@ class Preset(BaseBBOTServerModel): @classmethod def sanitize_preset(cls, v: dict[str, Any]) -> dict[str, Any]: # remote target information - for value in ("target", "targets", "whitelist", "blacklist"): + for value in ("target", "targets", "seeds", "blacklist"): v.pop(value, None) # remove strict scope setting (this is stored in the target) config = v.pop("config", {}) diff --git a/bbot_server/modules/scans/scans_api.py b/bbot_server/modules/scans/scans_api.py index e69d06ec..678f9d4b 100644 --- a/bbot_server/modules/scans/scans_api.py +++ b/bbot_server/modules/scans/scans_api.py @@ -215,8 +215,8 @@ async def start_scans_loop(self): # merge target and preset scan_preset = dict(scan.preset.preset) scan_preset["scan_name"] = scan.name - scan_preset["target"] = scan.target.seeds - scan_preset["whitelist"] = scan.target.whitelist + scan_preset["target"] = scan.target.target + scan_preset["seeds"] = scan.target.seeds scan_preset["blacklist"] = scan.target.blacklist config = scan_preset.get("config", {}) scope_config = config.get("scope", {}) diff --git a/bbot_server/modules/scans/scans_cli.py b/bbot_server/modules/scans/scans_cli.py index 6d3de1f4..30b276c6 100644 --- a/bbot_server/modules/scans/scans_cli.py +++ b/bbot_server/modules/scans/scans_cli.py @@ -76,8 +76,8 @@ def iter_scans(): finished = "" if scan.finished_at is None else self.timestamp_to_human(scan.finished_at) target_name = f"[{self.COLOR}]{scan.target.name}[/{self.COLOR}]" for attr, friendly_attr in ( + ("target_size", "Target"), ("seed_size", "Seeds"), - ("whitelist_size", "Whitelist"), ("blacklist_size", "Blacklist"), ): if getattr(scan.target, attr): diff --git a/bbot_server/modules/targets/targets_api.py b/bbot_server/modules/targets/targets_api.py index 2ff57273..418cd9d2 100644 --- a/bbot_server/modules/targets/targets_api.py +++ b/bbot_server/modules/targets/targets_api.py @@ -7,8 +7,8 @@ from bbot_server.utils.misc import utc_now from bbot_server.assets import Asset from bbot_server.applets.base import BaseApplet, api_endpoint -from bbot_server.modules.targets.targets_models import Target from bbot_server.modules.activity.activity_models import Activity +from bbot_server.modules.targets.targets_models import Target, CreateTarget class BlacklistedError(Exception): @@ -27,7 +27,7 @@ class TargetsApplet(BaseApplet): name = "Targets" description = "scan targets" watched_events = ["*"] - watched_activities = ["*"] + watched_activities = ["TARGET_CREATED", "TARGET_UPDATED"] attach_to = "scans" model = Target @@ -50,15 +50,12 @@ async def handle_event(self, event, asset): if asset is None or event.host is None: return - resolved_hosts = {"SELF": [event.host]} dns_children = getattr(event, "dns_children", {}) - for rdtype in ("A", "AAAA"): - resolved_hosts[rdtype] = dns_children.get(rdtype, []) # check event against each of our targets for target_id in await self.get_target_ids(): bbot_target = await self._get_bbot_target(target_id) - scope_result = self._check_scope(event.host, resolved_hosts, bbot_target, target_id, asset.scope) + scope_result = await self._check_scope(event.host, dns_children, bbot_target, target_id, asset.scope) if scope_result is not None: scope_result.set_event(event) if scope_result.type == "NEW_IN_SCOPE_ASSET": @@ -78,18 +75,18 @@ async def handle_activity(self, activity, asset: Asset = None): """ # when a target is created or modified, we run a scope refresh on all the assets # debounce is set to 0.0 here because it's critical we're using the latest version of the target - if activity.type in ("TARGET_CREATED", "TARGET_UPDATED"): - self.log.debug(f"Target created or updated. Refreshing asset scope") - target_ids = await self.get_target_ids(debounce=0.0) - for target_id in target_ids: - target = await self._get_bbot_target(target_id, debounce=0.0) - for host in await self.root.get_hosts(): - await self.refresh_asset_scope(host, target, target_id, emit_activity=True) + # if activity.type in ("TARGET_CREATED", "TARGET_UPDATED"): + self.log.debug(f"Target created or updated. Refreshing asset scope") + target_ids = await self.get_target_ids(debounce=0.0) + for target_id in target_ids: + target = await self._get_bbot_target(target_id, debounce=0.0) + for host in await self.root.get_hosts(): + await self.refresh_asset_scope(host, target, target_id, emit_activity=True) # otherwise, for individual assets, we just refresh the scope for the given host - elif activity.host: - asset_scope = await self.get_asset_scope(activity.host) - await self.root._update_asset(activity.host, {"scope": [str(target_id) for target_id in asset_scope]}) + # elif activity.host: + # asset_scope = await self.get_asset_scope(activity.host) + # await self.root._update_asset(activity.host, {"scope": [str(target_id) for target_id in asset_scope]}) return [] @@ -109,30 +106,32 @@ async def refresh_asset_scope(self, host: str, target: BBOTTarget, target_id: UU raise self.BBOTServerNotFoundError(f"Asset not found for host {host}") asset_scope = [UUID(_target_id) for _target_id in asset.get("scope", [])] asset_dns_links = asset.get("dns_links", {}) - scope_result = self._check_scope(host, asset_dns_links, target, target_id, asset_scope) + scope_result = await self._check_scope(host, asset_dns_links, target, target_id, asset_scope) if scope_result is not None: if scope_result.type == "NEW_IN_SCOPE_ASSET": asset_scope = sorted(set(asset_scope) | set([target_id])) else: asset_scope = sorted(set(asset_scope) - set([target_id])) - results = await self.root.assets.collection.update_many( + asset_results = await self.root.assets.collection.update_many( {"host": host}, {"$set": {"scope": [str(_target_id) for _target_id in asset_scope]}}, ) - self.log.debug(f"Updated {results.modified_count} assets for host {host}") + self.log.debug(f"Updated {asset_results.modified_count} assets for host {host}") if emit_activity: await self.emit_activity(scope_result) async def get_asset_scope(self, host: str): """ Given a host, get all the targets it's a part of + + This works by getting the asset and all its DNS links, then checking each one against all the targets """ asset = await self.root.assets.collection.find_one({"host": host}, {"dns_links": 1}) or {} asset_dns_links = asset.get("dns_links", {}) asset_scope = [] for target_id in await self.get_target_ids(): target = await self._get_bbot_target(target_id) - in_scope = self._check_scope(host, asset_dns_links, target, target_id) + in_scope = await self._check_scope(host, asset_dns_links, target, target_id) if in_scope: asset_scope.append(target_id) return sorted(asset_scope) @@ -164,29 +163,27 @@ async def set_default_target(self, id: str): @api_endpoint("/create", methods=["POST"], summary="Create a new scan target") async def create_target( self, - name: str = "", - description: str = "", - seeds: list[str] = [], - whitelist: list[str] = None, - blacklist: list[str] = [], - strict_dns_scope: bool = False, + target: CreateTarget, ) -> Target: - if not whitelist and not seeds: - raise self.BBOTServerValueError("Must provide at least one seed or whitelist entry") - if not name: - name = await self.get_available_target_name() + if not target.target and not target.seeds: + raise self.BBOTServerValueError("Must provide at least one seed or target entry") + if not target.name: + target.name = await self.get_available_target_name() target = Target( - name=name, - description=description, - seeds=seeds, - whitelist=whitelist, - blacklist=blacklist, - strict_dns_scope=strict_dns_scope, + name=target.name, + description=target.description, + seeds=target.seeds, + target=target.target, + blacklist=target.blacklist, + strict_dns_scope=target.strict_dns_scope, ) if await self.target_count() == 0: target.default = True with self._handle_duplicate_target(target): await self.collection.insert_one(target.model_dump()) + # if target is the default target, set all others to not be default + if target.default: + await self.collection.update_many({"id": {"$ne": str(target.id)}}, {"$set": {"default": False}}) # emit an activity to show the target was created await self.emit_activity( type="TARGET_CREATED", @@ -261,10 +258,10 @@ async def in_scope(self, host: str, target_id: UUID = None) -> bool: bbot_target = await self._get_bbot_target(target_id) return bbot_target.in_scope(host) - @api_endpoint("/whitelisted", methods=["GET"], summary="Check if a host or URL is whitelisted") - async def is_whitelisted(self, host: str, target_id: UUID = None) -> bool: + @api_endpoint("/in-target", methods=["GET"], summary="Check if a host or URL is in the target") + async def is_in_target(self, host: str, target_id: UUID = None) -> bool: bbot_target = await self._get_bbot_target(target_id) - return bbot_target.whitelisted(host) + return bbot_target.in_target(host) @api_endpoint("/blacklisted", methods=["GET"], summary="Check if a host or URL is blacklisted") async def is_blacklisted(self, host: str, target_id: UUID = None) -> bool: @@ -297,12 +294,14 @@ async def get_available_target_name(self) -> str: counter += 1 return f"Target {counter}" - def _check_scope(self, host, resolved_hosts, target: BBOTTarget, target_id, asset_scope=None) -> Activity: + async def _check_scope(self, host, resolved_hosts, target: BBOTTarget, target_id, asset_scope=None) -> Activity: """ Given a host and its DNS records, check whether it's in scope for a given target If the scope status changes, return an activity + TODO: we may be able to speed this up by using a single RadixTarget cache for all the targets. Then we'd be able to give it a host, and in a single go, have it return all matching targets. + Args: host: the host to check resolved_hosts: a dict of DNS records for the host @@ -314,7 +313,7 @@ def _check_scope(self, host, resolved_hosts, target: BBOTTarget, target_id, asse Returns: Activity: an activity that occurred as a result of the scope check """ - whitelisted_reason = "" + in_target_reason = "" blacklisted_reason = "" resolved_hosts = {k: v for k, v in resolved_hosts.items() if k in ("A", "AAAA")} resolved_hosts["SELF"] = [host] @@ -325,13 +324,13 @@ def _check_scope(self, host, resolved_hosts, target: BBOTTarget, target_id, asse # if any of the hosts are blacklisted, abort immediately if target.blacklisted(host): blacklisted_reason = f"{rdtype}->{host}" - whitelisted_reason = "" + in_target_reason = "" # break out of the loop raise BlacklistedError # check against whitelist - if not whitelisted_reason: - if target.whitelisted(host): - whitelisted_reason = f"{rdtype}->{host}" + if not in_target_reason: + if target.in_target(host): + in_target_reason = f"{rdtype}->{host}" except BlacklistedError: pass @@ -339,17 +338,21 @@ def _check_scope(self, host, resolved_hosts, target: BBOTTarget, target_id, asse if asset_scope is None: if blacklisted_reason: return False - elif whitelisted_reason: + elif in_target_reason: return True return False + target_name = (await self._get_target(id=target_id, fields=["name"])).get("name", "") + if blacklisted_reason: scope_after = sorted(set(asset_scope) - set([target_id])) # it used to be in-scope, but not anymore if scope_after != asset_scope: - self.log.debug(f"Host {host} used to be in scope for target {target_id}, but is now blacklisted") + self.log.debug( + f"Host {host} used to be in scope for target {target_name} ({target_id}), but is now blacklisted" + ) reason = f"blacklisted host {blacklisted_reason}" - description = f"Host [COLOR]{host}[/COLOR] became out-of-scope due to {reason}" + description = f"Host [COLOR]{host}[/COLOR] became out-of-scope for target [COLOR]{target_name}[/COLOR] due to {reason}" return self.make_activity( type="ASSET_SCOPE_CHANGED", detail={ @@ -363,13 +366,15 @@ def _check_scope(self, host, resolved_hosts, target: BBOTTarget, target_id, asse description=description, ) # event is in-scope for this target - elif whitelisted_reason: + elif in_target_reason: scope_after = sorted(set(asset_scope) | set([target_id])) # it wasn't in-scope, but now it is if scope_after != asset_scope: - self.log.debug(f"Host {host} used to be out-of-scope for target {target_id}, but is now whitelisted") - reason = f"whitelisted host {whitelisted_reason}" - description = f"Host [COLOR]{host}[/COLOR] became in-scope due to {reason}" + self.log.debug( + f"Host {host} used to be out-of-scope for target {target_name} ({target_id}), but is now in-scope" + ) + reason = f"in-scope host {in_target_reason}" + description = f"Host [COLOR]{host}[/COLOR] became in-scope for target [COLOR]{target_name}[/COLOR] due to {reason}" return self.make_activity( type="NEW_IN_SCOPE_ASSET", detail={ @@ -435,8 +440,8 @@ def _bbot_target(self, target: Target) -> BBOTTarget: Given a target pydantic instance, return a BBOTTarget instance capable of fast host lookups """ return BBOTTarget( - *target.seeds, - whitelist=target.whitelist, + target=target.target, + seeds=target.seeds, blacklist=target.blacklist, strict_dns_scope=target.strict_dns_scope, ) diff --git a/bbot_server/modules/targets/targets_cli.py b/bbot_server/modules/targets/targets_cli.py index 2af8dd5a..a9fb1c16 100644 --- a/bbot_server/modules/targets/targets_cli.py +++ b/bbot_server/modules/targets/targets_cli.py @@ -2,6 +2,7 @@ from typer import Argument from bbot_server.cli import common +from bbot_server.modules.targets.targets_models import CreateTarget from bbot_server.cli.base import BaseBBCTL, subcommand, Option, Annotated @@ -14,15 +15,17 @@ class TargetCTL(BaseBBCTL): @subcommand(help="Create a new target") def create( self, - seeds: Annotated[Path, Option("--seeds", "-s", help="File containing seeds")], - whitelist: Annotated[ + target: Annotated[ Path, Option( - "--whitelist", - "-w", - help="File containing whitelist. If not provided, the seeds will be used as the whitelist.", + "--target", + "-t", + help="File containing target. This determines what's in-scope.", ), ] = None, + seeds: Annotated[ + Path, Option("--seeds", "-s", help="File containing seeds. If not specified, will be copied from target.") + ] = None, blacklist: Annotated[Path, Option("--blacklist", "-b", help="File containing blacklist")] = None, name: Annotated[str, Option("--name", "-n", help="Target name")] = "", description: Annotated[str, Option("--description", "-d", help="Target description")] = "", @@ -35,17 +38,18 @@ def create( ), ] = False, ): - seeds = self._read_file(seeds, "seeds") - whitelist = None if not whitelist else self._read_file(whitelist, "whitelist") + seeds = None if not seeds else self._read_file(seeds, "seeds") + target = [] if not target else self._read_file(target, "target") blacklist = None if not blacklist else self._read_file(blacklist, "blacklist") - target = self.bbot_server.create_target( + target = CreateTarget( name=name, description=description, + target=target, seeds=seeds, - whitelist=whitelist, blacklist=blacklist, strict_dns_scope=strict_dns_scope, ) + target = self.bbot_server.create_target(target) self.log.info(f"Target created successfully:") self.print_json(target.model_dump(), colorize=True) @@ -75,8 +79,8 @@ def list( { "name": target.name, "description": target.description, + "target": target.target_size, "seeds": target.seed_size, - "whitelist": target.whitelist_size, "blacklist": target.blacklist_size, "strict_scope": "Yes" if target.strict_dns_scope else "No", "created": self.timestamp_to_human(target.created), @@ -89,8 +93,8 @@ def list( fieldnames=[ "name", "description", + "target", "seeds", - "whitelist", "blacklist", "strict_scope", "created", @@ -103,8 +107,8 @@ def list( table = self.Table() table.add_column("Name", style=self.COLOR) table.add_column("Description") + table.add_column("Target") table.add_column("Seeds") - table.add_column("Whitelist") table.add_column("Blacklist") table.add_column("Strict Scope") table.add_column("Created", style=self.DARK_COLOR) @@ -113,8 +117,8 @@ def list( table.add_row( target.name, target.description, + f"{target.target_size:,}", f"{target.seed_size:,}", - f"{target.whitelist_size:,}", f"{target.blacklist_size:,}", "Yes" if target.strict_dns_scope else "No", self.timestamp_to_human(target.created), diff --git a/bbot_server/modules/targets/targets_models.py b/bbot_server/modules/targets/targets_models.py index f514ca0d..a48ff1ac 100644 --- a/bbot_server/modules/targets/targets_models.py +++ b/bbot_server/modules/targets/targets_models.py @@ -1,7 +1,7 @@ import uuid -from pydantic import Field -from typing import Optional -from typing import Annotated +from functools import cached_property +from typing import Optional, Annotated +from pydantic import Field, computed_field from bbot.scanner.target import BBOTTarget from bbot_server.utils.misc import utc_now @@ -9,44 +9,102 @@ class BaseTarget(BaseBBOTServerModel): - description: str = "" - seeds: list[str] = [] - whitelist: Optional[list[str]] = None - blacklist: list[str] = [] - strict_dns_scope: bool = False - hash: Annotated[str, "indexed", "unique"] = "" - scope_hash: Annotated[str, "indexed"] = "" - seed_hash: Annotated[str, "indexed"] = "" - whitelist_hash: Annotated[str, "indexed"] = "" - blacklist_hash: Annotated[str, "indexed"] = "" - seed_size: int = 0 - whitelist_size: int = 0 - blacklist_size: int = 0 + """Base class for all target models.""" + + description: str = Field("", description="Target description") + target: Optional[list[str]] = Field( + default_factory=list, + description="List of BBOT targets, e.g. domains, IPs, CIDRs, URLs, etc. These determine the scope of the scan. They are also used as seeds if no seeds are provided.", + ) + seeds: Optional[list[str]] = Field( + None, + description="Domains, IPs, CIDRs, URLs, etc. to seed the scan. If not provided, the target list will be used as seeds.", + ) + blacklist: Optional[list[str]] = Field( + default_factory=list, + description="Domains, IPs, CIDRs, URLs, etc. to blacklist from the scan. If a host is blacklisted, it will not be scanned.", + ) + strict_dns_scope: bool = Field( + False, + description="If True, only the exact hosts themselves should be considered in-scope, not their subdomains", + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._bbot_target = BBOTTarget( - *self.seeds, whitelist=self.whitelist, blacklist=self.blacklist, strict_dns_scope=self.strict_dns_scope + target=self.target, seeds=self.seeds, blacklist=self.blacklist, strict_dns_scope=self.strict_dns_scope ) - self.hash = self.bbot_target.hash.hex() - self.scope_hash = self.bbot_target.scope_hash.hex() - self.seed_hash = self.bbot_target.seeds.hash.hex() - self.whitelist_hash = self.bbot_target.whitelist.hash.hex() - self.blacklist_hash = self.bbot_target.blacklist.hash.hex() - self.seed_size = len(self.bbot_target.seeds) - self.whitelist_size = 0 if not self.bbot_target._orig_whitelist else len(self.bbot_target.whitelist) - self.blacklist_size = len(self.bbot_target.blacklist) + # self.target = sorted(self.target.inputs) @property def bbot_target(self): return self._bbot_target + @computed_field( + description="Hash of the target. This is combined from the target, seeds, and blacklist hashes. Strict scope is also taken into account." + ) + @cached_property + def hash(self) -> Annotated[str, "indexed", "unique"]: + return self.bbot_target.hash.hex() + + @computed_field(description="Hash of the target list.") + @cached_property + def target_hash(self) -> Annotated[str, "indexed"]: + return self._bbot_target.target.hash.hex() + + @computed_field(description="Hash of the blacklist.") + @cached_property + def blacklist_hash(self) -> Annotated[str, "indexed"]: + return self._bbot_target.blacklist.hash.hex() + + @computed_field(description="Hash of the seeds.") + @cached_property + def seed_hash(self) -> Annotated[str, "indexed"]: + return self._bbot_target.seeds.hash.hex() + + @computed_field(description="Hash of the scope (target + blacklist + strict scope setting).") + @cached_property + def scope_hash(self) -> Annotated[str, "indexed"]: + return self._bbot_target.scope_hash.hex() + + @computed_field(description="Number of entries in the target list.") + @cached_property + def target_size(self) -> int: + return len(self.bbot_target.target) + + @computed_field(description="Number of entries in the blacklist.") + @cached_property + def blacklist_size(self) -> int: + return len(self.bbot_target.blacklist) + + @computed_field(description="Number of entries in the seeds list.") + @cached_property + def seed_size(self) -> int: + return 0 if not self.bbot_target._orig_seeds else len(self.bbot_target.seeds) + + +class CreateTarget(BaseTarget): + """Used for creating a new target.""" + + name: Annotated[str, "indexed", "unique", Field(description="Target name", default="")] + default: Annotated[ + bool, + "indexed", + Field(description="If True, this is the default target. There can only be one default target."), + ] = False + + +class Target(CreateTarget): + """Used for storing a target in the database.""" -class Target(BaseTarget): __table_name__ = "targets" __store_type__ = "user" - id: Annotated[uuid.UUID, "indexed", "unique"] = Field(default_factory=uuid.uuid4) - name: Annotated[str, "indexed", "unique"] - default: Annotated[bool, "indexed"] = False - created: Annotated[float, "indexed"] = Field(default_factory=utc_now) - modified: Annotated[float, "indexed"] = Field(default_factory=utc_now) + id: Annotated[uuid.UUID, "indexed", "unique"] = Field( + default_factory=uuid.uuid4, description="Universally Unique Target ID" + ) + created: Annotated[float, "indexed"] = Field( + default_factory=utc_now, description="Timestamp of when the target was created" + ) + modified: Annotated[float, "indexed"] = Field( + default_factory=utc_now, description="Timestamp of when the target was last modified" + ) diff --git a/poetry.lock b/poetry.lock index 899d034c..d8f22c9a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -62,24 +62,23 @@ files = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, - {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, + {file = "anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb"}, + {file = "anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" -sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -trio = ["trio (>=0.31.0)"] +trio = ["trio (>=0.31.0) ; python_version < \"3.10\"", "trio (>=0.32.0) ; python_version >= \"3.10\""] [[package]] name = "async-timeout" @@ -142,22 +141,22 @@ yara-python = "^4.5.1" type = "git" url = "https://github.com/blacklanternsecurity/bbot" reference = "3.0" -resolved_reference = "b3041f65a0796176bb49186df58ea6ebd3193050" +resolved_reference = "95b5dd45c879a555017a64ce5529719086f33c8c" [[package]] name = "beautifulsoup4" -version = "4.14.2" +version = "4.14.3" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, - {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, + {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, + {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, ] [package.dependencies] -soupsieve = ">1.2" +soupsieve = ">=1.6.1" typing-extensions = ">=4.0.0" [package.extras] @@ -460,104 +459,104 @@ markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\"", [[package]] name = "coverage" -version = "7.12.0" +version = "7.13.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b"}, - {file = "coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c"}, - {file = "coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832"}, - {file = "coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa"}, - {file = "coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73"}, - {file = "coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb"}, - {file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e"}, - {file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777"}, - {file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553"}, - {file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d"}, - {file = "coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef"}, - {file = "coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022"}, - {file = "coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f"}, - {file = "coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3"}, - {file = "coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e"}, - {file = "coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7"}, - {file = "coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245"}, - {file = "coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b"}, - {file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64"}, - {file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742"}, - {file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c"}, - {file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984"}, - {file = "coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6"}, - {file = "coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4"}, - {file = "coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc"}, - {file = "coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647"}, - {file = "coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736"}, - {file = "coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60"}, - {file = "coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8"}, - {file = "coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f"}, - {file = "coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70"}, - {file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0"}, - {file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068"}, - {file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b"}, - {file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937"}, - {file = "coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa"}, - {file = "coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a"}, - {file = "coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c"}, - {file = "coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941"}, - {file = "coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a"}, - {file = "coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d"}, - {file = "coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211"}, - {file = "coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d"}, - {file = "coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c"}, - {file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9"}, - {file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0"}, - {file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508"}, - {file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc"}, - {file = "coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8"}, - {file = "coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07"}, - {file = "coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc"}, - {file = "coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87"}, - {file = "coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6"}, - {file = "coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7"}, - {file = "coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560"}, - {file = "coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12"}, - {file = "coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296"}, - {file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507"}, - {file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d"}, - {file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2"}, - {file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455"}, - {file = "coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d"}, - {file = "coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c"}, - {file = "coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d"}, - {file = "coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92"}, - {file = "coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360"}, - {file = "coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac"}, - {file = "coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d"}, - {file = "coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c"}, - {file = "coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434"}, - {file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc"}, - {file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc"}, - {file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e"}, - {file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17"}, - {file = "coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933"}, - {file = "coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe"}, - {file = "coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d"}, - {file = "coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d"}, - {file = "coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03"}, - {file = "coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9"}, - {file = "coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6"}, - {file = "coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339"}, - {file = "coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e"}, - {file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13"}, - {file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f"}, - {file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1"}, - {file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b"}, - {file = "coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a"}, - {file = "coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291"}, - {file = "coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384"}, - {file = "coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a"}, - {file = "coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}, + {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}, + {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}, + {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}, + {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}, + {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}, + {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}, + {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}, + {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}, + {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}, + {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}, + {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}, + {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}, + {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}, + {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}, + {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}, + {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}, + {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}, + {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}, + {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}, + {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}, + {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}, + {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}, ] [package.dependencies] @@ -707,15 +706,15 @@ idna = ">=2.0.0" [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main", "dev"] markers = "python_version == \"3.10\"" files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, ] [package.dependencies] @@ -1550,99 +1549,99 @@ test = ["pytest (>=8.3.0,<8.4.0)", "pytest-benchmark (>=5.1.0,<5.2.0)", "pytest- [[package]] name = "orjson" -version = "3.11.4" +version = "3.11.5" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "orjson-3.11.4-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e3aa2118a3ece0d25489cbe48498de8a5d580e42e8d9979f65bf47900a15aba1"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a69ab657a4e6733133a3dca82768f2f8b884043714e8d2b9ba9f52b6efef5c44"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3740bffd9816fc0326ddc406098a3a8f387e42223f5f455f2a02a9f834ead80c"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65fd2f5730b1bf7f350c6dc896173d3460d235c4be007af73986d7cd9a2acd23"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fdc3ae730541086158d549c97852e2eea6820665d4faf0f41bf99df41bc11ea"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e10b4d65901da88845516ce9f7f9736f9638d19a1d483b3883dc0182e6e5edba"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb6a03a678085f64b97f9d4a9ae69376ce91a3a9e9b56a82b1580d8e1d501aff"}, - {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c82e4f0b1c712477317434761fbc28b044c838b6b1240d895607441412371ac"}, - {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d58c166a18f44cc9e2bad03a327dc2d1a3d2e85b847133cfbafd6bfc6719bd79"}, - {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94f206766bf1ea30e1382e4890f763bd1eefddc580e08fec1ccdc20ddd95c827"}, - {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:41bf25fb39a34cf8edb4398818523277ee7096689db352036a9e8437f2f3ee6b"}, - {file = "orjson-3.11.4-cp310-cp310-win32.whl", hash = "sha256:fa9627eba4e82f99ca6d29bc967f09aba446ee2b5a1ea728949ede73d313f5d3"}, - {file = "orjson-3.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:23ef7abc7fca96632d8174ac115e668c1e931b8fe4dde586e92a500bf1914dcc"}, - {file = "orjson-3.11.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e59d23cd93ada23ec59a96f215139753fbfe3a4d989549bcb390f8c00370b39"}, - {file = "orjson-3.11.4-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5c3aedecfc1beb988c27c79d52ebefab93b6c3921dbec361167e6559aba2d36d"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9e5301f1c2caa2a9a4a303480d79c9ad73560b2e7761de742ab39fe59d9175"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8873812c164a90a79f65368f8f96817e59e35d0cc02786a5356f0e2abed78040"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d7feb0741ebb15204e748f26c9638e6665a5fa93c37a2c73d64f1669b0ddc63"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ee5487fefee21e6910da4c2ee9eef005bee568a0879834df86f888d2ffbdd9"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d40d46f348c0321df01507f92b95a377240c4ec31985225a6668f10e2676f9a"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95713e5fc8af84d8edc75b785d2386f653b63d62b16d681687746734b4dfc0be"}, - {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad73ede24f9083614d6c4ca9a85fe70e33be7bf047ec586ee2363bc7418fe4d7"}, - {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:842289889de515421f3f224ef9c1f1efb199a32d76d8d2ca2706fa8afe749549"}, - {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3b2427ed5791619851c52a1261b45c233930977e7de8cf36de05636c708fa905"}, - {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c36e524af1d29982e9b190573677ea02781456b2e537d5840e4538a5ec41907"}, - {file = "orjson-3.11.4-cp311-cp311-win32.whl", hash = "sha256:87255b88756eab4a68ec61837ca754e5d10fa8bc47dc57f75cedfeaec358d54c"}, - {file = "orjson-3.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:e2d5d5d798aba9a0e1fede8d853fa899ce2cb930ec0857365f700dffc2c7af6a"}, - {file = "orjson-3.11.4-cp311-cp311-win_arm64.whl", hash = "sha256:6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045"}, - {file = "orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d4371de39319d05d3f482f372720b841c841b52f5385bd99c61ed69d55d9ab50"}, - {file = "orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e41fd3b3cac850eaae78232f37325ed7d7436e11c471246b87b2cd294ec94853"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600e0e9ca042878c7fdf189cf1b028fe2c1418cc9195f6cb9824eb6ed99cb938"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7bbf9b333f1568ef5da42bc96e18bf30fd7f8d54e9ae066d711056add508e415"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4806363144bb6e7297b8e95870e78d30a649fdc4e23fc84daa80c8ebd366ce44"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad355e8308493f527d41154e9053b86a5be892b3b359a5c6d5d95cda23601cb2"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a7517482667fb9f0ff1b2f16fe5829296ed7a655d04d68cd9711a4d8a4e708"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97eb5942c7395a171cbfecc4ef6701fc3c403e762194683772df4c54cfbb2210"}, - {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:149d95d5e018bdd822e3f38c103b1a7c91f88d38a88aada5c4e9b3a73a244241"}, - {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:624f3951181eb46fc47dea3d221554e98784c823e7069edb5dbd0dc826ac909b"}, - {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:03bfa548cf35e3f8b3a96c4e8e41f753c686ff3d8e182ce275b1751deddab58c"}, - {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:525021896afef44a68148f6ed8a8bf8375553d6066c7f48537657f64823565b9"}, - {file = "orjson-3.11.4-cp312-cp312-win32.whl", hash = "sha256:b58430396687ce0f7d9eeb3dd47761ca7d8fda8e9eb92b3077a7a353a75efefa"}, - {file = "orjson-3.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:c6dbf422894e1e3c80a177133c0dda260f81428f9de16d61041949f6a2e5c140"}, - {file = "orjson-3.11.4-cp312-cp312-win_arm64.whl", hash = "sha256:d38d2bc06d6415852224fcc9c0bfa834c25431e466dc319f0edd56cca81aa96e"}, - {file = "orjson-3.11.4-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2d6737d0e616a6e053c8b4acc9eccea6b6cce078533666f32d140e4f85002534"}, - {file = "orjson-3.11.4-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:afb14052690aa328cc118a8e09f07c651d301a72e44920b887c519b313d892ff"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38aa9e65c591febb1b0aed8da4d469eba239d434c218562df179885c94e1a3ad"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2cf4dfaf9163b0728d061bebc1e08631875c51cd30bf47cb9e3293bfbd7dcd5"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89216ff3dfdde0e4070932e126320a1752c9d9a758d6a32ec54b3b9334991a6a"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9daa26ca8e97fae0ce8aa5d80606ef8f7914e9b129b6b5df9104266f764ce436"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c8b2769dc31883c44a9cd126560327767f848eb95f99c36c9932f51090bfce9"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1469d254b9884f984026bd9b0fa5bbab477a4bfe558bba6848086f6d43eb5e73"}, - {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:68e44722541983614e37117209a194e8c3ad07838ccb3127d96863c95ec7f1e0"}, - {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8e7805fda9672c12be2f22ae124dcd7b03928d6c197544fe12174b86553f3196"}, - {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04b69c14615fb4434ab867bf6f38b2d649f6f300af30a6705397e895f7aec67a"}, - {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:639c3735b8ae7f970066930e58cf0ed39a852d417c24acd4a25fc0b3da3c39a6"}, - {file = "orjson-3.11.4-cp313-cp313-win32.whl", hash = "sha256:6c13879c0d2964335491463302a6ca5ad98105fc5db3565499dcb80b1b4bd839"}, - {file = "orjson-3.11.4-cp313-cp313-win_amd64.whl", hash = "sha256:09bf242a4af98732db9f9a1ec57ca2604848e16f132e3f72edfd3c5c96de009a"}, - {file = "orjson-3.11.4-cp313-cp313-win_arm64.whl", hash = "sha256:a85f0adf63319d6c1ba06fb0dbf997fced64a01179cf17939a6caca662bf92de"}, - {file = "orjson-3.11.4-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:42d43a1f552be1a112af0b21c10a5f553983c2a0938d2bbb8ecd8bc9fb572803"}, - {file = "orjson-3.11.4-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:26a20f3fbc6c7ff2cb8e89c4c5897762c9d88cf37330c6a117312365d6781d54"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3f20be9048941c7ffa8fc523ccbd17f82e24df1549d1d1fe9317712d19938e"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aac364c758dc87a52e68e349924d7e4ded348dedff553889e4d9f22f74785316"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5c54a6d76e3d741dcc3f2707f8eeb9ba2a791d3adbf18f900219b62942803b1"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28485bdca8617b79d44627f5fb04336897041dfd9fa66d383a49d09d86798bc"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfc2a484cad3585e4ba61985a6062a4c2ed5c7925db6d39f1fa267c9d166487f"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e34dbd508cb91c54f9c9788923daca129fe5b55c5b4eebe713bf5ed3791280cf"}, - {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b13c478fa413d4b4ee606ec8e11c3b2e52683a640b006bb586b3041c2ca5f606"}, - {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:724ca721ecc8a831b319dcd72cfa370cc380db0bf94537f08f7edd0a7d4e1780"}, - {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:977c393f2e44845ce1b540e19a786e9643221b3323dae190668a98672d43fb23"}, - {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e539e382cf46edec157ad66b0b0872a90d829a6b71f17cb633d6c160a223155"}, - {file = "orjson-3.11.4-cp314-cp314-win32.whl", hash = "sha256:d63076d625babab9db5e7836118bdfa086e60f37d8a174194ae720161eb12394"}, - {file = "orjson-3.11.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a54d6635fa3aaa438ae32e8570b9f0de36f3f6562c308d2a2a452e8b0592db1"}, - {file = "orjson-3.11.4-cp314-cp314-win_arm64.whl", hash = "sha256:78b999999039db3cf58f6d230f524f04f75f129ba3d1ca2ed121f8657e575d3d"}, - {file = "orjson-3.11.4-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:405261b0a8c62bcbd8e2931c26fdc08714faf7025f45531541e2b29e544b545b"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af02ff34059ee9199a3546f123a6ab4c86caf1708c79042caf0820dc290a6d4f"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b2eba969ea4203c177c7b38b36c69519e6067ee68c34dc37081fac74c796e10"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0baa0ea43cfa5b008a28d3c07705cf3ada40e5d347f0f44994a64b1b7b4b5350"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80fd082f5dcc0e94657c144f1b2a3a6479c44ad50be216cf0c244e567f5eae19"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3704d35e47d5bee811fb1cbd8599f0b4009b14d451c4c57be5a7e25eb89a13"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa447f2b5356779d914658519c874cf3b7629e99e63391ed519c28c8aea4919"}, - {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bba5118143373a86f91dadb8df41d9457498226698ebdf8e11cbb54d5b0e802d"}, - {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:622463ab81d19ef3e06868b576551587de8e4d518892d1afab71e0fbc1f9cffc"}, - {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3e0a700c4b82144b72946b6629968df9762552ee1344bfdb767fecdd634fbd5a"}, - {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6e18a5c15e764e5f3fc569b47872450b4bcea24f2a6354c0a0e95ad21045d5a9"}, - {file = "orjson-3.11.4-cp39-cp39-win32.whl", hash = "sha256:fb1c37c71cad991ef4d89c7a634b5ffb4447dbd7ae3ae13e8f5ee7f1775e7ab1"}, - {file = "orjson-3.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:e2985ce8b8c42d00492d0ed79f2bd2b6460d00f2fa671dfde4bf2e02f49bf5c6"}, - {file = "orjson-3.11.4.tar.gz", hash = "sha256:39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d"}, + {file = "orjson-3.11.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:df9eadb2a6386d5ea2bfd81309c505e125cfc9ba2b1b99a97e60985b0b3665d1"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc70da619744467d8f1f49a8cadae5ec7bbe054e5232d95f92ed8737f8c5870"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:073aab025294c2f6fc0807201c76fdaed86f8fc4be52c440fb78fbb759a1ac09"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:835f26fa24ba0bb8c53ae2a9328d1706135b74ec653ed933869b74b6909e63fd"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667c132f1f3651c14522a119e4dd631fad98761fa960c55e8e7430bb2a1ba4ac"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42e8961196af655bb5e63ce6c60d25e8798cd4dfbc04f4203457fa3869322c2e"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75412ca06e20904c19170f8a24486c4e6c7887dea591ba18a1ab572f1300ee9f"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6af8680328c69e15324b5af3ae38abbfcf9cbec37b5346ebfd52339c3d7e8a18"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a86fe4ff4ea523eac8f4b57fdac319faf037d3c1be12405e6a7e86b3fbc4756a"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e607b49b1a106ee2086633167033afbd63f76f2999e9236f638b06b112b24ea7"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7339f41c244d0eea251637727f016b3d20050636695bc78345cce9029b189401"}, + {file = "orjson-3.11.5-cp310-cp310-win32.whl", hash = "sha256:8be318da8413cdbbce77b8c5fac8d13f6eb0f0db41b30bb598631412619572e8"}, + {file = "orjson-3.11.5-cp310-cp310-win_amd64.whl", hash = "sha256:b9f86d69ae822cabc2a0f6c099b43e8733dda788405cba2665595b7e8dd8d167"}, + {file = "orjson-3.11.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9c8494625ad60a923af6b2b0bd74107146efe9b55099e20d7740d995f338fcd8"}, + {file = "orjson-3.11.5-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:7bb2ce0b82bc9fd1168a513ddae7a857994b780b2945a8c51db4ab1c4b751ebc"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67394d3becd50b954c4ecd24ac90b5051ee7c903d167459f93e77fc6f5b4c968"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:298d2451f375e5f17b897794bcc3e7b821c0f32b4788b9bcae47ada24d7f3cf7"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa5e4244063db8e1d87e0f54c3f7522f14b2dc937e65d5241ef0076a096409fd"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db2088b490761976c1b2e956d5d4e6409f3732e9d79cfa69f876c5248d1baf9"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ed66358f32c24e10ceea518e16eb3549e34f33a9d51f99ce23b0251776a1ef"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2021afda46c1ed64d74b555065dbd4c2558d510d8cec5ea6a53001b3e5e82a9"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b42ffbed9128e547a1647a3e50bc88ab28ae9daa61713962e0d3dd35e820c125"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8d5f16195bb671a5dd3d1dbea758918bada8f6cc27de72bd64adfbd748770814"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c0e5d9f7a0227df2927d343a6e3859bebf9208b427c79bd31949abcc2fa32fa5"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23d04c4543e78f724c4dfe656b3791b5f98e4c9253e13b2636f1af5d90e4a880"}, + {file = "orjson-3.11.5-cp311-cp311-win32.whl", hash = "sha256:c404603df4865f8e0afe981aa3c4b62b406e6d06049564d58934860b62b7f91d"}, + {file = "orjson-3.11.5-cp311-cp311-win_amd64.whl", hash = "sha256:9645ef655735a74da4990c24ffbd6894828fbfa117bc97c1edd98c282ecb52e1"}, + {file = "orjson-3.11.5-cp311-cp311-win_arm64.whl", hash = "sha256:1cbf2735722623fcdee8e712cbaaab9e372bbcb0c7924ad711b261c2eccf4a5c"}, + {file = "orjson-3.11.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:334e5b4bff9ad101237c2d799d9fd45737752929753bf4faf4b207335a416b7d"}, + {file = "orjson-3.11.5-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:ff770589960a86eae279f5d8aa536196ebda8273a2a07db2a54e82b93bc86626"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed24250e55efbcb0b35bed7caaec8cedf858ab2f9f2201f17b8938c618c8ca6f"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a66d7769e98a08a12a139049aac2f0ca3adae989817f8c43337455fbc7669b85"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86cfc555bfd5794d24c6a1903e558b50644e5e68e6471d66502ce5cb5fdef3f9"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a230065027bc2a025e944f9d4714976a81e7ecfa940923283bca7bbc1f10f626"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b29d36b60e606df01959c4b982729c8845c69d1963f88686608be9ced96dbfaa"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74099c6b230d4261fdc3169d50efc09abf38ace1a42ea2f9994b1d79153d477"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e697d06ad57dd0c7a737771d470eedc18e68dfdefcdd3b7de7f33dfda5b6212e"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e08ca8a6c851e95aaecc32bc44a5aa75d0ad26af8cdac7c77e4ed93acf3d5b69"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8b5f96c05fce7d0218df3fdfeb962d6b8cfff7e3e20264306b46dd8b217c0f3"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ddbfdb5099b3e6ba6d6ea818f61997bb66de14b411357d24c4612cf1ebad08ca"}, + {file = "orjson-3.11.5-cp312-cp312-win32.whl", hash = "sha256:9172578c4eb09dbfcf1657d43198de59b6cef4054de385365060ed50c458ac98"}, + {file = "orjson-3.11.5-cp312-cp312-win_amd64.whl", hash = "sha256:2b91126e7b470ff2e75746f6f6ee32b9ab67b7a93c8ba1d15d3a0caaf16ec875"}, + {file = "orjson-3.11.5-cp312-cp312-win_arm64.whl", hash = "sha256:acbc5fac7e06777555b0722b8ad5f574739e99ffe99467ed63da98f97f9ca0fe"}, + {file = "orjson-3.11.5-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3b01799262081a4c47c035dd77c1301d40f568f77cc7ec1bb7db5d63b0a01629"}, + {file = "orjson-3.11.5-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:61de247948108484779f57a9f406e4c84d636fa5a59e411e6352484985e8a7c3"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:894aea2e63d4f24a7f04a1908307c738d0dce992e9249e744b8f4e8dd9197f39"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ddc21521598dbe369d83d4d40338e23d4101dad21dae0e79fa20465dbace019f"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cce16ae2f5fb2c53c3eafdd1706cb7b6530a67cc1c17abe8ec747f5cd7c0c51"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e46c762d9f0e1cfb4ccc8515de7f349abbc95b59cb5a2bd68df5973fdef913f8"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7345c759276b798ccd6d77a87136029e71e66a8bbf2d2755cbdde1d82e78706"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75bc2e59e6a2ac1dd28901d07115abdebc4563b5b07dd612bf64260a201b1c7f"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:54aae9b654554c3b4edd61896b978568c6daa16af96fa4681c9b5babd469f863"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4bdd8d164a871c4ec773f9de0f6fe8769c2d6727879c37a9666ba4183b7f8228"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a261fef929bcf98a60713bf5e95ad067cea16ae345d9a35034e73c3990e927d2"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c028a394c766693c5c9909dec76b24f37e6a1b91999e8d0c0d5feecbe93c3e05"}, + {file = "orjson-3.11.5-cp313-cp313-win32.whl", hash = "sha256:2cc79aaad1dfabe1bd2d50ee09814a1253164b3da4c00a78c458d82d04b3bdef"}, + {file = "orjson-3.11.5-cp313-cp313-win_amd64.whl", hash = "sha256:ff7877d376add4e16b274e35a3f58b7f37b362abf4aa31863dadacdd20e3a583"}, + {file = "orjson-3.11.5-cp313-cp313-win_arm64.whl", hash = "sha256:59ac72ea775c88b163ba8d21b0177628bd015c5dd060647bbab6e22da3aad287"}, + {file = "orjson-3.11.5-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e446a8ea0a4c366ceafc7d97067bfd55292969143b57e3c846d87fc701e797a0"}, + {file = "orjson-3.11.5-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:53deb5addae9c22bbe3739298f5f2196afa881ea75944e7720681c7080909a81"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd00d49d6063d2b8791da5d4f9d20539c5951f965e45ccf4e96d33505ce68f"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fd15f9fc8c203aeceff4fda211157fad114dde66e92e24097b3647a08f4ee9e"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df95000fbe6777bf9820ae82ab7578e8662051bb5f83d71a28992f539d2cda7"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a8d676748fca47ade5bc3da7430ed7767afe51b2f8100e3cd65e151c0eaceb"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa0f513be38b40234c77975e68805506cad5d57b3dfd8fe3baa7f4f4051e15b4"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1863e75b92891f553b7922ce4ee10ed06db061e104f2b7815de80cdcb135ad"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4be86b58e9ea262617b8ca6251a2f0d63cc132a6da4b5fcc8e0a4128782c829"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b923c1c13fa02084eb38c9c065afd860a5cff58026813319a06949c3af5732ac"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1b6bd351202b2cd987f35a13b5e16471cf4d952b42a73c391cc537974c43ef6d"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb150d529637d541e6af06bbe3d02f5498d628b7f98267ff87647584293ab439"}, + {file = "orjson-3.11.5-cp314-cp314-win32.whl", hash = "sha256:9cc1e55c884921434a84a0c3dd2699eb9f92e7b441d7f53f3941079ec6ce7499"}, + {file = "orjson-3.11.5-cp314-cp314-win_amd64.whl", hash = "sha256:a4f3cb2d874e03bc7767c8f88adaa1a9a05cecea3712649c3b58589ec7317310"}, + {file = "orjson-3.11.5-cp314-cp314-win_arm64.whl", hash = "sha256:38b22f476c351f9a1c43e5b07d8b5a02eb24a6ab8e75f700f7d479d4568346a5"}, + {file = "orjson-3.11.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1b280e2d2d284a6713b0cfec7b08918ebe57df23e3f76b27586197afca3cb1e9"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d8a112b274fae8c5f0f01954cb0480137072c271f3f4958127b010dfefaec"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0a2ae6f09ac7bd47d2d5a5305c1d9ed08ac057cda55bb0a49fa506f0d2da00"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d87bd1896faac0d10b4f849016db81a63e4ec5df38757ffae84d45ab38aa71"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:801a821e8e6099b8c459ac7540b3c32dba6013437c57fdcaec205b169754f38c"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a0f6ac618c98c74b7fbc8c0172ba86f9e01dbf9f62aa0b1776c2231a7bffe5"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea7339bdd22e6f1060c55ac31b6a755d86a5b2ad3657f2669ec243f8e3b2bdb"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4dad582bc93cef8f26513e12771e76385a7e6187fd713157e971c784112aad56"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:0522003e9f7fba91982e83a97fec0708f5a714c96c4209db7104e6b9d132f111"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7403851e430a478440ecc1258bcbacbfbd8175f9ac1e39031a7121dd0de05ff8"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5f691263425d3177977c8d1dd896cde7b98d93cbf390b2544a090675e83a6a0a"}, + {file = "orjson-3.11.5-cp39-cp39-win32.whl", hash = "sha256:61026196a1c4b968e1b1e540563e277843082e9e97d78afa03eb89315af531f1"}, + {file = "orjson-3.11.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b94b947ac08586af635ef922d69dc9bc63321527a3a04647f4986a73f4bd30"}, + {file = "orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5"}, ] [[package]] @@ -1823,14 +1822,14 @@ files = [ [[package]] name = "pydantic" -version = "2.12.4" +version = "2.12.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e"}, - {file = "pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [package.dependencies] @@ -2036,83 +2035,83 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pymongo" -version = "4.15.4" +version = "4.15.5" description = "PyMongo - the Official MongoDB Python driver" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pymongo-4.15.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84c7c7624a1298295487d0dfd8dbec75d14db44c017b5087c7fe7d6996a96e3d"}, - {file = "pymongo-4.15.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71a5ab372ebe4e05453bae86a008f6db98b5702df551219fb2f137c394d71c3a"}, - {file = "pymongo-4.15.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eee407bf1058a8f0d5b203028997b42ea6fc80a996537cc2886f89573bc0770f"}, - {file = "pymongo-4.15.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e286f5b9c13963bcaf9b9241846d388ac5022225a9e11c5364393a8cc3eb49"}, - {file = "pymongo-4.15.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:67c3b84a2a0e1794b2fbfe22dc36711a03c6bc147d9d2e0f8072fabed7a65092"}, - {file = "pymongo-4.15.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:94e50149fb9d982c234d0efa9c0eec4a04db7e82a412d3dae2c4f03a9926360e"}, - {file = "pymongo-4.15.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1903c0966969cf3e7b30922956bd82eb09e6a3f3d7431a727d12f20104f66d3"}, - {file = "pymongo-4.15.4-cp310-cp310-win32.whl", hash = "sha256:20ffcd883b6e187ef878558d0ebf9f09cc46807b6520022592522d3cdd21022d"}, - {file = "pymongo-4.15.4-cp310-cp310-win_amd64.whl", hash = "sha256:68ea93e7d19d3aa3182a6e41ba68288b9b234a3b0a70b368feb95fff3f94413f"}, - {file = "pymongo-4.15.4-cp310-cp310-win_arm64.whl", hash = "sha256:abfe72630190c0dc8f2222b02af7c4e5f72809d06b2ccb3f3ca83f6a7b60e302"}, - {file = "pymongo-4.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b2967bda6ccac75aefad26c4ef295f5054181d69928bb9d1159227d6771e8887"}, - {file = "pymongo-4.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7df1fad859c61bdbe0e2a0dec8f5893729d99b4407b88568e0e542d25f383f57"}, - {file = "pymongo-4.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:990c4898787e706d0ab59141cf5085c981d89c3f86443cd6597939d9f25dd71d"}, - {file = "pymongo-4.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad7ff0347e8306fc62f146bdad0635d9eec1d26e246c97c14dd1a189d3480e3f"}, - {file = "pymongo-4.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd8c78c59fd7308239ef9bcafb7cd82f08cbc9466d1cfda22f9025c83468bf6d"}, - {file = "pymongo-4.15.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:44d95677aa23fe479bb531b393a4fad0210f808af52e4ab2b79c0b540c828957"}, - {file = "pymongo-4.15.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ab985e61376ae5a04f162fb6bdddaffc7beec883ffbd9d84ea86a71be794d74"}, - {file = "pymongo-4.15.4-cp311-cp311-win32.whl", hash = "sha256:2f811e93dbcba0c488518ceae7873a40a64b6ad273622a18923ef2442eaab55c"}, - {file = "pymongo-4.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:53bfcd8c11086a2457777cb4b1a6588d9dd6af77aeab47e04f2af02e3a077e59"}, - {file = "pymongo-4.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:2096964b2b93607ed80a62ac6664396a826b7fe34e2b1eed3f20784681a17827"}, - {file = "pymongo-4.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ab4eef031e722a8027c338c3d71704a8c85c17c64625d61c6effdf8a893b971"}, - {file = "pymongo-4.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e12551e28007a341d15ebca5a024ef487edf304d612fba5efa1fd6b4d9a95a9"}, - {file = "pymongo-4.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d21998fb9ccb3ea6d59a9f9971591b9efbcfbbe46350f7f8badef9b107707f3"}, - {file = "pymongo-4.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f83e8895d42eb51d259694affa9607c4d56e1c784928ccbbac568dc20df86a8"}, - {file = "pymongo-4.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0bd8126a507afa8ce4b96976c8e28402d091c40b7d98e3b5987a371af059d9e7"}, - {file = "pymongo-4.15.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e799e2cba7fcad5ab29f678784f90b1792fcb6393d571ecbe4c47d2888af30f3"}, - {file = "pymongo-4.15.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:563e793ad87633e50ad43a8cd2c740fbb17fca4a4637185996575ddbe99960b8"}, - {file = "pymongo-4.15.4-cp312-cp312-win32.whl", hash = "sha256:39bb3c12c772241778f4d7bf74885782c8d68b309d3c69891fe39c729334adbd"}, - {file = "pymongo-4.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6f43326f36bc540b04f5a7f1aa8be40b112d7fc9f6e785ae3797cd72a804ffdd"}, - {file = "pymongo-4.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:263cfa2731a4bbafdce2cf06cd511eba8957bd601b3cad9b4723f2543d42c730"}, - {file = "pymongo-4.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ff080f23a12c943346e2bba76cf19c3d14fb3625956792aa22b69767bfb36de"}, - {file = "pymongo-4.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c4690e01d03773f7af21b1a8428029bd534c9fe467c6b594c591d8b992c0a975"}, - {file = "pymongo-4.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78bfe3917d0606b30a91b02ad954c588007f82e2abb2575ac2665259b051a753"}, - {file = "pymongo-4.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f53c83c3fd80fdb412ce4177d4f59b70b9bb1add6106877da044cf21e996316b"}, - {file = "pymongo-4.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e41d6650c1cd77a8e7556ad65133455f819f8c8cdce3e9cf4bbf14252b7d805"}, - {file = "pymongo-4.15.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b60fd8125f52efffd697490b6ccebc6e09d44069ad9c8795df0a684a9a8f4b3c"}, - {file = "pymongo-4.15.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d1a1a0406acd000377f34ae91cdb501fa73601a2d071e4a661e0c862e1b166e"}, - {file = "pymongo-4.15.4-cp313-cp313-win32.whl", hash = "sha256:9c5710ed5f2af95315db0ee8ae02e9ff1e85e7b068c507d980bc24fe9d025257"}, - {file = "pymongo-4.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:61b0863c7f9b460314db79b7f8541d3b490b453ece49afd56b611b214fc4b3b1"}, - {file = "pymongo-4.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:0255af7d5c23c5e8cb4d9bb12906b142acebab0472117e1d5e3a8e6e689781cb"}, - {file = "pymongo-4.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:539f9fa5bb04a09fc2965cdcae3fc91d1c6a1f4f1965b34df377bc7119e3d7cd"}, - {file = "pymongo-4.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:68354a77cf78424d27216b1cb7c9b0f67da16aae855045279ba8d73bb61f5ad0"}, - {file = "pymongo-4.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a9a90d556c2ef1572d2aef525ef19477a82d659d117eb3a51fa99e617d07dc44"}, - {file = "pymongo-4.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1aac57614fb86a3fa707af3537c30eda5e7fd1be712c1f723296292ac057afe"}, - {file = "pymongo-4.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6c21b49c5e021d9ce02cac33525c722d4c6887f7cde19a5a9154f66cb845e84"}, - {file = "pymongo-4.15.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e93828768470026099119295c68ed0dbc0a50022558be5e334f6dbda054f1d32"}, - {file = "pymongo-4.15.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11840e9eb5a650ac190f2a3473631073daddbabdbb2779b6709dfddd3ba3b872"}, - {file = "pymongo-4.15.4-cp314-cp314-win32.whl", hash = "sha256:f0907b46df97b01911bf2e10ddbb23c2303629e482d81372031fd7f4313b9013"}, - {file = "pymongo-4.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:111d7f65ccbde908546cb36d14e22f12a73a4de236fd056f41ed515d1365f134"}, - {file = "pymongo-4.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:c689a5d057ef013612b5aa58e6bf52f7fdb186e22039f1a3719985b5d0399932"}, - {file = "pymongo-4.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cdfa57760745387cde93615a48f622bf1eeae8ae28103a8a5100b9389eec22f9"}, - {file = "pymongo-4.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4fd6ba610e5a54090c4055a15f38d19ad8bf11e6bbc5a173e945c755a16db455"}, - {file = "pymongo-4.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3c7945b8a5563aa3951db26ba534372fba4c781473f5d55ce6340b7523cb0f"}, - {file = "pymongo-4.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41e98a31e79d74e9d78bc1638b71c3a10a910eae7d3318e2ae8587c760931451"}, - {file = "pymongo-4.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d18d89073b5e752391c237d2ee86ceec1e02a4ad764b3029f24419eedd12723e"}, - {file = "pymongo-4.15.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edbff27a56a80b8fe5c0319200c44e63b1349bf20db27d9734ddcf23c0d72b35"}, - {file = "pymongo-4.15.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1d75f5b51304176631c12e5bf47eed021446669e5f99379b76fd2bd3929c1b4"}, - {file = "pymongo-4.15.4-cp314-cp314t-win32.whl", hash = "sha256:e1bf4e0689cc48e0cfa6aef17f107c298d8898de0c6e782ea5c98450ae93a62f"}, - {file = "pymongo-4.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3fc347ea5eda6c3a7177c3a9e4e9b4e570a444a351effda4a898c2d352a1ccd1"}, - {file = "pymongo-4.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:2d921b84c681c5385a6f7ba2b5740cb583544205a00877aad04b5b12ab86ad26"}, - {file = "pymongo-4.15.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e4c44bb6e781373915c56bc88f3b4849137869284e79c08a5b18f4c0d6adfd26"}, - {file = "pymongo-4.15.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:05e0f2ac285e24de802912908d64dfafca8bea5ab1718a88aab0f197b003dc28"}, - {file = "pymongo-4.15.4-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94f8e9ab6954899d60babe48418e41217dc510d6fa4305af7aabee244b2a9882"}, - {file = "pymongo-4.15.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9e2f96e7e7a769a7804121c02f3290a39ca4d78a398bc56c6e024728d350897"}, - {file = "pymongo-4.15.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e84c95b185dce012575adc74a18342a2581dc9bb939712125317e03d92148167"}, - {file = "pymongo-4.15.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:649d24ce86f90a8c74897dba35e34c6b86e0d7d7381d7dc18cafbd06dc78fbc3"}, - {file = "pymongo-4.15.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdab8a71f673b597dbe5cd610913525b59cba34835fc8c6ffb2b62f28028959"}, - {file = "pymongo-4.15.4-cp39-cp39-win32.whl", hash = "sha256:4eb3c2fea850104c41ce3f1f52f6a70f3d1a6998e9c63c197fcaab08c7c89e22"}, - {file = "pymongo-4.15.4-cp39-cp39-win_amd64.whl", hash = "sha256:4ea5dc8a0268ea2e12a6fcfc43bb8f3da969deed46734167238884cbb29c2598"}, - {file = "pymongo-4.15.4-cp39-cp39-win_arm64.whl", hash = "sha256:e6216290982178208b962edc9ba7ebc41b11f276a148ac3b496fc41f86963707"}, - {file = "pymongo-4.15.4.tar.gz", hash = "sha256:6ba7cdf46f03f406f77969a8081cfb659af16c0eee26b79a0a14e25f6c00827b"}, + {file = "pymongo-4.15.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a01a2054d50b50c121c720739a2216d855c48726b0002894de9b991cdd68a2a5"}, + {file = "pymongo-4.15.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e57968139d81367117ed7b75d921445a575d4d7e61536f5e860475df92ac0a9"}, + {file = "pymongo-4.15.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:266aa37e3673e5dcfdd359a81d27131fc133e49cf8e5d9f9f27a5845fac2cd1f"}, + {file = "pymongo-4.15.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2883da6bd0545cc2f12672f6a609b33d48e099a220872ca2bf9bf29fe96a32c3"}, + {file = "pymongo-4.15.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2fc32b354a608ec748d89bbe236b74b967890667eea1af54e92dfd8fbf26df52"}, + {file = "pymongo-4.15.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c006cbaa4b40d296dd2bb8828976866c876ead4c39032b761dcf26f1ba56fde"}, + {file = "pymongo-4.15.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce21e3dc5939b83d03f871090d83ac29fef055bd057f8d3074b6cad10f86b04c"}, + {file = "pymongo-4.15.5-cp310-cp310-win32.whl", hash = "sha256:1b545dcf66a9f06e9b501bfb0438e1eb9af67336e8a5cf36c4bc0a5d3fbe7a37"}, + {file = "pymongo-4.15.5-cp310-cp310-win_amd64.whl", hash = "sha256:1ecc544f515f828f05d3c56cd98063ba3ef8b75f534c63de43306d59f1e93fcd"}, + {file = "pymongo-4.15.5-cp310-cp310-win_arm64.whl", hash = "sha256:1151968ab90db146f0591b6c7db27ce4f73c7ffa0bbddc1d7fb7cb14c9f0b967"}, + {file = "pymongo-4.15.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:57157a4b936e28e2fbe7017b2f6a751da5e284675cab371f2c596d4e0e4f58f3"}, + {file = "pymongo-4.15.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2a34a7391f4cc54fc584e49db6f7c3929221a9da08b3af2d2689884a5943843"}, + {file = "pymongo-4.15.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:be040c8cdaf9c2d5ae9ab60a67ecab453ec19d9ccd457a678053fdceab5ee4c8"}, + {file = "pymongo-4.15.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:defe93944526b1774265c16acf014689cb1b0b18eb84a7b370083b214f9e18cd"}, + {file = "pymongo-4.15.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:816e66116f0ef868eff0463a8b28774af8b547466dbad30c8e82bf0325041848"}, + {file = "pymongo-4.15.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66c7b332532e0f021d784d04488dbf7ed39b7e7d6d5505e282ec8e9cf1025791"}, + {file = "pymongo-4.15.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:acc46a9e47efad8c5229e644a3774169013a46ee28ac72d1fa4edd67c0b7ee9b"}, + {file = "pymongo-4.15.5-cp311-cp311-win32.whl", hash = "sha256:b9836c28ba350d8182a51f32ef9bb29f0c40e82ba1dfb9e4371cd4d94338a55d"}, + {file = "pymongo-4.15.5-cp311-cp311-win_amd64.whl", hash = "sha256:3a45876c5c2ab44e2a249fb542eba2a026f60d6ab04c7ef3924eae338d9de790"}, + {file = "pymongo-4.15.5-cp311-cp311-win_arm64.whl", hash = "sha256:e4a48fc5c712b3db85c9987cfa7fde0366b7930018de262919afd9e52cfbc375"}, + {file = "pymongo-4.15.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c33477af1a50d1b4d86555e098fc2cf5992d839ad538dea0c00a8682162b7a75"}, + {file = "pymongo-4.15.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e6b30defa4a52d3698cd84d608963a8932f7e9b6ec5130087e7082552ac685e5"}, + {file = "pymongo-4.15.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:45fec063f5672e6173bcb09b492431e3641cc74399c2b996fcb995881c2cac61"}, + {file = "pymongo-4.15.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c6813110c0d9fde18674b7262f47a2270ae46c0ddd05711e6770caa3c9a3fb"}, + {file = "pymongo-4.15.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8ec48d1db9f44c737b13be4299a1782d5fde3e75423acbbbe927cb37ebbe87d"}, + {file = "pymongo-4.15.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f410694fdd76631ead7df6544cdeadaf2407179196c3642fced8e48bb21d0a6"}, + {file = "pymongo-4.15.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8c46765d6ac5727a899190aacdeec7a57f8c93346124ddd7e12633b573e2e65"}, + {file = "pymongo-4.15.5-cp312-cp312-win32.whl", hash = "sha256:647118a58dca7d3547714fc0b383aebf81f5852f4173dfd77dd34e80eea9d29b"}, + {file = "pymongo-4.15.5-cp312-cp312-win_amd64.whl", hash = "sha256:099d3e2dddfc75760c6a8fadfb99c1e88824a99c2c204a829601241dff9da049"}, + {file = "pymongo-4.15.5-cp312-cp312-win_arm64.whl", hash = "sha256:649cb906882c4058f467f334fb277083998ba5672ffec6a95d6700db577fd31a"}, + {file = "pymongo-4.15.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b736226f9001bbbd02f822acb9b9b6d28319f362f057672dfae2851f7da6125"}, + {file = "pymongo-4.15.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:60ea9f07fbbcc7c88f922082eb27436dce6756730fdef76a3a9b4c972d0a57a3"}, + {file = "pymongo-4.15.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:20af63218ae42870eaee31fb8cc4ce9e3af7f04ea02fc98ad751fb7a9c8d7be3"}, + {file = "pymongo-4.15.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20d9c11625392f1f8dec7688de5ce344e110ca695344efa313ae4839f13bd017"}, + {file = "pymongo-4.15.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1202b3e5357b161acb7b7cc98e730288a5c15544e5ef7254b33931cb9a27c36e"}, + {file = "pymongo-4.15.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:63af710e9700dbf91abccf119c5f5533b9830286d29edb073803d3b252862c0d"}, + {file = "pymongo-4.15.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22eeb86861cf7b8ee6886361d52abb88e3cd96c6f6d102e45e2604fc6e9e316"}, + {file = "pymongo-4.15.5-cp313-cp313-win32.whl", hash = "sha256:aad6efe82b085bf77cec2a047ded2c810e93eced3ccf1a8e3faec3317df3cd52"}, + {file = "pymongo-4.15.5-cp313-cp313-win_amd64.whl", hash = "sha256:ccc801f6d71ebee2ec2fb3acc64b218fa7cdb7f57933b2f8eee15396b662a0a0"}, + {file = "pymongo-4.15.5-cp313-cp313-win_arm64.whl", hash = "sha256:f043abdf20845bf29a554e95e4fe18d7d7a463095d6a1547699a12f80da91e02"}, + {file = "pymongo-4.15.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba0e75a390334221744e2666fd2d4c82419b580c9bc8d6e0d2d61459d263f3af"}, + {file = "pymongo-4.15.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:853ec7da97642eabaf94d3de4453a86365729327d920af167bf14b2e87b24dce"}, + {file = "pymongo-4.15.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7631304106487480ebbd8acbe44ff1e69d1fdc27e83d9753dc1fd227cea10761"}, + {file = "pymongo-4.15.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50505181365eba5d4d35c462870b3614c8eddd0b2407c89377c1a59380640dd9"}, + {file = "pymongo-4.15.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b75ec7006471299a571d6db1c5609ea4aa9c847a701e9b2953a8ede705d82db"}, + {file = "pymongo-4.15.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c3fc24cb1f4ec60ed83162d4bba0c26abc6c9ae78c928805583673f3b3ea6984"}, + {file = "pymongo-4.15.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21d17bb2934b0640863361c08dd06991f128a97f9bee19425a499227be9ae6b4"}, + {file = "pymongo-4.15.5-cp314-cp314-win32.whl", hash = "sha256:5a3974236cb842b4ef50a5a6bfad9c7d83a713af68ea3592ba240bbcb863305a"}, + {file = "pymongo-4.15.5-cp314-cp314-win_amd64.whl", hash = "sha256:73fa8a7eee44fd95ba7d5cf537340ff3ff34efeb1f7d6790532d0a6ed4dee575"}, + {file = "pymongo-4.15.5-cp314-cp314-win_arm64.whl", hash = "sha256:d41288ca2a3eb9ac7c8cad4ea86ef8d63b69dc46c9b65c2bbd35331ec2a0fc57"}, + {file = "pymongo-4.15.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:552670f0c8bff103656d4e4b1f2c018f789c9de03f7615ed5e547d5b1b83cda0"}, + {file = "pymongo-4.15.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41891b45f6ff1e23cfd1b7fbe40286664ad4507e2d2aa61c6d8c40eb6e11dded"}, + {file = "pymongo-4.15.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:524a8a593ae2eb1ec6db761daf0c03f98824e9882ab7df3d458d0c76c7ade255"}, + {file = "pymongo-4.15.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7ceb35c41b86711a1b284c604e2b944a2d46cb1b8dd3f8b430a9155491378f2"}, + {file = "pymongo-4.15.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3be2336715924be3a861b5e40c634376fd6bfe6dd1892d391566aa5a88a31307"}, + {file = "pymongo-4.15.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d65df9c015e33f74ea9d1abf474971abca21e347a660384f8227dbdab75a33ca"}, + {file = "pymongo-4.15.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83c05bea05e151754357f8e6bbb80d5accead5110dc58f64e283173c71ec9de2"}, + {file = "pymongo-4.15.5-cp314-cp314t-win32.whl", hash = "sha256:7c285614a3e8570b03174a25db642e449b0e7f77a6c9e487b73b05c9bf228ee6"}, + {file = "pymongo-4.15.5-cp314-cp314t-win_amd64.whl", hash = "sha256:aae7d96f7b2b1a2753349130797543e61e93ee2ace8faa7fbe0565e2eb5d815f"}, + {file = "pymongo-4.15.5-cp314-cp314t-win_arm64.whl", hash = "sha256:576a7d4b99465d38112c72f7f3d345f9d16aeeff0f923a3b298c13e15ab4f0ad"}, + {file = "pymongo-4.15.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:092ed5f3a53b546f8350a77976dabb0a11105d6b7c0f86a39934464168c97cff"}, + {file = "pymongo-4.15.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5762f6445a611b34eb500260303483520bd73e6816a39503378444d551e92f7c"}, + {file = "pymongo-4.15.5-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:397fa40b6d331949debd3e0892c420a81a44e7e0f5a570661910b0c57a7e7431"}, + {file = "pymongo-4.15.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d5710c0e04c37932984241282d3011304c35eb798a0026d84e1bd3525266d026"}, + {file = "pymongo-4.15.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a476310f3c9bdba08ab4b1d4309ee308a1b9e22823210fd7b48c83709e95ac4"}, + {file = "pymongo-4.15.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db5689bd2c1cf1dc4f4e94ec8a012ea521f9892a85b3c694fa9ace7cdc2d0416"}, + {file = "pymongo-4.15.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e2623deb5be1b5bc23319ba5ab435b5a526a1e92739ff0e0e9048823f295460"}, + {file = "pymongo-4.15.5-cp39-cp39-win32.whl", hash = "sha256:addaaa62c357e8de3d0fca2fce1acf5b72f4bbf4e7bb35ce1dd68e40e73880f9"}, + {file = "pymongo-4.15.5-cp39-cp39-win_amd64.whl", hash = "sha256:1e4070593ea98bc6def3c84cfc6de28da289e4ed944bb20845f9de9beefb0921"}, + {file = "pymongo-4.15.5-cp39-cp39-win_arm64.whl", hash = "sha256:01227e6bc75a949f7d3303005e27707a0e14a941dc63a183cd449c80e7853fe3"}, + {file = "pymongo-4.15.5.tar.gz", hash = "sha256:3a8d6bf2610abe0c97c567cf98bf5bba3e90ccc93cc03c9dde75fa11e4267b42"}, ] [package.dependencies] @@ -2680,14 +2679,14 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rich-toolkit" -version = "0.16.0" +version = "0.17.0" description = "Rich toolkit for building command-line applications" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "rich_toolkit-0.16.0-py3-none-any.whl", hash = "sha256:3f4307f678c5c1e22c36d89ac05f1cd145ed7174f19c1ce5a4d3664ba77c0f9e"}, - {file = "rich_toolkit-0.16.0.tar.gz", hash = "sha256:2f554b00b194776639f4d80f2706980756b659ceed9f345ebbd9de77d1bdd0f4"}, + {file = "rich_toolkit-0.17.0-py3-none-any.whl", hash = "sha256:06fb47a5c5259d6b480287cd38aff5f551b6e1a307f90ed592453dd360e4e71e"}, + {file = "rich_toolkit-0.17.0.tar.gz", hash = "sha256:17ca7a32e613001aa0945ddea27a246f6de01dfc4c12403254c057a8ee542977"}, ] [package.dependencies] @@ -2845,18 +2844,6 @@ files = [ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, ] -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - [[package]] name = "socksio" version = "1.0.0" @@ -3130,21 +3117,21 @@ files = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "urllib3-2.6.1-py3-none-any.whl", hash = "sha256:e67d06fe947c36a7ca39f4994b08d73922d40e6cca949907be05efa6fd75110b"}, + {file = "urllib3-2.6.1.tar.gz", hash = "sha256:5379eb6e1aba4088bae84f8242960017ec8d8e3decf30480b3a1abdaa9671a3f"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "uvicorn" diff --git a/pyproject.toml b/pyproject.toml index ab40ecb3..fd0e671e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "bbot-server" version = "0.1.0" description = "" authors = ["TheTechromancer"] -license = "GPL-v3.0" +license = "AGPL-3.0" [tool.poetry.dependencies] python = "^3.10" diff --git a/tests/test_applets/test_applet_activity.py b/tests/test_applets/test_applet_activity.py index b11c43ff..fe026423 100644 --- a/tests/test_applets/test_applet_activity.py +++ b/tests/test_applets/test_applet_activity.py @@ -1,4 +1,5 @@ from tests.test_applets.base import BaseAppletTest +from bbot_server.modules.targets.targets_models import CreateTarget class TestAppletActivity(BaseAppletTest): @@ -9,8 +10,10 @@ async def setup(self): assert [a async for a in self.bbot_server.list_activities()] == [] # create some targets - await self.bbot_server.create_target(name="evilcorp1", seeds=["www2.evilcorp.com"]) - await self.bbot_server.create_target(name="evilcorp2", seeds=["www.evilcorp.com", "api.evilcorp.com"]) + target1 = CreateTarget(name="evilcorp1", target=["tech.evilcorp.com"]) + target2 = CreateTarget(name="evilcorp2", target=["evilcorp.com"], blacklist=["api.evilcorp.com"]) + self.target1 = await self.bbot_server.create_target(target1) + self.target2 = await self.bbot_server.create_target(target2) async def after_scan_1(self): activities = [a async for a in self.bbot_server.list_activities()] @@ -31,6 +34,25 @@ async def after_scan_2(self): assert activities assert all(a.get("host", "").endswith("evilcorp.amazonaws.com") for a in activities) + activities = [a async for a in self.bbot_server.query_activities(domain="tech.evilcorp.com")] + sorted_descriptions = sorted(a["description"] for a in activities) + assert sorted_descriptions == [ + "Host t1.tech.evilcorp.com became in-scope for target evilcorp1 due to in-scope host SELF->t1.tech.evilcorp.com", + "Host t1.tech.evilcorp.com became in-scope for target evilcorp2 due to in-scope host SELF->t1.tech.evilcorp.com", + "Host t2.tech.evilcorp.com became in-scope for target evilcorp1 due to in-scope host SELF->t2.tech.evilcorp.com", + "Host t2.tech.evilcorp.com became in-scope for target evilcorp2 due to in-scope host SELF->t2.tech.evilcorp.com", + "New DNS link: t1.tech.evilcorp.com -(A)-> [192.168.1.1]", + "New DNS link: t2.tech.evilcorp.com -(A)-> [192.168.1.2]", + "New asset: [t1.tech.evilcorp.com]", + "New asset: [t2.tech.evilcorp.com]", + "New open port: [t1.tech.evilcorp.com:443]", + "New open port: [t1.tech.evilcorp.com:80]", + "New open port: [t2.tech.evilcorp.com:443]", + "New technology discovered on t1.tech.evilcorp.com: [cpe:/a:apache:http_server:2.4.12]", + "New technology discovered on t2.tech.evilcorp.com: [cpe:/a:apache:http_server:2.4.12]", + "New technology discovered on t2.tech.evilcorp.com: [cpe:/a:microsoft:internet_information_services]", + ] + # activities aggregation aggregate_result = [ a @@ -40,10 +62,17 @@ async def after_scan_2(self): ) ] assert aggregate_result == [ - {"_id": "t1.tech.evilcorp.com", "count": 5}, - {"_id": "t2.tech.evilcorp.com", "count": 5}, + {"_id": "t1.tech.evilcorp.com", "count": 7}, + {"_id": "t2.tech.evilcorp.com", "count": 7}, ] # test count count = await self.bbot_server.count_activities(domain="tech.evilcorp.com") - assert count == 10 + assert count == 14 + + # NOTE: we do not allow filtering activities by target ID. + # Why? Because activities can grow to a much larger size than assets, and maintaining up-to-date target IDs on them can become too expensive. + # If you want to filter activities by target ID, get the asset hosts you need, then query activities for those hosts. + # activities = [a async for a in self.bbot_server.query_activities(target_id=self.target1.id)] + # assert activities + # assert all(self.target1.id in a.scope for a in activities) diff --git a/tests/test_applets/test_applet_assets.py b/tests/test_applets/test_applet_assets.py index 2efd5bde..9b44fd2a 100644 --- a/tests/test_applets/test_applet_assets.py +++ b/tests/test_applets/test_applet_assets.py @@ -3,6 +3,7 @@ from bbot_server.assets import Asset from bbot_server.errors import BBOTServerValueError +from bbot_server.modules.targets.targets_models import CreateTarget from tests.test_applets.base import BaseAppletTest from ..conftest import INGEST_PROCESSING_DELAY @@ -269,10 +270,11 @@ async def after_archive(self): async def test_applet_target_filter(bbot_server, bbot_events): bbot_server = await bbot_server(needs_watchdog=True) - target1 = await bbot_server.create_target( - whitelist=["evilcorp.com", "127.0.0.0/30"], + target1 = CreateTarget( + target=["evilcorp.com", "127.0.0.0/30"], blacklist=["localhost.evilcorp.com"], ) + target1 = await bbot_server.create_target(target1) # ingest BBOT events scan1_events, scan2_events = bbot_events @@ -338,10 +340,11 @@ async def test_applet_target_filter(bbot_server, bbot_events): assert set(hosts) == all_hosts_target1 # new target - target = await bbot_server.create_target( - whitelist=["1.2.3.0/24"], + target = CreateTarget( + target=["1.2.3.0/24"], blacklist=["www2.evilcorp.com"], ) + target = await bbot_server.create_target(target) # wait for events to be tagged with new target await asyncio.sleep(1) diff --git a/tests/test_applets/test_applet_events.py b/tests/test_applets/test_applet_events.py index 87f2bab3..5300dece 100644 --- a/tests/test_applets/test_applet_events.py +++ b/tests/test_applets/test_applet_events.py @@ -85,7 +85,7 @@ async def after_scan_2(self): modules = {} for event in host_events: modules[event.module] = modules.get(event.module, 0) + 1 - assert modules == {"TARGET": 2, "speculate": 2} + assert modules == {"SEED": 2, "speculate": 2} host_events = [e async for e in self.bbot_server.list_events(host="com")] assert host_events == [] diff --git a/tests/test_applets/test_applet_findings.py b/tests/test_applets/test_applet_findings.py index 88b480db..d667510a 100644 --- a/tests/test_applets/test_applet_findings.py +++ b/tests/test_applets/test_applet_findings.py @@ -1,6 +1,7 @@ import asyncio from hashlib import sha1 from tests.test_applets.base import BaseAppletTest +from bbot_server.modules.targets.targets_models import CreateTarget class TestAppletFindings(BaseAppletTest): @@ -11,8 +12,10 @@ async def setup(self): assert [f async for f in self.bbot_server.list_findings()] == [] # create some targets - await self.bbot_server.create_target(name="evilcorp1", seeds=["www2.evilcorp.com"]) - await self.bbot_server.create_target(name="evilcorp2", seeds=["www.evilcorp.com", "api.evilcorp.com"]) + target1 = CreateTarget(name="evilcorp1", target=["www2.evilcorp.com"]) + target2 = CreateTarget(name="evilcorp2", target=["www.evilcorp.com", "api.evilcorp.com"]) + self.target1 = await self.bbot_server.create_target(target1) + self.target2 = await self.bbot_server.create_target(target2) async def after_scan_1(self): # we should have 2 findings @@ -98,7 +101,8 @@ async def after_scan_2(self): assert {f.host for f in findings2} == {"www.evilcorp.com", "api.evilcorp.com"} # create a new target that matches one finding - await self.bbot_server.create_target(name="evilcorp3", seeds=["www.evilcorp.com"]) + target = CreateTarget(name="evilcorp3", target=["www.evilcorp.com"]) + target = await self.bbot_server.create_target(target) # the finding should be automatically associated with the target for _ in range(60): findings = [f async for f in self.bbot_server.list_findings(target_id="evilcorp3")] diff --git a/tests/test_applets/test_applet_scans.py b/tests/test_applets/test_applet_scans.py index 371d01d5..4686b651 100644 --- a/tests/test_applets/test_applet_scans.py +++ b/tests/test_applets/test_applet_scans.py @@ -6,6 +6,7 @@ from bbot_server import BBOTServer from bbot_server.errors import BBOTServerValueError +from bbot_server.modules.targets.targets_models import CreateTarget from ..conftest import INGEST_PROCESSING_DELAY, log @@ -81,7 +82,8 @@ async def test_scan_with_invalid_preset(bbot_server, bbot_agent): preset = await bbot_server.create_preset( preset={"name": "preset1", "description": "preset1 description", "modules": ["invalid"]} ) - target = await bbot_server.create_target(name="target1", seeds=["127.0.0.1"]) + target = CreateTarget(name="target1", target=["127.0.0.1"]) + target = await bbot_server.create_target(target) await bbot_server.start_scan(name="scan1", preset_id=preset.id, target_id=target.id) for _ in range(30): @@ -117,9 +119,10 @@ async def watch_events(): else: assert False, "Agent did not become ready" - target = await bbot_server.create_target( - name="target1", seeds=["127.0.0.1"], whitelist=["127.0.0.2"], blacklist=["127.0.0.3"], strict_dns_scope=True + target = CreateTarget( + name="target1", target=["127.0.0.2"], seeds=["127.0.0.1"], blacklist=["127.0.0.3"], strict_dns_scope=True ) + target = await bbot_server.create_target(target) preset = await bbot_server.create_preset( preset={"name": "preset1", "description": "preset1 description", "scan_name": "teh_scan"} ) @@ -154,8 +157,8 @@ async def watch_events(): assert len(scan_events) == 2 for scan_event in scan_events: assert scan_event.data_json["name"] == "teh_scan" + assert scan_event.data_json["target"]["target"] == ["127.0.0.2"] assert scan_event.data_json["target"]["seeds"] == ["127.0.0.1"] - assert scan_event.data_json["target"]["whitelist"] == ["127.0.0.2"] assert scan_event.data_json["target"]["blacklist"] == ["127.0.0.3"] assert scan_event.data_json["target"]["strict_dns_scope"] == True @@ -201,7 +204,8 @@ async def test_queued_scan_cancellation(bbot_server): """ bbot_server = await bbot_server() - target = await bbot_server.create_target(name="target1", seeds=["evilcorp.com"]) + target = CreateTarget(name="target1", target=["evilcorp.com"]) + target = await bbot_server.create_target(target) preset = await bbot_server.create_preset(preset={"name": "preset1", "description": "preset1 description"}) scan = await bbot_server.start_scan(name="scan1", target_id=target.id, preset_id=preset.id) @@ -229,7 +233,8 @@ async def test_running_scan_cancellation(bbot_agent, bbot_watchdog): infinite_module_dir = Path(__file__).parent.parent / "bbot_modules" # start scan - target = await bbot_server.create_target(name="target1", seeds=["evilcorp.com"]) + target = CreateTarget(name="target1", target=["evilcorp.com"]) + target = await bbot_server.create_target(target) preset = await bbot_server.create_preset( preset={ "name": "preset1", diff --git a/tests/test_applets/test_applet_stats.py b/tests/test_applets/test_applet_stats.py index 69559caa..9ecdcea2 100644 --- a/tests/test_applets/test_applet_stats.py +++ b/tests/test_applets/test_applet_stats.py @@ -1,13 +1,15 @@ import asyncio +from bbot_server.modules.targets.targets_models import CreateTarget async def test_applet_stats(bbot_server, bbot_events): bbot_server = await bbot_server(needs_watchdog=True) - target1 = await bbot_server.create_target( - whitelist=["evilcorp.com"], + target1 = CreateTarget( + target=["evilcorp.com"], blacklist=["www.evilcorp.com"], ) + target1 = await bbot_server.create_target(target1) # ingest BBOT events for scan_events in bbot_events: diff --git a/tests/test_applets/test_applet_targets.py b/tests/test_applets/test_applet_targets.py index 27c58928..7f740583 100644 --- a/tests/test_applets/test_applet_targets.py +++ b/tests/test_applets/test_applet_targets.py @@ -3,6 +3,7 @@ from contextlib import suppress from tests.test_applets.base import BaseAppletTest +from bbot_server.modules.targets.targets_models import CreateTarget from bbot_server.errors import BBOTServerNotFoundError, BBOTServerValueError @@ -27,13 +28,14 @@ async def handle_activity(): assert num_targets == 0 # create a target - target1 = await bbot_server.create_target( + target1 = CreateTarget( name="target1", description="target1 description", + target=["127.0.0.1", "evilcorp.com"], seeds=["localhost"], - whitelist=["127.0.0.1", "evilcorp.com"], blacklist=["127.0.0.2"], ) + target1 = await bbot_server.create_target(target1) assert target1.created is not None assert target1.modified is not None @@ -51,7 +53,7 @@ async def handle_activity(): assert target.id == target1.id assert target.description == "target1 description" assert target.seeds == ["localhost"] - assert target.whitelist == ["127.0.0.1", "evilcorp.com"] + assert target.target == ["127.0.0.1", "evilcorp.com"] assert target.blacklist == ["127.0.0.2"] assert target.default is True @@ -62,7 +64,11 @@ async def handle_activity(): # creating a target with the same name should raise an error with pytest.raises(BBOTServerValueError, match='Target with name "target1" already exists'): try: - await bbot_server.create_target(name="target1", seeds=["localhost"]) + target = CreateTarget( + name="target1", + target=["localhost"], + ) + target = await bbot_server.create_target(target) except BBOTServerValueError as e: assert e.detail["name"] == "target1" raise @@ -70,26 +76,28 @@ async def handle_activity(): # creating a target with the same hash should raise an error with pytest.raises(BBOTServerValueError, match="Identical target already exists"): try: - await bbot_server.create_target( + target2 = CreateTarget( name="asdgasdgasdf", seeds=["localhost"], - whitelist=["127.0.0.1", "evilcorp.com"], + target=["127.0.0.1", "evilcorp.com"], blacklist=["127.0.0.2"], ) + target2 = await bbot_server.create_target(target2) except BBOTServerValueError as e: assert e.detail["hash"] == target1.hash raise # create a second target - target2 = await bbot_server.create_target( + target2 = CreateTarget( name="target2", description="target2 description", seeds=["localhost"], - whitelist=["127.0.0.1", "evilcorp.com", "localhost2"], + target=["127.0.0.1", "evilcorp.com", "localhost2"], blacklist=["127.0.0.2"], ) + target2 = await bbot_server.create_target(target2) - assert target2.whitelist_hash != target1.whitelist_hash + assert target2.target_hash != target1.target_hash assert target2.blacklist_hash == target1.blacklist_hash assert target2.seed_hash == target1.seed_hash assert target2.hash != target1.hash @@ -102,7 +110,7 @@ async def handle_activity(): assert target.id == target2.id assert target.description == "target2 description" assert target.seeds == ["localhost"] - assert target.whitelist == ["127.0.0.1", "evilcorp.com", "localhost2"] + assert target.target == ["127.0.0.1", "evilcorp.com", "localhost2"] assert target.blacklist == ["127.0.0.2"] assert target.default is False @@ -129,7 +137,7 @@ async def handle_activity(): # edit target2 target2.name = "target2_edited" target2.seeds = [] - target2.whitelist = [] + target2.target = [] target2.blacklist = [] await asyncio.sleep(0.1) await bbot_server.update_target(target2.id, target2) @@ -138,18 +146,19 @@ async def handle_activity(): target = targets[0] assert target.name == "target2_edited" assert target.seeds == [] - assert target.whitelist == [] + assert target.target == [] assert target.blacklist == [] assert abs(target.created - target.modified) >= 0.1, "Modified timestamp wasn't updated" # add target3 - target3 = await bbot_server.create_target( + target3 = CreateTarget( name="target3", description="target3 description", seeds=["localhost", "localhost3"], - whitelist=["127.0.0.1", "evilcorp.com", "localhost3"], + target=["127.0.0.1", "evilcorp.com", "localhost3"], blacklist=["127.0.0.2"], ) + target3 = await bbot_server.create_target(target3) # set target3 as the default target await bbot_server.set_default_target(target3.id) @@ -163,13 +172,14 @@ async def handle_activity(): assert target.default is False # create target4 - await bbot_server.create_target( + target4 = CreateTarget( name="target4", description="target4 description", seeds=["localhost"], - whitelist=["127.0.0.1", "evilcorp.com", "localhost4"], + target=["127.0.0.1", "evilcorp.com", "localhost4"], blacklist=["127.0.0.2"], ) + target4 = await bbot_server.create_target(target4) # deleting the default target without specifying a new default target should raise an error with pytest.raises( @@ -204,27 +214,32 @@ async def handle_activity(): async def test_target_default_names(bbot_server): bbot_server = await bbot_server() - with pytest.raises(BBOTServerValueError, match="Must provide at least one seed"): - await bbot_server.create_target() + target1 = CreateTarget() + with pytest.raises(BBOTServerValueError, match="Must provide at least one seed or target entry"): + await bbot_server.create_target(target1) - target1 = await bbot_server.create_target(seeds=["evilcorp.com"]) + target1 = CreateTarget(target=["evilcorp.com"]) + target1 = await bbot_server.create_target(target1) assert target1.name == "Target 1" - target2 = await bbot_server.create_target(seeds=["evilcorp.org"]) + target2 = CreateTarget(target=["evilcorp.org"]) + target2 = await bbot_server.create_target(target2) assert target2.name == "Target 2" - target3 = await bbot_server.create_target(seeds=["evilcorp.net"]) + target3 = CreateTarget(target=["evilcorp.net"]) + target3 = await bbot_server.create_target(target3) assert target3.name == "Target 3" async def test_target_size(bbot_server): bbot_server = await bbot_server() - target = await bbot_server.create_target( + target = CreateTarget( seeds=["evilcorp.com", "1.2.3.4/30"], - whitelist=["evilcorp.com", "1.2.3.4/29"], + target=["evilcorp.com", "1.2.3.4/29"], blacklist=["www.evilcorp.com", "test.evilcorp.com", "1.2.3.5/28"], ) + target = await bbot_server.create_target(target) assert target.seed_size == 5 # /30 (4 hosts) + 1 domain - assert target.whitelist_size == 9 # /29 (8 hosts) + 1 domain + assert target.target_size == 9 # /29 (8 hosts) + 1 domain assert target.blacklist_size == 18 # /28 (16 hosts) + 2 domains @@ -232,18 +247,19 @@ async def test_scope_checks(bbot_server): bbot_server = await bbot_server() # simple target - await bbot_server.create_target( + target1 = CreateTarget( name="target1", description="target1 description", - seeds=["evilcorp.com"], + target=["evilcorp.com"], ) + await bbot_server.create_target(target1) targets = await bbot_server.get_targets() assert len(targets) == 1 target = targets[0] assert target.name == "target1" - assert target.seeds == ["evilcorp.com"] - assert target.whitelist == None + assert target.target == ["evilcorp.com"] + assert target.seeds == None assert target.blacklist == [] assert await bbot_server.in_scope("evilcorp.com") == True @@ -254,13 +270,14 @@ async def test_scope_checks(bbot_server): assert await bbot_server.in_scope("http://test.evilcorp.net") == False # complex target - target2 = await bbot_server.create_target( + target2 = CreateTarget( name="target2", description="target2 description", seeds=["evilcorp.org"], - whitelist=["127.0.0.1/24", "external.evilcorp.org"], + target=["127.0.0.1/24", "external.evilcorp.org"], blacklist=["127.0.0.2", "test.external.evilcorp.org", "RE:plumbus"], ) + target2 = await bbot_server.create_target(target2) # default target is still target1 assert await bbot_server.in_scope("evilcorp.org") == False @@ -296,22 +313,23 @@ async def setup(self): assert await self.bbot_server.get_targets() == [] # target with domain blacklist - self.target1 = await self.bbot_server.create_target( + target1 = CreateTarget( name="evilcorp", description="evilcorp target", seeds=["evilcorp.com"], - whitelist=["evilcorp.com"], + target=["evilcorp.com"], blacklist=["www.evilcorp.com"], ) - + self.target1 = await self.bbot_server.create_target(target1) # target with IP blacklist - self.target2 = await self.bbot_server.create_target( + target2 = CreateTarget( name="www evilcorp", description="www evilcorp target", seeds=["evilcorp.com"], - whitelist=["www.evilcorp.com", "localhost.evilcorp.com", "127.0.0.1"], + target=["www.evilcorp.com", "localhost.evilcorp.com", "127.0.0.1"], blacklist=["127.0.0.2"], ) + self.target2 = await self.bbot_server.create_target(target2) async def after_scan_1(self): assets = [a async for a in self.bbot_server.list_assets()] @@ -353,8 +371,15 @@ async def after_scan_2(self): "127.0.0.1", } + target_1_assets_filtered = {a.host async for a in self.bbot_server.list_assets(target_id="evilcorp")} + assert target_1_assets_filtered == target_1_assets + target_2_assets_filtered = {a.host async for a in self.bbot_server.list_assets(target_id="www evilcorp")} + assert target_2_assets_filtered == target_2_assets + target_assets_default = {a.host async for a in self.bbot_server.list_assets(target_id="DEFAULT")} + assert target_assets_default == target_1_assets + # add evilcorp.azure.com to target2 - self.target2.whitelist = ["127.0.0.0/24"] + self.target2.target = ["127.0.0.0/24"] await self.bbot_server.update_target(self.target2.id, self.target2) await asyncio.sleep(1.0) diff --git a/tests/test_applets/test_applet_technologies.py b/tests/test_applets/test_applet_technologies.py index ddc6048a..0358a935 100644 --- a/tests/test_applets/test_applet_technologies.py +++ b/tests/test_applets/test_applet_technologies.py @@ -1,5 +1,6 @@ import asyncio from tests.test_applets.base import BaseAppletTest +from bbot_server.modules.targets.targets_models import CreateTarget class TestAppletTechnologies(BaseAppletTest): @@ -110,7 +111,8 @@ async def after_scan_2(self): } # create a new target that matches two technologies - await self.bbot_server.create_target(seeds=["t1.tech.evilcorp.com"], name="target1") + target = CreateTarget(name="target1", target=["t1.tech.evilcorp.com"]) + target = await self.bbot_server.create_target(target) # the technologies should be automatically associated with the target for _ in range(60): techs = [t async for t in self.bbot_server.list_technologies(target_id="target1")] diff --git a/tests/test_cli/test_cli_assetctl.py b/tests/test_cli/test_cli_assetctl.py index 12747b6b..8239350e 100644 --- a/tests/test_cli/test_cli_assetctl.py +++ b/tests/test_cli/test_cli_assetctl.py @@ -109,7 +109,7 @@ def test_cli_assetctl(bbot_server_http, bbot_watchdog, bbot_out_file, bbot_event "create", "--name", "test-target", - "--seeds", + "--target", str(target_file), "--blacklist", str(blacklist_file), diff --git a/tests/test_cli/test_cli_findingctl.py b/tests/test_cli/test_cli_findingctl.py index 91ba736b..9bdcc832 100644 --- a/tests/test_cli/test_cli_findingctl.py +++ b/tests/test_cli/test_cli_findingctl.py @@ -106,7 +106,7 @@ def test_cli_findingctl(bbot_server_http, bbot_watchdog, bbot_out_file): # create target target_file = BBOT_SERVER_TEST_DIR / "targets" target_file.write_text("www2.evilcorp.com") - command = BBCTL_COMMAND + ["scan", "target", "create", "--seeds", target_file, "--name", "evilcorp1"] + command = BBCTL_COMMAND + ["scan", "target", "create", "--target", target_file, "--name", "evilcorp1"] process = subprocess.run(command, capture_output=True, text=True) assert process.returncode == 0 diff --git a/tests/test_cli/test_cli_scanctl.py b/tests/test_cli/test_cli_scanctl.py index 8598e204..91c0e857 100644 --- a/tests/test_cli/test_cli_scanctl.py +++ b/tests/test_cli/test_cli_scanctl.py @@ -35,7 +35,7 @@ def test_cli_scan_start(bbot_server_http, bbot_watchdog, bbot_agent): "scan", "target", "create", - "--seeds", + "--target", str(target_file), "--name", "thetarget", @@ -90,11 +90,10 @@ def test_cli_scan_start(bbot_server_http, bbot_watchdog, bbot_agent): ) for _ in range(120): - process = subprocess.run(BBCTL_COMMAND + ["scan", "list", "--json"], capture_output=True, text=True) + process = subprocess.run(BBCTL_COMMAND + ["scan", "list", "--json"], capture_output=True, text=True, timeout=5) scans = [Scan(**orjson.loads(line)) for line in process.stdout.splitlines()] if scans[0].status == "FINISHED": break - sleep(0.5) else: assert False, f"Scan did not finish in time, scans: {scans}" @@ -156,12 +155,12 @@ def test_cli_scan_ingest(bbot_server_http, bbot_watchdog, bbot_out_file, bbot_ev assert {s.name for s in out_scan_runs} == {scan1_name, scan2_name} # test text version - process = subprocess.run(BBCTL_COMMAND + ["scan", "list"], capture_output=True, text=True) + process = subprocess.run(BBCTL_COMMAND + ["scan", "list"], capture_output=True, text=True, timeout=5) assert scan1_name in process.stdout assert scan2_name in process.stdout # test csv version - process = subprocess.run(BBCTL_COMMAND + ["scan", "list", "--csv"], capture_output=True, text=True) + process = subprocess.run(BBCTL_COMMAND + ["scan", "list", "--csv"], capture_output=True, text=True, timeout=5) assert len([l for l in process.stdout.splitlines() if l.strip()]) == 3 assert scan1_name in process.stdout assert scan2_name in process.stdout diff --git a/tests/test_cli/test_cli_targetctl.py b/tests/test_cli/test_cli_targetctl.py index c9c6d265..6572bef5 100644 --- a/tests/test_cli/test_cli_targetctl.py +++ b/tests/test_cli/test_cli_targetctl.py @@ -13,20 +13,20 @@ def test_cli_targetctl(bbot_server_http): assert process.stdout == "" # create a target (nonexistent file should fail) - seeds_file = BBOT_SERVER_TEST_DIR / "seeds.txt" - seeds_file.unlink(missing_ok=True) + target_file = BBOT_SERVER_TEST_DIR / "target.txt" + target_file.unlink(missing_ok=True) process = subprocess.run( - BBCTL_COMMAND + ["--no-color", "scan", "target", "create", "--seeds", str(seeds_file)], + BBCTL_COMMAND + ["--no-color", "scan", "target", "create", "--target", str(target_file)], capture_output=True, text=True, ) assert process.returncode == 1 - assert f"Unable to find seeds at {seeds_file}" in process.stderr + assert f"Unable to find target at {target_file}" in process.stderr # create a target - seeds_file.write_text("evilcorp.com\nevilcorp.net") + target_file.write_text("evilcorp.com\nevilcorp.net") process = subprocess.run( - BBCTL_COMMAND + ["--no-color", "scan", "target", "create", "--seeds", str(seeds_file)], + BBCTL_COMMAND + ["--no-color", "scan", "target", "create", "--target", str(target_file)], capture_output=True, text=True, ) @@ -34,11 +34,11 @@ def test_cli_targetctl(bbot_server_http): assert "Target created successfully" in process.stderr target = orjson.loads(process.stdout) assert target["name"] == "Target 1" - assert set(target["seeds"]) == {"evilcorp.com", "evilcorp.net"} + assert set(target["target"]) == {"evilcorp.com", "evilcorp.net"} # creating the same target again should fail process = subprocess.run( - BBCTL_COMMAND + ["--no-color", "scan", "target", "create", "--seeds", str(seeds_file)], + BBCTL_COMMAND + ["--no-color", "scan", "target", "create", "--target", str(target_file)], capture_output=True, text=True, ) @@ -46,9 +46,9 @@ def test_cli_targetctl(bbot_server_http): assert "Identical target already exists" in process.stderr # create a second target - seeds_file.write_text("evilcorp.org") + target_file.write_text("evilcorp.org") process = subprocess.run( - BBCTL_COMMAND + ["--no-color", "scan", "target", "create", "--seeds", str(seeds_file), "--strict-scope"], + BBCTL_COMMAND + ["--no-color", "scan", "target", "create", "--target", str(target_file), "--strict-scope"], capture_output=True, text=True, ) @@ -57,9 +57,9 @@ def test_cli_targetctl(bbot_server_http): assert process.returncode == 0 target2 = orjson.loads(process.stdout) assert target2["name"] == "Target 2" - assert set(target2["seeds"]) == {"evilcorp.org"} + assert set(target2["target"]) == {"evilcorp.org"} - seeds_file.unlink() + target_file.unlink() # list targets (json) process = subprocess.run(BBCTL_COMMAND + ["scan", "target", "list", "--json"], capture_output=True, text=True) @@ -67,8 +67,8 @@ def test_cli_targetctl(bbot_server_http): targets = [Target(**orjson.loads(line)) for line in process.stdout.splitlines()] assert len(targets) == 2 targets = {t.name: t for t in targets} - assert set(targets["Target 1"].seeds) == {"evilcorp.com", "evilcorp.net"} - assert set(targets["Target 2"].seeds) == {"evilcorp.org"} + assert set(targets["Target 1"].target) == {"evilcorp.com", "evilcorp.net"} + assert set(targets["Target 2"].target) == {"evilcorp.org"} assert targets["Target 1"].strict_dns_scope is False assert targets["Target 2"].strict_dns_scope is True @@ -77,14 +77,14 @@ def test_cli_targetctl(bbot_server_http): assert process.returncode == 0 lines = process.stdout.splitlines() assert len(lines) == 3 - assert lines[0] == "name,description,seeds,whitelist,blacklist,strict_scope,created,modified" + assert lines[0] == "name,description,target,seeds,blacklist,strict_scope,created,modified" assert lines[1].startswith("Target 1,,2,0,0,No,") assert lines[2].startswith("Target 2,,1,0,0,Yes,") # list targets (text) process = subprocess.run(BBCTL_COMMAND + ["scan", "target", "list"], capture_output=True, text=True) assert process.returncode == 0 - assert process.stdout.count("Target") == 2 + assert process.stdout.count("Target") == 3 # delete target1 (must specify name or id) process = subprocess.run( diff --git a/tests/test_cli/test_cli_technologyctl.py b/tests/test_cli/test_cli_technologyctl.py index 88b5261e..d71385a1 100644 --- a/tests/test_cli/test_cli_technologyctl.py +++ b/tests/test_cli/test_cli_technologyctl.py @@ -150,7 +150,7 @@ def test_cli_technologyctl(bbot_server_http, bbot_watchdog, bbot_out_file): # create a new target that matches two technologies target_file = BBOT_SERVER_TEST_DIR / "targets" target_file.write_text("t2.tech.evilcorp.com") - command = BBCTL_COMMAND + ["scan", "target", "create", "--seeds", target_file, "--name", "evilcorp1"] + command = BBCTL_COMMAND + ["scan", "target", "create", "--target", target_file, "--name", "evilcorp1"] process = subprocess.run(command, capture_output=True, text=True) assert process.returncode == 0