psengine 2.1.1__tar.gz → 2.3.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 (153) hide show
  1. psengine-2.3.0/PKG-INFO +108 -0
  2. psengine-2.3.0/README.md +52 -0
  3. {psengine-2.1.1 → psengine-2.3.0}/psengine/_version.py +1 -1
  4. {psengine-2.1.1 → psengine-2.3.0}/psengine/analyst_notes/errors.py +6 -6
  5. {psengine-2.1.1 → psengine-2.3.0}/psengine/analyst_notes/models.py +7 -3
  6. {psengine-2.1.1 → psengine-2.3.0}/psengine/analyst_notes/note_mgr.py +9 -9
  7. {psengine-2.1.1 → psengine-2.3.0}/psengine/base_http_client.py +3 -2
  8. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/classic_alert_mgr.py +6 -4
  9. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/markdown/markdown.py +11 -8
  10. {psengine-2.1.1 → psengine-2.3.0}/psengine/collective_insights/collective_insights.py +0 -5
  11. {psengine-2.1.1 → psengine-2.3.0}/psengine/collective_insights/insight.py +8 -3
  12. {psengine-2.1.1 → psengine-2.3.0}/psengine/detection/detection_mgr.py +5 -6
  13. {psengine-2.1.1 → psengine-2.3.0}/psengine/detection/models.py +8 -5
  14. {psengine-2.1.1 → psengine-2.3.0}/psengine/endpoints.py +16 -1
  15. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/constants.py +14 -17
  16. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/lookup.py +18 -0
  17. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/lookup_mgr.py +3 -1
  18. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/soar_mgr.py +2 -2
  19. psengine-2.3.0/psengine/fusion/__init__.py +22 -0
  20. psengine-2.3.0/psengine/fusion/errors.py +34 -0
  21. psengine-2.3.0/psengine/fusion/fusion_mgr.py +197 -0
  22. psengine-2.3.0/psengine/fusion/models.py +59 -0
  23. {psengine-2.1.1 → psengine-2.3.0}/psengine/helpers/helpers.py +12 -5
  24. psengine-2.3.0/psengine/malware_intel/__init__.py +16 -0
  25. psengine-2.3.0/psengine/malware_intel/errors.py +18 -0
  26. psengine-2.3.0/psengine/malware_intel/malware_intel.py +107 -0
  27. psengine-2.3.0/psengine/malware_intel/malware_intel_mgr.py +81 -0
  28. psengine-2.3.0/psengine/malware_intel/models.py +270 -0
  29. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/__init__.py +1 -0
  30. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +2 -3
  31. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +5 -1
  32. {psengine-2.1.1 → psengine-2.3.0}/psengine/rf_client.py +8 -5
  33. psengine-2.3.0/psengine/risk_history/__init__.py +16 -0
  34. psengine-2.3.0/psengine/risk_history/errors.py +18 -0
  35. psengine-2.3.0/psengine/risk_history/models.py +68 -0
  36. psengine-2.3.0/psengine/risk_history/risk_history_mgr.py +61 -0
  37. psengine-2.3.0/psengine.egg-info/PKG-INFO +108 -0
  38. {psengine-2.1.1 → psengine-2.3.0}/psengine.egg-info/SOURCES.txt +14 -1
  39. {psengine-2.1.1 → psengine-2.3.0}/psengine.egg-info/requires.txt +10 -4
  40. {psengine-2.1.1 → psengine-2.3.0}/pyproject.toml +14 -6
  41. psengine-2.1.1/PKG-INFO +0 -196
  42. psengine-2.1.1/README.rst +0 -146
  43. psengine-2.1.1/psengine.egg-info/PKG-INFO +0 -196
  44. {psengine-2.1.1 → psengine-2.3.0}/LICENSE +0 -0
  45. {psengine-2.1.1 → psengine-2.3.0}/psengine/__init__.py +0 -0
  46. {psengine-2.1.1 → psengine-2.3.0}/psengine/_sdk_id.py +0 -0
  47. {psengine-2.1.1 → psengine-2.3.0}/psengine/analyst_notes/__init__.py +0 -0
  48. {psengine-2.1.1 → psengine-2.3.0}/psengine/analyst_notes/constants.py +0 -0
  49. {psengine-2.1.1 → psengine-2.3.0}/psengine/analyst_notes/helpers.py +0 -0
  50. {psengine-2.1.1 → psengine-2.3.0}/psengine/analyst_notes/markdown.py +0 -0
  51. {psengine-2.1.1 → psengine-2.3.0}/psengine/analyst_notes/note.py +0 -0
  52. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/__init__.py +0 -0
  53. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/classic_alert.py +0 -0
  54. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/constants.py +0 -0
  55. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/errors.py +0 -0
  56. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/helpers.py +0 -0
  57. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
  58. {psengine-2.1.1 → psengine-2.3.0}/psengine/classic_alerts/models.py +0 -0
  59. {psengine-2.1.1 → psengine-2.3.0}/psengine/collective_insights/__init__.py +0 -0
  60. {psengine-2.1.1 → psengine-2.3.0}/psengine/collective_insights/constants.py +0 -0
  61. {psengine-2.1.1 → psengine-2.3.0}/psengine/collective_insights/errors.py +0 -0
  62. {psengine-2.1.1 → psengine-2.3.0}/psengine/collective_insights/models.py +0 -0
  63. {psengine-2.1.1 → psengine-2.3.0}/psengine/common_models.py +0 -0
  64. {psengine-2.1.1 → psengine-2.3.0}/psengine/config/__init__.py +0 -0
  65. {psengine-2.1.1 → psengine-2.3.0}/psengine/config/config.py +0 -0
  66. {psengine-2.1.1 → psengine-2.3.0}/psengine/config/errors.py +0 -0
  67. {psengine-2.1.1 → psengine-2.3.0}/psengine/constants.py +0 -0
  68. {psengine-2.1.1 → psengine-2.3.0}/psengine/detection/__init__.py +0 -0
  69. {psengine-2.1.1 → psengine-2.3.0}/psengine/detection/detection_rule.py +0 -0
  70. {psengine-2.1.1 → psengine-2.3.0}/psengine/detection/errors.py +0 -0
  71. {psengine-2.1.1 → psengine-2.3.0}/psengine/detection/helpers.py +0 -0
  72. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/__init__.py +0 -0
  73. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/errors.py +0 -0
  74. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/models/__init__.py +0 -0
  75. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
  76. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/models/lookup.py +0 -0
  77. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/models/soar.py +0 -0
  78. {psengine-2.1.1 → psengine-2.3.0}/psengine/enrich/soar.py +0 -0
  79. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_lists/__init__.py +0 -0
  80. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_lists/constants.py +0 -0
  81. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_lists/entity_list.py +0 -0
  82. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
  83. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_lists/errors.py +0 -0
  84. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_lists/models.py +0 -0
  85. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_match/__init__.py +0 -0
  86. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_match/entity_match.py +0 -0
  87. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_match/entity_match_mgr.py +0 -0
  88. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_match/errors.py +0 -0
  89. {psengine-2.1.1 → psengine-2.3.0}/psengine/entity_match/models.py +0 -0
  90. {psengine-2.1.1 → psengine-2.3.0}/psengine/errors.py +0 -0
  91. {psengine-2.1.1 → psengine-2.3.0}/psengine/helpers/__init__.py +0 -0
  92. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/__init__.py +0 -0
  93. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/constants.py +0 -0
  94. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/errors.py +0 -0
  95. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/identity.py +0 -0
  96. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/identity_mgr.py +0 -0
  97. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/models/__init__.py +0 -0
  98. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/models/common_models.py +0 -0
  99. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/models/detections.py +0 -0
  100. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/models/incident_report.py +0 -0
  101. {psengine-2.1.1 → psengine-2.3.0}/psengine/identity/models/lookup.py +0 -0
  102. {psengine-2.1.1 → psengine-2.3.0}/psengine/logger/__init__.py +0 -0
  103. {psengine-2.1.1 → psengine-2.3.0}/psengine/logger/constants.py +0 -0
  104. {psengine-2.1.1 → psengine-2.3.0}/psengine/logger/errors.py +0 -0
  105. {psengine-2.1.1 → psengine-2.3.0}/psengine/logger/rf_logger.py +0 -0
  106. {psengine-2.1.1 → psengine-2.3.0}/psengine/markdown/__init__.py +0 -0
  107. {psengine-2.1.1 → psengine-2.3.0}/psengine/markdown/markdown.py +0 -0
  108. {psengine-2.1.1 → psengine-2.3.0}/psengine/markdown/models.py +0 -0
  109. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/constants.py +0 -0
  110. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/errors.py +0 -0
  111. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/helpers.py +0 -0
  112. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/mappings.py +0 -0
  113. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  114. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
  115. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  116. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  117. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  118. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  119. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  120. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/__init__.py +0 -0
  121. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/common_models.py +0 -0
  122. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/panel_log.py +0 -0
  123. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
  124. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  125. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  126. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  127. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  128. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  129. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  130. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  131. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  132. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/pa_category.py +0 -0
  133. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
  134. {psengine-2.1.1 → psengine-2.3.0}/psengine/playbook_alerts/playbook_alerts.py +0 -0
  135. {psengine-2.1.1 → psengine-2.3.0}/psengine/py.typed +0 -0
  136. {psengine-2.1.1 → psengine-2.3.0}/psengine/risklists/__init__.py +0 -0
  137. {psengine-2.1.1 → psengine-2.3.0}/psengine/risklists/constants.py +0 -0
  138. {psengine-2.1.1 → psengine-2.3.0}/psengine/risklists/errors.py +0 -0
  139. {psengine-2.1.1 → psengine-2.3.0}/psengine/risklists/models.py +0 -0
  140. {psengine-2.1.1 → psengine-2.3.0}/psengine/risklists/risklist_mgr.py +0 -0
  141. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/__init__.py +0 -0
  142. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/base_stix_entity.py +0 -0
  143. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/complex_entity.py +0 -0
  144. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/constants.py +0 -0
  145. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/enriched_indicator.py +0 -0
  146. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/errors.py +0 -0
  147. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/helpers.py +0 -0
  148. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/rf_bundle.py +0 -0
  149. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/simple_entity.py +0 -0
  150. {psengine-2.1.1 → psengine-2.3.0}/psengine/stix2/util.py +0 -0
  151. {psengine-2.1.1 → psengine-2.3.0}/psengine.egg-info/dependency_links.txt +0 -0
  152. {psengine-2.1.1 → psengine-2.3.0}/psengine.egg-info/top_level.txt +0 -0
  153. {psengine-2.1.1 → psengine-2.3.0}/setup.cfg +0 -0
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: psengine
3
+ Version: 2.3.0
4
+ Summary: psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
5
+ Author-email: Moise Medici <moise.medici@recordedfuture.com>, Patrick Kinsella <patrick.kinsella@recordedfuture.com>, Ernest Bartosevic <ernest.bartosevic@recordedfuture.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/RecordedFuture-ProfessionalServices/psengine
8
+ Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/CHANGELOG/
9
+ Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Security
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: <3.14,>=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: requests>=2.27.1
24
+ Requires-Dist: jsonpath_ng<=1.6.1,>=1.5.3
25
+ Requires-Dist: stix2~=3.0.1
26
+ Requires-Dist: python-dateutil>=2.7.0
27
+ Requires-Dist: more-itertools<=10.2.0,>=9.0.0
28
+ Requires-Dist: pydantic<3.0.0,>=2.7
29
+ Requires-Dist: pydantic-settings[toml]<2.11.0,>=2.5.2
30
+ Requires-Dist: markdown-strings==3.4.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest==8.3.4; extra == "dev"
33
+ Requires-Dist: pytest-cov==6.0.0; extra == "dev"
34
+ Requires-Dist: pytest-mock==3.14.0; extra == "dev"
35
+ Requires-Dist: pytest-md==0.2.0; extra == "dev"
36
+ Requires-Dist: pytest-random-order==1.1.1; extra == "dev"
37
+ Requires-Dist: pytest-httpdbg==0.9.0; extra == "dev"
38
+ Requires-Dist: ruff~=0.11.0; extra == "dev"
39
+ Requires-Dist: mimesis>=12.1.0; extra == "dev"
40
+ Requires-Dist: build==1.3.0; extra == "dev"
41
+ Requires-Dist: wheel==0.45.1; extra == "dev"
42
+ Requires-Dist: setuptools==80.9.0; extra == "dev"
43
+ Provides-Extra: docs
44
+ Requires-Dist: ruff~=0.11.0; extra == "docs"
45
+ Requires-Dist: mike~=2.1.3; extra == "docs"
46
+ Requires-Dist: mkdocs~=1.6.1; extra == "docs"
47
+ Requires-Dist: mkdocs-material~=9.6.18; extra == "docs"
48
+ Requires-Dist: mkdocstrings[python]>=0.18; extra == "docs"
49
+ Requires-Dist: griffe-typingdoc~=0.2.8; extra == "docs"
50
+ Requires-Dist: mkdocs-codeinclude-plugin~=0.2.1; extra == "docs"
51
+ Requires-Dist: markdown-include~=0.8.1; extra == "docs"
52
+ Requires-Dist: mkdocs-exclude~=1.0.2; extra == "docs"
53
+ Requires-Dist: rich; extra == "docs"
54
+ Requires-Dist: logging-tree; extra == "docs"
55
+ Dynamic: license-file
56
+
57
+ **Documentation**: <https://recordedfuture-professionalservices.github.io/psengine/>
58
+
59
+ **Github**: <https://github.com/RecordedFuture-ProfessionalServices/psengine>
60
+
61
+ **PyPi**: <https://pypi.org/project/psengine/>
62
+
63
+ ---
64
+
65
+ PSEngine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
66
+
67
+ 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.
68
+
69
+ PSEngine is a Python package solely built and maintained by the Cyber Security Engineering team powering a number of high profile integrations, such as: Elasticsearch, QRadar, Anomali, Jira, TheHive, etc.
70
+
71
+ ## Installation
72
+
73
+ PSEngine is a Python package that can be installed using `pip`. To install PSengine, run the following command:
74
+
75
+ ```bash
76
+ pip install psengine
77
+ ```
78
+
79
+ PSEngine officially supports Python >= 3.9, < 3.14.
80
+
81
+
82
+ ## Supported Features & Best Practices
83
+
84
+ PSEngine is ready for the demands of building robust and reliable integrations.
85
+
86
+ It can easily interact with the following Recorded Future datasets:
87
+
88
+ - Analyst Notes
89
+ - Collective Insights
90
+ - Classic & Playbook Alerts
91
+ - Detection Rules
92
+ - Fusion File management
93
+ - Identity Exposures management
94
+ - List management
95
+ - Malware Sandbox reports download
96
+ - On demand IOC enrichment
97
+ - Risklists
98
+ - Risk History
99
+ - STIX conversion
100
+
101
+
102
+ And facilitate the development with features like:
103
+
104
+ - Built-in logging
105
+ - Easy configuration management
106
+ - Markdown creation from certain data types
107
+ - Proxy support
108
+
@@ -0,0 +1,52 @@
1
+ **Documentation**: <https://recordedfuture-professionalservices.github.io/psengine/>
2
+
3
+ **Github**: <https://github.com/RecordedFuture-ProfessionalServices/psengine>
4
+
5
+ **PyPi**: <https://pypi.org/project/psengine/>
6
+
7
+ ---
8
+
9
+ PSEngine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
10
+
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
+
13
+ PSEngine is a Python package solely built and maintained by the Cyber Security Engineering team powering a number of high profile integrations, such as: Elasticsearch, QRadar, Anomali, Jira, TheHive, etc.
14
+
15
+ ## Installation
16
+
17
+ PSEngine is a Python package that can be installed using `pip`. To install PSengine, run the following command:
18
+
19
+ ```bash
20
+ pip install psengine
21
+ ```
22
+
23
+ PSEngine officially supports Python >= 3.9, < 3.14.
24
+
25
+
26
+ ## Supported Features & Best Practices
27
+
28
+ PSEngine is ready for the demands of building robust and reliable integrations.
29
+
30
+ It can easily interact with the following Recorded Future datasets:
31
+
32
+ - Analyst Notes
33
+ - Collective Insights
34
+ - Classic & Playbook Alerts
35
+ - Detection Rules
36
+ - Fusion File management
37
+ - Identity Exposures management
38
+ - List management
39
+ - Malware Sandbox reports download
40
+ - On demand IOC enrichment
41
+ - Risklists
42
+ - Risk History
43
+ - STIX conversion
44
+
45
+
46
+ And facilitate the development with features like:
47
+
48
+ - Built-in logging
49
+ - Easy configuration management
50
+ - Markdown creation from certain data types
51
+ - Proxy support
52
+
@@ -11,4 +11,4 @@
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
13
 
14
- __version__ = '2.1.1'
14
+ __version__ = '2.3.0'
@@ -19,24 +19,24 @@ class AnalystNoteError(RecordedFutureError):
19
19
 
20
20
 
21
21
  class AnalystNoteLookupError(RecordedFutureError):
22
- """Error raise when cannot lookup an analyst note."""
22
+ """Raised when an analyst note cannot be retrieved."""
23
23
 
24
24
 
25
25
  class AnalystNoteSearchError(RecordedFutureError):
26
- """Error raise when cannot search analyst notes."""
26
+ """Raised when an analyst note cannot be searched."""
27
27
 
28
28
 
29
29
  class AnalystNoteAttachmentError(RecordedFutureError):
30
- """Error raise when cannot lookup an analyst note."""
30
+ """Raised when an analyst note attachment cannot be retrieved."""
31
31
 
32
32
 
33
33
  class AnalystNoteDeleteError(RecordedFutureError):
34
- """Error raise when cannot delete an analyst note."""
34
+ """Raised when an analyst note cannot be deleted."""
35
35
 
36
36
 
37
37
  class AnalystNotePreviewError(RecordedFutureError):
38
- """Error raise when cannot post to preview endpoint."""
38
+ """Raised when an analyst note cannot be previewed."""
39
39
 
40
40
 
41
41
  class AnalystNotePublishError(RecordedFutureError):
42
- """Error raise when cannot post to publish endpoint."""
42
+ """Raised when an analyst note cannot be published."""
@@ -13,11 +13,12 @@
13
13
 
14
14
  import logging
15
15
  from datetime import datetime
16
- from typing import Any, Optional, Union
16
+ from typing import Annotated, Any, Optional, Union
17
17
 
18
- from pydantic import Field, ValidationError, field_validator, model_validator
18
+ from pydantic import BeforeValidator, Field, ValidationError, field_validator, model_validator
19
19
 
20
20
  from ..common_models import IdNameType, IdNameTypeDescription, RFBaseModel
21
+ from ..helpers import Validators
21
22
 
22
23
 
23
24
  class DiamondModel(RFBaseModel):
@@ -198,7 +199,10 @@ class PreviewAttributesIn(RFBaseModel):
198
199
  text: str
199
200
  note_entities: Optional[list[str]] = []
200
201
  context_entities: Optional[list[str]] = []
201
- topic: Union[list[str], str, None] = []
202
+ topic: Annotated[
203
+ Union[list[str], str, None],
204
+ BeforeValidator(Validators.convert_str_to_list),
205
+ ] = []
202
206
  labels: Optional[list[str]] = []
203
207
  validation_urls: Optional[list[str]] = []
204
208
 
@@ -210,9 +210,6 @@ class AnalystNoteMgr:
210
210
  ValidationError: If any supplied parameter is of incorrect type.
211
211
  AnalystNotePreviewRequest: If connection error occurs.
212
212
  """
213
- if topic:
214
- topic = topic if isinstance(topic, list) else [topic]
215
-
216
213
  data = {
217
214
  'attributes': {
218
215
  'title': title,
@@ -262,9 +259,6 @@ class AnalystNoteMgr:
262
259
  ValidationError: If any supplied parameter is of incorrect type.
263
260
  AnalystNotePublishError: If connection error occurs.
264
261
  """
265
- if topic:
266
- topic = topic if isinstance(topic, list) else [topic]
267
-
268
262
  data = {
269
263
  'attributes': {
270
264
  'title': title,
@@ -308,15 +302,21 @@ class AnalystNoteMgr:
308
302
  Fetch and save an attachment from an analyst note:
309
303
 
310
304
  ```python
311
- from psengine.analyst_notes import save_attachment
305
+ from pathlib import Path
306
+
307
+ from psengine.analyst_notes import AnalystNoteMgr, save_attachment
308
+
309
+ OUTPUT_DIR = Path(__file__).parent / 'attachments'
310
+ OUTPUT_DIR.mkdir(exist_ok=True)
312
311
 
313
312
  # Note with PDF attachment
314
313
  attachment, extension = note_mgr.fetch_attachment('tPtLVw')
315
- save_attachment('tPtLVw', attachment, extension)
314
+ save_attachment('tPtLVw', attachment, extension, OUTPUT_DIR)
316
315
 
317
316
  # Note with YAR attachment
318
317
  attachment, extension = note_mgr.fetch_attachment('oJeqDP')
319
- save_attachment('oJeqDP', attachment, extension)
318
+ save_attachment('oJeqDP', attachment, extension, OUTPUT_DIR)
319
+
320
320
  ```
321
321
 
322
322
  Raises:
@@ -97,7 +97,7 @@ class BaseHTTPClient:
97
97
  self,
98
98
  method: Annotated[str, Doc('An HTTP method.')],
99
99
  url: Annotated[str, Doc('A URL to make the request to.')],
100
- data: Annotated[Union[dict, list[dict], None], Doc('A request body.')] = None,
100
+ data: Annotated[Union[dict, list[dict], bytes, None], Doc('A request body.')] = None,
101
101
  *,
102
102
  params: Annotated[Union[dict, None], Doc('HTTP query parameters.')] = None,
103
103
  headers: Annotated[
@@ -124,7 +124,8 @@ class BaseHTTPClient:
124
124
  if 'User-Agent' not in headers:
125
125
  headers['User-Agent'] = self._get_user_agent_header()
126
126
 
127
- data = json.dumps(data) if data is not None else None
127
+ if not isinstance(data, bytes):
128
+ data = json.dumps(data) if data is not None else None
128
129
 
129
130
  try:
130
131
  response = method_func(
@@ -157,10 +157,11 @@ class ClassicAlertMgr:
157
157
  Optional[list[str]],
158
158
  Doc(
159
159
  """
160
- Fields to include in the search result.
160
+ Fields to include in the fetch result.
161
161
 
162
162
  **Note:**
163
- Defaults fields are `id`, `log`, `title`, `rule` which are always retrieved.
163
+ All fields are collected by default. Specify the fields needed, however the fields
164
+ `id`, `log`, `title`, `rule` are always retrieved.
164
165
  Any provided fields are added to these."
165
166
  """
166
167
  ),
@@ -216,10 +217,11 @@ class ClassicAlertMgr:
216
217
  Optional[list[str]],
217
218
  Doc(
218
219
  """
219
- Fields to include in the search result.
220
+ Fields to include in the fetch result.
220
221
 
221
222
  **Note:**
222
- Defaults fields are `id`, `log`, `title`, `rule` which are always retrieved.
223
+ All fields are collected by default. Specify the fields needed, however the fields
224
+ `id`, `log`, `title`, `rule` are always retrieved.
223
225
  Any provided fields are added to these."
224
226
  """
225
227
  ),
@@ -18,7 +18,7 @@ from typing import TYPE_CHECKING
18
18
  from markdown_strings import blockquote, bold, esc_format, link
19
19
 
20
20
  if TYPE_CHECKING:
21
- from ..classic_alerts.classic_alert import ClassicAlert
21
+ from ...classic_alerts.classic_alert import ClassicAlert
22
22
 
23
23
  from ...constants import TIMESTAMP_STR, TRUNCATE_COMMENT
24
24
  from ...markdown import (
@@ -132,9 +132,9 @@ def _process_entities(entities, hit) -> list[list[str]]:
132
132
  def _hits_markdown(
133
133
  classic_alert: 'ClassicAlert',
134
134
  hits,
135
- include_fragment_entities: bool = True,
136
- include_triggered_by: bool = True,
137
- html_tags: bool = False,
135
+ include_fragment_entities: bool,
136
+ include_triggered_by: bool,
137
+ html_tags: bool,
138
138
  ) -> list:
139
139
  sections = []
140
140
  for idx, hit in enumerate(hits):
@@ -202,7 +202,9 @@ def _enriched_entities_markdown(classic_alert: 'ClassicAlert') -> list:
202
202
  return results
203
203
 
204
204
 
205
- def _target_entities_markdown(classic_alert: 'ClassicAlert', html_tags: bool = False) -> list:
205
+ def _target_entities_markdown(
206
+ classic_alert: 'ClassicAlert', triggered_by: bool, html_tags: bool = False
207
+ ) -> list:
206
208
  results = []
207
209
  for entity in classic_alert.enriched_entities:
208
210
  result = {'title': f'Target {entity.entity.name}'}
@@ -210,6 +212,7 @@ def _target_entities_markdown(classic_alert: 'ClassicAlert', html_tags: bool = F
210
212
  classic_alert,
211
213
  hits=entity.references,
212
214
  include_fragment_entities=False,
215
+ include_triggered_by=triggered_by,
213
216
  html_tags=html_tags,
214
217
  )
215
218
  if len(result['content']):
@@ -268,7 +271,7 @@ def _add_ai_insights_section(
268
271
 
269
272
 
270
273
  def _add_enriched_entities_sections(
271
- md_maker: MarkdownMaker, classic_alert: 'ClassicAlert', html_tags: bool
274
+ md_maker: MarkdownMaker, classic_alert: 'ClassicAlert', triggered_by: bool, html_tags: bool
272
275
  ) -> None:
273
276
  """Adds sections related to enriched entities (evidence and references)."""
274
277
  if any(x.evidence for x in classic_alert.enriched_entities):
@@ -278,7 +281,7 @@ def _add_enriched_entities_sections(
278
281
  if any(x.references for x in classic_alert.enriched_entities):
279
282
  md_maker.add_section(
280
283
  'Target Entities',
281
- _target_entities_markdown(classic_alert, html_tags),
284
+ _target_entities_markdown(classic_alert, triggered_by, html_tags),
282
285
  )
283
286
 
284
287
 
@@ -350,7 +353,7 @@ def _markdown_alert(
350
353
  _add_ai_insights_section(md_maker, classic_alert, ai_insights)
351
354
 
352
355
  if classic_alert.enriched_entities:
353
- _add_enriched_entities_sections(md_maker, classic_alert, html_tags)
356
+ _add_enriched_entities_sections(md_maker, classic_alert, triggered_by, html_tags)
354
357
  else:
355
358
  _add_hits_section_if_no_enriched_entities(
356
359
  md_maker, classic_alert, fragment_entities, triggered_by, html_tags
@@ -67,11 +67,6 @@ class CollectiveInsights:
67
67
  Raises:
68
68
  ValidationError: If any supplied parameter is of incorrect type.
69
69
  """
70
- malwares = malwares if isinstance(malwares, list) else [malwares] if malwares else None
71
- mitre_codes = (
72
- mitre_codes if isinstance(mitre_codes, list) else [mitre_codes] if mitre_codes else None
73
- )
74
-
75
70
  incident = {'id': incident_id, 'type': incident_type, 'name': incident_name}
76
71
  detection = {
77
72
  'id': detection_id,
@@ -13,10 +13,13 @@
13
13
 
14
14
  from datetime import datetime
15
15
  from functools import total_ordering
16
- from typing import Optional
16
+ from typing import Annotated, Optional
17
+
18
+ from pydantic import BeforeValidator
17
19
 
18
20
  from ..common_models import IdNameType, RFBaseModel
19
21
  from ..constants import TIMESTAMP_STR
22
+ from ..helpers import Validators
20
23
  from .models import (
21
24
  RequestDetection,
22
25
  RequestIOC,
@@ -59,8 +62,10 @@ class Insight(RFBaseModel):
59
62
  timestamp: datetime
60
63
  ioc: RequestIOC
61
64
  incident: Optional[IdNameType] = None
62
- mitre_codes: Optional[list[str]] = None
63
- malwares: Optional[list[str]] = None
65
+ mitre_codes: Annotated[Optional[list[str]], BeforeValidator(Validators.convert_str_to_list)] = (
66
+ None
67
+ )
68
+ malwares: Annotated[Optional[list[str]], BeforeValidator(Validators.convert_str_to_list)] = None
64
69
  detection: RequestDetection
65
70
 
66
71
  def __hash__(self):
@@ -45,22 +45,22 @@ class DetectionMgr:
45
45
  def search(
46
46
  self,
47
47
  detection_rule: Annotated[
48
- Union[list[str], str, None], Doc('Types of detection rules to search for.')
48
+ Union[str, list[str], None], Doc('Types of detection rules to search for.')
49
49
  ] = None,
50
50
  entities: Annotated[
51
51
  Optional[list[str]], Doc('List of entities to filter the search.')
52
52
  ] = None,
53
53
  created_before: Annotated[
54
- Optional[str], Doc('Filter for rules created before this date.')
54
+ Optional[str], Doc('Filter for rules created before this date or relative date.')
55
55
  ] = None,
56
56
  created_after: Annotated[
57
- Optional[str], Doc('Filter for rules created after this date.')
57
+ Optional[str], Doc('Filter for rules created after this date or relative date.')
58
58
  ] = None,
59
59
  updated_before: Annotated[
60
- Optional[str], Doc('Filter for rules updated before this date.')
60
+ Optional[str], Doc('Filter for rules updated before this date or relative date.')
61
61
  ] = None,
62
62
  updated_after: Annotated[
63
- Optional[str], Doc('Filter for rules updated after this date.')
63
+ Optional[str], Doc('Filter for rules updated after this date or relative date.')
64
64
  ] = None,
65
65
  doc_id: Annotated[Optional[str], Doc('Filter by document ID.')] = None,
66
66
  title: Annotated[Optional[str], Doc('Filter by title.')] = None,
@@ -82,7 +82,6 @@ class DetectionMgr:
82
82
  ValidationError: If any supplied parameter is of incorrect type.
83
83
  DetectionRuleSearchError: If connection error occurs.
84
84
  """
85
- detection_rule = [detection_rule] if isinstance(detection_rule, str) else detection_rule
86
85
  filters = {
87
86
  'types': detection_rule,
88
87
  'entities': entities,
@@ -12,11 +12,12 @@
12
12
  ##############################################################################################
13
13
 
14
14
  from datetime import datetime
15
- from typing import Optional
15
+ from typing import Annotated, Optional
16
16
 
17
- from pydantic import Field
17
+ from pydantic import BeforeValidator, Field
18
18
 
19
19
  from ..common_models import DetectionRuleType, RFBaseModel
20
+ from ..helpers import Validators
20
21
 
21
22
 
22
23
  class Entity(RFBaseModel):
@@ -34,12 +35,14 @@ class RuleContext(RFBaseModel):
34
35
 
35
36
 
36
37
  class TimeRange(RFBaseModel):
37
- after: Optional[datetime] = None
38
- before: Optional[datetime] = None
38
+ after: Annotated[Optional[datetime], BeforeValidator(Validators.convert_relative_time)] = None
39
+ before: Annotated[Optional[datetime], BeforeValidator(Validators.convert_relative_time)] = None
39
40
 
40
41
 
41
42
  class SearchFilter(RFBaseModel):
42
- types: Optional[list[DetectionRuleType]] = None
43
+ types: Annotated[
44
+ Optional[list[DetectionRuleType]], BeforeValidator(Validators.convert_str_to_list)
45
+ ] = None
43
46
  entities: Optional[list[str]] = None
44
47
  created: Optional[TimeRange] = None
45
48
  updated: Optional[TimeRange] = None
@@ -45,6 +45,9 @@ EP_CLASSIC_ALERTS_ID = EP_CLASSIC_ALERTS_V3 + '/alerts/{}'
45
45
  EP_RISKLIST = f'{BASE_URL}/{API_VERSION}/' + '{}/risklist'
46
46
  EP_FUSION_FILES = CONNECT_API_BASE_URL + '/fusion/files'
47
47
 
48
+ EP_FUSION_FILES_V3 = f'{BASE_URL}/fusion/v3/files/'
49
+ EP_FUSION_DIR_V3 = f'{BASE_URL}/fusion/v3/files/directory/'
50
+
48
51
  ###############################################################################
49
52
  # Playbook Alert Endpoints
50
53
  ###############################################################################
@@ -75,7 +78,7 @@ EP_SEARCH_LIST = EP_LIST + '/search'
75
78
  ###############################################################################
76
79
  # SOAR Endpoints
77
80
  ###############################################################################
78
- EP_SOAR_ENRICHMENT = CONNECT_API_BASE_URL + '/soar/enrichment'
81
+ EP_SOAR_ENRICHMENT = BASE_URL + '/soar/v3/enrichment'
79
82
 
80
83
  ###############################################################################
81
84
  # Detection Rules API Endpoints
@@ -111,3 +114,15 @@ EP_IDENTITY_IP_LOOKUP = EP_IDENTITY + 'ip/lookup'
111
114
  EP_IDENTITY_CREDENTIALS_SEARCH = EP_IDENTITY + 'credentials/search'
112
115
  EP_IDENTITY_CREDENTIALS_LOOKUP = EP_IDENTITY + 'credentials/lookup'
113
116
  EP_IDENTITY_DUMP_SEARCH = EP_IDENTITY + 'metadata/dump/search'
117
+
118
+ ###############################################################################
119
+ # Malware Intelligence API Endpoints
120
+ ###############################################################################
121
+ EP_MALWARE_INTELLIGENCE = BASE_URL + '/malware-intelligence/v1/'
122
+ EP_MALWARE_INTEL_REPORTS = EP_MALWARE_INTELLIGENCE + 'reports'
123
+
124
+ ###############################################################################
125
+ # Risk History API Endpoints
126
+ ###############################################################################
127
+ EP_RISK_HISTORY_BASE = BASE_URL + '/risk'
128
+ EP_RISK_HISTORY = EP_RISK_HISTORY_BASE + '/history'
@@ -23,40 +23,37 @@ from ..enrich import (
23
23
  EnrichedVulnerability,
24
24
  )
25
25
 
26
- SOAR_POST_ROWS = 2000
26
+ SOAR_POST_ROWS = 1000
27
27
 
28
28
  ALLOWED_ENTITIES = Literal[
29
+ 'Company',
29
30
  'company',
30
31
  'company_by_domain',
31
32
  'company/by_domain',
32
- 'domain',
33
- 'hash',
34
- 'ip',
35
- 'malware',
36
- 'url',
37
- 'vulnerability',
38
- 'Company',
39
33
  'Organization',
40
- 'InternetDomainName',
34
+ 'organization',
35
+ 'hash',
41
36
  'Hash',
37
+ 'InternetDomainName',
38
+ 'domain',
39
+ 'ip',
42
40
  'IpAddress',
43
41
  'Malware',
42
+ 'malware',
44
43
  'URL',
44
+ 'url',
45
45
  'CyberVulnerability',
46
+ 'vulnerability',
46
47
  ]
47
48
 
48
49
  ENTITY_FIELDS = ['entity', 'risk', 'timestamps']
49
50
  MALWARE_FIELDS = ['entity', 'timestamps']
50
51
  TYPE_MAPPING = {
51
52
  'company/by_domain': 'company_by_domain',
52
- 'Organization': 'company',
53
- 'Company': 'company',
54
- 'IpAddress': 'ip',
55
- 'InternetDomainName': 'domain',
56
- 'CyberVulnerability': 'vulnerability',
57
- 'URL': 'url',
58
- 'Malware': 'malware',
59
- 'Hash': 'hash',
53
+ 'organization': 'company',
54
+ 'ipaddress': 'ip',
55
+ 'internetdomainname': 'domain',
56
+ 'cybervulnerability': 'vulnerability',
60
57
  }
61
58
 
62
59
 
@@ -291,3 +291,21 @@ class EnrichmentData(RFBaseModel):
291
291
  f'Risk Score: {self.content.risk.score}, '
292
292
  f'Last Seen: {self.content.timestamps.last_seen.strftime(TIMESTAMP_STR)}'
293
293
  )
294
+
295
+ def links(self, from_section: str, entity_type: str) -> list[str]:
296
+ """Retrieve a list of entities from the links attribute of the specific type and section."""
297
+ results = []
298
+ if not hasattr(self.content, 'links'):
299
+ return []
300
+
301
+ results.extend(
302
+ entity.name
303
+ for hit in self.content.links.hits
304
+ for section in hit.sections
305
+ if section.section_id and section.section_id.name == from_section
306
+ for lst in section.lists
307
+ for entity in lst.entities
308
+ if entity.type_ == entity_type
309
+ )
310
+
311
+ return results
@@ -72,6 +72,7 @@ class LookupMgr:
72
72
  - `malware`
73
73
  - `Malware`
74
74
  - `Organization`
75
+ - `organization`
75
76
  - `url`
76
77
  - `URL`
77
78
  - `vulnerability`
@@ -181,6 +182,7 @@ class LookupMgr:
181
182
  - `malware`
182
183
  - `Malware`
183
184
  - `Organization`
185
+ - `organization`
184
186
  - `url`
185
187
  - `URL`
186
188
  - `vulnerability`
@@ -273,7 +275,7 @@ class LookupMgr:
273
275
  entity_type: str,
274
276
  fields: list,
275
277
  ):
276
- entity_type = TYPE_MAPPING.get(entity_type, entity_type)
278
+ entity_type = TYPE_MAPPING.get(entity_type.lower(), entity_type.lower())
277
279
 
278
280
  enriched = self._fetch_data(
279
281
  entity=entity,
@@ -61,7 +61,7 @@ class SoarMgr:
61
61
  vulnerabilities, URLs, and company domains. Uses multithreading if `max_workers` > 0.
62
62
 
63
63
  Endpoint:
64
- `v2/soar/enrichment`
64
+ `soar/v3/enrichment`
65
65
 
66
66
  Example:
67
67
  Simple bulk enrichment:
@@ -81,7 +81,7 @@ class SoarMgr:
81
81
  ```
82
82
 
83
83
  Save enriched results to file:
84
- ```
84
+ ```python
85
85
  from pathlib import Path
86
86
  from json import dumps
87
87
  from psengine.enrich import SoarMgr