psengine 2.12.0__tar.gz → 2.13.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 (195) hide show
  1. {psengine-2.12.0 → psengine-2.13.0}/PKG-INFO +1 -1
  2. {psengine-2.12.0 → psengine-2.13.0}/psengine/collective_insights/__init__.py +9 -2
  3. {psengine-2.12.0 → psengine-2.13.0}/psengine/collective_insights/collective_insights.py +134 -6
  4. {psengine-2.12.0 → psengine-2.13.0}/psengine/collective_insights/constants.py +11 -0
  5. {psengine-2.12.0 → psengine-2.13.0}/psengine/collective_insights/errors.py +4 -0
  6. {psengine-2.12.0 → psengine-2.13.0}/psengine/collective_insights/insight.py +22 -1
  7. psengine-2.13.0/psengine/collective_insights/models.py +352 -0
  8. {psengine-2.12.0 → psengine-2.13.0}/psengine/common_models.py +1 -0
  9. {psengine-2.12.0 → psengine-2.13.0}/psengine/endpoints.py +1 -0
  10. {psengine-2.12.0 → psengine-2.13.0}/pyproject.toml +1 -1
  11. {psengine-2.12.0 → psengine-2.13.0}/pyproject.toml.orig +1 -1
  12. psengine-2.12.0/psengine/collective_insights/models.py +0 -80
  13. {psengine-2.12.0 → psengine-2.13.0}/README.md +0 -0
  14. {psengine-2.12.0 → psengine-2.13.0}/psengine/__init__.py +0 -0
  15. {psengine-2.12.0 → psengine-2.13.0}/psengine/_sdk_id.py +0 -0
  16. {psengine-2.12.0 → psengine-2.13.0}/psengine/analyst_notes/__init__.py +0 -0
  17. {psengine-2.12.0 → psengine-2.13.0}/psengine/analyst_notes/constants.py +0 -0
  18. {psengine-2.12.0 → psengine-2.13.0}/psengine/analyst_notes/errors.py +0 -0
  19. {psengine-2.12.0 → psengine-2.13.0}/psengine/analyst_notes/helpers.py +0 -0
  20. {psengine-2.12.0 → psengine-2.13.0}/psengine/analyst_notes/markdown.py +0 -0
  21. {psengine-2.12.0 → psengine-2.13.0}/psengine/analyst_notes/models.py +0 -0
  22. {psengine-2.12.0 → psengine-2.13.0}/psengine/analyst_notes/note.py +0 -0
  23. {psengine-2.12.0 → psengine-2.13.0}/psengine/analyst_notes/note_mgr.py +0 -0
  24. {psengine-2.12.0 → psengine-2.13.0}/psengine/asi/__init__.py +0 -0
  25. {psengine-2.12.0 → psengine-2.13.0}/psengine/asi/asi.py +0 -0
  26. {psengine-2.12.0 → psengine-2.13.0}/psengine/asi/asi_mgr.py +0 -0
  27. {psengine-2.12.0 → psengine-2.13.0}/psengine/asi/client.py +0 -0
  28. {psengine-2.12.0 → psengine-2.13.0}/psengine/asi/constants.py +0 -0
  29. {psengine-2.12.0 → psengine-2.13.0}/psengine/asi/errors.py +0 -0
  30. {psengine-2.12.0 → psengine-2.13.0}/psengine/asi/models.py +0 -0
  31. {psengine-2.12.0 → psengine-2.13.0}/psengine/base_http_client.py +0 -0
  32. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/__init__.py +0 -0
  33. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/classic_alert.py +0 -0
  34. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
  35. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/constants.py +0 -0
  36. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/errors.py +0 -0
  37. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/helpers.py +0 -0
  38. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
  39. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/markdown/markdown.py +0 -0
  40. {psengine-2.12.0 → psengine-2.13.0}/psengine/classic_alerts/models.py +0 -0
  41. {psengine-2.12.0 → psengine-2.13.0}/psengine/config/__init__.py +0 -0
  42. {psengine-2.12.0 → psengine-2.13.0}/psengine/config/config.py +0 -0
  43. {psengine-2.12.0 → psengine-2.13.0}/psengine/config/errors.py +0 -0
  44. {psengine-2.12.0 → psengine-2.13.0}/psengine/constants.py +0 -0
  45. {psengine-2.12.0 → psengine-2.13.0}/psengine/detection/__init__.py +0 -0
  46. {psengine-2.12.0 → psengine-2.13.0}/psengine/detection/detection_mgr.py +0 -0
  47. {psengine-2.12.0 → psengine-2.13.0}/psengine/detection/detection_rule.py +0 -0
  48. {psengine-2.12.0 → psengine-2.13.0}/psengine/detection/errors.py +0 -0
  49. {psengine-2.12.0 → psengine-2.13.0}/psengine/detection/helpers.py +0 -0
  50. {psengine-2.12.0 → psengine-2.13.0}/psengine/detection/models.py +0 -0
  51. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/__init__.py +0 -0
  52. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/constants.py +0 -0
  53. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/errors.py +0 -0
  54. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/lookup.py +0 -0
  55. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/lookup_mgr.py +0 -0
  56. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/models/__init__.py +0 -0
  57. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
  58. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/models/lookup.py +0 -0
  59. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/models/soar.py +0 -0
  60. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/soar.py +0 -0
  61. {psengine-2.12.0 → psengine-2.13.0}/psengine/enrich/soar_mgr.py +0 -0
  62. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_lists/__init__.py +0 -0
  63. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_lists/constants.py +0 -0
  64. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_lists/entity_list.py +0 -0
  65. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
  66. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_lists/errors.py +0 -0
  67. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_lists/models.py +0 -0
  68. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_match/__init__.py +0 -0
  69. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_match/entity_match.py +0 -0
  70. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_match/entity_match_mgr.py +0 -0
  71. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_match/errors.py +0 -0
  72. {psengine-2.12.0 → psengine-2.13.0}/psengine/entity_match/models.py +0 -0
  73. {psengine-2.12.0 → psengine-2.13.0}/psengine/errors.py +0 -0
  74. {psengine-2.12.0 → psengine-2.13.0}/psengine/fusion/__init__.py +0 -0
  75. {psengine-2.12.0 → psengine-2.13.0}/psengine/fusion/errors.py +0 -0
  76. {psengine-2.12.0 → psengine-2.13.0}/psengine/fusion/fusion_mgr.py +0 -0
  77. {psengine-2.12.0 → psengine-2.13.0}/psengine/fusion/models.py +0 -0
  78. {psengine-2.12.0 → psengine-2.13.0}/psengine/helpers/__init__.py +0 -0
  79. {psengine-2.12.0 → psengine-2.13.0}/psengine/helpers/helpers.py +0 -0
  80. {psengine-2.12.0 → psengine-2.13.0}/psengine/helpers/validation.py +0 -0
  81. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/__init__.py +0 -0
  82. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/constants.py +0 -0
  83. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/errors.py +0 -0
  84. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/identity.py +0 -0
  85. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/identity_mgr.py +0 -0
  86. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/models/__init__.py +0 -0
  87. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/models/common_models.py +0 -0
  88. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/models/detections.py +0 -0
  89. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/models/incident_report.py +0 -0
  90. {psengine-2.12.0 → psengine-2.13.0}/psengine/identity/models/lookup.py +0 -0
  91. {psengine-2.12.0 → psengine-2.13.0}/psengine/links/__init__.py +0 -0
  92. {psengine-2.12.0 → psengine-2.13.0}/psengine/links/errors.py +0 -0
  93. {psengine-2.12.0 → psengine-2.13.0}/psengine/links/links.py +0 -0
  94. {psengine-2.12.0 → psengine-2.13.0}/psengine/links/links_mgr.py +0 -0
  95. {psengine-2.12.0 → psengine-2.13.0}/psengine/links/models.py +0 -0
  96. {psengine-2.12.0 → psengine-2.13.0}/psengine/logger/__init__.py +0 -0
  97. {psengine-2.12.0 → psengine-2.13.0}/psengine/logger/constants.py +0 -0
  98. {psengine-2.12.0 → psengine-2.13.0}/psengine/logger/errors.py +0 -0
  99. {psengine-2.12.0 → psengine-2.13.0}/psengine/logger/rf_logger.py +0 -0
  100. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/__init__.py +0 -0
  101. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/auto_sigma_mgr.py +0 -0
  102. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/auto_yara_mgr.py +0 -0
  103. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/constants.py +0 -0
  104. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/errors.py +0 -0
  105. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/helpers.py +0 -0
  106. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/malware_intel.py +0 -0
  107. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/malware_intel_mgr.py +0 -0
  108. {psengine-2.12.0 → psengine-2.13.0}/psengine/malware_intel/models.py +0 -0
  109. {psengine-2.12.0 → psengine-2.13.0}/psengine/markdown/__init__.py +0 -0
  110. {psengine-2.12.0 → psengine-2.13.0}/psengine/markdown/markdown.py +0 -0
  111. {psengine-2.12.0 → psengine-2.13.0}/psengine/markdown/markdown_strings.py +0 -0
  112. {psengine-2.12.0 → psengine-2.13.0}/psengine/markdown/models.py +0 -0
  113. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/__init__.py +0 -0
  114. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/constants.py +0 -0
  115. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/errors.py +0 -0
  116. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/helpers.py +0 -0
  117. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/mappings.py +0 -0
  118. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  119. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
  120. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  121. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_compromised_bank_checks.py +0 -0
  122. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  123. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  124. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  125. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  126. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_malicious_sites.py +0 -0
  127. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  128. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  129. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/__init__.py +0 -0
  130. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/common_models.py +0 -0
  131. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/__init__.py +0 -0
  132. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/code_repo.py +0 -0
  133. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/common.py +0 -0
  134. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/domain_abuse.py +0 -0
  135. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/generic.py +0 -0
  136. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/geopolitics.py +0 -0
  137. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/malicious_sites.py +0 -0
  138. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/panel_log_v2.py +0 -0
  139. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/tpr.py +0 -0
  140. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_log/vulnerability.py +0 -0
  141. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
  142. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  143. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_compromised_bank_checks.py +0 -0
  144. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  145. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  146. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  147. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  148. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_malicious_sites.py +0 -0
  149. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  150. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  151. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  152. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/pa_category.py +0 -0
  153. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
  154. {psengine-2.12.0 → psengine-2.13.0}/psengine/playbook_alerts/playbook_alerts.py +0 -0
  155. {psengine-2.12.0 → psengine-2.13.0}/psengine/rf_client.py +0 -0
  156. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_history/__init__.py +0 -0
  157. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_history/errors.py +0 -0
  158. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_history/models.py +0 -0
  159. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_history/risk_history_mgr.py +0 -0
  160. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_rules/__init__.py +0 -0
  161. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_rules/errors.py +0 -0
  162. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_rules/models.py +0 -0
  163. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_rules/risk_rule.py +0 -0
  164. {psengine-2.12.0 → psengine-2.13.0}/psengine/risk_rules/risk_rule_mgr.py +0 -0
  165. {psengine-2.12.0 → psengine-2.13.0}/psengine/risklists/__init__.py +0 -0
  166. {psengine-2.12.0 → psengine-2.13.0}/psengine/risklists/constants.py +0 -0
  167. {psengine-2.12.0 → psengine-2.13.0}/psengine/risklists/errors.py +0 -0
  168. {psengine-2.12.0 → psengine-2.13.0}/psengine/risklists/models.py +0 -0
  169. {psengine-2.12.0 → psengine-2.13.0}/psengine/risklists/risklist_mgr.py +0 -0
  170. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/__init__.py +0 -0
  171. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/client.py +0 -0
  172. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/constants.py +0 -0
  173. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/errors.py +0 -0
  174. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/models/__init__.py +0 -0
  175. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/models/analysis.py +0 -0
  176. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/models/behavioral_report.py +0 -0
  177. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/models/overview_report.py +0 -0
  178. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/models/static_report.py +0 -0
  179. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/sandbox.py +0 -0
  180. {psengine-2.12.0 → psengine-2.13.0}/psengine/sandbox/sandbox_mgr.py +0 -0
  181. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/__init__.py +0 -0
  182. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/base_stix_entity.py +0 -0
  183. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/complex_entity.py +0 -0
  184. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/constants.py +0 -0
  185. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/enriched_indicator.py +0 -0
  186. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/errors.py +0 -0
  187. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/helpers.py +0 -0
  188. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/rf_bundle.py +0 -0
  189. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/simple_entity.py +0 -0
  190. {psengine-2.12.0 → psengine-2.13.0}/psengine/stix2/util.py +0 -0
  191. {psengine-2.12.0 → psengine-2.13.0}/psengine/threat_maps/__init__.py +0 -0
  192. {psengine-2.12.0 → psengine-2.13.0}/psengine/threat_maps/errors.py +0 -0
  193. {psengine-2.12.0 → psengine-2.13.0}/psengine/threat_maps/models.py +0 -0
  194. {psengine-2.12.0 → psengine-2.13.0}/psengine/threat_maps/threat_map.py +0 -0
  195. {psengine-2.12.0 → psengine-2.13.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.12.0
3
+ Version: 2.13.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
@@ -13,6 +13,9 @@
13
13
 
14
14
  from .collective_insights import CollectiveInsights
15
15
  from .constants import (
16
+ ATOP_USE_CASE_DETECTION,
17
+ ATOP_USE_CASE_HUNTING,
18
+ ATOP_USE_CASE_PREVENTION,
16
19
  DETECTION_SUB_TYPE_SIGMA,
17
20
  DETECTION_SUB_TYPE_SNORT,
18
21
  DETECTION_SUB_TYPE_YARA,
@@ -24,6 +27,10 @@ from .constants import (
24
27
  ENTITY_IP,
25
28
  ENTITY_URL,
26
29
  ENTITY_VULNERABILITY,
30
+ SUBMISSION_METHOD_API,
31
+ SUBMISSION_METHOD_INTEGRATION,
32
+ SUBMISSION_METHOD_SANDBOX,
27
33
  )
28
- from .errors import CollectiveInsightsError
29
- from .insight import Insight, InsightsIn, InsightsOut
34
+ from .errors import CollectiveInsightsError, CollectiveInsightsSearchError
35
+ from .insight import Insight, InsightsIn, InsightsOut, SearchIn
36
+ from .models import SearchEntry, SearchFilters
@@ -13,17 +13,20 @@
13
13
 
14
14
  import json
15
15
  import logging
16
- from typing import Annotated
16
+ from datetime import datetime
17
+ from typing import Annotated, Literal
17
18
 
18
- from pydantic import validate_call
19
+ from pydantic import Field, validate_call
19
20
  from typing_extensions import Doc
20
21
 
21
- from ..endpoints import EP_COLLECTIVE_INSIGHTS_DETECTIONS
22
+ from ..constants import DEFAULT_LIMIT
23
+ from ..endpoints import EP_COLLECTIVE_INSIGHTS_DETECTIONS, EP_COLLECTIVE_INSIGHTS_SEARCH
22
24
  from ..helpers import connection_exceptions, debug_call
23
25
  from ..rf_client import RFClient
24
- from .constants import SUMMARY_DEFAULT
25
- from .errors import CollectiveInsightsError
26
- from .insight import Insight, InsightsIn, InsightsOut
26
+ from .constants import SEARCH_MAX_LIMIT, SEARCH_PAGE_SIZE, SUMMARY_DEFAULT
27
+ from .errors import CollectiveInsightsError, CollectiveInsightsSearchError
28
+ from .insight import Insight, InsightsIn, InsightsOut, SearchIn
29
+ from .models import Presence, SearchEntry
27
30
 
28
31
 
29
32
  class CollectiveInsights:
@@ -134,6 +137,131 @@ class CollectiveInsights:
134
137
 
135
138
  return InsightsIn.model_validate(response.json())
136
139
 
140
+ @validate_call
141
+ @debug_call
142
+ @connection_exceptions(ignore_status_code=[], exception_to_raise=CollectiveInsightsSearchError)
143
+ def search(
144
+ self,
145
+ indicator_type: Annotated[
146
+ list[str] | str | Presence | None,
147
+ Doc('IOC type filter (`ip`, `domain`, `hash`, `url`, `vulnerability`).'),
148
+ ] = None,
149
+ detection_type: Annotated[
150
+ list[str] | str | Presence | None,
151
+ Doc(
152
+ 'Detection method filter (`correlation`, `playbook`, `detection_rule`, '
153
+ '`sandbox`, `threat_hunt`, `vulnerability_scan`).',
154
+ ),
155
+ ] = None,
156
+ submission_method: Annotated[
157
+ list[str] | str | Presence | None,
158
+ Doc('Submission method filter (`api`, `integration`, `sandbox`).'),
159
+ ] = None,
160
+ organizations: Annotated[
161
+ list[str] | str | None, Doc('Filter by organization IDs (uhash).')
162
+ ] = None,
163
+ detection_rule_id: Annotated[
164
+ list[str] | str | Presence | None, Doc('Filter by associated detection rule IDs.')
165
+ ] = None,
166
+ detection_time_from: Annotated[
167
+ str | datetime | None, Doc('Start of the detection time range (inclusive).')
168
+ ] = None,
169
+ detection_time_to: Annotated[
170
+ str | datetime | None, Doc('End of the detection time range (inclusive).')
171
+ ] = None,
172
+ malware_id: Annotated[
173
+ list[str] | str | Presence | None,
174
+ Doc('Filter by associated malware entity IDs.'),
175
+ ] = None,
176
+ mitre_code_id: Annotated[
177
+ list[str] | str | Presence | None,
178
+ Doc('Filter by associated MITRE ATT&CK IDs (prefixed with `mitre:`).'),
179
+ ] = None,
180
+ threat_actor_id: Annotated[
181
+ list[str] | str | Presence | None,
182
+ Doc('Filter by associated threat actor entity IDs.'),
183
+ ] = None,
184
+ atop_use_case: Annotated[
185
+ list[str] | str | Presence | None,
186
+ Doc(
187
+ 'Filter by Autonomous Threat Operations use case '
188
+ '(`hunting`, `detection`, `prevention`).',
189
+ ),
190
+ ] = None,
191
+ atop_profile_id: Annotated[
192
+ list[str] | str | Presence | None,
193
+ Doc('Filter by Autonomous Threat Operations profile ID.'),
194
+ ] = None,
195
+ atop_job_id: Annotated[
196
+ list[str] | str | Presence | None,
197
+ Doc('Filter by Autonomous Threat Operations job ID.'),
198
+ ] = None,
199
+ integration_type_id: Annotated[
200
+ list[str] | str | Presence | None, Doc('Filter by integration type entity IDs.')
201
+ ] = None,
202
+ indicator_risk_score: Annotated[
203
+ dict | Literal['present', 'absent'] | None,
204
+ Doc(
205
+ 'Filter by indicator risk score at detection time. Pass `present`/`absent` '
206
+ 'or a range dict such as `{"gte": 50, "lt": 90}`.',
207
+ ),
208
+ ] = None,
209
+ max_results: Annotated[int, Doc('Maximum number of events to return.')] = Field(
210
+ ge=1, default=DEFAULT_LIMIT
211
+ ),
212
+ page_size: Annotated[int, Doc('Number of events per page (max 1000).')] = Field(
213
+ ge=1, le=SEARCH_MAX_LIMIT, default=SEARCH_PAGE_SIZE
214
+ ),
215
+ ) -> Annotated[
216
+ list[SearchEntry],
217
+ Doc('Enriched events matching the search criteria.'),
218
+ ]:
219
+ """Search enriched Collective Insights events.
220
+
221
+ Endpoint:
222
+ `collective-insights/search`
223
+
224
+ Raises:
225
+ CollectiveInsightsSearchError: If connection error occurs.
226
+ ValidationError: If any supplied parameter is of incorrect type.
227
+ """
228
+ data = {
229
+ 'filters': {
230
+ 'organizations': organizations,
231
+ 'indicator_type': indicator_type,
232
+ 'detection_rule': detection_rule_id,
233
+ 'detection_type': detection_type,
234
+ 'submission_method': submission_method,
235
+ 'detection_time': {'from': detection_time_from, 'to': detection_time_to},
236
+ 'associated_threats': {
237
+ 'malware': malware_id,
238
+ 'mitre_code': mitre_code_id,
239
+ 'threat_actor': threat_actor_id,
240
+ },
241
+ 'autonomous_threat_operations': {
242
+ 'use_case': atop_use_case,
243
+ 'profile': atop_profile_id,
244
+ 'job': atop_job_id,
245
+ },
246
+ 'integration_type': integration_type_id,
247
+ 'indicator': {'risk': {'score': {'at_detection': indicator_risk_score}}},
248
+ },
249
+ 'limit': min(page_size, max_results),
250
+ }
251
+ search_data = SearchIn.model_validate(data)
252
+ self.log.info(f'Searching Collective Insights events with query: {search_data.json()}')
253
+
254
+ results = self.rf_client.request_paged(
255
+ method='post',
256
+ url=EP_COLLECTIVE_INSIGHTS_SEARCH,
257
+ data=search_data.json(),
258
+ results_path='data',
259
+ offset_key='offset',
260
+ max_results=max_results,
261
+ )
262
+
263
+ return [SearchEntry.model_validate(r) for r in results]
264
+
137
265
  def _prepare_ci_request(
138
266
  self,
139
267
  insight: list[Insight],
@@ -42,3 +42,14 @@ DETECTION_SUB_FORMAT_MAPPING = {
42
42
  'detection_type': ['detection', 'type'],
43
43
  }
44
44
  SUMMARY_DEFAULT = True
45
+
46
+ SEARCH_MAX_LIMIT = 1000
47
+ SEARCH_PAGE_SIZE = 100
48
+
49
+ SUBMISSION_METHOD_API = 'api'
50
+ SUBMISSION_METHOD_INTEGRATION = 'integration'
51
+ SUBMISSION_METHOD_SANDBOX = 'sandbox'
52
+
53
+ ATOP_USE_CASE_HUNTING = 'hunting'
54
+ ATOP_USE_CASE_DETECTION = 'detection'
55
+ ATOP_USE_CASE_PREVENTION = 'prevention'
@@ -16,3 +16,7 @@ from ..errors import RecordedFutureError
16
16
 
17
17
  class CollectiveInsightsError(RecordedFutureError):
18
18
  """Error raised when there was an error with the Collective Insights API."""
19
+
20
+
21
+ class CollectiveInsightsSearchError(RecordedFutureError):
22
+ """Error raised when there was an error searching the Collective Insights API."""
@@ -15,16 +15,19 @@ from datetime import datetime
15
15
  from functools import total_ordering
16
16
  from typing import Annotated
17
17
 
18
- from pydantic import BeforeValidator
18
+ from pydantic import BeforeValidator, model_validator
19
19
 
20
20
  from ..common_models import IdNameType, RFBaseModel
21
21
  from ..constants import TIMESTAMP_STR
22
22
  from ..helpers import Validators
23
+ from .constants import SEARCH_PAGE_SIZE
23
24
  from .models import (
24
25
  RequestDetection,
25
26
  RequestIOC,
26
27
  RequestOptions,
28
+ SearchFilters,
27
29
  SubmissionResult,
30
+ _prune_none,
28
31
  )
29
32
 
30
33
 
@@ -94,3 +97,21 @@ class InsightsIn(RFBaseModel):
94
97
  """Validate data received from CI."""
95
98
 
96
99
  result: SubmissionResult
100
+
101
+
102
+ class SearchIn(RFBaseModel):
103
+ """Validate data sent to the `/search` endpoint."""
104
+
105
+ filters: SearchFilters | None = None
106
+ limit: int = SEARCH_PAGE_SIZE
107
+
108
+ @model_validator(mode='before')
109
+ @classmethod
110
+ def _drop_empty_filters(cls, data):
111
+ """Drop `filters` entirely when caller supplied only `None` values."""
112
+ if not isinstance(data, dict):
113
+ return data
114
+ filters = data.get('filters')
115
+ if isinstance(filters, dict) and not _prune_none(filters):
116
+ data = {k: v for k, v in data.items() if k != 'filters'}
117
+ return data
@@ -0,0 +1,352 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+ from datetime import datetime
15
+ from enum import Enum
16
+ from functools import total_ordering
17
+ from typing import Annotated, Literal
18
+
19
+ from pydantic import AfterValidator, BeforeValidator, Field, model_validator
20
+
21
+ from ..common_models import DetectionRuleType, IOCType, RFBaseModel
22
+ from ..constants import TIMESTAMP_STR
23
+ from ..helpers.helpers import Validators
24
+
25
+
26
+ def _to_list_or_presence(value):
27
+ """Coerce a scalar to a list; pass `present`/`absent` sentinels and `None` through."""
28
+ if value is None or value in ('present', 'absent'):
29
+ return value
30
+ return Validators.convert_str_to_list(value)
31
+
32
+
33
+ def _prune_none(data):
34
+ """Recursively drop keys with `None` or empty-container values from a dict."""
35
+ if not isinstance(data, dict):
36
+ return data
37
+ pruned = {}
38
+ for key, value in data.items():
39
+ cleaned = _prune_none(value)
40
+ if cleaned is None:
41
+ continue
42
+ if isinstance(cleaned, (dict, list)) and not cleaned:
43
+ continue
44
+ pruned[key] = cleaned
45
+ return pruned
46
+
47
+
48
+ class DetectionType(Enum):
49
+ detection_rule = 'detection_rule'
50
+ correlation = 'correlation'
51
+ playbook = 'playbook'
52
+ sandbox = 'sandbox'
53
+
54
+
55
+ class SummaryProcessed(RFBaseModel):
56
+ ip: int
57
+ domain: int
58
+ hash_: int = Field(alias='hash')
59
+ vulnerability: int
60
+ url: int
61
+
62
+
63
+ class ResponseSummary(RFBaseModel):
64
+ processed: SummaryProcessed
65
+
66
+
67
+ class RequestOptions(RFBaseModel):
68
+ debug: bool = False
69
+ summary: bool = True
70
+
71
+
72
+ class RequestIOC(RFBaseModel):
73
+ type_: IOCType = Field(alias='type')
74
+ value: str
75
+ source_type: str | None = None
76
+ field: str | None = None
77
+
78
+
79
+ class RequestDetection(RFBaseModel):
80
+ id_: str | None = Field(alias='id', default=None)
81
+ name: str | None = None
82
+ type_: DetectionType = Field(alias='type')
83
+ sub_type: DetectionRuleType | None = None
84
+
85
+ @model_validator(mode='before')
86
+ @classmethod
87
+ def validate_detection_rule(cls, data):
88
+ """Validate detection rule scenario.
89
+
90
+ - id must be present.
91
+ - sub_type must be present and should be one of DetectionRuleType
92
+ """
93
+ try:
94
+ detection_type = data['type']
95
+ except KeyError as e:
96
+ raise ValueError('type field is mandatory') from e
97
+
98
+ if detection_type == 'detection_rule' and not (data.get('id') and data.get('sub_type')):
99
+ raise ValueError(f'With {detection_type} the id and sub_type fields are mandatory')
100
+
101
+ return data
102
+
103
+
104
+ class SubmissionResult(RFBaseModel):
105
+ status: str
106
+ debug: bool
107
+ summary: ResponseSummary | None = None
108
+
109
+
110
+ class SearchDetectionType(Enum):
111
+ correlation = 'correlation'
112
+ playbook = 'playbook'
113
+ detection_rule = 'detection_rule'
114
+ sandbox = 'sandbox'
115
+ threat_hunt = 'threat_hunt'
116
+ vulnerability_scan = 'vulnerability_scan'
117
+
118
+
119
+ class SubmissionMethod(Enum):
120
+ api = 'api'
121
+ integration = 'integration'
122
+ sandbox = 'sandbox'
123
+
124
+
125
+ class ATOPUseCase(Enum):
126
+ hunting = 'hunting'
127
+ detection = 'detection'
128
+ prevention = 'prevention'
129
+
130
+
131
+ Presence = Literal['present', 'absent']
132
+
133
+
134
+ class IdFilter(RFBaseModel):
135
+ """Filter by a list of IDs or a `present`/`absent` sentinel.
136
+
137
+ Also accepts a bare value (str, list, `present`, `absent`) which is auto-wrapped
138
+ into `{'id': value}`.
139
+ """
140
+
141
+ id_: Annotated[
142
+ list[str] | Presence | None,
143
+ BeforeValidator(_to_list_or_presence),
144
+ ] = Field(alias='id', default=None)
145
+
146
+ @model_validator(mode='before')
147
+ @classmethod
148
+ def _wrap_bare_value(cls, data):
149
+ if data is None or isinstance(data, dict):
150
+ return data
151
+ return {'id': data}
152
+
153
+
154
+ class DetectionTimeFilter(RFBaseModel):
155
+ from_: datetime | None = Field(alias='from', default=None)
156
+ to: datetime | None = None
157
+
158
+
159
+ class AssociatedThreatsFilter(RFBaseModel):
160
+ malware: IdFilter | None = None
161
+ mitre_code: IdFilter | None = None
162
+ threat_actor: IdFilter | None = None
163
+
164
+
165
+ class ATOPFilter(RFBaseModel):
166
+ use_case: Annotated[
167
+ list[ATOPUseCase] | Presence | None,
168
+ BeforeValidator(_to_list_or_presence),
169
+ ] = None
170
+ profile: IdFilter | None = None
171
+ job: IdFilter | None = None
172
+
173
+
174
+ class RiskScoreRange(RFBaseModel):
175
+ gte: int | None = None
176
+ gt: int | None = None
177
+ lte: int | None = None
178
+ lt: int | None = None
179
+
180
+
181
+ class IndicatorRiskScoreFilter(RFBaseModel):
182
+ at_detection: RiskScoreRange | Presence | None = None
183
+
184
+
185
+ class IndicatorRiskFilter(RFBaseModel):
186
+ score: IndicatorRiskScoreFilter | None = None
187
+
188
+
189
+ class IndicatorFilter(RFBaseModel):
190
+ risk: IndicatorRiskFilter | None = None
191
+
192
+
193
+ class SearchFilters(RFBaseModel):
194
+ organizations: Annotated[
195
+ list[str] | None,
196
+ BeforeValidator(Validators.convert_str_to_list),
197
+ AfterValidator(Validators.check_uhash_prefix),
198
+ ] = None
199
+ indicator_type: Annotated[
200
+ list[IOCType] | Presence | None,
201
+ BeforeValidator(_to_list_or_presence),
202
+ ] = None
203
+ detection_rule: IdFilter | None = None
204
+ detection_type: Annotated[
205
+ list[SearchDetectionType] | Presence | None,
206
+ BeforeValidator(_to_list_or_presence),
207
+ ] = None
208
+ submission_method: Annotated[
209
+ list[SubmissionMethod] | Presence | None,
210
+ BeforeValidator(_to_list_or_presence),
211
+ ] = None
212
+ detection_time: DetectionTimeFilter | None = None
213
+ associated_threats: AssociatedThreatsFilter | None = None
214
+ autonomous_threat_operations: ATOPFilter | None = None
215
+ integration_type: IdFilter | None = None
216
+ indicator: IndicatorFilter | None = None
217
+
218
+ @model_validator(mode='before')
219
+ @classmethod
220
+ def _prune_input(cls, data):
221
+ """Recursively drop `None` values and empty containers from filter input."""
222
+ return _prune_none(data) if isinstance(data, dict) else data
223
+
224
+
225
+ class Entity(RFBaseModel):
226
+ id_: str = Field(alias='id')
227
+ type_: str | None = Field(alias='type', default=None)
228
+ name: str | None = None
229
+ display_name: str | None = Field(alias='displayName', default=None)
230
+
231
+
232
+ class IntegrationType(RFBaseModel):
233
+ id_: str = Field(alias='id')
234
+ name: str | None = None
235
+ display_name: str | None = Field(alias='displayName', default=None)
236
+
237
+
238
+ class ATOPProfile(RFBaseModel):
239
+ id_: str | None = Field(alias='id', default=None)
240
+
241
+
242
+ class ATOPJob(RFBaseModel):
243
+ id_: str | None = Field(alias='id', default=None)
244
+
245
+
246
+ class ATOPResult(RFBaseModel):
247
+ use_case: str | None = None
248
+ profile: ATOPProfile | None = None
249
+ job: ATOPJob | None = None
250
+
251
+
252
+ class IndicatorRiskScore(RFBaseModel):
253
+ at_detection: int | None = None
254
+
255
+
256
+ class IndicatorRisk(RFBaseModel):
257
+ score: IndicatorRiskScore | None = None
258
+
259
+
260
+ class SearchIndicator(RFBaseModel):
261
+ type_: IOCType = Field(alias='type')
262
+ value: str
263
+ field: str | None = None
264
+ risk: IndicatorRisk | None = None
265
+
266
+
267
+ class SearchDetectionRule(RFBaseModel):
268
+ id_: str = Field(alias='id')
269
+
270
+
271
+ class SearchIncident(RFBaseModel):
272
+ id_: str | None = Field(alias='id', default=None)
273
+ name: str | None = None
274
+ type_: str | None = Field(alias='type', default=None)
275
+
276
+
277
+ class SearchAssociatedThreats(RFBaseModel):
278
+ threat_actors: list[Entity] | None = None
279
+ malware: list[Entity] | None = None
280
+ mitre_codes: list[Entity] | None = None
281
+
282
+
283
+ @total_ordering
284
+ class SearchEntry(RFBaseModel):
285
+ """A single enriched Collective Insights event returned by `/search`.
286
+
287
+ This class supports hashing, equality comparison, string representation, and total ordering
288
+ of `SearchEntry` instances.
289
+
290
+ Hashing:
291
+ Returns a hash value based on the event `id_` and `detection_time`.
292
+
293
+ Equality:
294
+ Checks equality between two `SearchEntry` instances based on `id_` and `detection_time`.
295
+
296
+ Greater-than Comparison:
297
+ Defines a greater-than comparison between two `SearchEntry` instances based on the
298
+ `detection_time` and the `id_`.
299
+
300
+ String Representation:
301
+ Returns a string representation of the `SearchEntry` instance including the `id_`,
302
+ the IOC value, the detection time, and the detection type.
303
+
304
+ ```python
305
+ >>> print(entry)
306
+ Event ID: 1609a, IOC: 1.2.3.4, Detection Time: 2025-12-15 13:05:23, Detection Type: sandbox
307
+ ```
308
+
309
+ Ordering:
310
+ The ordering of `SearchEntry` instances is determined primarily by `detection_time`.
311
+ If two instances have the same `detection_time`, the event `id_` is used as a secondary
312
+ criterion.
313
+ """
314
+
315
+ id_: str = Field(alias='id')
316
+ organizations: list[str] | None = None
317
+ integration_type: IntegrationType | None = None
318
+ submission_method: str | None = None
319
+ detection_type: str | None = None
320
+ autonomous_threat_operations: ATOPResult | None = None
321
+ detection_time: datetime | None = None
322
+ indicator: SearchIndicator | None = None
323
+ action: str | None = None
324
+ action_category: str | None = None
325
+ detection_rules: list[SearchDetectionRule] | None = None
326
+ incident: SearchIncident | None = None
327
+ associated_threats: SearchAssociatedThreats | None = None
328
+ context: dict | None = None
329
+
330
+ def __hash__(self):
331
+ return hash((self.id_, self.detection_time))
332
+
333
+ def __eq__(self, other: 'SearchEntry'):
334
+ return (self.id_, self.detection_time) == (other.id_, other.detection_time)
335
+
336
+ def __gt__(self, other: 'SearchEntry'):
337
+ return (self.detection_time, self.id_) > (other.detection_time, other.id_)
338
+
339
+ def __str__(self):
340
+ ioc = self.indicator.value if self.indicator else 'N/A'
341
+ detection_time = (
342
+ self.detection_time.strftime(TIMESTAMP_STR) if self.detection_time else 'N/A'
343
+ )
344
+ return (
345
+ f'Event ID: {self.id_}, IOC: {ioc}, Detection Time: {detection_time}, '
346
+ f'Detection Type: {self.detection_type or "N/A"}'
347
+ )
348
+
349
+
350
+ class SearchCounts(RFBaseModel):
351
+ returned: int | None = None
352
+ total: int | None = None
@@ -94,6 +94,7 @@ class DetectionRuleType(Enum):
94
94
  yara = 'yara'
95
95
  snort = 'snort'
96
96
  suricata = 'suricata'
97
+ nuclei = 'nuclei'
97
98
 
98
99
 
99
100
  class ClearTextPassword(Secret[str]):
@@ -93,6 +93,7 @@ EP_DETECTION_RULES = BASE_URL + '/detection-rule/search'
93
93
  ###############################################################################
94
94
  EP_COLLECTIVE_INSIGHTS = BASE_URL + '/collective-insights'
95
95
  EP_COLLECTIVE_INSIGHTS_DETECTIONS = EP_COLLECTIVE_INSIGHTS + '/detections'
96
+ EP_COLLECTIVE_INSIGHTS_SEARCH = EP_COLLECTIVE_INSIGHTS + '/search'
96
97
 
97
98
  ###############################################################################
98
99
  # Analyst Notes API Endpoints
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "psengine"
3
- version = "2.12.0"
3
+ version = "2.13.0"
4
4
  readme = "README.md"
5
5
  license = "MIT"
6
6
  requires-python = ">=3.10, <3.15"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "psengine"
3
- version = "2.12.0"
3
+ version = "2.13.0"
4
4
  readme = "README.md"
5
5
  license = "MIT"
6
6
  requires-python = ">=3.10, <3.15"