psengine 2.6.0__tar.gz → 2.8.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.8.0}/PKG-INFO +5 -5
- {psengine-2.6.0 → psengine-2.8.0}/README.md +1 -1
- {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/markdown.py +4 -1
- {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/note.py +2 -2
- {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/note_mgr.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/asi_mgr.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/classic_alert_mgr.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/models.py +1 -1
- {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/collective_insights.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/endpoints.py +20 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/fusion/fusion_mgr.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/helpers/helpers.py +12 -3
- psengine-2.8.0/psengine/links/__init__.py +38 -0
- psengine-2.8.0/psengine/links/errors.py +26 -0
- psengine-2.8.0/psengine/links/links.py +152 -0
- psengine-2.8.0/psengine/links/links_mgr.py +214 -0
- psengine-2.8.0/psengine/links/models.py +94 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/auto_sigma_mgr.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/auto_yara_mgr.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/malware_intel_mgr.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risk_history/risk_history_mgr.py +5 -6
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/complex_entity.py +1 -1
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/enriched_indicator.py +1 -1
- psengine-2.8.0/psengine/threat_maps/__init__.py +30 -0
- psengine-2.8.0/psengine/threat_maps/errors.py +34 -0
- psengine-2.8.0/psengine/threat_maps/models.py +56 -0
- psengine-2.8.0/psengine/threat_maps/threat_map.py +134 -0
- psengine-2.8.0/psengine/threat_maps/threat_map_mgr.py +173 -0
- {psengine-2.6.0 → psengine-2.8.0}/pyproject.toml +12 -11
- {psengine-2.6.0 → psengine-2.8.0}/psengine/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/_sdk_id.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/asi.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/client.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/base_http_client.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/classic_alert.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/markdown/markdown.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/insight.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/common_models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/config/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/config/config.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/config/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/detection_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/detection_rule.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/lookup.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/lookup_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/models/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/models/lookup.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/models/soar.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/soar.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/soar_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/entity_list.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/entity_match.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/entity_match_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/fusion/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/fusion/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/fusion/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/helpers/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/identity.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/identity_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/common_models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/detections.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/incident_report.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/lookup.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/logger/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/logger/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/logger/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/logger/rf_logger.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/malware_intel.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/markdown/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/markdown/markdown.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/markdown/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/mappings.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/common_models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/panel_log.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/pa_category.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/playbook_alerts.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/py.typed +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/rf_client.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risk_history/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risk_history/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risk_history/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/models.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/risklist_mgr.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/__init__.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/base_stix_entity.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/constants.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/errors.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/helpers.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/rf_bundle.py +0 -0
- {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/simple_entity.py +0 -0
- {psengine-2.6.0 → psengine-2.8.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.8.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/
|
|
@@ -42,7 +42,7 @@ PSEngine is a simple, yet elegant, library for rapid development of integrations
|
|
|
42
42
|
|
|
43
43
|
PSEngine allows you to interact with the Recorded Future API extremely easily. There’s no need to manually build the URLs and query parameters, just use the modules dedicated to individual API endpoints.
|
|
44
44
|
|
|
45
|
-
PSEngine is a Python package solely built and maintained by the Recorded Future Cyber Security Engineering team powering a number of high profile integrations, such as: [Banshee](https://recordedfuture-professionalservices.github.io/banshee); [Recorded Future Alerts for QRadar](https://apps.xforce.ibmcloud.com/extension/b36efdf42b7bf5e3759d036dbcdbf606); Anomali ThreatStream: [Alerts](https://support.recordedfuture.com/hc/en-us/articles/29255683708691-Recorded-Future-Alerts-for-Anomali-ThreatStream) and [Analyst Notes](https://support.recordedfuture.com/hc/en-us/articles/12928414947475-Recorded-Future-Analyst-Notes-for-Anomali-ThreatStream) integrations; [Google SecOps](https://app.recordedfuture.com/portal/integration-center/detail/google-chronicle-nbfi?organization=uhash%3A5cJsHMHeSM&filter_tab=all) and many more.
|
|
45
|
+
PSEngine is a Python package solely built and maintained by the Recorded Future Cyber Security Engineering team powering a number of high profile integrations, such as: [PS Banshee](https://recordedfuture-professionalservices.github.io/ps-banshee/latest/); [Recorded Future Alerts for QRadar](https://apps.xforce.ibmcloud.com/extension/b36efdf42b7bf5e3759d036dbcdbf606); Anomali ThreatStream: [Alerts](https://support.recordedfuture.com/hc/en-us/articles/29255683708691-Recorded-Future-Alerts-for-Anomali-ThreatStream) and [Analyst Notes](https://support.recordedfuture.com/hc/en-us/articles/12928414947475-Recorded-Future-Analyst-Notes-for-Anomali-ThreatStream) integrations; [Google SecOps](https://app.recordedfuture.com/portal/integration-center/detail/google-chronicle-nbfi?organization=uhash%3A5cJsHMHeSM&filter_tab=all) and many more.
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
## Installation
|
|
@@ -10,7 +10,7 @@ PSEngine is a simple, yet elegant, library for rapid development of integrations
|
|
|
10
10
|
|
|
11
11
|
PSEngine allows you to interact with the Recorded Future API extremely easily. There’s no need to manually build the URLs and query parameters, just use the modules dedicated to individual API endpoints.
|
|
12
12
|
|
|
13
|
-
PSEngine is a Python package solely built and maintained by the Recorded Future Cyber Security Engineering team powering a number of high profile integrations, such as: [Banshee](https://recordedfuture-professionalservices.github.io/banshee); [Recorded Future Alerts for QRadar](https://apps.xforce.ibmcloud.com/extension/b36efdf42b7bf5e3759d036dbcdbf606); Anomali ThreatStream: [Alerts](https://support.recordedfuture.com/hc/en-us/articles/29255683708691-Recorded-Future-Alerts-for-Anomali-ThreatStream) and [Analyst Notes](https://support.recordedfuture.com/hc/en-us/articles/12928414947475-Recorded-Future-Analyst-Notes-for-Anomali-ThreatStream) integrations; [Google SecOps](https://app.recordedfuture.com/portal/integration-center/detail/google-chronicle-nbfi?organization=uhash%3A5cJsHMHeSM&filter_tab=all) and many more.
|
|
13
|
+
PSEngine is a Python package solely built and maintained by the Recorded Future Cyber Security Engineering team powering a number of high profile integrations, such as: [PS Banshee](https://recordedfuture-professionalservices.github.io/ps-banshee/latest/); [Recorded Future Alerts for QRadar](https://apps.xforce.ibmcloud.com/extension/b36efdf42b7bf5e3759d036dbcdbf606); Anomali ThreatStream: [Alerts](https://support.recordedfuture.com/hc/en-us/articles/29255683708691-Recorded-Future-Alerts-for-Anomali-ThreatStream) and [Analyst Notes](https://support.recordedfuture.com/hc/en-us/articles/12928414947475-Recorded-Future-Analyst-Notes-for-Anomali-ThreatStream) integrations; [Google SecOps](https://app.recordedfuture.com/portal/integration-center/detail/google-chronicle-nbfi?organization=uhash%3A5cJsHMHeSM&filter_tab=all) and many more.
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
@@ -36,9 +36,12 @@ EXTRACTED_KEYS = {
|
|
|
36
36
|
|
|
37
37
|
def _cleanup_insikt_note_text(note_text: str) -> str:
|
|
38
38
|
"""Clean up insikt note text to avoid markdown rendering issues."""
|
|
39
|
+
note_text = ''.join(
|
|
40
|
+
line if line.lstrip().startswith('|') else re.sub(r'--+', '', line)
|
|
41
|
+
for line in note_text.splitlines(keepends=True)
|
|
42
|
+
)
|
|
39
43
|
translation = {
|
|
40
44
|
r'\•': '+ ',
|
|
41
|
-
r'--+': '',
|
|
42
45
|
r'>>+': '',
|
|
43
46
|
r'<<+': '',
|
|
44
47
|
r'\*\*': '••',
|
|
@@ -80,9 +80,9 @@ class AnalystNote(RFBaseModel):
|
|
|
80
80
|
@property
|
|
81
81
|
def detection_rule_type(self) -> str | None:
|
|
82
82
|
"""Returns the attachment type if present, else None. It checks for specific types like
|
|
83
|
-
`sigma rule`, `yara rule`, and `
|
|
83
|
+
`sigma rule`, `yara rule`, `snort rule` and `suricata rule` in the topics of the note.
|
|
84
84
|
"""
|
|
85
|
-
topics_type = ('sigma rule', 'yara rule', 'snort rule')
|
|
85
|
+
topics_type = ('sigma rule', 'yara rule', 'snort rule', 'suricata rule')
|
|
86
86
|
|
|
87
87
|
topics = (
|
|
88
88
|
{topic.name.lower() for topic in self.attributes.topic if topic.name}
|
|
@@ -53,12 +53,11 @@ from .note import (
|
|
|
53
53
|
class AnalystNoteMgr:
|
|
54
54
|
"""Manages requests for Recorded Future analyst notes."""
|
|
55
55
|
|
|
56
|
-
def __init__(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"""
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
rf_token: Annotated[str | None, Doc('Recorded Future API token.')] = None,
|
|
59
|
+
):
|
|
60
|
+
"""Initializes the `AnalystNoteMgr` object."""
|
|
62
61
|
self.log = logging.getLogger(__name__)
|
|
63
62
|
self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
|
|
64
63
|
|
|
@@ -95,12 +95,11 @@ _ASSET_SEARCH_QUERY_MAP: dict[str, tuple[str, str, callable]] = {
|
|
|
95
95
|
class AttackSurfaceMgr:
|
|
96
96
|
"""Manages requests for Recorded Future SecurityTrails (ASI) API."""
|
|
97
97
|
|
|
98
|
-
def __init__(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"""
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
api_token: Annotated[str | None, Doc('ASI API token.')] = None,
|
|
101
|
+
):
|
|
102
|
+
"""Initializes the `AttackSurfaceMgr` object."""
|
|
104
103
|
self.log = logging.getLogger(__name__)
|
|
105
104
|
self.asi_client = ASIClient(api_token=api_token) if api_token else ASIClient()
|
|
106
105
|
|
|
@@ -43,12 +43,11 @@ from .errors import (
|
|
|
43
43
|
class ClassicAlertMgr:
|
|
44
44
|
"""Alert Manager for Classic Alert (v3) API."""
|
|
45
45
|
|
|
46
|
-
def __init__(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"""
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
rf_token: Annotated[str | None, Doc('Recorded Future API token.')] = None,
|
|
49
|
+
):
|
|
50
|
+
"""Initializes the ClassicAlertMgr object."""
|
|
52
51
|
self.log = logging.getLogger(__name__)
|
|
53
52
|
self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
|
|
54
53
|
|
|
@@ -29,12 +29,11 @@ from .insight import Insight, InsightsIn, InsightsOut
|
|
|
29
29
|
class CollectiveInsights:
|
|
30
30
|
"""Class for interacting with the Recorded Future Collective Insights API."""
|
|
31
31
|
|
|
32
|
-
def __init__(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"""
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
rf_token: Annotated[str | None, Doc('Recorded Future API token.')] = None,
|
|
35
|
+
):
|
|
36
|
+
"""Initializes the CollectiveInsights object."""
|
|
38
37
|
self.log = logging.getLogger(__name__)
|
|
39
38
|
self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
|
|
40
39
|
|
|
@@ -136,6 +136,16 @@ EP_AUTO_SIGMA_JOB_ID_RETRY = EP_AUTO_SIGMA_JOB_ID + '/retry'
|
|
|
136
136
|
EP_RISK_HISTORY_BASE = BASE_URL + '/risk'
|
|
137
137
|
EP_RISK_HISTORY = EP_RISK_HISTORY_BASE + '/history'
|
|
138
138
|
|
|
139
|
+
###############################################################################
|
|
140
|
+
# Links API Endpoints
|
|
141
|
+
###############################################################################
|
|
142
|
+
LINKS_BASE_URL = f'{BASE_URL}/links'
|
|
143
|
+
LINKS_METADATA_URL = f'{LINKS_BASE_URL}/metadata'
|
|
144
|
+
EP_LINKS_SEARCH = f'{LINKS_BASE_URL}/search'
|
|
145
|
+
EP_LINKS_METADATA_SECTIONS = f'{LINKS_METADATA_URL}/sections'
|
|
146
|
+
EP_LINKS_METADATA_EVENTS = f'{LINKS_METADATA_URL}/events'
|
|
147
|
+
EP_LINKS_METADATA_ENTITIES = f'{LINKS_METADATA_URL}/entities'
|
|
148
|
+
|
|
139
149
|
################################################################################
|
|
140
150
|
# Attack Surface Intelligence API Endpoints
|
|
141
151
|
################################################################################
|
|
@@ -147,3 +157,13 @@ EP_ASI_ASSET_EXPOSURES = f'{EP_ASI_ASSETS}/{{}}/exposures'
|
|
|
147
157
|
EP_ASI_ASSETS_SEARCH = f'{EP_ASI_ASSETS}/_search'
|
|
148
158
|
EP_ASI_EXPOSURES = f'{EP_ASI_PROJECTS}/{{}}/exposures'
|
|
149
159
|
EP_ASI_EXPOSURES_BY_SIGNATURE = f'{EP_ASI_EXPOSURES}/{{}}'
|
|
160
|
+
|
|
161
|
+
################################################################################
|
|
162
|
+
# Threat Map API Endpoints
|
|
163
|
+
################################################################################
|
|
164
|
+
EP_THREAT_MAPS_BASE = BASE_URL + '/threat'
|
|
165
|
+
EP_THREAT_MAPS_LIST = EP_THREAT_MAPS_BASE + '/maps'
|
|
166
|
+
EP_THREAT_MAP = EP_THREAT_MAPS_BASE + '/map/{}'
|
|
167
|
+
EP_THREAT_MAP_ORG = EP_THREAT_MAPS_BASE + '/map/{}/{}'
|
|
168
|
+
EP_ACTOR_SEARCH = EP_THREAT_MAPS_BASE + '/actor/search'
|
|
169
|
+
EP_CATEGORIES = EP_THREAT_MAPS_BASE + '/{}/categories'
|
|
@@ -36,12 +36,11 @@ from .models import DirectoryListOut, FileDeleteOut, FileGetOut, FileHeadOut, Fi
|
|
|
36
36
|
class FusionMgr:
|
|
37
37
|
"""Manages requests for Recorded Future Fusion files."""
|
|
38
38
|
|
|
39
|
-
def __init__(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"""
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
rf_token: Annotated[str | None, Doc('Recorded Future API token.')] = None,
|
|
42
|
+
):
|
|
43
|
+
"""Initializes the `FusionMgr` object."""
|
|
45
44
|
self.log = logging.getLogger(__name__)
|
|
46
45
|
self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
|
|
47
46
|
|
|
@@ -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
|
|
|
@@ -504,6 +504,15 @@ class Validators:
|
|
|
504
504
|
else input_time
|
|
505
505
|
)
|
|
506
506
|
|
|
507
|
+
@staticmethod
|
|
508
|
+
def is_rel_time_valid(
|
|
509
|
+
input_time: Annotated[str | None, Doc("Relative time string, e.g., '7d', '3h'.")],
|
|
510
|
+
):
|
|
511
|
+
"""Check that a relative time like `-3d` is valid."""
|
|
512
|
+
if input_time is not None and not TimeHelpers.is_rel_time_valid(input_time):
|
|
513
|
+
raise ValueError(f'Invalid relative time: {input_time}')
|
|
514
|
+
return input_time
|
|
515
|
+
|
|
507
516
|
@staticmethod
|
|
508
517
|
def check_uhash_prefix(
|
|
509
518
|
value: Annotated[str | list, Doc('String or list of strings to check for uhash prefix.')],
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
LinksError,
|
|
16
|
+
LinksMetadataError,
|
|
17
|
+
LinksSearchError,
|
|
18
|
+
)
|
|
19
|
+
from .links import (
|
|
20
|
+
EntityLinks,
|
|
21
|
+
LinkedEntity,
|
|
22
|
+
LinkedIOC,
|
|
23
|
+
LinkedMalware,
|
|
24
|
+
LinkedTA,
|
|
25
|
+
LinkedTTP,
|
|
26
|
+
)
|
|
27
|
+
from .links_mgr import LinksMgr
|
|
28
|
+
from .models import (
|
|
29
|
+
CriticalityAttribute,
|
|
30
|
+
EntityAttribute,
|
|
31
|
+
EntitySearchError,
|
|
32
|
+
GenericAttribute,
|
|
33
|
+
LinkSource,
|
|
34
|
+
MitreNameAttribute,
|
|
35
|
+
RiskAttribute,
|
|
36
|
+
SearchScope,
|
|
37
|
+
ThreatActorAttribute,
|
|
38
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
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 LinksError(RecordedFutureError):
|
|
18
|
+
"""Base class for all exceptions raised by the Links module."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LinksSearchError(LinksError):
|
|
22
|
+
"""Error raised when a Links search request fails."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LinksMetadataError(LinksError):
|
|
26
|
+
"""Error raised when fetching or validating Links metadata fails."""
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
|
|
15
|
+
from collections import defaultdict
|
|
16
|
+
|
|
17
|
+
from ..common_models import IdNameType, RFBaseModel
|
|
18
|
+
from .models import EntityAttribute, EntitySearchError, LinksFilterObjects, LinksLimitsObjects
|
|
19
|
+
|
|
20
|
+
LINK_IOC_TYPE = [
|
|
21
|
+
'type:InternetDomainName',
|
|
22
|
+
'type:CyberVulnerability',
|
|
23
|
+
'type:IpAddress',
|
|
24
|
+
'type:Hash',
|
|
25
|
+
'type:Url',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LinkedIOC(IdNameType):
|
|
30
|
+
"""Return linked iocs entities."""
|
|
31
|
+
|
|
32
|
+
risk_score: int
|
|
33
|
+
source: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LinkedTTP(IdNameType):
|
|
37
|
+
"""Return linked TTPs entities."""
|
|
38
|
+
|
|
39
|
+
display_name: str
|
|
40
|
+
source: str | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LinkedTA(IdNameType):
|
|
44
|
+
"""Return linked threat actors entities."""
|
|
45
|
+
|
|
46
|
+
source: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LinkedMalware(IdNameType):
|
|
50
|
+
"""Return linked malware entities."""
|
|
51
|
+
|
|
52
|
+
source: str | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class LinkedEntity(IdNameType):
|
|
56
|
+
"""An entity connected to the search target."""
|
|
57
|
+
|
|
58
|
+
source: str | None = None
|
|
59
|
+
section: str | None = None
|
|
60
|
+
attributes: list[EntityAttribute] = []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class EntityLinks(RFBaseModel):
|
|
64
|
+
"""The result set for a single entity that was queried."""
|
|
65
|
+
|
|
66
|
+
entity: IdNameType | None = None
|
|
67
|
+
links: list[LinkedEntity] = []
|
|
68
|
+
error: EntitySearchError | None = None
|
|
69
|
+
|
|
70
|
+
def iocs(self) -> list[LinkedIOC]:
|
|
71
|
+
"""Return linked indicators of compromise grouped by IOC type."""
|
|
72
|
+
iocs = defaultdict(list)
|
|
73
|
+
for link in self.links:
|
|
74
|
+
if link.type_ in LINK_IOC_TYPE:
|
|
75
|
+
ioc_score = next(
|
|
76
|
+
(attr.value for attr in link.attributes if attr.id_ == 'risk_score'),
|
|
77
|
+
0,
|
|
78
|
+
)
|
|
79
|
+
iocs[link.type_].append(
|
|
80
|
+
LinkedIOC(
|
|
81
|
+
id=link.id_,
|
|
82
|
+
type=link.type_,
|
|
83
|
+
name=link.name,
|
|
84
|
+
risk_score=ioc_score,
|
|
85
|
+
source=link.source,
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return iocs
|
|
90
|
+
|
|
91
|
+
def ttps(self) -> list[LinkedTTP]:
|
|
92
|
+
"""Return linked MITRE ATT&CK techniques and their display names."""
|
|
93
|
+
ttps = []
|
|
94
|
+
for link in self.links:
|
|
95
|
+
if link.type_ == 'type:MitreAttackIdentifier':
|
|
96
|
+
display_name = next(
|
|
97
|
+
(attr.value for attr in link.attributes if attr.id_ == 'display_name'),
|
|
98
|
+
'N/A',
|
|
99
|
+
)
|
|
100
|
+
ttps.append(
|
|
101
|
+
LinkedTTP(
|
|
102
|
+
id=link.id_,
|
|
103
|
+
type=link.type_,
|
|
104
|
+
name=link.name,
|
|
105
|
+
display_name=display_name,
|
|
106
|
+
source=link.source,
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return ttps
|
|
111
|
+
|
|
112
|
+
def threat_actors(self) -> list[LinkedTA]:
|
|
113
|
+
"""Return linked organizations marked as threat actors."""
|
|
114
|
+
tas = []
|
|
115
|
+
for link in self.links:
|
|
116
|
+
if link.type_ == 'type:Organization':
|
|
117
|
+
is_threat_actor = next(
|
|
118
|
+
(attr.value for attr in link.attributes if attr.id_ == 'threat_actor'),
|
|
119
|
+
False,
|
|
120
|
+
)
|
|
121
|
+
if is_threat_actor:
|
|
122
|
+
tas.append(
|
|
123
|
+
LinkedTA(
|
|
124
|
+
id=link.id_,
|
|
125
|
+
type=link.type_,
|
|
126
|
+
name=link.name,
|
|
127
|
+
source=link.source,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return tas
|
|
132
|
+
|
|
133
|
+
def malwares(self) -> list[LinkedMalware]:
|
|
134
|
+
"""Return linked malware entities."""
|
|
135
|
+
return [
|
|
136
|
+
LinkedMalware(
|
|
137
|
+
id=link.id_,
|
|
138
|
+
type=link.type_,
|
|
139
|
+
name=link.name,
|
|
140
|
+
source=link.source,
|
|
141
|
+
)
|
|
142
|
+
for link in self.links
|
|
143
|
+
if link.type_ == 'type:Malware'
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class LinksSearchIn(RFBaseModel):
|
|
148
|
+
"""Model for payload sent to POST `/links/search` endpoint."""
|
|
149
|
+
|
|
150
|
+
entities: list[str]
|
|
151
|
+
filters: LinksFilterObjects | None = None
|
|
152
|
+
limits: LinksLimitsObjects | None = None
|