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.
Files changed (163) hide show
  1. {psengine-2.6.0 → psengine-2.8.0}/PKG-INFO +5 -5
  2. {psengine-2.6.0 → psengine-2.8.0}/README.md +1 -1
  3. {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/markdown.py +4 -1
  4. {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/note.py +2 -2
  5. {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/note_mgr.py +5 -6
  6. {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/asi_mgr.py +5 -6
  7. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/classic_alert_mgr.py +5 -6
  8. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/models.py +1 -1
  9. {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/collective_insights.py +5 -6
  10. {psengine-2.6.0 → psengine-2.8.0}/psengine/endpoints.py +20 -0
  11. {psengine-2.6.0 → psengine-2.8.0}/psengine/fusion/fusion_mgr.py +5 -6
  12. {psengine-2.6.0 → psengine-2.8.0}/psengine/helpers/helpers.py +12 -3
  13. psengine-2.8.0/psengine/links/__init__.py +38 -0
  14. psengine-2.8.0/psengine/links/errors.py +26 -0
  15. psengine-2.8.0/psengine/links/links.py +152 -0
  16. psengine-2.8.0/psengine/links/links_mgr.py +214 -0
  17. psengine-2.8.0/psengine/links/models.py +94 -0
  18. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/auto_sigma_mgr.py +5 -6
  19. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/auto_yara_mgr.py +5 -6
  20. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/malware_intel_mgr.py +5 -6
  21. {psengine-2.6.0 → psengine-2.8.0}/psengine/risk_history/risk_history_mgr.py +5 -6
  22. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/complex_entity.py +1 -1
  23. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/enriched_indicator.py +1 -1
  24. psengine-2.8.0/psengine/threat_maps/__init__.py +30 -0
  25. psengine-2.8.0/psengine/threat_maps/errors.py +34 -0
  26. psengine-2.8.0/psengine/threat_maps/models.py +56 -0
  27. psengine-2.8.0/psengine/threat_maps/threat_map.py +134 -0
  28. psengine-2.8.0/psengine/threat_maps/threat_map_mgr.py +173 -0
  29. {psengine-2.6.0 → psengine-2.8.0}/pyproject.toml +12 -11
  30. {psengine-2.6.0 → psengine-2.8.0}/psengine/__init__.py +0 -0
  31. {psengine-2.6.0 → psengine-2.8.0}/psengine/_sdk_id.py +0 -0
  32. {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/__init__.py +0 -0
  33. {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/constants.py +0 -0
  34. {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/errors.py +0 -0
  35. {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/helpers.py +0 -0
  36. {psengine-2.6.0 → psengine-2.8.0}/psengine/analyst_notes/models.py +0 -0
  37. {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/__init__.py +0 -0
  38. {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/asi.py +0 -0
  39. {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/client.py +0 -0
  40. {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/constants.py +0 -0
  41. {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/errors.py +0 -0
  42. {psengine-2.6.0 → psengine-2.8.0}/psengine/asi/models.py +0 -0
  43. {psengine-2.6.0 → psengine-2.8.0}/psengine/base_http_client.py +0 -0
  44. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/__init__.py +0 -0
  45. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/classic_alert.py +0 -0
  46. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/constants.py +0 -0
  47. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/errors.py +0 -0
  48. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/helpers.py +0 -0
  49. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
  50. {psengine-2.6.0 → psengine-2.8.0}/psengine/classic_alerts/markdown/markdown.py +0 -0
  51. {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/__init__.py +0 -0
  52. {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/constants.py +0 -0
  53. {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/errors.py +0 -0
  54. {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/insight.py +0 -0
  55. {psengine-2.6.0 → psengine-2.8.0}/psengine/collective_insights/models.py +0 -0
  56. {psengine-2.6.0 → psengine-2.8.0}/psengine/common_models.py +0 -0
  57. {psengine-2.6.0 → psengine-2.8.0}/psengine/config/__init__.py +0 -0
  58. {psengine-2.6.0 → psengine-2.8.0}/psengine/config/config.py +0 -0
  59. {psengine-2.6.0 → psengine-2.8.0}/psengine/config/errors.py +0 -0
  60. {psengine-2.6.0 → psengine-2.8.0}/psengine/constants.py +0 -0
  61. {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/__init__.py +0 -0
  62. {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/detection_mgr.py +0 -0
  63. {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/detection_rule.py +0 -0
  64. {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/errors.py +0 -0
  65. {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/helpers.py +0 -0
  66. {psengine-2.6.0 → psengine-2.8.0}/psengine/detection/models.py +0 -0
  67. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/__init__.py +0 -0
  68. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/constants.py +0 -0
  69. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/errors.py +0 -0
  70. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/lookup.py +0 -0
  71. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/lookup_mgr.py +0 -0
  72. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/models/__init__.py +0 -0
  73. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
  74. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/models/lookup.py +0 -0
  75. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/models/soar.py +0 -0
  76. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/soar.py +0 -0
  77. {psengine-2.6.0 → psengine-2.8.0}/psengine/enrich/soar_mgr.py +0 -0
  78. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/__init__.py +0 -0
  79. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/constants.py +0 -0
  80. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/entity_list.py +0 -0
  81. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
  82. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/errors.py +0 -0
  83. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_lists/models.py +0 -0
  84. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/__init__.py +0 -0
  85. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/entity_match.py +0 -0
  86. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/entity_match_mgr.py +0 -0
  87. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/errors.py +0 -0
  88. {psengine-2.6.0 → psengine-2.8.0}/psengine/entity_match/models.py +0 -0
  89. {psengine-2.6.0 → psengine-2.8.0}/psengine/errors.py +0 -0
  90. {psengine-2.6.0 → psengine-2.8.0}/psengine/fusion/__init__.py +0 -0
  91. {psengine-2.6.0 → psengine-2.8.0}/psengine/fusion/errors.py +0 -0
  92. {psengine-2.6.0 → psengine-2.8.0}/psengine/fusion/models.py +0 -0
  93. {psengine-2.6.0 → psengine-2.8.0}/psengine/helpers/__init__.py +0 -0
  94. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/__init__.py +0 -0
  95. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/constants.py +0 -0
  96. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/errors.py +0 -0
  97. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/identity.py +0 -0
  98. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/identity_mgr.py +0 -0
  99. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/__init__.py +0 -0
  100. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/common_models.py +0 -0
  101. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/detections.py +0 -0
  102. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/incident_report.py +0 -0
  103. {psengine-2.6.0 → psengine-2.8.0}/psengine/identity/models/lookup.py +0 -0
  104. {psengine-2.6.0 → psengine-2.8.0}/psengine/logger/__init__.py +0 -0
  105. {psengine-2.6.0 → psengine-2.8.0}/psengine/logger/constants.py +0 -0
  106. {psengine-2.6.0 → psengine-2.8.0}/psengine/logger/errors.py +0 -0
  107. {psengine-2.6.0 → psengine-2.8.0}/psengine/logger/rf_logger.py +0 -0
  108. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/__init__.py +0 -0
  109. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/constants.py +0 -0
  110. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/errors.py +0 -0
  111. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/helpers.py +0 -0
  112. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/malware_intel.py +0 -0
  113. {psengine-2.6.0 → psengine-2.8.0}/psengine/malware_intel/models.py +0 -0
  114. {psengine-2.6.0 → psengine-2.8.0}/psengine/markdown/__init__.py +0 -0
  115. {psengine-2.6.0 → psengine-2.8.0}/psengine/markdown/markdown.py +0 -0
  116. {psengine-2.6.0 → psengine-2.8.0}/psengine/markdown/models.py +0 -0
  117. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/__init__.py +0 -0
  118. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/constants.py +0 -0
  119. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/errors.py +0 -0
  120. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/helpers.py +0 -0
  121. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/mappings.py +0 -0
  122. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  123. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
  124. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  125. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  126. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  127. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  128. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  129. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  130. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  131. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/__init__.py +0 -0
  132. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/common_models.py +0 -0
  133. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/panel_log.py +0 -0
  134. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
  135. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  136. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  137. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  138. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  139. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  140. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  141. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  142. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  143. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/pa_category.py +0 -0
  144. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
  145. {psengine-2.6.0 → psengine-2.8.0}/psengine/playbook_alerts/playbook_alerts.py +0 -0
  146. {psengine-2.6.0 → psengine-2.8.0}/psengine/py.typed +0 -0
  147. {psengine-2.6.0 → psengine-2.8.0}/psengine/rf_client.py +0 -0
  148. {psengine-2.6.0 → psengine-2.8.0}/psengine/risk_history/__init__.py +0 -0
  149. {psengine-2.6.0 → psengine-2.8.0}/psengine/risk_history/errors.py +0 -0
  150. {psengine-2.6.0 → psengine-2.8.0}/psengine/risk_history/models.py +0 -0
  151. {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/__init__.py +0 -0
  152. {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/constants.py +0 -0
  153. {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/errors.py +0 -0
  154. {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/models.py +0 -0
  155. {psengine-2.6.0 → psengine-2.8.0}/psengine/risklists/risklist_mgr.py +0 -0
  156. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/__init__.py +0 -0
  157. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/base_stix_entity.py +0 -0
  158. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/constants.py +0 -0
  159. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/errors.py +0 -0
  160. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/helpers.py +0 -0
  161. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/rf_bundle.py +0 -0
  162. {psengine-2.6.0 → psengine-2.8.0}/psengine/stix2/simple_entity.py +0 -0
  163. {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.6.0
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.6.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,<=10.2.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.13.1
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 `snort rule` in the topics of the note.
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__(self, rf_token: str = None):
57
- """Initializes the `AnalystNoteMgr` object.
58
-
59
- Args:
60
- rf_token (str, optional): Recorded Future API token.
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__(self, api_token: str = None):
99
- """Initializes the `AttackSurfaceMgr` object.
100
-
101
- Args:
102
- api_token (str, optional): ASI API token.
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__(self, rf_token: str = None):
47
- """Initializes the ClassicAlertMgr object.
48
-
49
- Args:
50
- rf_token (str, optional): Recorded Future API token.
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
 
@@ -94,7 +94,7 @@ class AlertCounts(RFBaseModel):
94
94
 
95
95
  class NotificationSettings(RFBaseModel):
96
96
  email_subscribers: list[IdName]
97
- mobile_subsribers: list[IdName] | None = None
97
+ mobile_subscribers: list[IdName] | None = None
98
98
 
99
99
 
100
100
  class Evidence(RFBaseModel):
@@ -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__(self, rf_token: str = None):
33
- """Initializes the CollectiveInsights object.
34
-
35
- Args:
36
- rf_token (str, optional): Recorded Future API token.
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__(self, rf_token: str = None):
40
- """Initializes the `FusionMgr` object.
41
-
42
- Args:
43
- rf_token (str, optional): Recorded Future API token.
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 writeable') from err
329
- # In case it already exists, check if it is writeable
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 writeable')
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