psengine 2.6.0__tar.gz → 2.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {psengine-2.6.0 → psengine-2.7.0}/PKG-INFO +4 -4
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/models.py +1 -1
- {psengine-2.6.0 → psengine-2.7.0}/psengine/endpoints.py +10 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/helpers/helpers.py +3 -3
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/complex_entity.py +1 -1
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/enriched_indicator.py +1 -1
- psengine-2.7.0/psengine/threat_maps/__init__.py +30 -0
- psengine-2.7.0/psengine/threat_maps/errors.py +34 -0
- psengine-2.7.0/psengine/threat_maps/models.py +56 -0
- psengine-2.7.0/psengine/threat_maps/threat_map.py +134 -0
- psengine-2.7.0/psengine/threat_maps/threat_map_mgr.py +173 -0
- {psengine-2.6.0 → psengine-2.7.0}/pyproject.toml +12 -11
- {psengine-2.6.0 → psengine-2.7.0}/README.md +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/_sdk_id.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/analyst_notes/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/analyst_notes/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/analyst_notes/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/analyst_notes/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/analyst_notes/markdown.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/analyst_notes/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/analyst_notes/note.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/analyst_notes/note_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/asi/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/asi/asi.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/asi/asi_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/asi/client.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/asi/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/asi/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/asi/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/base_http_client.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/classic_alert.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/classic_alerts/markdown/markdown.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/collective_insights/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/collective_insights/collective_insights.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/collective_insights/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/collective_insights/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/collective_insights/insight.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/collective_insights/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/common_models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/config/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/config/config.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/config/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/detection/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/detection/detection_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/detection/detection_rule.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/detection/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/detection/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/detection/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/lookup.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/lookup_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/models/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/models/lookup.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/models/soar.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/soar.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/enrich/soar_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_lists/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_lists/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_lists/entity_list.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_lists/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_lists/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_match/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_match/entity_match.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_match/entity_match_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_match/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/entity_match/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/fusion/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/fusion/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/fusion/fusion_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/fusion/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/helpers/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/identity.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/identity_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/models/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/models/common_models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/models/detections.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/models/incident_report.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/identity/models/lookup.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/logger/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/logger/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/logger/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/logger/rf_logger.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/auto_sigma_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/auto_yara_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/malware_intel.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/malware_intel_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/malware_intel/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/markdown/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/markdown/markdown.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/markdown/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/mappings.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/common_models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/panel_log.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/pa_category.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/playbook_alerts.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/py.typed +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/rf_client.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risk_history/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risk_history/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risk_history/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risk_history/risk_history_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risklists/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risklists/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risklists/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risklists/models.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/risklists/risklist_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/base_stix_entity.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/rf_bundle.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/simple_entity.py +0 -0
- {psengine-2.6.0 → psengine-2.7.0}/psengine/stix2/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: psengine
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
|
|
5
5
|
Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
|
|
6
6
|
Author: Moise Medici, Patrick Kinsella, Ernest Bartosevic
|
|
@@ -18,12 +18,12 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
19
|
Requires-Dist: typing-extensions>=4.8.0
|
|
20
20
|
Requires-Dist: requests>=2.27.1
|
|
21
|
-
Requires-Dist: jsonpath-ng>=1.5.3,<=1.
|
|
21
|
+
Requires-Dist: jsonpath-ng>=1.5.3,<=1.8.0
|
|
22
22
|
Requires-Dist: stix2~=3.0.1
|
|
23
23
|
Requires-Dist: python-dateutil>=2.7.0
|
|
24
|
-
Requires-Dist: more-itertools>=9.0.0,<=
|
|
24
|
+
Requires-Dist: more-itertools>=9.0.0,<=11.0.2
|
|
25
25
|
Requires-Dist: pydantic>=2.7,<3.0.0
|
|
26
|
-
Requires-Dist: pydantic-settings[toml]>=2.12.2,<2.
|
|
26
|
+
Requires-Dist: pydantic-settings[toml]>=2.12.2,<2.14.1
|
|
27
27
|
Requires-Dist: markdown-strings==3.4.0
|
|
28
28
|
Requires-Python: >=3.10, <3.15
|
|
29
29
|
Project-URL: Homepage, https://recordedfuture-professionalservices.github.io/psengine/latest/
|
|
@@ -147,3 +147,13 @@ EP_ASI_ASSET_EXPOSURES = f'{EP_ASI_ASSETS}/{{}}/exposures'
|
|
|
147
147
|
EP_ASI_ASSETS_SEARCH = f'{EP_ASI_ASSETS}/_search'
|
|
148
148
|
EP_ASI_EXPOSURES = f'{EP_ASI_PROJECTS}/{{}}/exposures'
|
|
149
149
|
EP_ASI_EXPOSURES_BY_SIGNATURE = f'{EP_ASI_EXPOSURES}/{{}}'
|
|
150
|
+
|
|
151
|
+
################################################################################
|
|
152
|
+
# Threat Map API Endpoints
|
|
153
|
+
################################################################################
|
|
154
|
+
EP_THREAT_MAPS_BASE = BASE_URL + '/threat'
|
|
155
|
+
EP_THREAT_MAPS_LIST = EP_THREAT_MAPS_BASE + '/maps'
|
|
156
|
+
EP_THREAT_MAP = EP_THREAT_MAPS_BASE + '/map/{}'
|
|
157
|
+
EP_THREAT_MAP_ORG = EP_THREAT_MAPS_BASE + '/map/{}/{}'
|
|
158
|
+
EP_ACTOR_SEARCH = EP_THREAT_MAPS_BASE + '/actor/search'
|
|
159
|
+
EP_CATEGORIES = EP_THREAT_MAPS_BASE + '/{}/categories'
|
|
@@ -325,10 +325,10 @@ class OSHelpers:
|
|
|
325
325
|
try:
|
|
326
326
|
path.mkdir(parents=True, exist_ok=True)
|
|
327
327
|
except PermissionError as err:
|
|
328
|
-
raise WriteFileError(f'Directory {path} is not
|
|
329
|
-
# In case it already exists, check if it is
|
|
328
|
+
raise WriteFileError(f'Directory {path} is not writable') from err
|
|
329
|
+
# In case it already exists, check if it is writable
|
|
330
330
|
if not os.access(path, os.W_OK):
|
|
331
|
-
raise WriteFileError(f'Directory {path} is not
|
|
331
|
+
raise WriteFileError(f'Directory {path} is not writable')
|
|
332
332
|
return path
|
|
333
333
|
|
|
334
334
|
|
|
@@ -225,7 +225,7 @@ class IndicatorEntity(BaseStixEntity):
|
|
|
225
225
|
"""
|
|
226
226
|
if not create_indicator and not create_obs:
|
|
227
227
|
raise STIX2TransformError(
|
|
228
|
-
'
|
|
228
|
+
'Indicator must create at least one of "Observable" or "Indicator"',
|
|
229
229
|
)
|
|
230
230
|
|
|
231
231
|
type_ = CONVERTED_TYPES.get(type_, type_)
|
|
@@ -126,7 +126,7 @@ class EnrichedIndicator(IndicatorEntity):
|
|
|
126
126
|
"""Creates relationship between object and indicator/observabe.
|
|
127
127
|
|
|
128
128
|
Raises:
|
|
129
|
-
STIX2TransformError: Generic
|
|
129
|
+
STIX2TransformError: Generic transform error
|
|
130
130
|
"""
|
|
131
131
|
if isinstance(obj, IndicatorEntity):
|
|
132
132
|
sources = []
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from .errors import (
|
|
15
|
+
ThreatActorSearchError,
|
|
16
|
+
ThreatMapCategoriesError,
|
|
17
|
+
ThreatMapFetchError,
|
|
18
|
+
ThreatMapInfoError,
|
|
19
|
+
ThreatMapsError,
|
|
20
|
+
)
|
|
21
|
+
from .threat_map import (
|
|
22
|
+
EntityCategory,
|
|
23
|
+
ThreatActorAttributes,
|
|
24
|
+
ThreatActorProfile,
|
|
25
|
+
ThreatMap,
|
|
26
|
+
ThreatMapEntity,
|
|
27
|
+
ThreatMapFetchIn,
|
|
28
|
+
ThreatMapInfo,
|
|
29
|
+
)
|
|
30
|
+
from .threat_map_mgr import ThreatMapMgr
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from ..errors import RecordedFutureError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ThreatMapsError(RecordedFutureError):
|
|
18
|
+
"""Error raised when there was an issue with the threat maps API."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ThreatMapFetchError(ThreatMapsError):
|
|
22
|
+
"""Error raised when there was an issue fetching a threat map."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ThreatMapInfoError(ThreatMapsError):
|
|
26
|
+
"""Error raised when there was an error fetching available threat maps."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ThreatMapCategoriesError(ThreatMapsError):
|
|
30
|
+
"""Error raised when there was an error searching threat categories."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ThreatActorSearchError(ThreatMapsError):
|
|
34
|
+
"""Error raised when there was an error searching threat actors."""
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from enum import Enum
|
|
16
|
+
|
|
17
|
+
from ..common_models import IdName, RFBaseModel
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ThreatMapAxis(Enum):
|
|
21
|
+
opportunity = 'opportunity'
|
|
22
|
+
intent = 'intent'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ThreatMapType(Enum):
|
|
26
|
+
actors = 'actors'
|
|
27
|
+
malware = 'malware'
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def category_slug(self) -> str:
|
|
31
|
+
"""Return the URL slug used by the categories endpoint.
|
|
32
|
+
|
|
33
|
+
The map endpoint uses `actors`/`malware` (this enum's value), but the
|
|
34
|
+
categories endpoint uses the singular `actor`/`malware` — see api.md.
|
|
35
|
+
"""
|
|
36
|
+
return 'actor' if self is ThreatMapType.actors else 'malware'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class LogEntry(RFBaseModel):
|
|
40
|
+
watchlist: IdName | None = None
|
|
41
|
+
entity: IdName
|
|
42
|
+
severity: int
|
|
43
|
+
axis: str
|
|
44
|
+
date: datetime
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class EntityAttributes(RFBaseModel):
|
|
48
|
+
name: str
|
|
49
|
+
alias: list[str] = []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ThreatActorAttributes(RFBaseModel):
|
|
53
|
+
name: str
|
|
54
|
+
common_names: list[str] = []
|
|
55
|
+
alias: list[str] = []
|
|
56
|
+
categories: list[IdName] = []
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from functools import total_ordering
|
|
16
|
+
from typing import Annotated
|
|
17
|
+
|
|
18
|
+
from pydantic import BeforeValidator, Field
|
|
19
|
+
|
|
20
|
+
from ..common_models import IdName, RFBaseModel
|
|
21
|
+
from ..helpers.helpers import Validators
|
|
22
|
+
from .models import EntityAttributes, LogEntry, ThreatActorAttributes
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@total_ordering
|
|
26
|
+
class ThreatMapEntity(RFBaseModel):
|
|
27
|
+
"""Model to validate data received from the `threat/map/{type}` endpoint.
|
|
28
|
+
|
|
29
|
+
This class supports string representation, equality comparison, and hashing of `ThreatMapEntity`
|
|
30
|
+
instances.
|
|
31
|
+
|
|
32
|
+
Hashing:
|
|
33
|
+
Defines uniqueness of a `ThreatMapEntity` object by the entity ID.
|
|
34
|
+
|
|
35
|
+
Equality:
|
|
36
|
+
Validates equality between two `ThreatMapEntity` objects based on the entity ID.
|
|
37
|
+
|
|
38
|
+
Greater-than Comparison:
|
|
39
|
+
Defines a greater-than comparison between two `ThreatMapEntity` instances based on
|
|
40
|
+
`opportunity`, `intent` and `prevalence`. Lastly on `id_`
|
|
41
|
+
|
|
42
|
+
String Representation:
|
|
43
|
+
Returns a string representation of the `ThreatMapEntity` instance including the
|
|
44
|
+
entity match name, ID, opportunity, and intent or prevalence depending on category.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
>>> print(entity)
|
|
48
|
+
Entity Name: BlueDelta, ID: L37nw-, Opportunity: 65, Intent: 65'
|
|
49
|
+
```
|
|
50
|
+
Ordering:
|
|
51
|
+
The ordering of `ThreatMapEntity` instances is determined primarily by the `opportunity`
|
|
52
|
+
score followed by the `intent` and `prevalence`.
|
|
53
|
+
If two instances have the same scores, the `id_` is used as a last criterion.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
id_: str = Field(alias='id')
|
|
57
|
+
name: str
|
|
58
|
+
alias: list[str]
|
|
59
|
+
categories: list[IdName]
|
|
60
|
+
intent: int | None = None
|
|
61
|
+
prevalence: int | None = None
|
|
62
|
+
opportunity: int
|
|
63
|
+
log_entries: list[LogEntry]
|
|
64
|
+
|
|
65
|
+
def __hash__(self):
|
|
66
|
+
return hash(self.id_)
|
|
67
|
+
|
|
68
|
+
def __eq__(self, other: 'ThreatMapEntity'):
|
|
69
|
+
return self.id_ == other.id_
|
|
70
|
+
|
|
71
|
+
def __gt__(self, other: 'ThreatMapEntity'):
|
|
72
|
+
return (self.opportunity, self.intent or 0, self.prevalence or 0, self.id_) > (
|
|
73
|
+
other.opportunity or 0,
|
|
74
|
+
other.intent or 0,
|
|
75
|
+
other.prevalence or 0,
|
|
76
|
+
other.id_,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def __str__(self):
|
|
80
|
+
key = 'intent' if self.intent is not None else 'prevalence'
|
|
81
|
+
score = getattr(self, key)
|
|
82
|
+
return (
|
|
83
|
+
f'Entity Name: {self.name}, ID: {self.id_}, '
|
|
84
|
+
f'Opportunity: {self.opportunity}, {key.capitalize()}: {score}'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ThreatMap(RFBaseModel):
|
|
89
|
+
"""Model for payload received by POST `/threat/map/{type}` endpoint."""
|
|
90
|
+
|
|
91
|
+
threat_map: list[ThreatMapEntity]
|
|
92
|
+
date: datetime = Field(description='Threat map generation timestamp')
|
|
93
|
+
|
|
94
|
+
def __str__(self):
|
|
95
|
+
return '\n'.join(str(entity) for entity in sorted(self.threat_map))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ThreatMapInfo(RFBaseModel):
|
|
99
|
+
"""Model for payload received by GET `/threat/maps` endpoint."""
|
|
100
|
+
|
|
101
|
+
name: str
|
|
102
|
+
type_: str = Field(alias='type')
|
|
103
|
+
organization: IdName
|
|
104
|
+
url: str
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class EntityCategory(RFBaseModel):
|
|
108
|
+
"""Model for payload received by GET `threat/{type}/categories` endpoint."""
|
|
109
|
+
|
|
110
|
+
id_: str = Field(alias='id')
|
|
111
|
+
type_: str = Field(alias='type')
|
|
112
|
+
attributes: EntityAttributes
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ThreatActorProfile(RFBaseModel):
|
|
116
|
+
"""Model for payload received by POST `threat/actor/search` endpoint."""
|
|
117
|
+
|
|
118
|
+
id_: str = Field(alias='id')
|
|
119
|
+
type_: str = Field(alias='type')
|
|
120
|
+
attributes: ThreatActorAttributes
|
|
121
|
+
|
|
122
|
+
def __str__(self):
|
|
123
|
+
attr = self.attributes
|
|
124
|
+
common = f', Common Names: {", ".join(attr.common_names)}' if attr.common_names else ''
|
|
125
|
+
return f'ID: {self.id_} Name: {attr.name}' + common
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ThreatMapFetchIn(RFBaseModel):
|
|
129
|
+
"""Model to validate `threat/map/{org}/{type}` endpoint payload sent."""
|
|
130
|
+
|
|
131
|
+
malware: Annotated[list[str] | None, BeforeValidator(Validators.convert_str_to_list)] = None
|
|
132
|
+
actors: Annotated[list[str] | None, BeforeValidator(Validators.convert_str_to_list)] = None
|
|
133
|
+
categories: Annotated[list[str] | None, BeforeValidator(Validators.convert_str_to_list)] = None
|
|
134
|
+
watchlists: Annotated[list[str] | None, BeforeValidator(Validators.convert_str_to_list)] = None
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Annotated, Literal
|
|
16
|
+
|
|
17
|
+
from pydantic import Field, validate_call
|
|
18
|
+
from typing_extensions import Doc
|
|
19
|
+
|
|
20
|
+
from ..constants import DEFAULT_LIMIT
|
|
21
|
+
from ..endpoints import (
|
|
22
|
+
EP_ACTOR_SEARCH,
|
|
23
|
+
EP_CATEGORIES,
|
|
24
|
+
EP_THREAT_MAP,
|
|
25
|
+
EP_THREAT_MAP_ORG,
|
|
26
|
+
EP_THREAT_MAPS_LIST,
|
|
27
|
+
)
|
|
28
|
+
from ..helpers import debug_call
|
|
29
|
+
from ..helpers.helpers import connection_exceptions
|
|
30
|
+
from ..rf_client import RFClient
|
|
31
|
+
from .errors import (
|
|
32
|
+
ThreatActorSearchError,
|
|
33
|
+
ThreatMapCategoriesError,
|
|
34
|
+
ThreatMapFetchError,
|
|
35
|
+
ThreatMapInfoError,
|
|
36
|
+
)
|
|
37
|
+
from .models import ThreatMapType
|
|
38
|
+
from .threat_map import (
|
|
39
|
+
EntityCategory,
|
|
40
|
+
ThreatActorProfile,
|
|
41
|
+
ThreatMap,
|
|
42
|
+
ThreatMapFetchIn,
|
|
43
|
+
ThreatMapInfo,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
MAP_TYPE = Literal['actors', 'malware']
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ThreatMapMgr:
|
|
50
|
+
"""Manages requests for Recorded Future Threat Maps API."""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
rf_token: Annotated[str | None, Doc('Recorded Future API token.')] = None,
|
|
55
|
+
):
|
|
56
|
+
"""Initialize the `ThreatMapMgr` object."""
|
|
57
|
+
self.log = logging.getLogger(__name__)
|
|
58
|
+
self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
|
|
59
|
+
|
|
60
|
+
@debug_call
|
|
61
|
+
@validate_call
|
|
62
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=ThreatMapInfoError)
|
|
63
|
+
def fetch_available_maps(
|
|
64
|
+
self,
|
|
65
|
+
) -> Annotated[list[ThreatMapInfo], Doc('A list of available threat maps.')]:
|
|
66
|
+
"""Fetch available threat maps for the organization.
|
|
67
|
+
|
|
68
|
+
Endpoint:
|
|
69
|
+
`threat/maps`
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
73
|
+
ThreatMapInfoError: If connection error occurs.
|
|
74
|
+
"""
|
|
75
|
+
maps_response = self.rf_client.request(method='get', url=EP_THREAT_MAPS_LIST).json()['data']
|
|
76
|
+
return [ThreatMapInfo.model_validate(entry) for entry in maps_response]
|
|
77
|
+
|
|
78
|
+
@debug_call
|
|
79
|
+
@validate_call
|
|
80
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=ThreatMapCategoriesError)
|
|
81
|
+
def fetch_entity_categories(
|
|
82
|
+
self,
|
|
83
|
+
map_type: Annotated[MAP_TYPE, Doc('Type of threat map.')],
|
|
84
|
+
) -> Annotated[list[EntityCategory], Doc('A list of threat map taxonomy categories.')]:
|
|
85
|
+
"""Fetch the entity category taxonomy used to filter threat maps.
|
|
86
|
+
|
|
87
|
+
Endpoint:
|
|
88
|
+
`threat/{type}/categories`
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
92
|
+
ThreatMapCategoriesError: If connection error occurs.
|
|
93
|
+
"""
|
|
94
|
+
map_type = ThreatMapType(map_type)
|
|
95
|
+
url = EP_CATEGORIES.format(map_type.category_slug)
|
|
96
|
+
cat_response = self.rf_client.request(method='get', url=url).json()['data']
|
|
97
|
+
return [EntityCategory.model_validate(ent) for ent in cat_response]
|
|
98
|
+
|
|
99
|
+
@debug_call
|
|
100
|
+
@validate_call
|
|
101
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=ThreatActorSearchError)
|
|
102
|
+
def search_threat_actor(
|
|
103
|
+
self,
|
|
104
|
+
name: Annotated[
|
|
105
|
+
str | None, Doc('Free text search of threat actor names, common names, or aliases.')
|
|
106
|
+
] = None,
|
|
107
|
+
max_results: Annotated[
|
|
108
|
+
int | None, Doc('Limit the total number of results returned.')
|
|
109
|
+
] = DEFAULT_LIMIT,
|
|
110
|
+
actors_per_page: Annotated[
|
|
111
|
+
int | None, Doc('The number of threat actors per page for pagination.')
|
|
112
|
+
] = Field(ge=1, le=10_000, default=DEFAULT_LIMIT),
|
|
113
|
+
) -> Annotated[
|
|
114
|
+
list[ThreatActorProfile], Doc('A list of threat actors matching the search criteria.')
|
|
115
|
+
]:
|
|
116
|
+
"""Search Recorded Future's threat actor database by name, alias, or classification.
|
|
117
|
+
|
|
118
|
+
Endpoint:
|
|
119
|
+
`threat/actor/search`
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
123
|
+
ThreatActorSearchError: If connection error occurs.
|
|
124
|
+
"""
|
|
125
|
+
data = {
|
|
126
|
+
'name': name,
|
|
127
|
+
'limit': min(max_results or DEFAULT_LIMIT, actors_per_page or DEFAULT_LIMIT),
|
|
128
|
+
}
|
|
129
|
+
search_response = self.rf_client.request_paged(
|
|
130
|
+
method='post',
|
|
131
|
+
url=EP_ACTOR_SEARCH,
|
|
132
|
+
data=data,
|
|
133
|
+
results_path='data',
|
|
134
|
+
offset_key='offset',
|
|
135
|
+
max_results=max_results or DEFAULT_LIMIT,
|
|
136
|
+
)
|
|
137
|
+
return [ThreatActorProfile.model_validate(ta) for ta in search_response]
|
|
138
|
+
|
|
139
|
+
@debug_call
|
|
140
|
+
@validate_call
|
|
141
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=ThreatMapFetchError)
|
|
142
|
+
def fetch_map(
|
|
143
|
+
self,
|
|
144
|
+
map_type: Annotated[MAP_TYPE, Doc('Type of threat map.')],
|
|
145
|
+
org_id: Annotated[str | None, Doc('Organization ID.')] = None,
|
|
146
|
+
malware: Annotated[str | list[str] | None, Doc('Filter by malware entity ID(s).')] = None,
|
|
147
|
+
actors: Annotated[str | list[str] | None, Doc('Filter by threat actor ID(s).')] = None,
|
|
148
|
+
categories: Annotated[str | list[str] | None, Doc('Filter by category ID(s).')] = None,
|
|
149
|
+
watchlists: Annotated[str | list[str] | None, Doc('Filter by watch list ID(s).')] = None,
|
|
150
|
+
) -> Annotated[ThreatMap, Doc('Threat map with entities matching filter criteria.')]:
|
|
151
|
+
"""Fetch a threat map with optional entity, category, and watchlist filters.
|
|
152
|
+
|
|
153
|
+
Endpoint:
|
|
154
|
+
`threat/map/{type}` or `threat/map/{org_id}/{type}`
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
158
|
+
ThreatMapFetchError: If connection error occurs.
|
|
159
|
+
"""
|
|
160
|
+
body = {'categories': categories, 'watchlists': watchlists}
|
|
161
|
+
map_type = ThreatMapType(map_type).value
|
|
162
|
+
if map_type is ThreatMapType.actors:
|
|
163
|
+
body['actors'] = actors
|
|
164
|
+
else:
|
|
165
|
+
body['malware'] = malware
|
|
166
|
+
|
|
167
|
+
url = (
|
|
168
|
+
EP_THREAT_MAP_ORG.format(org_id, map_type) if org_id else EP_THREAT_MAP.format(map_type)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
data = ThreatMapFetchIn.model_validate(body).json()
|
|
172
|
+
map_response = self.rf_client.request(method='post', url=url, data=data).json()['data']
|
|
173
|
+
return ThreatMap.model_validate(map_response)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "psengine"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.7.0"
|
|
4
4
|
readme = "README.md"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
requires-python = ">=3.10, <3.15"
|
|
@@ -26,15 +26,15 @@ classifiers=[
|
|
|
26
26
|
"Programming Language :: Python :: 3.14"
|
|
27
27
|
]
|
|
28
28
|
|
|
29
|
-
dependencies = [
|
|
30
|
-
"typing_extensions >= 4.8.0",
|
|
29
|
+
dependencies = [
|
|
30
|
+
"typing_extensions >= 4.8.0",
|
|
31
31
|
"requests>=2.27.1",
|
|
32
|
-
"jsonpath_ng>=1.5.3, <=1.
|
|
32
|
+
"jsonpath_ng>=1.5.3, <=1.8.0",
|
|
33
33
|
"stix2~=3.0.1",
|
|
34
34
|
"python-dateutil>=2.7.0",
|
|
35
|
-
"more-itertools>=9.0.0, <=
|
|
35
|
+
"more-itertools>=9.0.0, <=11.0.2",
|
|
36
36
|
"pydantic>=2.7, <3.0.0",
|
|
37
|
-
"pydantic-settings[toml]>=2.12.2,<2.
|
|
37
|
+
"pydantic-settings[toml]>=2.12.2,<2.14.1",
|
|
38
38
|
"markdown-strings==3.4.0"
|
|
39
39
|
]
|
|
40
40
|
|
|
@@ -51,24 +51,24 @@ dev = [
|
|
|
51
51
|
"pytest-mock==3.15.1",
|
|
52
52
|
"pytest-md==0.2.0",
|
|
53
53
|
"pytest-random-order==1.2.0",
|
|
54
|
-
"pytest-httpdbg==0.10.
|
|
54
|
+
"pytest-httpdbg==0.10.2",
|
|
55
55
|
"ruff~=0.15.8",
|
|
56
56
|
"mimesis>=19.1.0",
|
|
57
57
|
"freezegun>=1.5.5",
|
|
58
58
|
]
|
|
59
59
|
|
|
60
60
|
docs = [
|
|
61
|
-
"mike
|
|
61
|
+
"mike>=2.1.4,<2.3.0",
|
|
62
62
|
"mkdocs~=1.6.1",
|
|
63
|
-
"mkdocs-material
|
|
63
|
+
"mkdocs-material>=9.6.18,<9.8.0",
|
|
64
64
|
"mkdocstrings[python]>=0.18",
|
|
65
|
-
"griffe-typingdoc
|
|
65
|
+
"griffe-typingdoc>=0.2.8,<0.4.0",
|
|
66
66
|
"mkdocs-codeinclude-plugin~=0.2.1",
|
|
67
67
|
"markdown-include~=0.8.1",
|
|
68
68
|
"mkdocs-exclude~=1.0.2",
|
|
69
69
|
|
|
70
70
|
# Tools for examples
|
|
71
|
-
"rich==
|
|
71
|
+
"rich==15.0.0",
|
|
72
72
|
"logging-tree==1.10",
|
|
73
73
|
]
|
|
74
74
|
|
|
@@ -78,6 +78,7 @@ build-backend = "uv_build"
|
|
|
78
78
|
|
|
79
79
|
[tool.uv]
|
|
80
80
|
default-groups = "all"
|
|
81
|
+
exclude-newer = "7 days"
|
|
81
82
|
|
|
82
83
|
[tool.uv.build-backend]
|
|
83
84
|
module-root = ""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py
RENAMED
|
File without changes
|
{psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py
RENAMED
|
File without changes
|
{psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py
RENAMED
|
File without changes
|
{psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py
RENAMED
|
File without changes
|
{psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py
RENAMED
|
File without changes
|
{psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py
RENAMED
|
File without changes
|
|
File without changes
|
{psengine-2.6.0 → psengine-2.7.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|