diff --git a/content/response_integrations/google/observe_it/.python-version b/content/response_integrations/google/observe_it/.python-version
new file mode 100644
index 000000000..902b2c90c
--- /dev/null
+++ b/content/response_integrations/google/observe_it/.python-version
@@ -0,0 +1 @@
+3.11
\ No newline at end of file
diff --git a/content/response_integrations/google/observe_it/actions/Ping.py b/content/response_integrations/google/observe_it/actions/Ping.py
new file mode 100644
index 000000000..8f48384e0
--- /dev/null
+++ b/content/response_integrations/google/observe_it/actions/Ping.py
@@ -0,0 +1,100 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+from soar_sdk.SiemplifyUtils import output_handler
+from soar_sdk.ScriptResult import EXECUTION_STATE_COMPLETED, EXECUTION_STATE_FAILED
+from soar_sdk.SiemplifyAction import SiemplifyAction
+from TIPCommon import extract_configuration_param
+
+from ..core.ObserveITManager import ObserveITManager
+from ..core.ObserveITConstants import PROVIDER_NAME, PING_SCRIPT_NAME
+from ..core.ObserveITExceptions import ObserveITException
+
+
+@output_handler
+def main():
+ siemplify = SiemplifyAction()
+ siemplify.script_name = PING_SCRIPT_NAME
+ siemplify.LOGGER.info("=" * 20 + " Main - Params Init " + "=" * 20)
+
+ api_root = extract_configuration_param(
+ siemplify,
+ provider_name=PROVIDER_NAME,
+ param_name="API Root",
+ input_type=str,
+ is_mandatory=True,
+ print_value=True,
+ )
+
+ client_id = extract_configuration_param(
+ siemplify,
+ provider_name=PROVIDER_NAME,
+ param_name="Client ID",
+ input_type=str,
+ is_mandatory=True,
+ print_value=False,
+ )
+
+ client_secret = extract_configuration_param(
+ siemplify,
+ provider_name=PROVIDER_NAME,
+ param_name="Client Secret",
+ input_type=str,
+ is_mandatory=True,
+ print_value=False,
+ )
+
+ verify_ssl = extract_configuration_param(
+ siemplify,
+ provider_name=PROVIDER_NAME,
+ param_name="Verify SSL",
+ input_type=bool,
+ is_mandatory=True,
+ print_value=True,
+ )
+
+ siemplify.LOGGER.info("=" * 20 + " Main - Started " + "=" * 20)
+
+ try:
+ manager = ObserveITManager(
+ api_root=api_root,
+ client_id=client_id,
+ client_secret=client_secret,
+ verify_ssl=verify_ssl,
+ )
+
+ manager.test_connectivity()
+
+ output_message = "Successfully connected to the ObserveIT server with the provided connection parameters!"
+ result = "true"
+ status = EXECUTION_STATE_COMPLETED
+
+ except ObserveITException as e:
+ output_message = f"Failed to connect to the ObserveIT server! Error is {e}"
+ result = "false"
+ status = EXECUTION_STATE_FAILED
+ siemplify.LOGGER.error(output_message)
+ siemplify.LOGGER.exception(e)
+
+ siemplify.LOGGER.info("=" * 20 + " Main - Finished " + "=" * 20)
+ siemplify.LOGGER.info(f"Status: {status}")
+ siemplify.LOGGER.info(f"Result: {result}")
+ siemplify.LOGGER.info(f"Output Message: {output_message}")
+
+ siemplify.end(output_message, result, status)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/content/response_integrations/google/observe_it/actions/Ping.yaml b/content/response_integrations/google/observe_it/actions/Ping.yaml
new file mode 100644
index 000000000..784eb2eb9
--- /dev/null
+++ b/content/response_integrations/google/observe_it/actions/Ping.yaml
@@ -0,0 +1,23 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Ping
+description: Test connectivity to the ObserveIT with parameters provided at the integration
+ configuration page on the Marketplace tab.
+documentation_link: https://cloud.google.com/chronicle/docs/soar/marketplace-integrations/observeit#ping
+integration_identifier: ObserveIT
+parameters: []
+dynamic_results_metadata: []
+creator: klim.lyapin@siemplify.co
+simulation_data_json: '{"Entities":[]}'
diff --git a/content/response_integrations/google/observe_it/actions/__init__.py b/content/response_integrations/google/observe_it/actions/__init__.py
new file mode 100644
index 000000000..9f71a2dc3
--- /dev/null
+++ b/content/response_integrations/google/observe_it/actions/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/content/response_integrations/google/observe_it/connectors/ObserveitAlertsConnector.py b/content/response_integrations/google/observe_it/connectors/ObserveitAlertsConnector.py
new file mode 100644
index 000000000..954aabbb1
--- /dev/null
+++ b/content/response_integrations/google/observe_it/connectors/ObserveitAlertsConnector.py
@@ -0,0 +1,314 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import sys
+import os
+from datetime import timedelta
+
+from soar_sdk.SiemplifyConnectors import SiemplifyConnectorExecution
+from soar_sdk.SiemplifyUtils import (
+ output_handler,
+ utc_now,
+ convert_string_to_datetime,
+ convert_datetime_to_unix_time,
+ unix_now,
+)
+from EnvironmentCommon import EnvironmentHandleForFileSystem, validate_map_file_exists
+from TIPCommon import extract_connector_param
+
+from ..core.ObserveITManager import ObserveITManager
+from ..core.ObserveITValidator import ObserveITValidator
+from ..core.ObserveITCommon import ObserveITCommon
+from ..core.ObserveITConstants import (
+ IDS_FILE,
+ MAP_FILE,
+ ALERTS_CONNECTOR_NAME,
+ WHITELIST_FILTER,
+ BLACKLIST_FILTER,
+ ACCEPTABLE_TIME_INTERVAL_IN_MINUTES,
+)
+
+
+@output_handler
+def main(is_test_run):
+ connector_starting_time = unix_now()
+ alerts = []
+ all_alerts = []
+ siemplify = SiemplifyConnectorExecution()
+ siemplify.script_name = ALERTS_CONNECTOR_NAME
+
+ if is_test_run:
+ siemplify.LOGGER.info(
+ '***** This is an "IDE Play Button" "Run Connector once" test run ******'
+ )
+
+ siemplify.LOGGER.info("=" * 20 + " Main - Params Init " + "=" * 20)
+
+ environment = extract_connector_param(
+ siemplify,
+ param_name="Environment Field Name",
+ input_type=str,
+ is_mandatory=False,
+ print_value=True,
+ )
+
+ environment_regex = extract_connector_param(
+ siemplify,
+ param_name="Environment Regex Pattern",
+ input_type=str,
+ is_mandatory=False,
+ print_value=True,
+ )
+
+ api_root = extract_connector_param(
+ siemplify,
+ param_name="API Root",
+ input_type=str,
+ is_mandatory=True,
+ print_value=True,
+ )
+
+ client_id = extract_connector_param(
+ siemplify,
+ param_name="Client ID",
+ input_type=str,
+ is_mandatory=True,
+ print_value=False,
+ )
+
+ client_secret = extract_connector_param(
+ siemplify,
+ param_name="Client Secret",
+ input_type=str,
+ is_mandatory=True,
+ print_value=False,
+ )
+
+ severity = extract_connector_param(
+ siemplify,
+ param_name="Lowest Severity To Fetch",
+ input_type=str,
+ is_mandatory=True,
+ print_value=True,
+ )
+
+ offset_hours = extract_connector_param(
+ siemplify,
+ param_name="Fetch Max Hours Backwards",
+ input_type=int,
+ is_mandatory=False,
+ print_value=True,
+ )
+
+ limit = extract_connector_param(
+ siemplify,
+ param_name="Max Alerts To Fetch",
+ input_type=int,
+ is_mandatory=False,
+ print_value=True,
+ )
+
+ whitelist_as_blacklist = extract_connector_param(
+ siemplify,
+ param_name="Use whitelist as a blacklist",
+ input_type=bool,
+ is_mandatory=True,
+ print_value=True,
+ )
+
+ verify_ssl = extract_connector_param(
+ siemplify,
+ param_name="Use SSL",
+ input_type=bool,
+ is_mandatory=True,
+ print_value=True,
+ )
+
+ python_process_timeout = extract_connector_param(
+ siemplify,
+ param_name="PythonProcessTimeout",
+ input_type=int,
+ is_mandatory=True,
+ print_value=True,
+ )
+
+ try:
+ ObserveITValidator.validate_severity(severity)
+
+ whitelist_as_blacklist = (
+ BLACKLIST_FILTER if whitelist_as_blacklist else WHITELIST_FILTER
+ )
+
+ siemplify.LOGGER.info("=" * 20 + " Main - Started " + "=" * 20)
+
+ map_file_path = os.path.join(siemplify.run_folder, MAP_FILE)
+ validate_map_file_exists(map_file_path, siemplify.LOGGER)
+
+ observe_it_common = ObserveITCommon(siemplify.LOGGER)
+ environment_common = EnvironmentHandleForFileSystem(
+ map_file_path,
+ siemplify.LOGGER,
+ environment,
+ environment_regex,
+ siemplify.context.connector_info.environment,
+ )
+
+ if is_test_run:
+ siemplify.LOGGER.info("This is a test run. Ignoring stored timestamps")
+ last_success_time_datetime = observe_it_common.validate_timestamp(
+ utc_now() - timedelta(hours=offset_hours), offset_hours
+ )
+ else:
+ last_success_time_datetime = observe_it_common.validate_timestamp(
+ siemplify.fetch_timestamp(datetime_format=True), offset_hours
+ )
+
+ # Read already existing alerts ids
+ existing_ids_file_path = os.path.join(siemplify.run_folder, IDS_FILE)
+ existing_ids = observe_it_common.read_ids(existing_ids_file_path)
+
+ observe_it_manager = ObserveITManager(
+ api_root=api_root,
+ client_id=client_id,
+ client_secret=client_secret,
+ verify_ssl=verify_ssl,
+ )
+
+ if is_test_run:
+ siemplify.LOGGER.info("This is a TEST run. Only 1 alert will be processed.")
+ limit = 1
+
+ fetched_alerts = observe_it_manager.get_alerts(
+ severity=severity,
+ timestamp=convert_datetime_to_unix_time(last_success_time_datetime),
+ limit=limit,
+ )
+
+ siemplify.LOGGER.info(
+ f"Fetched {len(fetched_alerts)} incidents since {last_success_time_datetime.isoformat()}."
+ )
+
+ filtered_alerts = observe_it_common.filter_old_ids(
+ alerts=fetched_alerts, existing_ids=existing_ids
+ )
+
+ siemplify.LOGGER.info(
+ f"Filtered {len(filtered_alerts)} new incidents since {last_success_time_datetime.isoformat()}."
+ )
+
+ filtered_alerts = sorted(filtered_alerts, key=lambda inc: inc.rising_value)
+ except Exception as e:
+ siemplify.LOGGER.error(str(e))
+ siemplify.LOGGER.exception(e)
+ sys.exit(1)
+
+ for alert in filtered_alerts:
+ try:
+ if observe_it_common.is_approaching_timeout(
+ connector_starting_time, python_process_timeout
+ ):
+ siemplify.LOGGER.info(
+ "Timeout is approaching. Connector will gracefully exit."
+ )
+ break
+
+ if len(alerts) >= limit:
+ siemplify.LOGGER.info(f"Stop processing alerts, limit {limit} reached")
+ break
+
+ siemplify.LOGGER.info(f"Processing alert {alert.id}")
+
+ if not alert.pass_time_filter():
+ siemplify.LOGGER.info(
+ f"Alert {alert.id} is newer than {ACCEPTABLE_TIME_INTERVAL_IN_MINUTES} minutes. Stopping connector..."
+ )
+ # Breaking connector loop because next alerts can't pass acceptable time anyway.
+ break
+
+ all_alerts.append(alert)
+ existing_ids.append(alert.id)
+
+ if not alert.pass_whitelist_or_blacklist_filter(
+ siemplify.whitelist, whitelist_as_blacklist
+ ):
+ siemplify.LOGGER.info(
+ f"Alert with id: {alert.id} and name: {alert.rule_name} did not pass {whitelist_as_blacklist} filter. Skipping..."
+ )
+ continue
+
+ is_overflowed = False
+ siemplify.LOGGER.info(
+ f"Started creating alert {alert.id}", alert_id=alert.id
+ )
+ alert_info = alert.to_alert_info(environment_common)
+ siemplify.LOGGER.info(
+ f"Finished creating Alert {alert.id}", alert_id=alert.id
+ )
+
+ try:
+ is_overflowed = siemplify.is_overflowed_alert(
+ environment=alert_info.environment,
+ alert_identifier=alert_info.ticket_id,
+ alert_name=alert_info.rule_generator,
+ product=alert_info.device_product,
+ )
+
+ except Exception as e:
+ siemplify.LOGGER.error(
+ f"Error validation connector overflow, ERROR: {e}"
+ )
+ siemplify.LOGGER.exception(e)
+
+ if is_test_run:
+ raise
+
+ if is_overflowed:
+ siemplify.LOGGER.info(
+ f"{alert_info.rule_generator}-{alert_info.ticket_id}-{alert_info.environment}-{alert_info.device_product} found as overflow alert. Skipping..."
+ )
+ continue
+ else:
+ alerts.append(alert_info)
+ siemplify.LOGGER.info(f"Alert {alert.id} was created.")
+
+ except Exception as e:
+ siemplify.LOGGER.error(
+ f"Failed to process incident {alert.id}", alert_id=alert.id
+ )
+ siemplify.LOGGER.exception(e)
+
+ if is_test_run:
+ raise
+
+ if not is_test_run:
+ if all_alerts:
+ new_timestamp = convert_string_to_datetime(all_alerts[-1].rising_value)
+ siemplify.save_timestamp(new_timestamp=new_timestamp)
+ siemplify.LOGGER.info(
+ f"New timestamp {new_timestamp.isoformat()} has been saved"
+ )
+
+ observe_it_common.write_ids(existing_ids_file_path, existing_ids)
+
+ siemplify.LOGGER.info(f"Alerts Processed: {len(alerts)} of {len(all_alerts)}")
+ siemplify.LOGGER.info(f"Created total of {len(alerts)} alerts")
+
+ siemplify.LOGGER.info("=" * 20 + " Main - Finished " + "=" * 20)
+ siemplify.return_package(alerts)
+
+
+if __name__ == "__main__":
+ is_test_run = not (len(sys.argv) < 2 or sys.argv[1] == "True")
+ main(is_test_run)
diff --git a/content/response_integrations/google/observe_it/connectors/ObserveitAlertsConnector.yaml b/content/response_integrations/google/observe_it/connectors/ObserveitAlertsConnector.yaml
new file mode 100644
index 000000000..88170c1df
--- /dev/null
+++ b/content/response_integrations/google/observe_it/connectors/ObserveitAlertsConnector.yaml
@@ -0,0 +1,147 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: ObserveIT - Alerts Connector
+parameters:
+- name: DeviceProductField
+ default_value: Product Name
+ type: string
+ description: Enter the source field name in order to retrieve the Product Field
+ name.
+ is_mandatory: true
+ is_advanced: false
+ mode: regular
+- name: EventClassId
+ default_value: eventType
+ type: string
+ description: Enter the source field name in order to retrieve the Event Field
+ name.
+ is_mandatory: true
+ is_advanced: false
+ mode: regular
+- name: Environment Field Name
+ default_value: ''
+ type: string
+ description: Describes the name of the field where the environment name is stored.
+ If the environment field isn't found, the environment is the default environment.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Environment Regex Pattern
+ default_value: .*
+ type: string
+ description: 'A regex pattern to run on the value found in the "Environment Field
+ Name" field.
+
+ Default is .* to catch all and return the value unchanged. Used to allow the
+ user to manipulate the environment field via regex logic If the regex pattern
+ is null or empty, or the environment value is null, the final environment
+ result is the default environment.'
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: PythonProcessTimeout
+ default_value: 180
+ type: integer
+ description: Timeout limit for the python process running the current script.
+ is_mandatory: true
+ is_advanced: false
+ mode: regular
+- name: API Root
+ default_value: https://x.x.x.x:x
+ type: string
+ description: API root of ObserveIT server.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Client ID
+ default_value: ''
+ type: string
+ description: Client ID of the ObserveIT app.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Client Secret
+ default_value: ''
+ type: password
+ description: Client Secret of the ObserveIT app.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Lowest Severity To Fetch
+ default_value: Medium
+ type: string
+ description: 'Lowest severity that will be used to fetch Alerts. Possible values:
+ Low, Medium, High, Critical.'
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Fetch Max Hours Backwards
+ default_value: 1
+ type: integer
+ description: Number of hours before the first connector iteration to retrieve
+ alerts from. This parameter applies to the initial connector iteration after
+ you enable the connector for the first time, or used as a fallback value in
+ cases where connector's last run timestamp expires.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Max Alerts To Fetch
+ default_value: 25
+ type: integer
+ description: How many alerts to process per one connector iteration.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Use whitelist as a blacklist
+ default_value: 'false'
+ type: boolean
+ description: If enabled, whitelist will be used as a blacklist.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Use SSL
+ default_value: 'true'
+ type: boolean
+ description: Option to enable SSL/TLS connection
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Proxy Server Address
+ default_value: ''
+ type: string
+ description: The address of the proxy server to use
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Proxy Username
+ default_value: ''
+ type: string
+ description: The proxy username to authenticate with
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Proxy Password
+ default_value: ''
+ type: password
+ description: The proxy password to authenticate with
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+description: Pull alerts from ObserveIT.
+integration: ObserveIT
+documentation_link: https://cloud.google.com/chronicle/docs/soar/marketplace-integrations/observeit#observeit-alerts-connector
+rules: []
+is_connector_rules_supported: false
+creator: klim.lyapin@siemplify.co
diff --git a/content/response_integrations/google/observe_it/connectors/__init__.py b/content/response_integrations/google/observe_it/connectors/__init__.py
new file mode 100644
index 000000000..9f71a2dc3
--- /dev/null
+++ b/content/response_integrations/google/observe_it/connectors/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/content/response_integrations/google/observe_it/core/ObserveITBuilder.py b/content/response_integrations/google/observe_it/core/ObserveITBuilder.py
new file mode 100644
index 000000000..6ccc9e16e
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITBuilder.py
@@ -0,0 +1,51 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import re
+
+from .ObserveITDatamodels import Alert
+
+
+class ObserveITBuilder:
+ def build_alert(self, alert_data):
+ # type: (dict) -> Alert
+ """
+ Build alert from response dict
+ @param alert_data: Response dict
+ @return: Alert
+ """
+ return Alert(raw_data=alert_data, **self._change_param_names(alert_data))
+
+ def _change_param_names(self, data):
+ # type: (dict) -> dict
+ """
+ Convert all camel keys in dict to snake one
+ @param data: dictionary with camel case keys
+ @return: dictionary with snake case keys
+ """
+ return {
+ self._covert_camel_to_snake(key): value for key, value in list(data.items())
+ }
+
+ @staticmethod
+ def _covert_camel_to_snake(camel):
+ # type: (str or unicode) -> str or unicode
+ """
+ Converts camel case to snake
+ @param camel: Camel case string
+ @return: Snake case string
+ """
+ camel = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", camel)
+ return re.sub("([a-z0-9])([A-Z])", r"\1_\2", camel).lower().replace("__", "_")
diff --git a/content/response_integrations/google/observe_it/core/ObserveITCommon.py b/content/response_integrations/google/observe_it/core/ObserveITCommon.py
new file mode 100644
index 000000000..8732f7e72
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITCommon.py
@@ -0,0 +1,136 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+from soar_sdk.SiemplifyUtils import utc_now, unix_now
+import datetime
+import json
+import os
+
+from .ObserveITConstants import LIMIT_IDS_IN_IDS_FILE, ALERT_ID_FIELD, TIMEOUT_THRESHOLD
+
+
+class ObserveITCommon:
+ def __init__(self, siemplify_logger):
+ self.siemplify_logger = siemplify_logger
+
+ @staticmethod
+ def is_approaching_timeout(connector_starting_time, python_process_timeout):
+ """
+ Check if a timeout is approaching.
+ :return: {bool} True if timeout is close, False otherwise
+ """
+ processing_time_ms = unix_now() - connector_starting_time
+ return processing_time_ms > python_process_timeout * 1000 * TIMEOUT_THRESHOLD
+
+ @staticmethod
+ def validate_timestamp(last_run_timestamp, offset_in_hours):
+ """
+ Validate timestamp in range
+ :param last_run_timestamp: {datetime} last run timestamp
+ :param offset_in_hours: {datetime} last run timestamp
+ :return: {datetime} if first run, return current time minus offset time, else return timestamp from file
+ """
+ current_time = utc_now()
+ # Check if first run
+ if current_time - last_run_timestamp > datetime.timedelta(
+ hours=offset_in_hours
+ ):
+ return current_time - datetime.timedelta(hours=offset_in_hours)
+ else:
+ return last_run_timestamp
+
+ def read_ids(self, ids_file_path):
+ """
+ Read existing alerts IDs from ids file (from last 24h only)
+ :param ids_file_path: {unicode} Path to the IDS file.
+ :return: {list} List of ids
+ """
+ if not os.path.exists(ids_file_path):
+ return []
+
+ try:
+ with open(ids_file_path, "r") as f:
+ return json.loads(f.read())
+ except Exception as e:
+ self.siemplify_logger.error(f"Unable to read ids file: {e}")
+ self.siemplify_logger.exception(e)
+ return []
+
+ def write_ids(self, ids_file_path, ids):
+ """
+ Write ids to the ids file
+ :param ids_file_path: {unicode} Path to the IDS file.
+ :param ids: {list} The ids to write to the file
+ :return: {None}
+ """
+ ids = ids[-LIMIT_IDS_IN_IDS_FILE:]
+ try:
+ if not os.path.exists(os.path.dirname(ids_file_path)):
+ os.makedirs(os.path.dirname(ids_file_path))
+
+ with open(ids_file_path, "w") as f:
+ try:
+ for chunk in json.JSONEncoder().iterencode(ids):
+ f.write(chunk)
+ except:
+ f.seek(0)
+ f.truncate()
+ f.write("[]")
+ raise
+ except Exception as e:
+ self.siemplify_logger.error(
+ f"Failed writing IDs to IDs file, ERROR: {str(e)}"
+ )
+ self.siemplify_logger.exception(e)
+
+ @staticmethod
+ def filter_old_ids(alerts, existing_ids, id_field=ALERT_ID_FIELD):
+ """
+ Filter ids that were already processed
+ :param alerts: {list} The objects to filter
+ :param existing_ids: {list} The ids to filter
+ :param id_field: {str or unicode} Id filed to get from alert
+ :return: {list} The filtered alerts
+ """
+ new_alerts = []
+
+ for alert in alerts:
+ if getattr(alert, id_field) not in existing_ids:
+ new_alerts.append(alert)
+
+ return new_alerts
+
+ @staticmethod
+ def convert_comma_separated_to_list(comma_separated):
+ # type: (unicode or str) -> list
+ """
+ Convert comma-separated string to list
+ @param comma_separated: String with comma-separated values
+ @return: List of values
+ """
+ return (
+ [item.strip() for item in comma_separated.split(",")]
+ if comma_separated
+ else []
+ )
+
+ @staticmethod
+ def convert_list_to_comma_separated_string(iterable):
+ # type: (list or set) -> unicode
+ """
+ Convert list to comma separated string
+ @param iterable: List or Set to covert
+ """
+ return ", ".join(iterable)
diff --git a/content/response_integrations/google/observe_it/core/ObserveITConstants.py b/content/response_integrations/google/observe_it/core/ObserveITConstants.py
new file mode 100644
index 000000000..7049ade7d
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITConstants.py
@@ -0,0 +1,43 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+PROVIDER_NAME = "ObserveIT"
+DEVICE_VENDOR = "ObserveIT"
+DEVICE_PRODUCT = "ObserveIT"
+ALERTS_LIMIT = 100
+# Do not change the order, It's used in Manager._get_severities_from
+SEVERITIES = ["Low", "Medium", "High", "Critical"]
+
+
+# REQUEST METHODS
+GET = "GET"
+POST = "POST"
+
+# CONNECTORS
+ALERTS_CONNECTOR_NAME = f"{PROVIDER_NAME} - Alerts Connector"
+IDS_FILE = "ids.json"
+MAP_FILE = "map.json"
+ALERT_ID_FIELD = "id"
+LIMIT_IDS_IN_IDS_FILE = 1000
+TIMEOUT_THRESHOLD = 0.9
+ACCEPTABLE_TIME_INTERVAL_IN_MINUTES = 5
+WHITELIST_FILTER = "whitelist"
+BLACKLIST_FILTER = "blacklist"
+
+# ACTIONS
+PING_SCRIPT_NAME = f"{PROVIDER_NAME} - Ping"
+
+# SIEM
+OBSERVE_IT_TO_SIEM_SEVERITY = {"Low": 40, "Medium": 60, "High": 80, "Critical": 100}
diff --git a/content/response_integrations/google/observe_it/core/ObserveITDatamodels.py b/content/response_integrations/google/observe_it/core/ObserveITDatamodels.py
new file mode 100644
index 000000000..31683a37d
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITDatamodels.py
@@ -0,0 +1,186 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import datetime
+import uuid
+from soar_sdk.SiemplifyUtils import (
+ convert_string_to_unix_time,
+ convert_string_to_datetime,
+ utc_now,
+)
+from soar_sdk.SiemplifyConnectorsDataModel import AlertInfo
+
+from .ObserveITConstants import (
+ DEVICE_VENDOR,
+ DEVICE_PRODUCT,
+ BLACKLIST_FILTER,
+ ACCEPTABLE_TIME_INTERVAL_IN_MINUTES,
+ OBSERVE_IT_TO_SIEM_SEVERITY,
+)
+
+
+class BaseData:
+ def __init__(self, raw_data):
+ self.raw_data = raw_data
+
+ def to_json(self):
+ return self.raw_data
+
+
+class Alert(BaseData):
+ def __init__(
+ self,
+ raw_data,
+ id=None,
+ created_at=None,
+ observed_at=None,
+ timezone_offset=None,
+ session_id=None,
+ session_day=None,
+ endpoint_id=None,
+ user_activity_event_id=None,
+ endpoint_name=None,
+ domain_name=None,
+ login_name=None,
+ secondary_domain_name=None,
+ secondary_login_name=None,
+ remote_host_name=None,
+ remote_address=None,
+ os=None,
+ application_name=None,
+ window_title=None,
+ process_executable=None,
+ command=None,
+ command_params=None,
+ accessed_url=None,
+ accessed_site_name=None,
+ severity=None,
+ rule_name=None,
+ rule_desc=None,
+ rule_category_name=None,
+ details=None,
+ sql_command=None,
+ database_name=None,
+ sql_user_name=None,
+ user_activity_observed_at=None,
+ collector_url=None,
+ session_url=None,
+ event_playback_url=None,
+ details_url=None,
+ collector_id=None,
+ rising_value=None,
+ **kwargs
+ ):
+ super(Alert, self).__init__(raw_data)
+ self.id = id
+ self.created_at = created_at
+ self.observed_at = observed_at
+ self.timezone_offset = timezone_offset
+ self.session_id = session_id
+ self.session_day = session_day
+ self.endpoint_id = endpoint_id
+ self.user_activity_event_id = user_activity_event_id
+ self.endpoint_name = endpoint_name
+ self.domain_name = domain_name
+ self.login_name = login_name
+ self.secondary_domain_name = secondary_domain_name
+ self.secondary_login_name = secondary_login_name
+ self.remote_host_name = remote_host_name
+ self.remote_address = remote_address
+ self.os = os
+ self.application_name = application_name
+ self.window_title = window_title
+ self.process_executable = process_executable
+ self.command = command
+ self.command_params = command_params
+ self.accessed_url = accessed_url
+ self.accessed_site_name = accessed_site_name
+ self.severity = severity
+ self.rule_name = rule_name
+ self.rule_desc = rule_desc
+ self.rule_category_name = rule_category_name
+ self.details = details
+ self.sql_command = sql_command
+ self.database_name = database_name
+ self.sql_user_name = sql_user_name
+ self.user_activity_observed_at = user_activity_observed_at
+ self.collector_url = collector_url
+ self.session_url = session_url
+ self.event_playback_url = event_playback_url
+ self.details_url = details_url
+ self.collector_id = collector_id
+ self.rising_value = rising_value
+
+ @property
+ def priority(self):
+ """
+ Converts API severity format to SIEM priority
+ @return: SIEM priority
+ """
+ return OBSERVE_IT_TO_SIEM_SEVERITY.get(self.severity, -1)
+
+ def to_alert_info(self, environment):
+ # type: (EnvironmentHandle) -> AlertInfo
+ """
+ Creates Siemplify Alert Info based on API alert information
+ @param environment: EnvironmentHandle object
+ @return: Alert Info object
+ """
+ alert_info = AlertInfo()
+ alert_info.ticket_id = self.id
+ alert_info.display_id = str(uuid.uuid4())
+ alert_info.name = self.rule_name
+ alert_info.description = self.rule_desc
+ alert_info.device_vendor = DEVICE_VENDOR
+ alert_info.device_product = DEVICE_PRODUCT
+ alert_info.priority = self.priority
+ alert_info.rule_generator = self.rule_category_name
+ alert_info.start_time = convert_string_to_unix_time(self.created_at)
+ alert_info.end_time = convert_string_to_unix_time(self.created_at)
+ alert_info.events = [self.raw_data]
+ alert_info.environment = environment.get_environment(self.raw_data)
+ alert_info.extensions = {
+ "collectorUrl": self.collector_url,
+ "sessionUrl": self.session_url,
+ "eventPlaybackUrl": self.event_playback_url,
+ "detailsUrl": self.details_url,
+ }
+
+ return alert_info
+
+ def pass_time_filter(self):
+ # type: () -> bool
+ """
+ Check if now - created_at time is older than acceptable time in minutes
+ @return: Is older or not
+ """
+ return utc_now() - convert_string_to_datetime(
+ self.created_at
+ ) > datetime.timedelta(minutes=ACCEPTABLE_TIME_INTERVAL_IN_MINUTES)
+
+ def pass_whitelist_or_blacklist_filter(self, rules_list, whitelist_filter_type):
+ """
+ Determine whether threat pass the whitelist/blacklist filter or not.
+ :param rules_list: {list} The rules list provided by user.
+ :param whitelist_filter_type: {unicode} whitelist filter type. Possible values are WHITELIST_FILTER, BLACKLIST_FILTER
+ :return: {bool} Whether threat pass the whitelist/blacklist filter or not.
+ """
+ if not rules_list:
+ return True
+
+ if whitelist_filter_type == BLACKLIST_FILTER:
+ return self.rule_name not in rules_list
+
+ return self.rule_name in rules_list
diff --git a/content/response_integrations/google/observe_it/core/ObserveITEndpoints.py b/content/response_integrations/google/observe_it/core/ObserveITEndpoints.py
new file mode 100644
index 000000000..7d5334b7a
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITEndpoints.py
@@ -0,0 +1,54 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import urllib.parse
+
+from .ObserveITConstants import GET, POST
+
+
+class ObserveITEndpoints:
+ @staticmethod
+ def get_authorization_endpoint(api_root):
+ # type: (str or unicode) -> (str or unicode, str or unicode)
+ """
+ Get method and endpoint to call with
+ @param api_root: API Root
+ @return: Method and Endpoint
+ """
+ return POST, urllib.parse.urljoin(api_root, "/v2/apis/auth/oauth/token")
+
+ @staticmethod
+ def get_test_connectivity_endpoint(api_root):
+ # type: (str or unicode) -> (str or unicode, str or unicode)
+ """
+ Get method and endpoint to call with
+ @param api_root: API Root
+ @return: Method and Endpoint
+ """
+ return GET, urllib.parse.urljoin(
+ api_root, "/v2/apis/report;realm=observeit/_health"
+ )
+
+ @staticmethod
+ def get_alerts_endpoint(api_root):
+ # type: (str or unicode) -> (str or unicode, str or unicode)
+ """
+ Get method and endpoint to call with
+ @param api_root: API Root
+ @return: Method and Endpoint
+ """
+ return GET, urllib.parse.urljoin(
+ api_root, "/v2/apis/report;realm=observeit/reports/alert_v0/data"
+ )
diff --git a/content/response_integrations/google/observe_it/core/ObserveITExceptions.py b/content/response_integrations/google/observe_it/core/ObserveITExceptions.py
new file mode 100644
index 000000000..5b045c93d
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITExceptions.py
@@ -0,0 +1,33 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+class ObserveITException(Exception):
+ pass
+
+
+class ObserveITConnectivityException(ObserveITException):
+ pass
+
+
+class ObserveITAuthorizationException(ObserveITException):
+ pass
+
+
+class ObserveITAlertsException(ObserveITException):
+ pass
+
+
+class ObserveITSeverityException(ObserveITException):
+ pass
diff --git a/content/response_integrations/google/observe_it/core/ObserveITManager.py b/content/response_integrations/google/observe_it/core/ObserveITManager.py
new file mode 100644
index 000000000..982aed9ed
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITManager.py
@@ -0,0 +1,125 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import requests
+
+from .ObserveITEndpoints import ObserveITEndpoints
+from .ObserveITPayload import ObserveITPayload
+from .ObserveITBuilder import ObserveITBuilder
+from .ObserveITExceptions import (
+ ObserveITException,
+ ObserveITAuthorizationException,
+ ObserveITConnectivityException,
+ ObserveITAlertsException,
+)
+from .ObserveITConstants import ALERTS_LIMIT, SEVERITIES
+
+
+class ObserveITManager:
+ def __init__(self, api_root, client_id, client_secret, verify_ssl=False):
+ self.api_root = api_root
+ self.builder = ObserveITBuilder()
+ self.session = requests.Session()
+ self.session.verify = verify_ssl
+ self.session.headers.update(
+ {
+ "Authorization": f"Bearer {self._get_authorization_token(client_id, client_secret)}"
+ }
+ )
+
+ def _get_authorization_token(self, client_id, client_secret):
+ # type: (str or unicode, str or unicode) -> str or unicode
+ """
+ Get Authorization token
+ @param client_id: Client ID to authorize with
+ @param client_secret: Client Secret to authorize with
+ @return: Access token
+ """
+ method, url = ObserveITEndpoints.get_authorization_endpoint(self.api_root)
+ payload = ObserveITPayload.get_authorization_payload(client_id, client_secret)
+
+ response = self.session.request(method, url, **payload)
+ self._validate_response(response, ObserveITAuthorizationException)
+
+ return response.json().get("access_token")
+
+ def test_connectivity(self):
+ # type: () -> bool
+ """
+ Test connectivity
+ @return: Is connected successfully or not
+ """
+ method, url = ObserveITEndpoints.get_test_connectivity_endpoint(self.api_root)
+ payload = ObserveITPayload.get_test_connectivity_payload()
+
+ response = self.session.request(method, url, **payload)
+ self._validate_response(response, ObserveITConnectivityException)
+
+ return response.json().get("_status", {}).get("status") == 200
+
+ def get_alerts(self, severity, timestamp, limit=ALERTS_LIMIT):
+ # type: (str or unicode, int, int) -> [Alert]
+ """
+ Get alerts with filtering.
+ @param severity: Lowest severity to start from
+ @param timestamp: Timestamp to start from
+ @param limit: How many alerts to take
+ @return: List of Alerts
+ """
+ method, url = ObserveITEndpoints.get_alerts_endpoint(self.api_root)
+ severities = self._get_severities_from(severity)
+ payload = ObserveITPayload.get_alerts_payload(
+ severities, timestamp, max(limit, ALERTS_LIMIT)
+ )
+
+ response = self.session.request(method, url, **payload)
+ self._validate_response(response, ObserveITAlertsException)
+
+ alerts_data = response.json().get("data", [])
+
+ return [self.builder.build_alert(alert_data) for alert_data in alerts_data]
+
+ @staticmethod
+ def _get_severities_from(lowest_severity):
+ # type: (str or unicode) -> list
+ """
+ Get the highest severities started from the lowest.
+ Ex. Low -> [Low, Medium, High, Critical]
+ Ex. High -> [High, Critical]
+ Ex. Unknown -> []
+ @param lowest_severity: Lowest severity to start from
+ @return: List of the highest severities
+ """
+ return (
+ SEVERITIES[SEVERITIES.index(lowest_severity) :]
+ if lowest_severity in SEVERITIES
+ else []
+ )
+
+ @staticmethod
+ def _validate_response(response, custom_exception=ObserveITException):
+ # type: (requests.Response, type(ObserveITException)) -> None or ObserveITException
+ """
+ Validate Response
+ @param response: Response
+ @param custom_exception: Exception with which to raise
+ """
+ try:
+ response.raise_for_status()
+ except requests.HTTPError as e:
+ response_json = response.json()
+ raise custom_exception(
+ f'{response_json.get("_status", {}).get("message")}. \n{e}'
+ )
diff --git a/content/response_integrations/google/observe_it/core/ObserveITPayload.py b/content/response_integrations/google/observe_it/core/ObserveITPayload.py
new file mode 100644
index 000000000..f97ccf87c
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITPayload.py
@@ -0,0 +1,64 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+class ObserveITPayload:
+ @staticmethod
+ def get_authorization_payload(client_id, client_secret):
+ # type: (str or unicode, str or unicode) -> dict
+ """
+ Get payload dict to make request with
+ @param client_id: Client ID to authorize with
+ @param client_secret: Client Secret to authorize with
+ @return: Payload for request
+ """
+ return {
+ "data": {
+ "grant_type": "client_credentials",
+ "client_id": client_id,
+ "client_secret": client_secret,
+ "scope": "*",
+ },
+ "headers": {"Content-Type": "application/x-www-form-urlencoded"},
+ }
+
+ @staticmethod
+ def get_test_connectivity_payload():
+ # type: () -> dict
+ """
+ Get payload dict to make request with
+ @return: Payload for request
+ """
+ return {}
+
+ @staticmethod
+ def get_alerts_payload(severities, timestamp, limit):
+ # type: (str or unicode, int, int) -> dict
+ """
+ Get payload dict to make request with
+ @param severities: Severities to start from
+ @param timestamp: Timestamp to start from
+ @param limit: How many alerts to take
+ @return: Payload for request
+ """
+ severities_filter = ",".join(
+ [f"eq(severity,{severity})" for severity in severities]
+ )
+
+ return {
+ "params": {
+ # TODO: Write a constructor for RQL.
+ "rql": f"and(select(),or({severities_filter}),ge(risingValue,epoch:{timestamp}),limit({limit},0))"
+ }
+ }
diff --git a/content/response_integrations/google/observe_it/core/ObserveITValidator.py b/content/response_integrations/google/observe_it/core/ObserveITValidator.py
new file mode 100644
index 000000000..16f212dbc
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/ObserveITValidator.py
@@ -0,0 +1,33 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+from .ObserveITExceptions import ObserveITSeverityException
+from .ObserveITConstants import OBSERVE_IT_TO_SIEM_SEVERITY
+from .ObserveITCommon import ObserveITCommon
+
+
+class ObserveITValidator:
+ @staticmethod
+ def validate_severity(severity):
+ # type: (str or unicode) -> None or ObserveITSeverityException
+ """
+ Validate if severity is acceptable
+ @param severity: Severity. Ex. Low
+ """
+ acceptable_severities = list(OBSERVE_IT_TO_SIEM_SEVERITY.keys())
+ if severity not in acceptable_severities:
+ raise ObserveITSeverityException(
+ f'Severity "{severity}" is not in {ObserveITCommon.convert_list_to_comma_separated_string(acceptable_severities)}'
+ )
diff --git a/content/response_integrations/google/observe_it/core/__init__.py b/content/response_integrations/google/observe_it/core/__init__.py
new file mode 100644
index 000000000..9f71a2dc3
--- /dev/null
+++ b/content/response_integrations/google/observe_it/core/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/content/response_integrations/google/observe_it/definition.yaml b/content/response_integrations/google/observe_it/definition.yaml
new file mode 100644
index 000000000..e430498e2
--- /dev/null
+++ b/content/response_integrations/google/observe_it/definition.yaml
@@ -0,0 +1,47 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+identifier: ObserveIT
+name: ObserveIT
+parameters:
+- name: API Root
+ default_value: https://
:
+ type: string
+ description: ''
+ is_mandatory: true
+ integration_identifier: ObserveIT
+- name: Client ID
+ default_value: ''
+ type: string
+ description: ''
+ is_mandatory: true
+ integration_identifier: ObserveIT
+- name: Client Secret
+ default_value: ''
+ type: password
+ description: ''
+ is_mandatory: true
+ integration_identifier: ObserveIT
+- name: Verify SSL
+ default_value: 'True'
+ type: boolean
+ description: ''
+ is_mandatory: true
+ integration_identifier: ObserveIT
+documentation_link: https://cloud.google.com/chronicle/docs/soar/marketplace-integrations/observeit
+categories:
+- Endpoint Security
+- Security
+svg_logo_path: resources/logo.svg
+image_path: resources/image.png
diff --git a/content/response_integrations/google/observe_it/pyproject.toml b/content/response_integrations/google/observe_it/pyproject.toml
new file mode 100644
index 000000000..6a6190cbf
--- /dev/null
+++ b/content/response_integrations/google/observe_it/pyproject.toml
@@ -0,0 +1,34 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[project]
+name = "ObserveIT"
+version = "6.0"
+description = "The ObserveIT platform correlates activity and data movement, empowering security teams to identify user risk, detect to insider-led data breaches, and accelerate security incident response. Leveraging a powerful contextual intelligence engine and a library of over 400 threat templates drawn from customers and leading cybersecurity frameworks, ObserveIT delivers rapid time to value and proven capability to streamline insider threat programs."
+requires-python = ">=3.11"
+dependencies = [ "requests==2.32.4", "tipcommon",]
+
+[dependency-groups]
+dev = [ "pytest>=9.0.3", "pytest-json-report>=1.5.0", "soar-sdk",]
+
+[tool.uv]
+[[tool.uv.index]]
+url = "https://pypi.org/simple"
+default = true
+
+[tool.uv.sources.tipcommon]
+path = "../../../../packages/tipcommon/whls/TIPCommon-1.0.11-py2.py3-none-any.whl"
+
+[tool.uv.sources.soar-sdk]
+git = "https://github.com/chronicle/soar-sdk.git"
diff --git a/content/response_integrations/google/observe_it/release_notes.yaml b/content/response_integrations/google/observe_it/release_notes.yaml
new file mode 100644
index 000000000..ccf74b0fe
--- /dev/null
+++ b/content/response_integrations/google/observe_it/release_notes.yaml
@@ -0,0 +1,80 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+- description: NEW! ObserveIT integration
+ integration_version: 1.0
+ item_name: ObserveIT
+ item_type: Integration
+ publish_time: '2020-06-04'
+ ticket_number: TIPG-3160
+ new: true
+ regressive: false
+ deprecated: false
+ removed: false
+- description: ' Added integration support for the Playbook Simulator feature, allowing
+ you to build, test and edit your workflow logic in a pre-production environment.'
+ integration_version: 2.0
+ item_name: ObserveIT
+ item_type: Integration
+ publish_time: '2020-11-04'
+ ticket_number: TIPG-5303
+ new: false
+ regressive: false
+ deprecated: false
+ removed: false
+- description: 'ObserveIT integration - Important - Updated the integration code
+ to work with Python version 3.11. To ensure compatibility and avoid disruptions,
+ follow the upgrade best practices described in the following document: https://cloud.google.com/chronicle/docs/soar/respond/integrations-setup/upgrade-python-versions'
+ integration_version: 3.0
+ item_name: ObserveIT
+ item_type: Integration
+ publish_time: '2024-07-14'
+ ticket_number: '331751541'
+ new: false
+ regressive: false
+ deprecated: false
+ removed: false
+- description: ObserveIT - Alerts Connector - Improved description for 'Fetch Max
+ Hours Backwards' parameter.
+ integration_version: 4.0
+ item_name: ObserveIT - Alerts Connector
+ item_type: Connector
+ publish_time: '2024-08-08'
+ ticket_number: '333133526'
+ new: false
+ regressive: false
+ deprecated: false
+ removed: false
+- description: Updated integration metadata
+ integration_version: 5.0
+ item_name: ObserveIT
+ item_type: Integration
+ publish_time: '2025-10-29'
+ ticket_number: ''
+ new: false
+ regressive: false
+ deprecated: false
+ removed: false
+
+- description: 'Integration - Source code for the integration is now available publicly
+ on Github. Link to repo: https://github.com/chronicle/content-hub'
+ integration_version: 6.0
+ item_name: ObserveIT
+ item_type: Integration
+ publish_time: '2026-04-20'
+ new: false
+ regressive: false
+ deprecated: false
+ removed: false
+ ticket_number: '495762513'
diff --git a/content/response_integrations/google/observe_it/resources/ai/actions_ai_description.yaml b/content/response_integrations/google/observe_it/resources/ai/actions_ai_description.yaml
new file mode 100644
index 000000000..68f16a115
--- /dev/null
+++ b/content/response_integrations/google/observe_it/resources/ai/actions_ai_description.yaml
@@ -0,0 +1,93 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Ping:
+ ai_description: >-
+ ### General Description
+
+ The **Ping** action is a diagnostic tool designed to verify the connectivity between
+ Google SecOps and the ObserveIT server. It validates the provided configuration
+ parameters, including the API Root and OAuth2 credentials, by attempting to authenticate
+ and reach a health check endpoint.
+
+
+ ### Parameters Description
+
+ | Parameter | Type | Mandatory | Description |
+
+ | :--- | :--- | :--- | :--- |
+
+ | API Root | String | Yes | The base URL of the ObserveIT instance (e.g., `https://observeit.example.com`).
+ |
+
+ | Client ID | String | Yes | The Client ID required for OAuth2 authentication.
+ |
+
+ | Client Secret | String | Yes | The Client Secret required for OAuth2 authentication.
+ |
+
+ | Verify SSL | Boolean | Yes | Determines whether to verify the SSL certificate
+ of the ObserveIT server. |
+
+
+ ### Additional Notes
+
+ - This action does not process entities or modify any data.
+
+ - It is typically used during the initial setup of the ObserveIT integration or
+ for troubleshooting communication issues.
+
+
+ ### Flow Description
+
+ 1. **Configuration Retrieval**: The action extracts the API Root, Client ID, Client
+ Secret, and SSL verification settings from the integration's configuration.
+
+ 2. **Authentication**: It initializes the `ObserveITManager`, which triggers an
+ OAuth2 token request to the `/v2/apis/auth/oauth/token` endpoint using the provided
+ credentials.
+
+ 3. **Health Check**: Upon successful authentication, the action performs a GET
+ request to the ObserveIT health check endpoint (`/v2/apis/report;realm=observeit/_health`).
+
+ 4. **Status Validation**: The action checks if the health check response returns
+ a status code of 200. If successful, it reports a positive connection; otherwise,
+ it catches exceptions and reports a failure.
+ capabilities:
+ can_create_case_comments: false
+ can_create_insight: false
+ can_modify_alert_data: false
+ can_mutate_external_data: false
+ can_mutate_internal_data: false
+ can_update_entities: false
+ external_data_mutation_explanation: null
+ fetches_data: false
+ internal_data_mutation_explanation: null
+ categories:
+ enrichment: false
+ entity_usage:
+ entity_types: []
+ filters_by_additional_properties: false
+ filters_by_alert_identifier: false
+ filters_by_case_identifier: false
+ filters_by_creation_time: false
+ filters_by_entity_type: false
+ filters_by_identifier: false
+ filters_by_is_artifact: false
+ filters_by_is_enriched: false
+ filters_by_is_internal: false
+ filters_by_is_pivot: false
+ filters_by_is_suspicious: false
+ filters_by_is_vulnerable: false
+ filters_by_modification_time: false
diff --git a/content/response_integrations/google/observe_it/resources/image.png b/content/response_integrations/google/observe_it/resources/image.png
new file mode 100644
index 000000000..cde5b2ad2
Binary files /dev/null and b/content/response_integrations/google/observe_it/resources/image.png differ
diff --git a/content/response_integrations/google/observe_it/resources/logo.svg b/content/response_integrations/google/observe_it/resources/logo.svg
new file mode 100644
index 000000000..92d04c3b7
--- /dev/null
+++ b/content/response_integrations/google/observe_it/resources/logo.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/content/response_integrations/google/observe_it/tests/__init__.py b/content/response_integrations/google/observe_it/tests/__init__.py
new file mode 100644
index 000000000..9f71a2dc3
--- /dev/null
+++ b/content/response_integrations/google/observe_it/tests/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/content/response_integrations/google/observe_it/tests/common.py b/content/response_integrations/google/observe_it/tests/common.py
new file mode 100644
index 000000000..cb340e5ef
--- /dev/null
+++ b/content/response_integrations/google/observe_it/tests/common.py
@@ -0,0 +1,21 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import pathlib
+import json
+from integration_testing.common import get_def_file_content
+INTEGRATION_PATH: pathlib.Path = pathlib.Path(__file__).parent.parent
+CONFIG_PATH: pathlib.Path = pathlib.Path(__file__).parent / 'config.json'
+CONFIG: dict = get_def_file_content(CONFIG_PATH) if CONFIG_PATH.exists() else {}
\ No newline at end of file
diff --git a/content/response_integrations/google/observe_it/tests/config.json b/content/response_integrations/google/observe_it/tests/config.json
new file mode 100644
index 000000000..a8332b45c
--- /dev/null
+++ b/content/response_integrations/google/observe_it/tests/config.json
@@ -0,0 +1,6 @@
+{
+ "API Root": "https://:",
+ "Client ID": "",
+ "Client Secret": "",
+ "Verify SSL": "True"
+}
\ No newline at end of file
diff --git a/content/response_integrations/google/observe_it/tests/test_defaults/__init__.py b/content/response_integrations/google/observe_it/tests/test_defaults/__init__.py
new file mode 100644
index 000000000..9f71a2dc3
--- /dev/null
+++ b/content/response_integrations/google/observe_it/tests/test_defaults/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/content/response_integrations/google/observe_it/tests/test_defaults/test_imports.py b/content/response_integrations/google/observe_it/tests/test_defaults/test_imports.py
new file mode 100644
index 000000000..1243de833
--- /dev/null
+++ b/content/response_integrations/google/observe_it/tests/test_defaults/test_imports.py
@@ -0,0 +1,57 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+
+import importlib
+import pathlib
+
+from .. import common
+
+
+VALID_SUFFIXES = (".py",)
+
+
+def import_all_integration_modules(integration: pathlib.Path) -> None:
+ if not integration.exists():
+ msg: str = f"Cannot find integration {integration.name}"
+ raise AssertionError(msg)
+
+ imports: list[str] = _get_integration_modules_import_strings(integration)
+ for import_ in imports:
+ importlib.import_module(import_)
+
+
+def _get_integration_modules_import_strings(integration: pathlib.Path) -> list[str]:
+ results: list[str] = []
+ for package in integration.iterdir():
+ if not package.is_dir():
+ continue
+
+ for module in package.iterdir():
+ if not module.is_file() or module.suffix not in VALID_SUFFIXES:
+ continue
+
+ import_: str = _get_import_string(integration.stem, package.stem, module.stem)
+ results.append(import_)
+
+ return results
+
+
+def _get_import_string(integration: str, package: str, module: str) -> str:
+ return f"{integration}.{package}.{module}"
+
+
+def test_imports() -> None:
+ import_all_integration_modules(common.INTEGRATION_PATH)
diff --git a/content/response_integrations/google/observe_it/uv.lock b/content/response_integrations/google/observe_it/uv.lock
new file mode 100644
index 000000000..2c5682572
--- /dev/null
+++ b/content/response_integrations/google/observe_it/uv.lock
@@ -0,0 +1,579 @@
+version = 1
+revision = 3
+requires-python = ">=3.11"
+
+[[package]]
+name = "arrow"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+ { name = "tzdata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.2.25"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
+]
+
+[[package]]
+name = "cffi"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" },
+ { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
+ { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
+ { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
+ { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
+ { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
+ { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
+ { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
+ { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
+ { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
+ { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
+ { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
+ { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
+]
+
+[[package]]
+name = "chardet"
+version = "7.4.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/19/b6/9df434a8eeba2e6628c465a1dfa31034228ef79b26f76f46278f4ef7e49d/chardet-7.4.3.tar.gz", hash = "sha256:cc1d4eb92a4ec1c2df3b490836ffa46922e599d34ce0bb75cf41fd2bf6303d56", size = 784800, upload-time = "2026-04-13T21:33:39.803Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/19/52/505c207f334d51e937cbaa27ff95776e16e2d120e13cbe491cd7b3a70b50/chardet-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:25a862cddc6a9ac07023e808aedd297115345fbaabc2690479481ddc0f980e09", size = 870747, upload-time = "2026-04-13T21:32:56.916Z" },
+ { url = "https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97", size = 853210, upload-time = "2026-04-13T21:32:58.309Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/99/f6a822ad1bde25a4c38dc3e770485e78e0893dfd871cd6e18ed3ea3a795e/chardet-7.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc50f28bad067393cce0af9091052c3b8df7a23115afd8ba7b2e0947f0cef1f8", size = 873625, upload-time = "2026-04-13T21:32:59.606Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9", size = 883436, upload-time = "2026-04-13T21:33:01.351Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/63/0f43e3acf2c436fdb32a0f904aeb03a2904d2126eed34a042a194d235926/chardet-7.4.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c45e116dd51b66226a53ade3f9f635e870de5399b90e00ce45dcc311093bf4", size = 876589, upload-time = "2026-04-13T21:33:02.636Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578", size = 941866, upload-time = "2026-04-13T21:33:04.282Z" },
+ { url = "https://files.pythonhosted.org/packages/61/33/29de185079e6675c3f375546e30a559b7ddc75ce972f18d6e566cd9ea4eb/chardet-7.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:75d3c65cc16bddf40b8da1fd25ba84fca5f8070f2b14e86083653c1c85aee971", size = 874870, upload-time = "2026-04-13T21:33:05.977Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/2f/4c5af01fd1a7506a1d5375403d68925eac70289229492db5aa68b58103d8/chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:29af5999f654e8729d251f1724a62b538b1262d9292cccaefddf8a02aae1ef6a", size = 854859, upload-time = "2026-04-13T21:33:07.381Z" },
+ { url = "https://files.pythonhosted.org/packages/36/21/edb36ad5dfa48d7f8eed97ab43931ecdaa8c15166c21b1d614967e49d681/chardet-7.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:626f00299ad62dfe937058a09572beed442ccc7b58f87aa667949b20fd3db235", size = 875032, upload-time = "2026-04-13T21:33:08.741Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/59/a32a241d861cf180853a11c8e5a67641cb1b2af13c3a5ccce83ec07e2c9f/chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a4904dd5f071b7a7d7f50b4a67a86db3c902d243bf31708f1d5cde2f68239cb", size = 888283, upload-time = "2026-04-13T21:33:10.213Z" },
+ { url = "https://files.pythonhosted.org/packages/87/2e/e1ee6a77abf3782c00e05b89c4d4328c8353bf9500661c4348df1dd68614/chardet-7.4.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d2879598bc220689e8ce509fe9c3f37ad2fca53a36be9c9bd91abdd91dd364f", size = 879974, upload-time = "2026-04-13T21:33:11.448Z" },
+ { url = "https://files.pythonhosted.org/packages/32/60/fca69c534602a7ced04280c952a246ad1edde2a6ca3a164f65d32ac41fe7/chardet-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:4b2799bd58e7245cfa8d4ab2e8ad1d76a5c3a5b1f32318eb6acca4c69a3e7101", size = 943973, upload-time = "2026-04-13T21:33:12.756Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/43/79ac9b4db5bc87020c9dbc419125371d80882d1d197e9c4765ba8682b605/chardet-7.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e4486df251b8962e86ea9f139ca235aa6e0542a00f7844c9a04160afb99aa9", size = 873769, upload-time = "2026-04-13T21:33:14.002Z" },
+ { url = "https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4fbff1907925b0c5a1064cffb5e040cd5e338585c9c552625f30de6bc2f3107a", size = 853991, upload-time = "2026-04-13T21:33:15.564Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/07/a29380ee0b215d23d77733b5ad60c5c0c7969650e080c667acdf9462040d/chardet-7.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:365135eaf37ba65a828f8e668eb0a8c38c479dcbec724dc25f4dfd781049c357", size = 874024, upload-time = "2026-04-13T21:33:16.915Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfc134b70c846c21ead8e43ada3ae1a805fff732f6922f8abcf2ff27b8f6493d", size = 887410, upload-time = "2026-04-13T21:33:18.368Z" },
+ { url = "https://files.pythonhosted.org/packages/63/1c/44a9a9e0c59c185a5d307ceaeee8768afa1558f0a24f7a4b5fa11b67586b/chardet-7.4.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9acd9988a93e09390f3cd231201ea7166c415eb8da1b735928990ffc05cb9fbb", size = 879269, upload-time = "2026-04-13T21:33:20.377Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131", size = 944155, upload-time = "2026-04-13T21:33:21.694Z" },
+ { url = "https://files.pythonhosted.org/packages/70/a8/bf0811d859e13801279a2ae64f37a408027b282f2047bc0001c75dd356ad/chardet-7.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d892d3dcd652fdef53e3d6327d39b17c0df40a899dfc919abaeb64c974497531", size = 872887, upload-time = "2026-04-13T21:33:23.328Z" },
+ { url = "https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:acc46d1b8b7d5783216afe15db56d1c179b9a40e5a1558bc13164c4fd20674c4", size = 853964, upload-time = "2026-04-13T21:33:24.724Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/81/17fa103ea9caf5d325a5e4051ab2ba65996fd66baa60b81ee41af1f54e10/chardet-7.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ac3bf11c645734a1701a3804e43eabd98851838192267d08c353a834ab79fea", size = 876006, upload-time = "2026-04-13T21:33:26.098Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/20/193faab46a68ea550587331a698c3dca8099f8901d10937c4443135c7ed9/chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e3bd9f936e04bae89c254262af08d9e5b98f805175ba1e29d454e6cba3107b7", size = 887680, upload-time = "2026-04-13T21:33:27.49Z" },
+ { url = "https://files.pythonhosted.org/packages/40/c6/94a3c673327392652ee8bdea9a45bc8a5f5365197a7387d68f0eed007115/chardet-7.4.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:27cc23da03630cdecc9aa81a895aa86629c211f995cd57651f0fbc280717bf93", size = 879865, upload-time = "2026-04-13T21:33:29.052Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/2c/cad8b5e3623a987f3c930b68e2bdd06cfc388cd91cd42ed05f1227701b73/chardet-7.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:b95c934b9ad59e2ba8abb9be49df70d3ad1b0d95d864b9fdb7588d4fa8bd921c", size = 939594, upload-time = "2026-04-13T21:33:31.391Z" },
+ { url = "https://files.pythonhosted.org/packages/33/e0/d06e42fd6f02a58e5e227e5106587751cb38adcff0aaf949add744b78b6e/chardet-7.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c77867f0c1cb8bd819502249fcdc500364aedb07881e11b743726fa2148e7b6e", size = 889714, upload-time = "2026-04-13T21:33:32.772Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/ed/40d091954d48abea037baae6be8fb79905e5f78d34d12ea955132c7d8011/chardet-7.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cf1efeaf65a6ef2f5b9cc3a1df6f08ba2831b369ccaa4c7018eaf90aa757bb11", size = 872319, upload-time = "2026-04-13T21:33:34.427Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/77/82a46821dbfbdfe062710d2bf2ede13426304e3567a23c57d919c0c31630/chardet-7.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f3504c139a2ad544077dd2d9e412cd08b01786843d76997cd43bb6de311723c", size = 892021, upload-time = "2026-04-13T21:33:35.766Z" },
+ { url = "https://files.pythonhosted.org/packages/49/57/42d30c562bda5b4a839766c1aad8d5856b798ad2a1c3247b72a679afec94/chardet-7.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457f619882ba66327d4d8d14c6c342269bdb1e4e1c38e8117df941d14d351b04", size = 902509, upload-time = "2026-04-13T21:33:37.096Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/6c/0a40afdb50a0fe041ab95553b835a8160b6cf0e81edf2ae2fe9f5224cbf9/chardet-7.4.3-py3-none-any.whl", hash = "sha256:1173b74051570cf08099d7429d92e4882d375ad4217f92a6e5240ccfb26f231e", size = 626562, upload-time = "2026-04-13T21:33:38.559Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" },
+ { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" },
+ { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" },
+ { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" },
+ { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" },
+ { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
+ { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
+ { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
+ { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
+ { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
+ { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
+ { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
+ { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
+ { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
+ { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
+ { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
+ { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
+ { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
+ { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
+ { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
+ { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
+ { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
+ { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
+ { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
+ { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
+ { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
+ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "cryptography"
+version = "46.0.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" },
+ { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" },
+ { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" },
+ { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" },
+ { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" },
+ { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" },
+ { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" },
+ { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" },
+ { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" },
+ { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" },
+ { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" },
+ { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" },
+ { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" },
+ { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" },
+ { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" },
+ { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
+ { url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" },
+ { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" },
+]
+
+[[package]]
+name = "google-auth"
+version = "2.49.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cryptography" },
+ { name = "pyasn1-modules" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c6/fc/e925290a1ad95c975c459e2df070fac2b90954e13a0370ac505dff78cb99/google_auth-2.49.2.tar.gz", hash = "sha256:c1ae38500e73065dcae57355adb6278cf8b5c8e391994ae9cbadbcb9631ab409", size = 333958, upload-time = "2026-04-10T00:41:21.888Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/73/76/d241a5c927433420507215df6cac1b1fa4ac0ba7a794df42a84326c68da8/google_auth-2.49.2-py3-none-any.whl", hash = "sha256:c2720924dfc82dedb962c9f52cabb2ab16714fd0a6a707e40561d217574ed6d5", size = 240638, upload-time = "2026-04-10T00:41:14.501Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "observeit"
+version = "6.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "requests" },
+ { name = "tipcommon" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "pytest-json-report" },
+ { name = "soar-sdk" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "requests", specifier = "==2.32.4" },
+ { name = "tipcommon", path = "../../../../../packages/tipcommon/whls/TIPCommon-1.0.11-py2.py3-none-any.whl" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=9.0.3" },
+ { name = "pytest-json-report", specifier = ">=1.5.0" },
+ { name = "soar-sdk", git = "https://github.com/chronicle/soar-sdk.git" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pyasn1"
+version = "0.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
+]
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyasn1" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.20.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
+]
+
+[[package]]
+name = "pyopenssl"
+version = "26.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cryptography" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8e/11/a62e1d33b373da2b2c2cd9eb508147871c80f12b1cacde3c5d314922afdd/pyopenssl-26.0.0.tar.gz", hash = "sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc", size = 185534, upload-time = "2026-03-15T14:28:26.353Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/7d/d4f7d908fa8415571771b30669251d57c3cf313b36a856e6d7548ae01619/pyopenssl-26.0.0-py3-none-any.whl", hash = "sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81", size = 57969, upload-time = "2026-03-15T14:28:24.864Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
+]
+
+[[package]]
+name = "pytest-json-report"
+version = "1.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pytest" },
+ { name = "pytest-metadata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4f/d3/765dae9712fcd68d820338908c1337e077d5fdadccd5cacf95b9b0bea278/pytest-json-report-1.5.0.tar.gz", hash = "sha256:2dde3c647851a19b5f3700729e8310a6e66efb2077d674f27ddea3d34dc615de", size = 21241, upload-time = "2022-03-15T21:03:10.2Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/35/d07400c715bf8a88aa0c1ee9c9eb6050ca7fe5b39981f0eea773feeb0681/pytest_json_report-1.5.0-py3-none-any.whl", hash = "sha256:9897b68c910b12a2e48dd849f9a284b2c79a732a8a9cb398452ddd23d3c8c325", size = 13222, upload-time = "2022-03-15T21:03:08.65Z" },
+]
+
+[[package]]
+name = "pytest-metadata"
+version = "3.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952, upload-time = "2024-02-12T19:38:44.887Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428, upload-time = "2024-02-12T19:38:42.531Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "pytz"
+version = "2026.1.post1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
+]
+
+[[package]]
+name = "requests-toolbelt"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "soar-sdk"
+version = "0.2.0"
+source = { git = "https://github.com/chronicle/soar-sdk.git#5c563da488afa729eeba2195d3569de1370ab106" }
+dependencies = [
+ { name = "arrow" },
+ { name = "chardet" },
+ { name = "cryptography" },
+ { name = "google-auth" },
+ { name = "pyopenssl" },
+ { name = "python-dateutil" },
+ { name = "pytz" },
+ { name = "requests" },
+ { name = "requests-toolbelt" },
+ { name = "six" },
+]
+
+[[package]]
+name = "tipcommon"
+version = "1.0.11"
+source = { path = "../../../../../packages/tipcommon/whls/TIPCommon-1.0.11-py2.py3-none-any.whl" }
+dependencies = [
+ { name = "chardet" },
+ { name = "requests" },
+]
+wheels = [
+ { filename = "tipcommon-1.0.11-py2.py3-none-any.whl", hash = "sha256:9193a56f9c51a66a5d8c05a982abe74cb0c25b3636951fc98288d7d0193ef915" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "chardet" },
+ { name = "requests" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2026.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
+]
diff --git a/content/response_integrations/google/outpost24/.python-version b/content/response_integrations/google/outpost24/.python-version
new file mode 100644
index 000000000..902b2c90c
--- /dev/null
+++ b/content/response_integrations/google/outpost24/.python-version
@@ -0,0 +1 @@
+3.11
\ No newline at end of file
diff --git a/content/response_integrations/google/outpost24/actions/EnrichEntities.py b/content/response_integrations/google/outpost24/actions/EnrichEntities.py
new file mode 100755
index 000000000..bd17344a4
--- /dev/null
+++ b/content/response_integrations/google/outpost24/actions/EnrichEntities.py
@@ -0,0 +1,276 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+from soar_sdk.SiemplifyUtils import output_handler, convert_dict_to_json_result_dict
+from soar_sdk.ScriptResult import EXECUTION_STATE_COMPLETED, EXECUTION_STATE_FAILED
+from soar_sdk.SiemplifyAction import SiemplifyAction
+from TIPCommon import extract_configuration_param, extract_action_param, construct_csv
+from ..core.Outpost24Manager import Outpost24Manager
+from ..core.Outpost24Exceptions import DeviceNotFoundError
+from ..core.constants import (
+ INTEGRATION_NAME,
+ INTEGRATION_DISPLAY_NAME,
+ ENRICH_ENTITIES_SCRIPT_NAME,
+ ENRICHMENT_PREFIX,
+ SUPORTED_RISK_LEVELS,
+)
+from ..core.UtilsManager import load_csv_to_list
+from soar_sdk.SiemplifyDataModel import EntityTypes
+
+SUPPORTED_ENTITY_TYPES = [EntityTypes.ADDRESS, EntityTypes.HOSTNAME]
+
+
+@output_handler
+def main():
+ siemplify = SiemplifyAction()
+ siemplify.script_name = ENRICH_ENTITIES_SCRIPT_NAME
+ siemplify.LOGGER.info("----------------- Main - Param Init -----------------")
+
+ api_root = extract_configuration_param(
+ siemplify,
+ provider_name=INTEGRATION_NAME,
+ param_name="API Root",
+ is_mandatory=True,
+ print_value=True,
+ )
+ username = extract_configuration_param(
+ siemplify,
+ provider_name=INTEGRATION_NAME,
+ param_name="Username",
+ is_mandatory=True,
+ print_value=False,
+ )
+ password = extract_configuration_param(
+ siemplify,
+ provider_name=INTEGRATION_NAME,
+ param_name="Password",
+ is_mandatory=True,
+ )
+ verify_ssl = extract_configuration_param(
+ siemplify,
+ provider_name=INTEGRATION_NAME,
+ param_name="Verify SSL",
+ is_mandatory=True,
+ input_type=bool,
+ print_value=True,
+ )
+
+ siemplify.LOGGER.info("----------------- Main - Started -----------------")
+
+ status = EXECUTION_STATE_COMPLETED
+ result = True
+ output_message = ""
+ json_result = {}
+ successful_entities = []
+ failed_entities = []
+ invalid_risk_levels = None
+ try:
+ create_insight = extract_action_param(
+ siemplify,
+ param_name="Create Insight",
+ is_mandatory=False,
+ print_value=True,
+ default_value=True,
+ input_type=bool,
+ )
+ risk_level_filter = extract_action_param(
+ siemplify,
+ param_name="Finding Risk Level Filter",
+ default_value="Low, Medium, High, Critical, Recommendation, Initial",
+ is_mandatory=False,
+ print_value=True,
+ input_type=str,
+ )
+ max_findings_to_return = extract_action_param(
+ siemplify,
+ param_name="Max Findings To Return",
+ default_value="",
+ is_mandatory=False,
+ print_value=True,
+ input_type=int,
+ )
+ return_finding_information = extract_action_param(
+ siemplify,
+ param_name="Return Finding Information",
+ is_mandatory=False,
+ print_value=True,
+ default_value=True,
+ input_type=bool,
+ )
+ finding_type = extract_action_param(
+ siemplify,
+ param_name="Finding Type",
+ default_value="All",
+ is_mandatory=False,
+ print_value=True,
+ input_type=str,
+ )
+
+ manager = Outpost24Manager(
+ api_root=api_root,
+ username=username,
+ password=password,
+ verify_ssl=verify_ssl,
+ siemplify_logger=siemplify.LOGGER,
+ )
+ manager.test_connectivity()
+
+ if max_findings_to_return < 1:
+ raise Exception(
+ f'Given value of {max_findings_to_return} for parameter "Max Findings '
+ 'To Return" is non positive.'
+ )
+
+ # check the risk level input
+ if return_finding_information:
+ if risk_level_filter is not None:
+ risk_level_filter = load_csv_to_list(risk_level_filter, "Risk Levels")
+ risk_level_filter = [risk.lower() for risk in risk_level_filter]
+ invalid_risk_levels = list(
+ set(risk_level_filter) - set(SUPORTED_RISK_LEVELS)
+ )
+ invalid_risk_levels_message = ",".join(invalid_risk_levels)
+ if invalid_risk_levels is not None:
+ raise Exception(
+ f"invalid risk level filter values provided: "
+ f"{invalid_risk_levels_message}. Possible values: "
+ f"Recommendation, Initial, Low, Medium, High, Critical."
+ )
+
+ suitable_entities = [
+ entity
+ for entity in siemplify.target_entities
+ if entity.entity_type in SUPPORTED_ENTITY_TYPES
+ ]
+
+ for entity in suitable_entities:
+ siemplify.LOGGER.info(f"Started processing entity: {entity.identifier}")
+ try:
+ if entity.entity_type == EntityTypes.HOSTNAME:
+ entity_details = manager.get_device_information(
+ entity.identifier,
+ is_hostname=True,
+ risk_level_filter=risk_level_filter,
+ return_finding_information=return_finding_information,
+ finding_type=finding_type,
+ max_findings_to_return=max_findings_to_return,
+ )
+
+ else:
+ entity_details = manager.get_device_information(
+ entity.identifier,
+ risk_level_filter=risk_level_filter,
+ return_finding_information=return_finding_information,
+ finding_type=finding_type,
+ max_findings_to_return=max_findings_to_return,
+ )
+
+ if entity_details and entity_details.raw_data:
+ json_result[entity.identifier] = entity_details.to_json(
+ return_finding_information=return_finding_information
+ )
+ entity.additional_properties.update(
+ entity_details.to_enrichment_data(
+ prefix=ENRICHMENT_PREFIX,
+ return_finding_information=return_finding_information,
+ )
+ )
+ entity.is_enriched = True
+ successful_entities.append(entity)
+ siemplify.result.add_data_table(
+ entity.identifier,
+ data_table=construct_csv(entity_details.to_table()),
+ )
+ if (
+ return_finding_information
+ and entity_details.to_findings_table()
+ ):
+ siemplify.result.add_data_table(
+ f"Findings: {entity.identifier}",
+ data_table=construct_csv(
+ entity_details.to_findings_table()
+ ),
+ )
+
+ if create_insight:
+ siemplify.add_entity_insight(
+ entity,
+ entity_details.as_insight(
+ return_finding_information=return_finding_information
+ ),
+ triggered_by=INTEGRATION_DISPLAY_NAME,
+ )
+ else:
+ failed_entities.append(entity)
+
+ except DeviceNotFoundError as e:
+ failed_entities.append(entity)
+ siemplify.LOGGER.info(
+ f"Failed processing entity {entity.identifier}. Reason: {e}"
+ )
+ continue
+
+ except Exception as e:
+ failed_entities.append(entity)
+ siemplify.LOGGER.info(
+ f"Failed processing entity {entity.identifier}. Reason: {e}"
+ )
+ continue
+
+ if successful_entities:
+ output_message += (
+ "Successfully enriched the following entities using {}: \n{}".format(
+ INTEGRATION_NAME,
+ "\n".join([entity.identifier for entity in successful_entities]),
+ )
+ )
+ siemplify.update_entities(successful_entities)
+ siemplify.result.add_result_json(
+ convert_dict_to_json_result_dict(json_result)
+ )
+
+ if failed_entities:
+ output_message += (
+ "\nAction wasn't able to enrich the following entities "
+ "using {}: \n{}").format(
+ INTEGRATION_NAME,
+ "\n".join([entity.identifier for entity in failed_entities]),
+ )
+
+ if not successful_entities:
+ result = False
+ output_message = "None of the provided entities were enriched."
+
+ except Exception as e:
+ siemplify.LOGGER.error(
+ f"General error performing action {ENRICH_ENTITIES_SCRIPT_NAME}"
+ )
+ siemplify.LOGGER.exception(e)
+ result = False
+ status = EXECUTION_STATE_FAILED
+ output_message = (
+ f"Error executing action {ENRICH_ENTITIES_SCRIPT_NAME}. Reason: {e}"
+ )
+
+ siemplify.LOGGER.info("----------------- Main - Finished -----------------")
+ siemplify.LOGGER.info(f"Status: {status}")
+ siemplify.LOGGER.info(f"Result: {result}")
+ siemplify.LOGGER.info(f"Output Message: {output_message}")
+
+ siemplify.end(output_message, result, status)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/content/response_integrations/google/outpost24/actions/EnrichEntities.yaml b/content/response_integrations/google/outpost24/actions/EnrichEntities.yaml
new file mode 100644
index 000000000..d14adff1c
--- /dev/null
+++ b/content/response_integrations/google/outpost24/actions/EnrichEntities.yaml
@@ -0,0 +1,61 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Enrich Entities
+description: 'Enrich entities using information from Outpost24. Supported entities:
+ IP Address, Hostname.'
+documentation_link: https://cloud.google.com/chronicle/docs/soar/marketplace-integrations/outpost24#enrich_entities
+integration_identifier: Outpost24
+parameters:
+- name: Finding Risk Level Filter
+ default_value: Recommendation, Initial, Low, Medium, High, Critical
+ type: string
+ description: 'Specify a comma-separated list of risk level findings that will
+ be used during filtering. Possible values: Initial, Recommendation, Low, Medium,
+ High, Critical. If nothing is provided, action will fetch findings with all
+ risk levels.'
+ is_mandatory: false
+- name: Max Findings To Return
+ default_value: '100'
+ type: string
+ description: Specify how many findings to process per entity. If nothing is provided,
+ action will return 100 findings.
+ is_mandatory: false
+- name: Return Finding Information
+ default_value: true
+ type: boolean
+ description: If enabled, action will also retrieve information about findings
+ that were found on the endpoint.
+ is_mandatory: false
+- name: Finding Type
+ default_value: All
+ type: ddl
+ optional_values:
+ - All
+ - Vulnerability
+ - Information
+ description: Specify what kind of findings should be returned.
+ is_mandatory: false
+- name: Create Insight
+ default_value: true
+ type: boolean
+ description: If enabled, action will create an insight containing all of the retrieved
+ information about the entity.
+ is_mandatory: false
+dynamic_results_metadata:
+- result_example_path: resources/enrich_entities_JsonResult_example.json
+ result_name: JsonResult
+ show_result: true
+creator: admin
+simulation_data_json: '{"Entities": ["HOSTNAME","ADDRESS"]}'
diff --git a/content/response_integrations/google/outpost24/actions/Ping.py b/content/response_integrations/google/outpost24/actions/Ping.py
new file mode 100755
index 000000000..b986f3609
--- /dev/null
+++ b/content/response_integrations/google/outpost24/actions/Ping.py
@@ -0,0 +1,95 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+from soar_sdk.SiemplifyUtils import output_handler
+from soar_sdk.ScriptResult import EXECUTION_STATE_COMPLETED, EXECUTION_STATE_FAILED
+from soar_sdk.SiemplifyAction import SiemplifyAction
+from TIPCommon import extract_configuration_param
+from ..core.Outpost24Manager import Outpost24Manager
+from ..core.constants import INTEGRATION_NAME, INTEGRATION_DISPLAY_NAME, PING_SCRIPT_NAME
+
+
+@output_handler
+def main():
+ siemplify = SiemplifyAction()
+ siemplify.script_name = PING_SCRIPT_NAME
+ siemplify.LOGGER.info("----------------- Main - Param Init -----------------")
+
+ api_root = extract_configuration_param(
+ siemplify,
+ provider_name=INTEGRATION_NAME,
+ param_name="API Root",
+ is_mandatory=True,
+ print_value=True,
+ )
+ username = extract_configuration_param(
+ siemplify,
+ provider_name=INTEGRATION_NAME,
+ param_name="Username",
+ is_mandatory=True,
+ print_value=True,
+ )
+ password = extract_configuration_param(
+ siemplify,
+ provider_name=INTEGRATION_NAME,
+ param_name="Password",
+ is_mandatory=True,
+ )
+ verify_ssl = extract_configuration_param(
+ siemplify,
+ provider_name=INTEGRATION_NAME,
+ param_name="Verify SSL",
+ is_mandatory=True,
+ input_type=bool,
+ print_value=True,
+ )
+
+ siemplify.LOGGER.info("----------------- Main - Started -----------------")
+
+ try:
+ manager = Outpost24Manager(
+ api_root=api_root,
+ username=username,
+ password=password,
+ verify_ssl=verify_ssl,
+ siemplify_logger=siemplify.LOGGER,
+ )
+ manager.test_connectivity()
+ result = True
+ status = EXECUTION_STATE_COMPLETED
+ output_message = (
+ f"Successfully connected to the {INTEGRATION_DISPLAY_NAME} server with "
+ "the provided connection parameters!"
+ )
+
+ except Exception as e:
+ siemplify.LOGGER.error(f"General error performing action {PING_SCRIPT_NAME}")
+ siemplify.LOGGER.exception(e)
+ result = False
+ status = EXECUTION_STATE_FAILED
+ output_message = (
+ f"Failed to connect to the {INTEGRATION_DISPLAY_NAME} server! Error is {e}"
+ )
+
+ siemplify.LOGGER.info("----------------- Main - Finished -----------------")
+ siemplify.LOGGER.info(f"Status: {status}")
+ siemplify.LOGGER.info(f"Result: {result}")
+ siemplify.LOGGER.info(f"Output Message: {output_message}")
+
+ siemplify.end(output_message, result, status)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/content/response_integrations/google/outpost24/actions/Ping.yaml b/content/response_integrations/google/outpost24/actions/Ping.yaml
new file mode 100644
index 000000000..9b7cadffc
--- /dev/null
+++ b/content/response_integrations/google/outpost24/actions/Ping.yaml
@@ -0,0 +1,22 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Ping
+description: Test connectivity to the Outpost24 with parameters provided at the integration
+ configuration page on the Marketplace tab.
+documentation_link: https://cloud.google.com/chronicle/docs/soar/marketplace-integrations/outpost24#ping
+integration_identifier: Outpost24
+parameters: []
+dynamic_results_metadata: []
+creator: admin
diff --git a/content/response_integrations/google/outpost24/actions/__init__.py b/content/response_integrations/google/outpost24/actions/__init__.py
new file mode 100644
index 000000000..9f71a2dc3
--- /dev/null
+++ b/content/response_integrations/google/outpost24/actions/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/content/response_integrations/google/outpost24/connectors/OutscanFindingsConnector.py b/content/response_integrations/google/outpost24/connectors/OutscanFindingsConnector.py
new file mode 100755
index 000000000..a199baf9c
--- /dev/null
+++ b/content/response_integrations/google/outpost24/connectors/OutscanFindingsConnector.py
@@ -0,0 +1,288 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+from soar_sdk.SiemplifyUtils import output_handler, unix_now
+from soar_sdk.SiemplifyConnectors import SiemplifyConnectorExecution
+from TIPCommon import extract_connector_param
+from ..core.constants import (
+ CONNECTOR_NAME,
+ DEFAULT_LIMIT,
+ DEFAULT_TIME_FRAME,
+ SEVERITIES,
+ POSSIBLE_TYPES,
+)
+from ..core.UtilsManager import (
+ read_ids,
+ write_ids,
+ is_approaching_timeout,
+ get_environment_common,
+ pass_whitelist_filter,
+ convert_comma_separated_to_list,
+ convert_list_to_comma_string,
+ save_timestamp,
+ get_last_success_time,
+ DATETIME_FORMAT,
+ is_overflowed,
+ pass_severity_filter,
+)
+from ..core.Outpost24Manager import Outpost24Manager
+from soar_sdk.SiemplifyConnectorsDataModel import AlertInfo
+import sys
+
+
+connector_starting_time = unix_now()
+
+
+@output_handler
+def main(is_test_run):
+ siemplify = SiemplifyConnectorExecution()
+ siemplify.script_name = CONNECTOR_NAME
+ processed_alerts = []
+
+ if is_test_run:
+ siemplify.LOGGER.info(
+ '***** This is an "IDE Play Button"\\"Run Connector once" test run ******'
+ )
+
+ siemplify.LOGGER.info("------------------- Main - Param Init -------------------")
+
+ api_root = extract_connector_param(
+ siemplify, param_name="API Root", is_mandatory=True, print_value=True
+ )
+ username = extract_connector_param(
+ siemplify, param_name="Username", is_mandatory=True, print_value=True
+ )
+ password = extract_connector_param(
+ siemplify, param_name="Password", is_mandatory=True
+ )
+ verify_ssl = extract_connector_param(
+ siemplify,
+ param_name="Verify SSL",
+ is_mandatory=True,
+ input_type=bool,
+ print_value=True,
+ )
+
+ environment_field_name = extract_connector_param(
+ siemplify, param_name="Environment Field Name", print_value=True
+ )
+ environment_regex_pattern = extract_connector_param(
+ siemplify, param_name="Environment Regex Pattern", print_value=True
+ )
+
+ script_timeout = extract_connector_param(
+ siemplify,
+ param_name="PythonProcessTimeout",
+ is_mandatory=True,
+ input_type=int,
+ print_value=True,
+ )
+ lowest_risk_to_fetch = extract_connector_param(
+ siemplify, param_name="Lowest Risk To Fetch", print_value=True
+ )
+ type_filter = extract_connector_param(
+ siemplify, param_name="Type Filter", print_value=True, is_mandatory=True
+ )
+ days_backwards = extract_connector_param(
+ siemplify,
+ param_name="Max Days Backwards",
+ input_type=int,
+ default_value=DEFAULT_TIME_FRAME,
+ print_value=True,
+ )
+ fetch_limit = extract_connector_param(
+ siemplify,
+ param_name="Max Findings To Fetch",
+ input_type=int,
+ default_value=DEFAULT_LIMIT,
+ print_value=True,
+ )
+ whitelist_as_a_blacklist = extract_connector_param(
+ siemplify,
+ "Use whitelist as a blacklist",
+ is_mandatory=True,
+ input_type=bool,
+ print_value=True,
+ )
+ device_product_field = extract_connector_param(
+ siemplify, "DeviceProductField", is_mandatory=True
+ )
+
+ try:
+ siemplify.LOGGER.info("------------------- Main - Started -------------------")
+
+ if days_backwards < 1:
+ siemplify.LOGGER.info(
+ "Max Days Backwards must be greater than zero. The default value "
+ f"{DEFAULT_TIME_FRAME} will be used"
+ )
+ days_backwards = DEFAULT_TIME_FRAME
+
+ if fetch_limit < 1:
+ siemplify.LOGGER.info(
+ "Max Findings To Fetch must be greater than zero. The default value "
+ f"{DEFAULT_LIMIT} will be used"
+ )
+ fetch_limit = DEFAULT_LIMIT
+
+ if lowest_risk_to_fetch and lowest_risk_to_fetch.lower() not in SEVERITIES:
+ raise Exception(
+ "Invalid value given for Lowest Risk To Fetch parameter. Possible "
+ "values are: "
+ f"{convert_list_to_comma_string([severity.title() for severity in SEVERITIES])}."
+ )
+
+ invalid_types = [
+ type
+ for type in convert_comma_separated_to_list(type_filter)
+ if type.title() not in POSSIBLE_TYPES
+ ]
+
+ if invalid_types:
+ raise Exception(
+ "Invalid value provided for the parameter 'Type Filter'. Possible "
+ f"values: {convert_list_to_comma_string(POSSIBLE_TYPES)}."
+ )
+
+ # Read already existing alerts ids
+ existing_ids = read_ids(siemplify)
+ siemplify.LOGGER.info(
+ f"Successfully loaded {len(existing_ids)} existing findings from ids file"
+ )
+
+ manager = Outpost24Manager(
+ api_root=api_root,
+ username=username,
+ password=password,
+ verify_ssl=verify_ssl,
+ siemplify_logger=siemplify.LOGGER,
+ )
+
+ last_success_time = get_last_success_time(
+ siemplify=siemplify,
+ offset_with_metric={"days": days_backwards},
+ time_format=DATETIME_FORMAT,
+ )
+
+ fetched_alerts = []
+ filtered_alerts = manager.get_findings(
+ existing_ids=existing_ids,
+ limit=fetch_limit,
+ start_timestamp=last_success_time,
+ type_filter=type_filter,
+ )
+
+ siemplify.LOGGER.info(f"Fetched {len(filtered_alerts)} alerts")
+
+ if is_test_run:
+ siemplify.LOGGER.info("This is a TEST run. Only 1 alert will be processed.")
+ filtered_alerts = filtered_alerts[:1]
+
+ for alert in filtered_alerts:
+ try:
+ if is_approaching_timeout(script_timeout, connector_starting_time):
+ siemplify.LOGGER.info(
+ "Timeout is approaching. Connector will gracefully exit"
+ )
+ break
+
+ if len(processed_alerts) >= fetch_limit:
+ # Provide slicing for the alerts amount.
+ siemplify.LOGGER.info(
+ "Reached max number of alerts cycle. No more alerts will be "
+ "processed in this cycle."
+ )
+ break
+
+ siemplify.LOGGER.info(f"Started processing alert {alert.id}")
+
+ # Update existing alerts
+ existing_ids.append(alert.id)
+ fetched_alerts.append(alert)
+
+ if not pass_filters(
+ siemplify,
+ whitelist_as_a_blacklist,
+ alert,
+ "product_name",
+ lowest_risk_to_fetch,
+ ):
+ continue
+
+ alert_info = alert.get_alert_info(
+ alert_info=AlertInfo(),
+ environment_common=get_environment_common(
+ siemplify, environment_field_name, environment_regex_pattern
+ ),
+ device_product_field=device_product_field,
+ )
+
+ if is_overflowed(siemplify, alert_info, is_test_run):
+ siemplify.LOGGER.info(
+ f"{alert_info.rule_generator}-{alert_info.ticket_id}-"
+ f"{alert_info.environment}-{alert_info.device_product} found "
+ "as overflow alert. Skipping..."
+ )
+ # If is overflowed we should skip
+ continue
+
+ processed_alerts.append(alert_info)
+ siemplify.LOGGER.info(f"Alert {alert.id} was created.")
+
+ except Exception as e:
+ siemplify.LOGGER.error(f"Failed to process alert {alert.id}")
+ siemplify.LOGGER.exception(e)
+
+ if is_test_run:
+ raise
+
+ siemplify.LOGGER.info(f"Finished processing alert {alert.id}")
+
+ if not is_test_run:
+ siemplify.LOGGER.info("Saving existing ids.")
+ write_ids(siemplify, existing_ids)
+ save_timestamp(
+ siemplify=siemplify, alerts=fetched_alerts, timestamp_key="last_seen"
+ )
+
+ except Exception as e:
+ siemplify.LOGGER.error(f"Got exception on main handler. Error: {e}")
+ siemplify.LOGGER.exception(e)
+
+ if is_test_run:
+ raise
+
+ siemplify.LOGGER.info(f"Created total of {len(processed_alerts)} cases")
+ siemplify.LOGGER.info("------------------- Main - Finished -------------------")
+ siemplify.return_package(processed_alerts)
+
+
+def pass_filters(
+ siemplify, whitelist_as_a_blacklist, alert, model_key, lowest_risk_to_fetch
+):
+ # All alert filters should be checked here
+ if not pass_whitelist_filter(siemplify, whitelist_as_a_blacklist, alert, model_key):
+ return False
+
+ if not pass_severity_filter(siemplify, alert, lowest_risk_to_fetch):
+ return False
+
+ return True
+
+
+if __name__ == "__main__":
+ # Connectors are run in iterations. The interval is configurable from the ConnectorsScreen UI.
+ is_test = not (len(sys.argv) < 2 or sys.argv[1] == "True")
+ main(is_test)
diff --git a/content/response_integrations/google/outpost24/connectors/OutscanFindingsConnector.yaml b/content/response_integrations/google/outpost24/connectors/OutscanFindingsConnector.yaml
new file mode 100644
index 000000000..113fd66e3
--- /dev/null
+++ b/content/response_integrations/google/outpost24/connectors/OutscanFindingsConnector.yaml
@@ -0,0 +1,157 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Outpost24 - Outscan Findings Connector
+parameters:
+- name: DeviceProductField
+ default_value: Product Name
+ type: string
+ description: Enter the source field name in order to retrieve the Product Field
+ name.
+ is_mandatory: true
+ is_advanced: false
+ mode: regular
+- name: EventClassId
+ default_value: type
+ type: string
+ description: Enter the source field name in order to retrieve the Event Field
+ name.
+ is_mandatory: true
+ is_advanced: false
+ mode: regular
+- name: Environment Field Name
+ default_value: ''
+ type: string
+ description: Describes the name of the field where the environment name is stored.
+ If the environment field isn't found, the environment is the default environment.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Environment Regex Pattern
+ default_value: .*
+ type: string
+ description: A regex pattern to run on the value found in the "Environment Field
+ Name" field. Default is .* to catch all and return the value unchanged. Used
+ to allow the user to manipulate the environment field via regex logic. If
+ the regex pattern is null or empty, or the environment value is null, the
+ final environment result is the default environment.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: PythonProcessTimeout
+ default_value: 300
+ type: integer
+ description: Timeout limit for the python process running the current script.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: API Root
+ default_value: https://your-appliance.outpost24.com
+ type: string
+ description: API root of the Outpost24 instance.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Username
+ default_value: ''
+ type: string
+ description: Username of the Outpost24 account.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Password
+ default_value: ''
+ type: password
+ description: Password of the Outpost24 account.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Verify SSL
+ default_value: true
+ type: boolean
+ description: If enabled, verify the SSL certificate for the connection to the
+ Outpost24 server is valid.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Lowest Risk To Fetch
+ default_value: ''
+ type: string
+ description: 'Lowest risk that needs to be used to fetch alerts. Possible values:
+ Initial, Recommendation, Low, Medium, High, Critical. If nothing is specified,
+ the connector will ingest alerts with all risk levels.'
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Type Filter
+ default_value: Vulnerability, Information, Port
+ type: string
+ description: 'Comma-separated list of type filters for the finding. Possible values:
+ Vulnerability, Information, Port.'
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Max Days Backwards
+ default_value: 1
+ type: integer
+ description: Number of hours before the first connector iteration to retrieve
+ findings from. This parameter applies to the initial connector iteration after
+ you enable the connector for the first time, or used as a fallback value in
+ cases where connector's last run timestamp expires.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Max Findings To Fetch
+ default_value: 100
+ type: integer
+ description: 'How many findings to process per one connector iteration. Default:
+ 100.'
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Use whitelist as a blacklist
+ default_value: false
+ type: boolean
+ description: If enabled, whitelist will be used as a blacklist.
+ is_mandatory: true
+ is_advanced: false
+ mode: script
+- name: Proxy Server Address
+ default_value: ''
+ type: string
+ description: The address of the proxy server to use.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Proxy Username
+ default_value: ''
+ type: string
+ description: The proxy username to authenticate with.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+- name: Proxy Password
+ default_value: ''
+ type: password
+ description: The proxy password to authenticate with.
+ is_mandatory: false
+ is_advanced: false
+ mode: script
+description: 'Pull information about outscan findings from Outpost24. Note: whitelist
+ filter works with "productName" parameter.'
+integration: Outpost24
+documentation_link: https://cloud.google.com/chronicle/docs/soar/marketplace-integrations/outpost24#outscan-findings-connector
+rules: []
+is_connector_rules_supported: false
+creator: admin
diff --git a/content/response_integrations/google/outpost24/connectors/__init__.py b/content/response_integrations/google/outpost24/connectors/__init__.py
new file mode 100644
index 000000000..9f71a2dc3
--- /dev/null
+++ b/content/response_integrations/google/outpost24/connectors/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/content/response_integrations/google/outpost24/core/Outpost24Exceptions.py b/content/response_integrations/google/outpost24/core/Outpost24Exceptions.py
new file mode 100755
index 000000000..0d95a95b9
--- /dev/null
+++ b/content/response_integrations/google/outpost24/core/Outpost24Exceptions.py
@@ -0,0 +1,29 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+class Outpost24GeneralException(Exception):
+ """
+ General exception for Outpost24
+ """
+
+ pass
+
+
+class DeviceNotFoundError(Exception):
+ """
+ Exception for Outpost24 when no device - hostname or IP Aaddress is not found
+ """
+
+ pass
diff --git a/content/response_integrations/google/outpost24/core/Outpost24Manager.py b/content/response_integrations/google/outpost24/core/Outpost24Manager.py
new file mode 100755
index 000000000..464528b77
--- /dev/null
+++ b/content/response_integrations/google/outpost24/core/Outpost24Manager.py
@@ -0,0 +1,286 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import requests
+import json
+from .constants import (
+ PING_URL,
+ GET_TOKEN_URL,
+ GET_DEVICES_URL,
+ DEFAULT_API_LIMIT,
+ FINDING_TYPES,
+ GET_FINDINGS_URL,
+ DEFAULT_LIMIT,
+)
+from urllib.parse import urlencode
+from .Outpost24Parser import Outpost24Parser
+from .Outpost24Exceptions import DeviceNotFoundError
+from .UtilsManager import (
+ validate_response,
+ filter_old_alerts,
+ filter_alerts_by_timestamp,
+)
+from soar_sdk.SiemplifyUtils import convert_string_to_datetime
+
+
+class Outpost24Manager:
+ def __init__(self, api_root, username, password, verify_ssl, siemplify_logger=None):
+ """
+ The method is used to init an object of Manager class
+ :param api_root: {str} Outpost24 API root
+ :param username: {str} Outpost24 username
+ :param password: {str} Outpost24 password
+ :param verify_ssl: {bool} Specifies if certificate that is configured on the api root should be validated
+ :param siemplify_logger: Siemplify logger
+ """
+ self.api_root = api_root[:-1] if api_root.endswith("/") else api_root
+ self.username = username
+ self.password = password
+ self.verify_ssl = verify_ssl
+ self.siemplify_logger = siemplify_logger
+ self.session = requests.session()
+ self.session.verify = verify_ssl
+ self.set_auth_token()
+ self.parser = Outpost24Parser()
+
+ def set_auth_token(self):
+ """
+ Set Authorization header to request session.
+ """
+ self.session.headers.update(
+ {"Authorization": f"Bearer {self.get_auth_token()}"}
+ )
+
+ def get_auth_token(self):
+ """
+ Send request in order to generate token.
+ :return: {str} The authorization token
+ """
+ url = GET_TOKEN_URL.format(self.api_root)
+
+ payload = {"username": self.username, "password": self.password}
+
+ response = self.session.post(url, data=payload)
+ validate_response(response)
+ return response.text
+
+ def test_connectivity(self):
+ """
+ Test connectivity
+ """
+ url = PING_URL.format(self.api_root)
+ response = self.session.get(url)
+ validate_response(response)
+
+ def get_device_information(
+ self,
+ entity_identifier,
+ risk_level_filter,
+ return_finding_information,
+ finding_type,
+ max_findings_to_return,
+ is_hostname=False,
+ ):
+ """
+ Function that gets the device(s) information from Outpost24, and request additional findings if needed
+ :param entity_identifier: {str} Entity identifier
+ :param risk_level_filter: {list} Risk levels to use in filters
+ :param return_finding_information: {bool} True if additional findings should be returned, False otherwise
+ :param finding_type: {str} Finding Type - All, Vulnerability, Information
+ :param max_findings_to_return: {str} Limit of how many findings to return
+ :param is_hostname: {bool} True if the processed entity is hostname type, False if it's IP address type
+ :return {EntityObject} Entity object containing all the data
+ """
+ params = {}
+
+ if is_hostname:
+ # For hostnames we can apply filters in the API request
+ params = {"filter": [{"field": "hostname", "value": entity_identifier}]}
+ params = urlencode(params)
+
+ url = GET_DEVICES_URL.format(self.api_root)
+ response = self.session.get(url, params=params)
+ validate_response(response)
+ json_result = response.json()
+
+ if len(json_result) > 0:
+ entity_basic_details = self.parser.build_entity_object(
+ raw_data=json_result[0]
+ )
+ else:
+ raise DeviceNotFoundError(
+ f"Entity: {entity_identifier} not found in Outpost24."
+ )
+ else:
+ # For IP Addresses we need to fetch everything and then filter the correct
+ # IP address on our side
+ params = {"limit": DEFAULT_API_LIMIT, "offset": 0}
+ url = GET_DEVICES_URL.format(self.api_root)
+ response = self.session.get(url, params=params)
+ validate_response(response)
+ json_result = response.json()
+ num_of_results = len(json_result)
+ total_number_of_results = num_of_results
+
+ if num_of_results < 1:
+ raise DeviceNotFoundError(f"No devices found in Outpost24.")
+
+ while (
+ num_of_results == DEFAULT_API_LIMIT
+ ): # the API response doesn't have any indicator that it has next page,
+ # only if you fetch the same number of entities as the limit,
+ # it indicates that it has more data on next pages
+ params.update({"offset": total_number_of_results})
+ response = self.session.get(url, params=params)
+ validate_response(response)
+ json_result.extend(response.json())
+ num_of_results = len(response.json())
+ total_number_of_results = total_number_of_results + num_of_results
+
+ ip_address_details = self.parser.find_ip_address(
+ raw_data=json_result, entity_identifier=entity_identifier
+ )
+ if ip_address_details is None:
+ raise DeviceNotFoundError(
+ f"Entity: {entity_identifier} not found in Outpost24."
+ )
+
+ entity_basic_details = self.parser.build_entity_object(
+ raw_data=ip_address_details
+ )
+
+ if return_finding_information:
+ finding_information_filter = []
+
+ finding_information_filter.append(
+ {"field": "targetId", "value": entity_basic_details.id}
+ )
+ finding_information_filter.append(
+ {"field": "type", "value": FINDING_TYPES.get(finding_type)}
+ )
+
+ params = {
+ "sort": "-lastSeen",
+ "limit": DEFAULT_API_LIMIT,
+ "offset": 0,
+ "filter": finding_information_filter,
+ }
+ params = urlencode(params)
+
+ url = GET_FINDINGS_URL.format(self.api_root)
+ response = self.session.get(url, params=params)
+ validate_response(response)
+ json_result = response.json()
+
+ filtered_results = self.parser.filter_found_information(
+ data=json_result, risk_level_filter=risk_level_filter
+ )
+
+ num_of_results = len(json_result)
+ total_number_of_results = num_of_results
+ number_of_filtered_results = len(filtered_results)
+
+ if number_of_filtered_results > max_findings_to_return:
+ filtered_results = filtered_results[:max_findings_to_return]
+ number_of_filtered_results = len(filtered_results)
+
+ while (
+ num_of_results == DEFAULT_API_LIMIT
+ and number_of_filtered_results < max_findings_to_return
+ ):
+ params.update(
+ {
+ "sort": "-lastSeen",
+ "limit": DEFAULT_API_LIMIT,
+ "offset": total_number_of_results,
+ "filter": finding_information_filter,
+ }
+ )
+ params = urlencode(params)
+ response = self.session.get(url, params=params)
+ validate_response(response)
+ json_result.extend(response.json())
+ filtered_results_page = self.parser.filter_found_information(
+ data=json_result, risk_level_filter=risk_level_filter
+ )
+
+ if (
+ number_of_filtered_results + len(filtered_results_page)
+ > max_findings_to_return
+ ):
+ number_od_results_to_get = (
+ max_findings_to_return - number_of_filtered_results
+ )
+ filtered_results_page = filtered_results_page[
+ :number_od_results_to_get
+ ]
+
+ filtered_results = filtered_results + filtered_results_page
+ number_of_filtered_results = len(filtered_results)
+
+ num_of_results = len(response.json())
+ total_number_of_results = total_number_of_results + num_of_results
+
+ self.parser.add_findings_to_entity_object(
+ entity_object=entity_basic_details, data=filtered_results
+ )
+
+ return entity_basic_details
+
+ def get_findings(self, existing_ids, limit, start_timestamp, type_filter):
+ """
+ Get findings
+ :param existing_ids: {list} The list of existing ids
+ :param limit: {int} The limit for results
+ :param start_timestamp: {datetime} The timestamp for oldest finding to fetch
+ :param type_filter: {str} Type filter to apply
+ :return: {list} The list of filtered Finding objects
+ """
+ request_url = GET_FINDINGS_URL.format(self.api_root)
+ params = {
+ "sort": "-lastSeen",
+ "limit": DEFAULT_LIMIT,
+ "filter": json.dumps(
+ [{"field": "type", "value": type_filter, "comparison": "eq"}]
+ ),
+ "offset": 0,
+ }
+ response = self.session.get(request_url, params=params)
+ validate_response(response)
+ json_result = response.json()
+ findings = self.parser.build_findings_list(json_result)
+
+ while len(
+ json_result
+ ) >= DEFAULT_LIMIT and start_timestamp <= convert_string_to_datetime(
+ findings[-1].last_seen
+ ):
+ params.update({"offset": len(findings)})
+ response = self.session.get(request_url, params=params)
+ validate_response(response)
+ json_result = response.json()
+ findings.extend(self.parser.build_findings_list(json_result))
+
+ filtered_by_timestamp = filter_alerts_by_timestamp(
+ logger=self.siemplify_logger,
+ alerts=findings,
+ last_success_time=start_timestamp,
+ )
+ filtered_findings = filter_old_alerts(
+ logger=self.siemplify_logger,
+ alerts=filtered_by_timestamp,
+ existing_ids=existing_ids,
+ )
+ return sorted(filtered_findings, key=lambda finding: finding.last_seen)[:limit]
diff --git a/content/response_integrations/google/outpost24/core/Outpost24Parser.py b/content/response_integrations/google/outpost24/core/Outpost24Parser.py
new file mode 100644
index 000000000..8697055d2
--- /dev/null
+++ b/content/response_integrations/google/outpost24/core/Outpost24Parser.py
@@ -0,0 +1,95 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+from .datamodels import *
+from collections import Counter
+
+
+class Outpost24Parser:
+
+ @staticmethod
+ def build_entity_object(raw_data):
+
+ return EntityObject(
+ raw_data=raw_data,
+ hostname=raw_data.get("hostname"),
+ ip=raw_data.get("ip"),
+ exposed=raw_data.get("exposed"),
+ created=raw_data.get("created"),
+ first_seen=raw_data.get("firstSeen"),
+ source=raw_data.get("source"),
+ criticality=raw_data.get("businessCriticality"),
+ id=raw_data.get("id"),
+ )
+
+ @staticmethod
+ def find_ip_address(raw_data, entity_identifier):
+
+ for ip_address in raw_data:
+ if ip_address.get("ip") == entity_identifier:
+ return ip_address
+
+ @staticmethod
+ def filter_found_information(data, risk_level_filter):
+
+ filtered_results = []
+ for information in data:
+ if information.get("riskLevel").lower() in risk_level_filter:
+ filtered_results.append(information)
+
+ return filtered_results
+
+ @staticmethod
+ def add_findings_to_entity_object(entity_object, data):
+
+ type_counter = Counter([finding["type"] for finding in data])
+ risk_level_counter = Counter([finding["riskLevel"] for finding in data])
+
+ count_initial_findings = risk_level_counter["Initial"]
+ count_medium_findings = risk_level_counter["Medium"]
+ count_low_findings = risk_level_counter["Low"]
+ count_recommendation_findings = risk_level_counter["Recommendation"]
+ count_high_findings = risk_level_counter["High"]
+ count_critical_findings = risk_level_counter["Critical"]
+ count_information_findings = type_counter["Information"]
+ count_vulnerability_findings = type_counter["Vulnerability"]
+
+ entity_object.set_findings_parameters(
+ findings_raw_data=data,
+ count_initial_findings=count_initial_findings,
+ count_recommendation_findings=count_recommendation_findings,
+ count_low_findings=count_low_findings,
+ count_medium_findings=count_medium_findings,
+ count_high_findings=count_high_findings,
+ count_critical_findings=count_critical_findings,
+ count_information_findings=count_information_findings,
+ count_vulnerability_findings=count_vulnerability_findings,
+ )
+
+ def build_findings_list(self, raw_data):
+ return [self.build_finding_object(item) for item in raw_data]
+
+ def build_finding_object(self, raw_data):
+ return Finding(
+ raw_data=raw_data,
+ id=raw_data.get("id"),
+ name=raw_data.get("name"),
+ data=raw_data.get("data"),
+ description=raw_data.get("description"),
+ risk_level=raw_data.get("riskLevel"),
+ type=raw_data.get("type"),
+ last_seen=raw_data.get("lastSeen"),
+ product_name=raw_data.get("productName"),
+ )
diff --git a/content/response_integrations/google/outpost24/core/UtilsManager.py b/content/response_integrations/google/outpost24/core/UtilsManager.py
new file mode 100755
index 000000000..62e971a41
--- /dev/null
+++ b/content/response_integrations/google/outpost24/core/UtilsManager.py
@@ -0,0 +1,389 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import json
+import os
+import requests
+from .Outpost24Exceptions import Outpost24GeneralException
+import datetime
+from soar_sdk.SiemplifyUtils import (
+ utc_now,
+ convert_datetime_to_unix_time,
+ unix_now,
+ convert_string_to_unix_time,
+ convert_string_to_datetime,
+)
+from TIPCommon import validate_map_file
+from EnvironmentCommon import EnvironmentHandle
+from .constants import SEVERITIES
+
+
+# Move to TIPCommon
+STORED_IDS_LIMIT = 1000
+TIMEOUT_THRESHOLD = 0.9
+WHITELIST_FILTER = 1
+BLACKLIST_FILTER = 2
+UNIX_FORMAT = 1
+DATETIME_FORMAT = 2
+
+
+def validate_response(response, error_msg="An error occurred"):
+ """
+ Validate response
+ :param response: {requests.Response} The response to validate
+ :param error_msg: {str} Default message to display on error
+ """
+ try:
+ response.raise_for_status()
+
+ except requests.HTTPError as error:
+ raise Outpost24GeneralException(
+ f"{error_msg}: {error} {error.response.content}"
+ ) from error
+
+
+def load_csv_to_list(csv: str, param_name: str):
+ """
+ Load comma separated values represented as string to a list. Remove duplicates if exist
+ :param csv: {str} of comma separated values with delimiter ','
+ :param param_name: {str} the name of the parameter we are loading csv to list
+ :return: {[str]} List of separated string values
+ raise Outpost24GeneralException if failed to parse csv string
+ """
+ try:
+ return list({t.strip() for t in csv.split(",")})
+ except Exception as error:
+ raise Outpost24GeneralException(
+ f'Failed to load comma separated string parameter "{param_name}"'
+ ) from error
+
+
+# Move to TIPCommon
+def is_approaching_timeout(
+ python_process_timeout, connector_starting_time, timeout_threshold=TIMEOUT_THRESHOLD
+):
+ """
+ Check if a timeout is approaching.
+ :param python_process_timeout: {int} The python process timeout
+ :param connector_starting_time: {int} The connector start unix time
+ :param timeout_threshold: {int} Determines which part of the execution time is available for execution
+ :return: {bool} True if timeout is close, False otherwise
+ """
+ processing_time_ms = unix_now() - connector_starting_time
+ return processing_time_ms > python_process_timeout * 1000 * timeout_threshold
+
+
+def get_last_success_time(
+ siemplify,
+ offset_with_metric,
+ time_format=DATETIME_FORMAT,
+ print_value=True,
+ date_time_format=None,
+):
+ """
+ Get last success time datetime
+ :param siemplify: {siemplify} Siemplify object
+ :param offset_with_metric: {dict} metric and value. Ex {"hours": 1}
+ :param time_format: {int} The format of the output time. Ex DATETIME, UNIX
+ :param print_value: {bool} Whether log the value or not
+ :param date_time_format: {str} Datetime format to return data
+ :return: {time} If first run, return current time minus offset time, else return timestamp from file
+ """
+ last_run_timestamp = siemplify.fetch_timestamp(datetime_format=True)
+ offset = datetime.timedelta(**offset_with_metric)
+ current_time = utc_now()
+ # Check if first run
+ raw_datetime = (
+ current_time - offset
+ if current_time - last_run_timestamp > offset
+ else last_run_timestamp
+ )
+ unix_result = convert_datetime_to_unix_time(raw_datetime)
+ datetime_result = (
+ raw_datetime
+ if not date_time_format
+ else raw_datetime.strftime(date_time_format)
+ )
+
+ if print_value:
+ siemplify.LOGGER.info(
+ f"Last success time. Date time:{datetime_result}. Unix:{unix_result}"
+ )
+
+ return unix_result if time_format == UNIX_FORMAT else datetime_result
+
+
+# Move to TIPCommon
+def get_environment_common(
+ siemplify, environment_field_name, environment_regex_pattern, map_file="map.json"
+):
+ """
+ Get environment common
+ :param siemplify: {siemplify} Siemplify object
+ :param environment_field_name: {str} The environment field name
+ :param environment_regex_pattern: {str} The environment regex pattern
+ :param map_file: {str} The map file
+ :return: {EnvironmentHandle}
+ """
+ map_file_path = os.path.join(siemplify.run_folder, map_file)
+ validate_map_file(siemplify, map_file_path)
+ return EnvironmentHandle(
+ map_file_path,
+ siemplify.LOGGER,
+ environment_field_name,
+ environment_regex_pattern,
+ siemplify.context.connector_info.environment,
+ )
+
+
+def filter_alerts_by_timestamp(
+ logger, alerts, last_success_time, timestamp_key="last_seen"
+):
+ """
+ Filter alerts that were already processed
+ :param logger: {SiemplifyLogger} Siemplify logger
+ :param alerts: {list} List of Finding objects
+ :param last_success_time: {datetime} Datetime to filter by
+ :param timestamp_key: {str} The key of timestamp
+ :return: {list} List of filtered Finding objects
+ """
+ filtered_alerts = []
+
+ for alert in alerts:
+ timestamp = convert_string_to_datetime(getattr(alert, timestamp_key))
+
+ if timestamp >= last_success_time:
+ filtered_alerts.append(alert)
+ else:
+ logger.info(
+ f"The alert {alert.id} is older than {last_success_time}. Skipping..."
+ )
+
+ return filtered_alerts
+
+
+# Move to TIPCommon
+def filter_old_alerts(logger, alerts, existing_ids, id_key="id"):
+ """
+ Filter alerts that were already processed
+ :param logger: {SiemplifyLogger} Siemplify logger
+ :param alerts: {list} List of Alert objects
+ :param existing_ids: {list} List of ids to filter
+ :param id_key: {str} The key of identifier
+ :return: {list} List of filtered Alert objects
+ """
+ filtered_alerts = []
+
+ for alert in alerts:
+ id = getattr(alert, id_key)
+
+ if id not in existing_ids:
+ filtered_alerts.append(alert)
+ else:
+ logger.info(f"The alert {id} skipped since it has been fetched before")
+
+ return filtered_alerts
+
+
+# Move to TIPCommon
+def read_ids(siemplify, ids_file_name="ids.json"):
+ """
+ Read existing alerts IDs from ids file (from last 24h only)
+ :param siemplify: {Siemplify} Siemplify object.
+ :param ids_file_name: {str} The name of the ids file
+ :return: {list} List of ids
+ """
+ ids_file_path = os.path.join(siemplify.run_folder, ids_file_name)
+
+ if not os.path.exists(ids_file_path):
+ return []
+
+ try:
+ with open(ids_file_path, "r") as f:
+ return json.loads(f.read())
+ except Exception as e:
+ siemplify.LOGGER.error(f"Unable to read ids file: {e}")
+ siemplify.LOGGER.exception(e)
+ return []
+
+
+# Move to TIPCommon
+def write_ids(siemplify, ids, ids_file_name="ids.json"):
+ """
+ Write ids to the ids file
+ :param siemplify: {Siemplify} Siemplify object.
+ :param ids: {list} The ids to write to the file
+ :param ids_file_name: {str} The name of the ids file.
+ :return: {bool}
+ """
+ ids = ids[-STORED_IDS_LIMIT:]
+
+ try:
+ ids_file_path = os.path.join(siemplify.run_folder, ids_file_name)
+
+ if not os.path.exists(os.path.dirname(ids_file_path)):
+ os.makedirs(os.path.dirname(ids_file_path))
+
+ with open(ids_file_path, "w") as f:
+ try:
+ for chunk in json.JSONEncoder().iterencode(ids):
+ f.write(chunk)
+ except:
+ # Move seeker to start of the file
+ f.seek(0)
+ # Empty the content of the file (the partially written content that was written before the exception)
+ f.truncate()
+ # Write an empty dict to the events data file
+ f.write("[]")
+ raise
+
+ siemplify.LOGGER.info(f"Write ids. Total ids={len(ids)}")
+ return True
+
+ except Exception as e:
+ siemplify.LOGGER.error(f"Failed writing IDs to IDs file, ERROR: {e}")
+ siemplify.LOGGER.exception(e)
+ return False
+
+
+# Move to TIPCommon
+def is_overflowed(siemplify, alert_info, is_test_run):
+ """
+ Check if overflowed
+ :param siemplify: {Siemplify} Siemplify object.
+ :param alert_info: {AlertInfo}
+ :param is_test_run: {bool} Whether test run or not.
+ :return: {bool}
+ """
+ try:
+ return siemplify.is_overflowed_alert(
+ environment=alert_info.environment,
+ alert_identifier=alert_info.ticket_id,
+ alert_name=alert_info.rule_generator,
+ product=alert_info.device_product,
+ )
+
+ except Exception as e:
+ siemplify.LOGGER.error(f"Error validation connector overflow, ERROR: {e}")
+ siemplify.LOGGER.exception(e)
+
+ if is_test_run:
+ raise
+
+ return False
+
+
+def save_timestamp(
+ siemplify,
+ alerts,
+ timestamp_key="timestamp",
+ incrementation_value=0,
+ log_timestamp=True,
+):
+ """
+ Save last timestamp for given alerts
+ :param siemplify: {Siemplify} Siemplify object
+ :param alerts: {list} The list of alerts to find the last timestamp
+ :param timestamp_key: {str} key for getting timestamp from alert
+ :param incrementation_value: {int} The value to increment last timestamp by milliseconds
+ :param log_timestamp: {bool} Whether log timestamp or not
+ :return: {bool} Is timestamp updated
+ """
+ if not alerts:
+ siemplify.LOGGER.info("Timestamp is not updated since no alerts fetched")
+ return False
+
+ alerts = sorted(alerts, key=lambda alert: getattr(alert, timestamp_key))
+ last_timestamp = (
+ convert_string_to_unix_time(getattr(alerts[-1], timestamp_key))
+ + incrementation_value
+ )
+
+ if log_timestamp:
+ siemplify.LOGGER.info(f"Last timestamp is: {last_timestamp}")
+
+ siemplify.save_timestamp(new_timestamp=last_timestamp)
+ return True
+
+
+def pass_whitelist_filter(
+ siemplify, whitelist_as_a_blacklist, model, model_key, whitelist=None
+):
+ # whitelist filter
+ whitelist = whitelist or siemplify.whitelist
+ whitelist_filter_type = (
+ BLACKLIST_FILTER if whitelist_as_a_blacklist else WHITELIST_FILTER
+ )
+ model_value = getattr(model, model_key)
+ model_values = model_value if isinstance(model_value, list) else [model_value]
+
+ if whitelist:
+ for value in model_values:
+ if whitelist_filter_type == BLACKLIST_FILTER and value in whitelist:
+ siemplify.LOGGER.info(f"'{value}' did not pass blacklist filter.")
+ return False
+
+ if whitelist_filter_type == WHITELIST_FILTER and value not in whitelist:
+ siemplify.LOGGER.info(f"'{value}' did not pass whitelist filter.")
+ return False
+
+ return True
+
+
+def pass_severity_filter(siemplify, alert, lowest_severity):
+ # severity filter
+ if lowest_severity:
+ filtered_severities = (
+ SEVERITIES[SEVERITIES.index(lowest_severity.lower()) :]
+ if lowest_severity.lower() in SEVERITIES
+ else []
+ )
+ if not filtered_severities:
+ siemplify.LOGGER.info(
+ "Risk is not checked. Invalid value provided for "
+ "'Lowest Risk To Fetch' parameter. Possible values are: "
+ f"{convert_list_to_comma_string([severity.title() for severity in SEVERITIES])}."
+ )
+ if filtered_severities and alert.risk_level.lower() not in filtered_severities:
+ siemplify.LOGGER.info(
+ f"Finding with risk: {alert.risk_level} did not pass filter. Lowest "
+ f"risk to fetch is {lowest_severity}."
+ )
+ return False
+ return True
+
+
+def convert_comma_separated_to_list(comma_separated):
+ """
+ Convert comma-separated string to list
+ :param comma_separated: String with comma-separated values
+ :return: List of values
+ """
+ return (
+ [item.strip() for item in comma_separated.split(",")] if comma_separated else []
+ )
+
+
+def convert_list_to_comma_string(values_list):
+ """
+ Convert list to comma-separated string
+ :param values_list: List of values
+ :return: String with comma-separated values
+ """
+ return (
+ ", ".join(str(v) for v in values_list)
+ if values_list and isinstance(values_list, list)
+ else values_list
+ )
diff --git a/content/response_integrations/google/outpost24/core/__init__.py b/content/response_integrations/google/outpost24/core/__init__.py
new file mode 100644
index 000000000..9f71a2dc3
--- /dev/null
+++ b/content/response_integrations/google/outpost24/core/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/content/response_integrations/google/outpost24/core/constants.py b/content/response_integrations/google/outpost24/core/constants.py
new file mode 100755
index 000000000..4541ab19b
--- /dev/null
+++ b/content/response_integrations/google/outpost24/core/constants.py
@@ -0,0 +1,92 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+INTEGRATION_NAME = "Outpost24"
+INTEGRATION_DISPLAY_NAME = "Outpost24"
+
+# Actions
+PING_SCRIPT_NAME = f"{INTEGRATION_DISPLAY_NAME} - Ping"
+ENRICH_ENTITIES_SCRIPT_NAME = f"{INTEGRATION_DISPLAY_NAME} - Enrich Entities"
+
+GET_TOKEN_URL = "{}/opi/rest/auth/login"
+PING_URL = "{}/opi/rest/outscan/findings?limit=1"
+GET_DEVICES_URL = "{}/opi/rest/outscan/targets"
+GET_FINDINGS_URL = "{}/opi/rest/outscan/findings"
+
+SUPORTED_RISK_LEVELS = [
+ "low",
+ "medium",
+ "high",
+ "critical",
+ "initial",
+ "recommendation",
+]
+ENRICHMENT_PREFIX = "Outpost24"
+DEFAULT_API_LIMIT = 1000
+
+# Connector
+CONNECTOR_NAME = f"{INTEGRATION_DISPLAY_NAME} - Outscan Findings Connector"
+DEFAULT_TIME_FRAME = 1
+DEFAULT_LIMIT = 100
+DEVICE_VENDOR = "Outpost24"
+DEVICE_PRODUCT = "Outpost24"
+
+POSSIBLE_TYPES = ["Information", "Vulnerability", "Port"]
+
+SEVERITY_MAP = {
+ "Initial": -1,
+ "Recommendation": -1,
+ "Low": 40,
+ "Medium": 60,
+ "High": 80,
+ "Critical": 100,
+}
+
+SEVERITIES = ["initial", "recommendation", "low", "medium", "high", "critical"]
+
+FINDING_TYPES = {
+ "All": "Vulnerability, Information",
+ "Vulnerability": "Vulnerability",
+ "Information": "Information",
+}
+
+RISK_COLOR_MAP = {
+ "LOW": "#ffff00",
+ "MEDIUM": "#ff9900",
+ "HIGH": "#ff0000",
+ "CRITICAL": "#ff0000",
+}
+
+INSIGHT_HTML_TEMPLATE = """
+
+
+
+
diff --git a/content/response_integrations/google/outpost24/widgets/EnrichEntities.yaml b/content/response_integrations/google/outpost24/widgets/EnrichEntities.yaml
new file mode 100644
index 000000000..e47faffc9
--- /dev/null
+++ b/content/response_integrations/google/outpost24/widgets/EnrichEntities.yaml
@@ -0,0 +1,44 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+title: Outpost24 - Enrich Entities
+type: html
+scope: alert
+action_identifier: Enrich Entities
+description: This widget highlights the most important items in Enrich Entities
+data_definition:
+ html_height: 400
+ safe_rendering: false
+ widget_definition_scope: both
+ type: html
+condition_group:
+ conditions:
+ - field_name: '[{stepInstanceName}.JsonResult]'
+ value: '['
+ match_type: starts_with
+ custom_operator_name: null
+ - field_name: '[{stepInstanceName}.JsonResult]'
+ value: '{'
+ match_type: contains
+ custom_operator_name: null
+ - field_name: '[{stepInstanceName}.JsonResult]'
+ value: Entity
+ match_type: contains
+ custom_operator_name: null
+ - field_name: '[{stepInstanceName}.JsonResult]'
+ value: EntityResult
+ match_type: contains
+ custom_operator_name: null
+ logical_operator: and
+default_size: half_width
diff --git a/content/response_integrations/google/ruff.toml b/content/response_integrations/google/ruff.toml
index 66852f15d..328fcb43c 100644
--- a/content/response_integrations/google/ruff.toml
+++ b/content/response_integrations/google/ruff.toml
@@ -84,6 +84,8 @@ preview = true
"stealthwatch_v610/**" = ["ALL"]
"symantec_content_analysis/**" = ["ALL"]
"symantec_icdx/**" = ["ALL"]
+"observe_it/**" = ["ALL"]
+"outpost24/**" = ["ALL"]
[format]
# Like Black, use double quotes for strings.