psengine 2.9.0__tar.gz → 2.10.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 (190) hide show
  1. {psengine-2.9.0 → psengine-2.10.0}/PKG-INFO +1 -1
  2. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/classic_alert.py +2 -0
  3. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/markdown/markdown.py +10 -0
  4. {psengine-2.9.0 → psengine-2.10.0}/psengine/endpoints.py +5 -0
  5. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_lists/__init__.py +15 -1
  6. psengine-2.10.0/psengine/entity_lists/constants.py +90 -0
  7. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_lists/entity_list.py +109 -1
  8. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_lists/models.py +83 -1
  9. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/__init__.py +2 -0
  10. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/constants.py +17 -3
  11. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/errors.py +4 -0
  12. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/mappings.py +8 -0
  13. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/markdown.py +17 -0
  14. psengine-2.10.0/psengine/playbook_alerts/markdown/markdown_compromised_bank_checks.py +187 -0
  15. psengine-2.10.0/psengine/playbook_alerts/markdown/markdown_malicious_sites.py +147 -0
  16. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/__init__.py +10 -0
  17. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/__init__.py +96 -0
  18. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/code_repo.py +48 -0
  19. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/common.py +111 -0
  20. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/domain_abuse.py +82 -0
  21. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/generic.py +73 -0
  22. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/geopolitics.py +47 -0
  23. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/malicious_sites.py +141 -0
  24. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/panel_log_v2.py +127 -0
  25. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/tpr.py +43 -0
  26. psengine-2.10.0/psengine/playbook_alerts/models/panel_log/vulnerability.py +38 -0
  27. psengine-2.10.0/psengine/playbook_alerts/models/pba_compromised_bank_checks.py +66 -0
  28. psengine-2.10.0/psengine/playbook_alerts/models/pba_malicious_sites.py +224 -0
  29. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/pa_category.py +2 -0
  30. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/playbook_alert_mgr.py +102 -7
  31. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/playbook_alerts.py +248 -1
  32. {psengine-2.9.0 → psengine-2.10.0}/pyproject.toml +1 -1
  33. {psengine-2.9.0 → psengine-2.10.0}/pyproject.toml.orig +1 -1
  34. psengine-2.9.0/psengine/entity_lists/constants.py +0 -19
  35. psengine-2.9.0/psengine/playbook_alerts/models/panel_log.py +0 -328
  36. {psengine-2.9.0 → psengine-2.10.0}/README.md +0 -0
  37. {psengine-2.9.0 → psengine-2.10.0}/psengine/__init__.py +0 -0
  38. {psengine-2.9.0 → psengine-2.10.0}/psengine/_sdk_id.py +0 -0
  39. {psengine-2.9.0 → psengine-2.10.0}/psengine/analyst_notes/__init__.py +0 -0
  40. {psengine-2.9.0 → psengine-2.10.0}/psengine/analyst_notes/constants.py +0 -0
  41. {psengine-2.9.0 → psengine-2.10.0}/psengine/analyst_notes/errors.py +0 -0
  42. {psengine-2.9.0 → psengine-2.10.0}/psengine/analyst_notes/helpers.py +0 -0
  43. {psengine-2.9.0 → psengine-2.10.0}/psengine/analyst_notes/markdown.py +0 -0
  44. {psengine-2.9.0 → psengine-2.10.0}/psengine/analyst_notes/models.py +0 -0
  45. {psengine-2.9.0 → psengine-2.10.0}/psengine/analyst_notes/note.py +0 -0
  46. {psengine-2.9.0 → psengine-2.10.0}/psengine/analyst_notes/note_mgr.py +0 -0
  47. {psengine-2.9.0 → psengine-2.10.0}/psengine/asi/__init__.py +0 -0
  48. {psengine-2.9.0 → psengine-2.10.0}/psengine/asi/asi.py +0 -0
  49. {psengine-2.9.0 → psengine-2.10.0}/psengine/asi/asi_mgr.py +0 -0
  50. {psengine-2.9.0 → psengine-2.10.0}/psengine/asi/client.py +0 -0
  51. {psengine-2.9.0 → psengine-2.10.0}/psengine/asi/constants.py +0 -0
  52. {psengine-2.9.0 → psengine-2.10.0}/psengine/asi/errors.py +0 -0
  53. {psengine-2.9.0 → psengine-2.10.0}/psengine/asi/models.py +0 -0
  54. {psengine-2.9.0 → psengine-2.10.0}/psengine/base_http_client.py +0 -0
  55. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/__init__.py +0 -0
  56. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
  57. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/constants.py +0 -0
  58. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/errors.py +0 -0
  59. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/helpers.py +0 -0
  60. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
  61. {psengine-2.9.0 → psengine-2.10.0}/psengine/classic_alerts/models.py +0 -0
  62. {psengine-2.9.0 → psengine-2.10.0}/psengine/collective_insights/__init__.py +0 -0
  63. {psengine-2.9.0 → psengine-2.10.0}/psengine/collective_insights/collective_insights.py +0 -0
  64. {psengine-2.9.0 → psengine-2.10.0}/psengine/collective_insights/constants.py +0 -0
  65. {psengine-2.9.0 → psengine-2.10.0}/psengine/collective_insights/errors.py +0 -0
  66. {psengine-2.9.0 → psengine-2.10.0}/psengine/collective_insights/insight.py +0 -0
  67. {psengine-2.9.0 → psengine-2.10.0}/psengine/collective_insights/models.py +0 -0
  68. {psengine-2.9.0 → psengine-2.10.0}/psengine/common_models.py +0 -0
  69. {psengine-2.9.0 → psengine-2.10.0}/psengine/config/__init__.py +0 -0
  70. {psengine-2.9.0 → psengine-2.10.0}/psengine/config/config.py +0 -0
  71. {psengine-2.9.0 → psengine-2.10.0}/psengine/config/errors.py +0 -0
  72. {psengine-2.9.0 → psengine-2.10.0}/psengine/constants.py +0 -0
  73. {psengine-2.9.0 → psengine-2.10.0}/psengine/detection/__init__.py +0 -0
  74. {psengine-2.9.0 → psengine-2.10.0}/psengine/detection/detection_mgr.py +0 -0
  75. {psengine-2.9.0 → psengine-2.10.0}/psengine/detection/detection_rule.py +0 -0
  76. {psengine-2.9.0 → psengine-2.10.0}/psengine/detection/errors.py +0 -0
  77. {psengine-2.9.0 → psengine-2.10.0}/psengine/detection/helpers.py +0 -0
  78. {psengine-2.9.0 → psengine-2.10.0}/psengine/detection/models.py +0 -0
  79. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/__init__.py +0 -0
  80. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/constants.py +0 -0
  81. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/errors.py +0 -0
  82. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/lookup.py +0 -0
  83. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/lookup_mgr.py +0 -0
  84. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/models/__init__.py +0 -0
  85. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
  86. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/models/lookup.py +0 -0
  87. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/models/soar.py +0 -0
  88. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/soar.py +0 -0
  89. {psengine-2.9.0 → psengine-2.10.0}/psengine/enrich/soar_mgr.py +0 -0
  90. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
  91. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_lists/errors.py +0 -0
  92. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_match/__init__.py +0 -0
  93. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_match/entity_match.py +0 -0
  94. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_match/entity_match_mgr.py +0 -0
  95. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_match/errors.py +0 -0
  96. {psengine-2.9.0 → psengine-2.10.0}/psengine/entity_match/models.py +0 -0
  97. {psengine-2.9.0 → psengine-2.10.0}/psengine/errors.py +0 -0
  98. {psengine-2.9.0 → psengine-2.10.0}/psengine/fusion/__init__.py +0 -0
  99. {psengine-2.9.0 → psengine-2.10.0}/psengine/fusion/errors.py +0 -0
  100. {psengine-2.9.0 → psengine-2.10.0}/psengine/fusion/fusion_mgr.py +0 -0
  101. {psengine-2.9.0 → psengine-2.10.0}/psengine/fusion/models.py +0 -0
  102. {psengine-2.9.0 → psengine-2.10.0}/psengine/helpers/__init__.py +0 -0
  103. {psengine-2.9.0 → psengine-2.10.0}/psengine/helpers/helpers.py +0 -0
  104. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/__init__.py +0 -0
  105. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/constants.py +0 -0
  106. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/errors.py +0 -0
  107. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/identity.py +0 -0
  108. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/identity_mgr.py +0 -0
  109. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/models/__init__.py +0 -0
  110. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/models/common_models.py +0 -0
  111. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/models/detections.py +0 -0
  112. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/models/incident_report.py +0 -0
  113. {psengine-2.9.0 → psengine-2.10.0}/psengine/identity/models/lookup.py +0 -0
  114. {psengine-2.9.0 → psengine-2.10.0}/psengine/links/__init__.py +0 -0
  115. {psengine-2.9.0 → psengine-2.10.0}/psengine/links/errors.py +0 -0
  116. {psengine-2.9.0 → psengine-2.10.0}/psengine/links/links.py +0 -0
  117. {psengine-2.9.0 → psengine-2.10.0}/psengine/links/links_mgr.py +0 -0
  118. {psengine-2.9.0 → psengine-2.10.0}/psengine/links/models.py +0 -0
  119. {psengine-2.9.0 → psengine-2.10.0}/psengine/logger/__init__.py +0 -0
  120. {psengine-2.9.0 → psengine-2.10.0}/psengine/logger/constants.py +0 -0
  121. {psengine-2.9.0 → psengine-2.10.0}/psengine/logger/errors.py +0 -0
  122. {psengine-2.9.0 → psengine-2.10.0}/psengine/logger/rf_logger.py +0 -0
  123. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/__init__.py +0 -0
  124. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/auto_sigma_mgr.py +0 -0
  125. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/auto_yara_mgr.py +0 -0
  126. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/constants.py +0 -0
  127. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/errors.py +0 -0
  128. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/helpers.py +0 -0
  129. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/malware_intel.py +0 -0
  130. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/malware_intel_mgr.py +0 -0
  131. {psengine-2.9.0 → psengine-2.10.0}/psengine/malware_intel/models.py +0 -0
  132. {psengine-2.9.0 → psengine-2.10.0}/psengine/markdown/__init__.py +0 -0
  133. {psengine-2.9.0 → psengine-2.10.0}/psengine/markdown/markdown.py +0 -0
  134. {psengine-2.9.0 → psengine-2.10.0}/psengine/markdown/markdown_strings.py +0 -0
  135. {psengine-2.9.0 → psengine-2.10.0}/psengine/markdown/models.py +0 -0
  136. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/helpers.py +0 -0
  137. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  138. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  139. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  140. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  141. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  142. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  143. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  144. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  145. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/common_models.py +0 -0
  146. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
  147. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  148. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  149. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  150. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  151. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  152. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  153. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  154. {psengine-2.9.0 → psengine-2.10.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  155. {psengine-2.9.0 → psengine-2.10.0}/psengine/rf_client.py +0 -0
  156. {psengine-2.9.0 → psengine-2.10.0}/psengine/risk_history/__init__.py +0 -0
  157. {psengine-2.9.0 → psengine-2.10.0}/psengine/risk_history/errors.py +0 -0
  158. {psengine-2.9.0 → psengine-2.10.0}/psengine/risk_history/models.py +0 -0
  159. {psengine-2.9.0 → psengine-2.10.0}/psengine/risk_history/risk_history_mgr.py +0 -0
  160. {psengine-2.9.0 → psengine-2.10.0}/psengine/risklists/__init__.py +0 -0
  161. {psengine-2.9.0 → psengine-2.10.0}/psengine/risklists/constants.py +0 -0
  162. {psengine-2.9.0 → psengine-2.10.0}/psengine/risklists/errors.py +0 -0
  163. {psengine-2.9.0 → psengine-2.10.0}/psengine/risklists/models.py +0 -0
  164. {psengine-2.9.0 → psengine-2.10.0}/psengine/risklists/risklist_mgr.py +0 -0
  165. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/__init__.py +0 -0
  166. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/client.py +0 -0
  167. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/constants.py +0 -0
  168. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/errors.py +0 -0
  169. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/models/__init__.py +0 -0
  170. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/models/analysis.py +0 -0
  171. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/models/behavioral_report.py +0 -0
  172. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/models/overview_report.py +0 -0
  173. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/models/static_report.py +0 -0
  174. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/sandbox.py +0 -0
  175. {psengine-2.9.0 → psengine-2.10.0}/psengine/sandbox/sandbox_mgr.py +0 -0
  176. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/__init__.py +0 -0
  177. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/base_stix_entity.py +0 -0
  178. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/complex_entity.py +0 -0
  179. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/constants.py +0 -0
  180. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/enriched_indicator.py +0 -0
  181. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/errors.py +0 -0
  182. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/helpers.py +0 -0
  183. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/rf_bundle.py +0 -0
  184. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/simple_entity.py +0 -0
  185. {psengine-2.9.0 → psengine-2.10.0}/psengine/stix2/util.py +0 -0
  186. {psengine-2.9.0 → psengine-2.10.0}/psengine/threat_maps/__init__.py +0 -0
  187. {psengine-2.9.0 → psengine-2.10.0}/psengine/threat_maps/errors.py +0 -0
  188. {psengine-2.9.0 → psengine-2.10.0}/psengine/threat_maps/models.py +0 -0
  189. {psengine-2.9.0 → psengine-2.10.0}/psengine/threat_maps/threat_map.py +0 -0
  190. {psengine-2.9.0 → psengine-2.10.0}/psengine/threat_maps/threat_map_mgr.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.9.0
3
+ Version: 2.10.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
@@ -208,6 +208,7 @@ class ClassicAlert(RFBaseModel):
208
208
  html_tags: Annotated[bool, Doc('Include HTML tags in the markdown.')] = False,
209
209
  character_limit: Annotated[int | None, Doc('Character limit for the markdown.')] = None,
210
210
  defang_iocs: Annotated[bool, Doc('Defang IOCs in hits.')] = False,
211
+ comment: Annotated[bool, Doc('Include reviewer note in the markdown.')] = False,
211
212
  ) -> Annotated[str, Doc('Markdown representation of the alert.')]:
212
213
  """Return a markdown string representation of the `ClassicAlert` instance.
213
214
 
@@ -229,6 +230,7 @@ class ClassicAlert(RFBaseModel):
229
230
  html_tags=html_tags,
230
231
  character_limit=character_limit,
231
232
  defang_iocs=defang_iocs,
233
+ comment=comment,
232
234
  )
233
235
 
234
236
  @property
@@ -306,6 +306,13 @@ def _add_hits_section_if_no_enriched_entities(
306
306
  )
307
307
 
308
308
 
309
+ def _add_comment_section(
310
+ md_maker: MarkdownMaker, classic_alert: 'ClassicAlert', comment: bool
311
+ ) -> None:
312
+ if comment and classic_alert.review.note:
313
+ md_maker.add_section('Reviewer Note', classic_alert.review.note)
314
+
315
+
309
316
  def _markdown_alert(
310
317
  classic_alert: 'ClassicAlert',
311
318
  owner_org: bool = False,
@@ -315,6 +322,7 @@ def _markdown_alert(
315
322
  html_tags: bool = False,
316
323
  character_limit: int = None,
317
324
  defang_iocs: bool = False,
325
+ comment: bool = False,
318
326
  ) -> str:
319
327
  """Returns a markdown string representation of the `ClassicAlert` instance.
320
328
 
@@ -331,6 +339,7 @@ def _markdown_alert(
331
339
  html_tags (bool, optional): Include HTML tags in the markdown. Defaults to False.
332
340
  character_limit (int, optional): Character limit for the markdown. Defaults to None.
333
341
  defang_iocs (bool, optional): Defang IOCs in hits. Defaults to False.
342
+ comment (bool, optional): Include reviewer note in the markdown. Defaults to False.
334
343
 
335
344
  Raises:
336
345
  AlertMarkdownError: If fields are not available.
@@ -351,6 +360,7 @@ def _markdown_alert(
351
360
  _add_summary_section(md_maker, classic_alert)
352
361
  _add_owner_org_section(md_maker, classic_alert, owner_org)
353
362
  _add_ai_insights_section(md_maker, classic_alert, ai_insights)
363
+ _add_comment_section(md_maker, classic_alert, comment)
354
364
 
355
365
  if classic_alert.enriched_entities:
356
366
  _add_enriched_entities_sections(md_maker, classic_alert, triggered_by, html_tags)
@@ -53,10 +53,13 @@ EP_PLAYBOOK_ALERT = BASE_URL + '/playbook-alert'
53
53
  EP_PLAYBOOK_ALERT_SEARCH = EP_PLAYBOOK_ALERT + '/search'
54
54
  EP_PLAYBOOK_ALERT_COMMON = EP_PLAYBOOK_ALERT + '/common'
55
55
  EP_PLAYBOOK_ALERT_CODE_REPO_LEAKAGE = EP_PLAYBOOK_ALERT + '/code_repo_leakage'
56
+ EP_PLAYBOOK_ALERT_COMPROMISED_BANK_CHECKS = EP_PLAYBOOK_ALERT + '/compromised_bank_checks'
56
57
  EP_PLAYBOOK_ALERT_CYBER_VULNERABILITY = EP_PLAYBOOK_ALERT + '/vulnerability'
57
58
  EP_PLAYBOOK_ALERT_DOMAIN_ABUSE = EP_PLAYBOOK_ALERT + '/domain_abuse'
58
59
  EP_PLAYBOOK_ALERT_GEOPOLITICS_FACILITY = EP_PLAYBOOK_ALERT + '/geopolitics_facility'
59
60
  EP_PLAYBOOK_ALERT_IDENTITY_NOVEL_EXPOSURES = EP_PLAYBOOK_ALERT + '/identity_novel_exposures'
61
+ EP_PLAYBOOK_ALERT_MALICIOUS_SITES = EP_PLAYBOOK_ALERT + '/malicious_sites'
62
+ EP_PLAYBOOK_ALERT_MALICIOUS_SITES_CREATE = EP_PLAYBOOK_ALERT_MALICIOUS_SITES + '/create'
60
63
  EP_PLAYBOOK_ALERT_THIRD_PARTY_RISK = EP_PLAYBOOK_ALERT + '/third_party_risk'
61
64
  EP_PLAYBOOK_ALERT_MALWARE_REPORT = EP_PLAYBOOK_ALERT + '/malware_report'
62
65
 
@@ -72,6 +75,8 @@ EP_ENTITY_LOOKUP = BASE_URL + '/entity-match/entity/{}'
72
75
  EP_LIST = BASE_URL + '/list'
73
76
  EP_CREATE_LIST = EP_LIST + '/create'
74
77
  EP_SEARCH_LIST = EP_LIST + '/search'
78
+ EP_LIST_ENTITIES_WITH_TAGS = EP_LIST + '/{}/entitiesWithTags'
79
+ EP_LIST_ENTITY_TAGS = EP_LIST + '/{}/entity/tags'
75
80
 
76
81
  ###############################################################################
77
82
  # SOAR Endpoints
@@ -11,6 +11,20 @@
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
13
 
14
- from .entity_list import EntityList, ListEntity, ListInfoOut, ListStatusOut
14
+ from .constants import ListTagName
15
+ from .entity_list import (
16
+ EntityList,
17
+ ListEntity,
18
+ ListEntityWithTags,
19
+ ListInfoOut,
20
+ ListStatusOut,
21
+ )
15
22
  from .entity_list_mgr import EntityListMgr
16
23
  from .errors import ListApiError, ListResolutionError, ListStateError
24
+ from .models import (
25
+ EntityNotResolvedOperation,
26
+ ListEntityTag,
27
+ ReplaceEntityTagsOut,
28
+ TagsUnchangedOperation,
29
+ TagsUpdatedOperation,
30
+ )
@@ -0,0 +1,90 @@
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 enum import Enum
15
+
16
+ IS_READY_INCREMENT = 5
17
+
18
+ ADD_OP = 'add'
19
+ REMOVE_OP = 'remove'
20
+ UNCHANGED_NAME = 'unchanged'
21
+ ERROR_NAME = 'error'
22
+
23
+
24
+ class ListTagName(str, Enum):
25
+ """The 57 predefined tag values accepted by `list/{id}/entity/tags`.
26
+
27
+ Provided for discoverability only - `update_entity_tags` accepts plain strings too,
28
+ and the API is the authority on which values are valid. `name` values here are the
29
+ API values (`tier1`), not display names (`Tier 1`).
30
+
31
+ See https://docs.recordedfuture.com/reference/lists-available-tags.
32
+ """
33
+
34
+ THIRD_PARTY = '3rd_party'
35
+ FOURTH_PARTY = '4th_party'
36
+ AVAILABILITY = 'availability'
37
+ BUSINESS_CONTINUITY = 'business_continuity'
38
+ C_SUITE = 'c_suite'
39
+ CEO = 'ceo'
40
+ CFO = 'cfo'
41
+ CLOUD = 'cloud'
42
+ CONFIDENTIAL_DATA = 'confidential_data'
43
+ CONFIDENTIALITY = 'confidentiality'
44
+ CONFIRMED = 'confirmed'
45
+ COO = 'coo'
46
+ CRITICAL = 'critical'
47
+ CRITICAL_INFRASTRUCTURE = 'critical_infrastructure'
48
+ CUSTOMER_DATA = 'customer_data'
49
+ CYBER_VENDOR = 'cyber_vendor'
50
+ DEVELOPMENT = 'development'
51
+ DMZ = 'dmz'
52
+ DORA = 'dora'
53
+ ECOMMERCE = 'ecommerce'
54
+ EOL = 'eol'
55
+ EOS = 'eos'
56
+ FALSE_NEGATIVE = 'false_negative'
57
+ FALSE_POSITIVE = 'false_positive'
58
+ FINANCIAL = 'financial'
59
+ FINISHED_GOODS = 'finished_goods'
60
+ GDPR = 'gdpr'
61
+ HIGH = 'high'
62
+ HIPAA = 'hipaa'
63
+ INFORMATION_AND_COMMUNICATION_TECHNOLOGY = 'information_and_communication_technology'
64
+ INTEGRITY = 'integrity'
65
+ INTERNAL = 'internal'
66
+ INTERNET_FACING = 'internet_facing'
67
+ ISO_27001 = 'iso_27001'
68
+ LOW = 'low'
69
+ M_AND_A = 'm_and_a'
70
+ MEDIUM = 'medium'
71
+ MONITORING = 'monitoring'
72
+ MOST_CRITICAL_SUPPLIER = 'most_critical_supplier'
73
+ NETWORK_CONNECTIVITY = 'network_connectivity'
74
+ NO_PATCH_AVAILABLE = 'no_patch_available'
75
+ PCI_DSS = 'pci_dss'
76
+ PII = 'pii'
77
+ POTENTIAL = 'potential'
78
+ PRODUCTION = 'production'
79
+ PROTECTED_HEALTH_INFORMATION = 'protected_health_information'
80
+ RAW_MATERIALS = 'raw_materials'
81
+ SOX = 'sox'
82
+ SUBSIDIARY = 'subsidiary'
83
+ TEMP_INCIDENT = 'temp_incident'
84
+ TIER0 = 'tier0'
85
+ TIER1 = 'tier1'
86
+ TIER2 = 'tier2'
87
+ TIER3 = 'tier3'
88
+ TRUE_NEGATIVE = 'true_negative'
89
+ TRUE_POSITIVE = 'true_positive'
90
+ UNPATCHED = 'unpatched'
@@ -22,7 +22,7 @@ from typing_extensions import Doc
22
22
 
23
23
  from ..common_models import IdNameType, RFBaseModel
24
24
  from ..constants import TIMESTAMP_STR
25
- from ..endpoints import EP_LIST
25
+ from ..endpoints import EP_LIST, EP_LIST_ENTITIES_WITH_TAGS, EP_LIST_ENTITY_TAGS
26
26
  from ..entity_match import EntityMatchMgr, MatchApiError
27
27
  from ..helpers import debug_call
28
28
  from ..helpers.helpers import connection_exceptions
@@ -31,9 +31,13 @@ from .constants import ADD_OP, ERROR_NAME, IS_READY_INCREMENT, REMOVE_OP, UNCHAN
31
31
  from .errors import ListApiError
32
32
  from .models import (
33
33
  AddEntityRequestModel,
34
+ EntityNotResolvedOperation,
34
35
  ListEntityOperationResponse,
36
+ ListEntityTag,
35
37
  OwnerOrganisationDetails,
36
38
  RemoveEntityRequestModel,
39
+ ReplaceEntityTagsIn,
40
+ ReplaceEntityTagsOut,
37
41
  )
38
42
 
39
43
 
@@ -85,6 +89,17 @@ class ListEntity(RFBaseModel):
85
89
  )
86
90
 
87
91
 
92
+ class ListEntityWithTags(ListEntity):
93
+ """Validate data received from `/{listId}/entitiesWithTags` endpoint.
94
+
95
+ Identical to `ListEntity`, with the addition of the list-specific `tags` assigned to the
96
+ entity. Tags are predefined and only populated for lists whose type has tagging enabled -
97
+ see `ListEntityTag`.
98
+ """
99
+
100
+ tags: list[ListEntityTag] = []
101
+
102
+
88
103
  class EntityList(ListInfoOut):
89
104
  """Validate data received from `/create` endpoint."""
90
105
 
@@ -273,6 +288,99 @@ class EntityList(ListInfoOut):
273
288
  response = self.rf_client.request('get', url)
274
289
  return [ListEntity.model_validate(entity) for entity in response.json()]
275
290
 
291
+ @debug_call
292
+ @connection_exceptions(ignore_status_code=[], exception_to_raise=ListApiError)
293
+ def entities_with_tags(
294
+ self,
295
+ ) -> Annotated[
296
+ list[ListEntityWithTags],
297
+ Doc('Response from the `list/{id}/entitiesWithTags` endpoint.'),
298
+ ]:
299
+ """Get entities for a list, together with their list-specific tags.
300
+
301
+ Tags are **not** free-text. They are a fixed set of 57 predefined values, so arbitrary
302
+ tag strings cannot be expected. See
303
+ https://docs.recordedfuture.com/reference/lists-available-tags for the full list of valid
304
+ tags, their display names, and their API values.
305
+
306
+ `tags` is only populated for lists whose type has tagging enabled server-side (currently
307
+ Third-Parties Watch Lists). A list of any other type returns its entities with an empty
308
+ `tags` list rather than an error, so an empty `tags` does not indicate a failure. A list
309
+ whose type supports tagging but which has not yet been migrated server-side responds 400
310
+ instead.
311
+
312
+ Endpoint:
313
+ `list/{id}/entitiesWithTags`
314
+
315
+ Raises:
316
+ ListApiError: If connection error occurs. The API responds 400 if the list's type
317
+ supports tagging but the list has not been migrated to support tags, and 404 if
318
+ the list does not exist.
319
+ """
320
+ url = EP_LIST_ENTITIES_WITH_TAGS.format(self.id_)
321
+ response = self.rf_client.request('get', url)
322
+ return [ListEntityWithTags.model_validate(entity) for entity in response.json()]
323
+
324
+ @debug_call
325
+ @validate_call
326
+ @connection_exceptions(ignore_status_code=[], exception_to_raise=ListApiError)
327
+ def update_entity_tags(
328
+ self,
329
+ entity: Annotated[
330
+ str | tuple[str, str], Doc('ID or (name, type) tuple of the entity to tag.')
331
+ ],
332
+ tags: Annotated[
333
+ list[str],
334
+ Doc('Complete set of tags to apply. Replaces every tag already on the entity.'),
335
+ ],
336
+ ) -> Annotated[
337
+ ReplaceEntityTagsOut, Doc('Response from the `list/{id}/entity/tags` endpoint.')
338
+ ]:
339
+ """Replace all tags on an entity in a list.
340
+
341
+ This **replaces** rather than appends: `tags` becomes the entity's complete tag set,
342
+ and passing an empty list clears every tag from it.
343
+
344
+ Tags are **not** free-text. They are a fixed set of 57 predefined values - pass them as
345
+ plain strings or as `ListTagName` members. Invalid names are rejected by the API with a
346
+ 400. See https://docs.recordedfuture.com/reference/lists-available-tags for the full
347
+ list.
348
+
349
+ Tagging is only enabled for lists whose type supports it (currently Third-Parties Watch
350
+ Lists); other list types respond 403.
351
+
352
+ If `entity` is a `(name, type)` tuple that cannot be resolved, the returned
353
+ `operation` is an `EntityNotResolvedOperation` rather than an exception, matching
354
+ `add` and `remove`.
355
+
356
+ Endpoint:
357
+ `list/{id}/entity/tags`
358
+
359
+ Raises:
360
+ ValidationError: if any supplied parameter is of incorrect type.
361
+ ListApiError: If connection error occurs. The API responds 400 for an invalid tag
362
+ name, entity, or list, 403 if tag updates are not enabled for the list type,
363
+ and 404 if the entity is not on the list.
364
+ """
365
+ if isinstance(entity, str):
366
+ resolved_entity_id = entity
367
+ else:
368
+ resolved_entity = self.match_mgr.resolve_entity_id(entity[0], entity_type=entity[1])
369
+ if not resolved_entity.is_found:
370
+ return ReplaceEntityTagsOut(
371
+ operation=EntityNotResolvedOperation(
372
+ status='entity_not_resolved', message=str(resolved_entity.content)
373
+ )
374
+ )
375
+ resolved_entity_id = resolved_entity.content.id_
376
+
377
+ request_body = {'entity': {'id': resolved_entity_id}, 'tags': tags}
378
+ ReplaceEntityTagsIn.model_validate(request_body)
379
+
380
+ url = EP_LIST_ENTITY_TAGS.format(self.id_)
381
+ response = self.rf_client.request('post', url, data=request_body)
382
+ return ReplaceEntityTagsOut.model_validate(response.json())
383
+
276
384
  @debug_call
277
385
  @connection_exceptions(ignore_status_code=[], exception_to_raise=ListApiError)
278
386
  def text_entries(
@@ -12,15 +12,33 @@
12
12
  ##############################################################################################
13
13
 
14
14
 
15
+ from datetime import datetime
16
+ from typing import Literal
17
+
15
18
  from pydantic import Field
16
19
 
17
- from ..common_models import RFBaseModel
20
+ from ..common_models import IdName, RFBaseModel
18
21
 
19
22
 
20
23
  class EntityID(RFBaseModel):
21
24
  id_: str = Field(alias='id')
22
25
 
23
26
 
27
+ class ListEntityTag(IdName):
28
+ """Validate a single tag received from `/{listId}/entitiesWithTags` endpoint.
29
+
30
+ Tags are **not** free-text. They are a fixed set of 57 predefined values, populated only for
31
+ lists whose type has tagging enabled (currently Third-Parties Watch Lists), so arbitrary tag
32
+ strings cannot be supplied or expected. `id` always has the form
33
+ `enum:EntityListTag:<name>`, for example `enum:EntityListTag:tier1`.
34
+
35
+ `name` is the tag's API value (`tier1`), not its display name (`Tier 1`).
36
+
37
+ See https://docs.recordedfuture.com/reference/lists-available-tags for the full list of
38
+ valid tags.
39
+ """
40
+
41
+
24
42
  class Organisation(RFBaseModel):
25
43
  organisation_id: str
26
44
  organisation_name: str
@@ -84,3 +102,67 @@ class ListEntityOperationResponse(RFBaseModel):
84
102
  """Validate data received from `/{listId}/entity/remove` endpoint."""
85
103
 
86
104
  result: str
105
+
106
+
107
+ class TagsUpdatedOperation(RFBaseModel):
108
+ """Tags on the entity were changed."""
109
+
110
+ status: Literal['tags_updated']
111
+ tags_before: list[str]
112
+ tags_after: list[str]
113
+ tags_added: list[str]
114
+ tags_removed: list[str]
115
+ updated: datetime
116
+
117
+
118
+ class TagsUnchangedOperation(RFBaseModel):
119
+ """Requested tags already matched the entity's tags, so nothing changed."""
120
+
121
+ status: Literal['tags_unchanged']
122
+ current_tags: list[str]
123
+ message: str
124
+
125
+
126
+ class EntityNotResolvedOperation(RFBaseModel):
127
+ """A `(name, type)` tuple could not be resolved to an entity ID.
128
+
129
+ Produced by psengine, never returned by the API. Mirrors how `add` and `remove`
130
+ surface a failed lookup in their result rather than raising.
131
+ """
132
+
133
+ status: Literal['entity_not_resolved']
134
+ message: str
135
+
136
+
137
+ class ReplaceEntityTagsIn(RFBaseModel):
138
+ """Validate data sent to `/{listId}/entity/tags` endpoint."""
139
+
140
+ entity: EntityID
141
+ tags: list[str]
142
+
143
+
144
+ class ReplaceEntityTagsOut(RFBaseModel):
145
+ """Validate data received from `/{listId}/entity/tags` endpoint.
146
+
147
+ `operation` is discriminated on `status`, so each variant only exposes the fields the
148
+ API actually returns for it.
149
+ """
150
+
151
+ entity_id: str | None = None
152
+ operation: TagsUpdatedOperation | TagsUnchangedOperation | EntityNotResolvedOperation = Field(
153
+ discriminator='status'
154
+ )
155
+
156
+ @property
157
+ def changed(self) -> bool:
158
+ """Whether the call actually changed the entity's tags."""
159
+ return self.operation.status == 'tags_updated'
160
+
161
+ @property
162
+ def current_tags(self) -> list[str] | None:
163
+ """The entity's tags after the call, or None if the entity was not resolved."""
164
+ if isinstance(self.operation, TagsUpdatedOperation):
165
+ return self.operation.tags_after
166
+ if isinstance(self.operation, TagsUnchangedOperation):
167
+ return self.operation.current_tags
168
+ return None
@@ -14,6 +14,7 @@
14
14
  from .constants import PBA_WITH_IMAGES_INST
15
15
  from .errors import (
16
16
  PlaybookAlertBulkFetchError,
17
+ PlaybookAlertCreateError,
17
18
  PlaybookAlertError,
18
19
  PlaybookAlertFetchError,
19
20
  PlaybookAlertRetrieveImageError,
@@ -30,6 +31,7 @@ from .playbook_alerts import (
30
31
  PBA_Generic,
31
32
  PBA_GeopoliticsFacility,
32
33
  PBA_IdentityNovelExposure,
34
+ PBA_MaliciousSites,
33
35
  PBA_MalwareReport,
34
36
  PBA_ThirdPartyRisk,
35
37
  PreviewAlertOut,
@@ -17,10 +17,12 @@ from ..constants import ROOT_DIR
17
17
  from ..playbook_alerts.pa_category import PACategory
18
18
  from .playbook_alerts import (
19
19
  PBA_CodeRepoLeakage,
20
+ PBA_CompromisedBankChecks,
20
21
  PBA_CyberVulnerability,
21
22
  PBA_DomainAbuse,
22
23
  PBA_GeopoliticsFacility,
23
24
  PBA_IdentityNovelExposure,
25
+ PBA_MaliciousSites,
24
26
  PBA_MalwareReport,
25
27
  PBA_ThirdPartyRisk,
26
28
  )
@@ -39,6 +41,7 @@ PLAYBOOK_ALERT_TYPE = (
39
41
  | PBA_ThirdPartyRisk
40
42
  | PBA_GeopoliticsFacility
41
43
  | PBA_MalwareReport
44
+ | PBA_MaliciousSites
42
45
  )
43
46
 
44
47
  PLAYBOOK_ALERT_INST = (
@@ -49,14 +52,25 @@ PLAYBOOK_ALERT_INST = (
49
52
  PBA_ThirdPartyRisk,
50
53
  PBA_GeopoliticsFacility,
51
54
  PBA_MalwareReport,
55
+ PBA_MaliciousSites,
52
56
  )
53
57
 
54
58
 
55
- PBA_WITH_IMAGES_TYPE = PBA_DomainAbuse | PBA_GeopoliticsFacility
59
+ PBA_WITH_IMAGES_TYPE = (
60
+ PBA_DomainAbuse | PBA_GeopoliticsFacility | PBA_CompromisedBankChecks | PBA_MaliciousSites
61
+ )
56
62
  PBA_WITH_IMAGES_VALIDATOR = (
57
- Literal[PACategory.DOMAIN_ABUSE.value] | Literal[PACategory.GEOPOLITICS_FACILITY.value]
63
+ Literal[PACategory.DOMAIN_ABUSE.value]
64
+ | Literal[PACategory.GEOPOLITICS_FACILITY.value]
65
+ | Literal[PACategory.COMPROMISED_BANK_CHECKS.value]
66
+ | Literal[PACategory.MALICIOUS_SITES.value]
58
67
  )
59
68
 
60
- PBA_WITH_IMAGES_INST = (PBA_DomainAbuse, PBA_GeopoliticsFacility)
69
+ PBA_WITH_IMAGES_INST = (
70
+ PBA_DomainAbuse,
71
+ PBA_GeopoliticsFacility,
72
+ PBA_CompromisedBankChecks,
73
+ PBA_MaliciousSites,
74
+ )
61
75
 
62
76
  ALERTS_PER_PAGE = 50
@@ -37,3 +37,7 @@ class PlaybookAlertBulkFetchError(PlaybookAlertError):
37
37
 
38
38
  class PlaybookAlertRetrieveImageError(PlaybookAlertError):
39
39
  """Error raised when playbook alert image fetch fails."""
40
+
41
+
42
+ class PlaybookAlertCreateError(PlaybookAlertError):
43
+ """Error raised when playbook alert creation fails."""
@@ -13,40 +13,48 @@
13
13
 
14
14
  from ..endpoints import (
15
15
  EP_PLAYBOOK_ALERT_CODE_REPO_LEAKAGE,
16
+ EP_PLAYBOOK_ALERT_COMPROMISED_BANK_CHECKS,
16
17
  EP_PLAYBOOK_ALERT_CYBER_VULNERABILITY,
17
18
  EP_PLAYBOOK_ALERT_DOMAIN_ABUSE,
18
19
  EP_PLAYBOOK_ALERT_GEOPOLITICS_FACILITY,
19
20
  EP_PLAYBOOK_ALERT_IDENTITY_NOVEL_EXPOSURES,
21
+ EP_PLAYBOOK_ALERT_MALICIOUS_SITES,
20
22
  EP_PLAYBOOK_ALERT_MALWARE_REPORT,
21
23
  EP_PLAYBOOK_ALERT_THIRD_PARTY_RISK,
22
24
  )
23
25
  from .pa_category import PACategory
24
26
  from .playbook_alerts import (
25
27
  PBA_CodeRepoLeakage,
28
+ PBA_CompromisedBankChecks,
26
29
  PBA_CyberVulnerability,
27
30
  PBA_DomainAbuse,
28
31
  PBA_GeopoliticsFacility,
29
32
  PBA_IdentityNovelExposure,
33
+ PBA_MaliciousSites,
30
34
  PBA_MalwareReport,
31
35
  PBA_ThirdPartyRisk,
32
36
  )
33
37
 
34
38
  CATEGORY_ENDPOINTS = {
35
39
  PACategory.CODE_REPO_LEAKAGE.value: EP_PLAYBOOK_ALERT_CODE_REPO_LEAKAGE,
40
+ PACategory.COMPROMISED_BANK_CHECKS.value: EP_PLAYBOOK_ALERT_COMPROMISED_BANK_CHECKS,
36
41
  PACategory.CYBER_VULNERABILITY.value: EP_PLAYBOOK_ALERT_CYBER_VULNERABILITY,
37
42
  PACategory.DOMAIN_ABUSE.value: EP_PLAYBOOK_ALERT_DOMAIN_ABUSE,
38
43
  PACategory.GEOPOLITICS_FACILITY.value: EP_PLAYBOOK_ALERT_GEOPOLITICS_FACILITY,
39
44
  PACategory.IDENTITY_NOVEL_EXPOSURES.value: EP_PLAYBOOK_ALERT_IDENTITY_NOVEL_EXPOSURES,
40
45
  PACategory.THIRD_PARTY_RISK.value: EP_PLAYBOOK_ALERT_THIRD_PARTY_RISK,
41
46
  PACategory.MALWARE_REPORT.value: EP_PLAYBOOK_ALERT_MALWARE_REPORT,
47
+ PACategory.MALICIOUS_SITES.value: EP_PLAYBOOK_ALERT_MALICIOUS_SITES,
42
48
  }
43
49
 
44
50
  CATEGORY_TO_OBJECT_MAP = {
45
51
  PACategory.CODE_REPO_LEAKAGE.value: PBA_CodeRepoLeakage,
52
+ PACategory.COMPROMISED_BANK_CHECKS.value: PBA_CompromisedBankChecks,
46
53
  PACategory.CYBER_VULNERABILITY.value: PBA_CyberVulnerability,
47
54
  PACategory.DOMAIN_ABUSE.value: PBA_DomainAbuse,
48
55
  PACategory.GEOPOLITICS_FACILITY.value: PBA_GeopoliticsFacility,
49
56
  PACategory.IDENTITY_NOVEL_EXPOSURES.value: PBA_IdentityNovelExposure,
50
57
  PACategory.THIRD_PARTY_RISK.value: PBA_ThirdPartyRisk,
51
58
  PACategory.MALWARE_REPORT.value: PBA_MalwareReport,
59
+ PACategory.MALICIOUS_SITES.value: PBA_MaliciousSites,
52
60
  }
@@ -16,12 +16,15 @@ from ...markdown import (
16
16
  MarkdownMaker,
17
17
  )
18
18
  from ...markdown.markdown_strings import bold, link
19
+ from ..models import panel_log
19
20
  from ..pa_category import PACategory
20
21
  from .markdown_code_repo import _code_repo_markdown
22
+ from .markdown_compromised_bank_checks import _compromised_bank_check_markdown
21
23
  from .markdown_cyber_vulnerability import _cyber_vulnerability_markdown
22
24
  from .markdown_domain_abuse import _domain_abuse_markdown
23
25
  from .markdown_geopolitics_facility import _geopolitics_facility_markdown
24
26
  from .markdown_identity_exposure import _identity_exposure_markdown
27
+ from .markdown_malicious_sites import _malicious_sites_markdown
25
28
  from .markdown_malware_report import _malware_report_markdown
26
29
  from .markdown_third_party_risk import _third_party_risk_markdown
27
30
 
@@ -37,6 +40,8 @@ MARKDOWN_BY_PBA_TYPE = {
37
40
  PACategory.GEOPOLITICS_FACILITY.value: _geopolitics_facility_markdown,
38
41
  PACategory.CYBER_VULNERABILITY.value: _cyber_vulnerability_markdown,
39
42
  PACategory.MALWARE_REPORT.value: _malware_report_markdown,
43
+ PACategory.MALICIOUS_SITES.value: _malicious_sites_markdown,
44
+ PACategory.COMPROMISED_BANK_CHECKS.value: _compromised_bank_check_markdown,
40
45
  }
41
46
 
42
47
 
@@ -67,6 +72,7 @@ def _markdown_playbook_alert(
67
72
  defang_iocs: bool = False,
68
73
  iocs_to_defang: list = None,
69
74
  extra_context: list = None,
75
+ comments: bool = False,
70
76
  ) -> str:
71
77
  md_maker = MarkdownMaker(
72
78
  addendum=TRUNCATE_COMMENT.format(
@@ -78,6 +84,17 @@ def _markdown_playbook_alert(
78
84
  )
79
85
  _generic_pba_summary(playbook_alert, md_maker)
80
86
 
87
+ if comments:
88
+ comment_lines = []
89
+ for entry in playbook_alert.panel_log_v2 or []:
90
+ for change in entry.changes:
91
+ if isinstance(change, panel_log.CommentChange):
92
+ who = entry.author_name or 'Unknown'
93
+ when = entry.created.strftime(TIMESTAMP_STR)
94
+ comment_lines.append(f'{who} ({when}): {change.comment}\n\n')
95
+ if comment_lines:
96
+ md_maker.add_section('Comments', comment_lines)
97
+
81
98
  if markdown := MARKDOWN_BY_PBA_TYPE.get(playbook_alert.category, _unsupported_pba)(
82
99
  playbook_alert, md_maker, html_tags, extra_context
83
100
  ):