psengine 2.0.6__tar.gz → 2.0.7__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 (127) hide show
  1. {psengine-2.0.6 → psengine-2.0.7}/PKG-INFO +1 -1
  2. {psengine-2.0.6 → psengine-2.0.7}/psengine/_version.py +1 -1
  3. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/markdown/markdown.py +2 -1
  4. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_lists/entity_list.py +2 -1
  5. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/__init__.py +3 -0
  6. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/constants.py +28 -5
  7. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/errors.py +4 -0
  8. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/helpers.py +5 -3
  9. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/markdown/markdown.py +5 -23
  10. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/markdown/markdown_code_repo.py +1 -1
  11. psengine-2.0.7/psengine/playbook_alerts/markdown/markdown_malware_report.py +66 -0
  12. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/pba_malware_report.py +6 -6
  13. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/pa_category.py +0 -1
  14. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/playbook_alert_mgr.py +190 -195
  15. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/playbook_alerts.py +13 -19
  16. {psengine-2.0.6 → psengine-2.0.7}/psengine/rf_client.py +37 -16
  17. {psengine-2.0.6 → psengine-2.0.7}/psengine/risklists/risklist_mgr.py +13 -0
  18. {psengine-2.0.6 → psengine-2.0.7}/psengine.egg-info/PKG-INFO +1 -1
  19. {psengine-2.0.6 → psengine-2.0.7}/psengine.egg-info/SOURCES.txt +1 -1
  20. {psengine-2.0.6 → psengine-2.0.7}/pyproject.toml +1 -5
  21. psengine-2.0.6/psengine.egg-info/entry_points.txt +0 -2
  22. {psengine-2.0.6 → psengine-2.0.7}/LICENSE +0 -0
  23. {psengine-2.0.6 → psengine-2.0.7}/README.rst +0 -0
  24. {psengine-2.0.6 → psengine-2.0.7}/psengine/__init__.py +0 -0
  25. {psengine-2.0.6 → psengine-2.0.7}/psengine/_sdk_id.py +0 -0
  26. {psengine-2.0.6 → psengine-2.0.7}/psengine/analyst_notes/__init__.py +0 -0
  27. {psengine-2.0.6 → psengine-2.0.7}/psengine/analyst_notes/constants.py +0 -0
  28. {psengine-2.0.6 → psengine-2.0.7}/psengine/analyst_notes/errors.py +0 -0
  29. {psengine-2.0.6 → psengine-2.0.7}/psengine/analyst_notes/helpers.py +0 -0
  30. {psengine-2.0.6 → psengine-2.0.7}/psengine/analyst_notes/markdown.py +0 -0
  31. {psengine-2.0.6 → psengine-2.0.7}/psengine/analyst_notes/models.py +0 -0
  32. {psengine-2.0.6 → psengine-2.0.7}/psengine/analyst_notes/note.py +0 -0
  33. {psengine-2.0.6 → psengine-2.0.7}/psengine/analyst_notes/note_mgr.py +0 -0
  34. {psengine-2.0.6 → psengine-2.0.7}/psengine/base_http_client.py +0 -0
  35. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/__init__.py +0 -0
  36. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/classic_alert.py +0 -0
  37. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
  38. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/constants.py +0 -0
  39. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/errors.py +0 -0
  40. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/helpers.py +0 -0
  41. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/markdown/__init__.py +0 -0
  42. {psengine-2.0.6 → psengine-2.0.7}/psengine/classic_alerts/models.py +0 -0
  43. {psengine-2.0.6 → psengine-2.0.7}/psengine/collective_insights/__init__.py +0 -0
  44. {psengine-2.0.6 → psengine-2.0.7}/psengine/collective_insights/collective_insights.py +0 -0
  45. {psengine-2.0.6 → psengine-2.0.7}/psengine/collective_insights/constants.py +0 -0
  46. {psengine-2.0.6 → psengine-2.0.7}/psengine/collective_insights/errors.py +0 -0
  47. {psengine-2.0.6 → psengine-2.0.7}/psengine/collective_insights/insight.py +0 -0
  48. {psengine-2.0.6 → psengine-2.0.7}/psengine/collective_insights/models.py +0 -0
  49. {psengine-2.0.6 → psengine-2.0.7}/psengine/common_models.py +0 -0
  50. {psengine-2.0.6 → psengine-2.0.7}/psengine/config/__init__.py +0 -0
  51. {psengine-2.0.6 → psengine-2.0.7}/psengine/config/config.py +0 -0
  52. {psengine-2.0.6 → psengine-2.0.7}/psengine/config/errors.py +0 -0
  53. {psengine-2.0.6 → psengine-2.0.7}/psengine/constants.py +0 -0
  54. {psengine-2.0.6 → psengine-2.0.7}/psengine/detection/__init__.py +0 -0
  55. {psengine-2.0.6 → psengine-2.0.7}/psengine/detection/detection_mgr.py +0 -0
  56. {psengine-2.0.6 → psengine-2.0.7}/psengine/detection/detection_rule.py +0 -0
  57. {psengine-2.0.6 → psengine-2.0.7}/psengine/detection/errors.py +0 -0
  58. {psengine-2.0.6 → psengine-2.0.7}/psengine/detection/helpers.py +0 -0
  59. {psengine-2.0.6 → psengine-2.0.7}/psengine/detection/models.py +0 -0
  60. {psengine-2.0.6 → psengine-2.0.7}/psengine/endpoints.py +0 -0
  61. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/__init__.py +0 -0
  62. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/constants.py +0 -0
  63. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/errors.py +0 -0
  64. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/lookup.py +0 -0
  65. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/lookup_mgr.py +0 -0
  66. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/models/__init__.py +0 -0
  67. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/models/base_enriched_entity.py +0 -0
  68. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/models/lookup.py +0 -0
  69. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/models/soar.py +0 -0
  70. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/soar.py +0 -0
  71. {psengine-2.0.6 → psengine-2.0.7}/psengine/enrich/soar_mgr.py +3 -3
  72. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_lists/__init__.py +0 -0
  73. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_lists/constants.py +0 -0
  74. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_lists/entity_list_mgr.py +0 -0
  75. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_lists/errors.py +0 -0
  76. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_lists/models.py +0 -0
  77. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_match/__init__.py +0 -0
  78. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_match/entity_match.py +0 -0
  79. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_match/entity_match_mgr.py +0 -0
  80. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_match/errors.py +0 -0
  81. {psengine-2.0.6 → psengine-2.0.7}/psengine/entity_match/models.py +0 -0
  82. {psengine-2.0.6 → psengine-2.0.7}/psengine/errors.py +0 -0
  83. {psengine-2.0.6 → psengine-2.0.7}/psengine/helpers/__init__.py +0 -0
  84. {psengine-2.0.6 → psengine-2.0.7}/psengine/helpers/helpers.py +0 -0
  85. {psengine-2.0.6 → psengine-2.0.7}/psengine/logger/__init__.py +0 -0
  86. {psengine-2.0.6 → psengine-2.0.7}/psengine/logger/constants.py +0 -0
  87. {psengine-2.0.6 → psengine-2.0.7}/psengine/logger/errors.py +0 -0
  88. {psengine-2.0.6 → psengine-2.0.7}/psengine/logger/rf_logger.py +0 -0
  89. {psengine-2.0.6 → psengine-2.0.7}/psengine/markdown/__init__.py +0 -0
  90. {psengine-2.0.6 → psengine-2.0.7}/psengine/markdown/markdown.py +0 -0
  91. {psengine-2.0.6 → psengine-2.0.7}/psengine/markdown/models.py +0 -0
  92. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/mappings.py +0 -0
  93. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  94. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  95. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  96. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  97. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  98. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  99. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/__init__.py +0 -0
  100. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/common_models.py +0 -0
  101. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/panel_log.py +0 -0
  102. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/panel_status.py +0 -0
  103. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  104. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  105. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  106. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  107. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  108. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  109. {psengine-2.0.6 → psengine-2.0.7}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  110. {psengine-2.0.6 → psengine-2.0.7}/psengine/risklists/__init__.py +0 -0
  111. {psengine-2.0.6 → psengine-2.0.7}/psengine/risklists/constants.py +0 -0
  112. {psengine-2.0.6 → psengine-2.0.7}/psengine/risklists/errors.py +0 -0
  113. {psengine-2.0.6 → psengine-2.0.7}/psengine/risklists/models.py +0 -0
  114. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/__init__.py +0 -0
  115. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/base_stix_entity.py +0 -0
  116. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/complex_entity.py +0 -0
  117. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/constants.py +0 -0
  118. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/enriched_indicator.py +0 -0
  119. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/errors.py +0 -0
  120. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/helpers.py +0 -0
  121. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/rf_bundle.py +0 -0
  122. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/simple_entity.py +0 -0
  123. {psengine-2.0.6 → psengine-2.0.7}/psengine/stix2/util.py +0 -0
  124. {psengine-2.0.6 → psengine-2.0.7}/psengine.egg-info/dependency_links.txt +0 -0
  125. {psengine-2.0.6 → psengine-2.0.7}/psengine.egg-info/requires.txt +0 -0
  126. {psengine-2.0.6 → psengine-2.0.7}/psengine.egg-info/top_level.txt +0 -0
  127. {psengine-2.0.6 → psengine-2.0.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.0.6
3
+ Version: 2.0.7
4
4
  Summary: psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
5
5
  Author-email: Moise Medici <moise.medici@recordedfuture.com>, Patrick Kinsella <patrick.kinsella@recordedfuture.com>, Ernest Bartosevic <ernest.bartosevic@recordedfuture.com>
6
6
  License-Expression: MIT
@@ -11,4 +11,4 @@
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
13
 
14
- __version__ = '2.0.6'
14
+ __version__ = '2.0.7'
@@ -94,7 +94,8 @@ def _process_hit_fragment(
94
94
  content.append(f'{blockquote(fragment)}\n')
95
95
  else:
96
96
  content.append(
97
- f'_Reference text is missing, check the Recorded Future {link("Portal", str(classic_alert.url.portal))} for more information._\n' # noqa: E501
97
+ '_Reference text is missing, check the Recorded Future '
98
+ f'{link("Portal", str(classic_alert.url.portal))} for more information._\n'
98
99
  )
99
100
 
100
101
  if include_triggered_by:
@@ -314,7 +314,8 @@ class EntityList(RFBaseModel):
314
314
  response = self.rf_client.request('get', url)
315
315
  validated_status = ListStatusOut.model_validate(response.json())
316
316
  self.log.debug(
317
- f"List '{self.name}' status: {validated_status.status}, entities: {validated_status.size}" # noqa: E501
317
+ f"List '{self.name}' status: {validated_status.status}, "
318
+ f'entities: {validated_status.size}'
318
319
  )
319
320
 
320
321
  return validated_status
@@ -11,7 +11,9 @@
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
13
 
14
+ from .constants import PBA_WITH_IMAGES_INST
14
15
  from .errors import (
16
+ PlaybookAlertBulkFetchError,
15
17
  PlaybookAlertError,
16
18
  PlaybookAlertFetchError,
17
19
  PlaybookAlertRetrieveImageError,
@@ -27,6 +29,7 @@ from .playbook_alerts import (
27
29
  PBA_DomainAbuse,
28
30
  PBA_Generic,
29
31
  PBA_IdentityNovelExposure,
32
+ PBA_MalwareReport,
30
33
  PBA_ThirdPartyRisk,
31
34
  PreviewAlertOut,
32
35
  SearchIn,
@@ -10,30 +10,53 @@
10
10
  # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
- from typing import Union
13
+ from pathlib import Path
14
+ from typing import Literal, Union
14
15
 
16
+ from ..constants import ROOT_DIR
17
+ from ..playbook_alerts.pa_category import PACategory
15
18
  from .playbook_alerts import (
16
19
  PBA_CodeRepoLeakage,
17
20
  PBA_CyberVulnerability,
18
21
  PBA_DomainAbuse,
19
- PBA_Generic,
20
22
  PBA_GeopoliticsFacility,
21
23
  PBA_IdentityNovelExposure,
24
+ PBA_MalwareReport,
22
25
  PBA_ThirdPartyRisk,
23
26
  )
24
27
 
25
28
  STATUS_PANEL_NAME = 'status'
26
- DEFAULT_PBA_OUTPUT_DIR = 'playbook_alerts'
27
- DEFAULT_PBA_FILE_NAME = 'playbook_alerts_{}.json'
29
+
30
+ DEFAULT_ALERTS_OUTPUT_DIR = Path(ROOT_DIR) / 'playbook_alerts'
31
+ PLAYBOOK_ALERTS_OUTPUT_FNAME = 'rf_playbook_alerts_'
32
+
28
33
 
29
34
  PLAYBOOK_ALERT_TYPE = Union[
30
- PBA_Generic,
31
35
  PBA_CodeRepoLeakage,
32
36
  PBA_CyberVulnerability,
33
37
  PBA_DomainAbuse,
34
38
  PBA_IdentityNovelExposure,
35
39
  PBA_ThirdPartyRisk,
40
+ PBA_GeopoliticsFacility,
41
+ PBA_MalwareReport,
36
42
  ]
43
+ PLAYBOOK_ALERT_INST = (
44
+ PBA_CodeRepoLeakage,
45
+ PBA_CyberVulnerability,
46
+ PBA_DomainAbuse,
47
+ PBA_IdentityNovelExposure,
48
+ PBA_ThirdPartyRisk,
49
+ PBA_GeopoliticsFacility,
50
+ PBA_MalwareReport,
51
+ )
52
+
37
53
 
38
54
  PBA_WITH_IMAGES_TYPE = Union[PBA_DomainAbuse, PBA_GeopoliticsFacility]
55
+ PBA_WITH_IMAGES_VALIDATOR = Union[
56
+ Literal[PACategory.DOMAIN_ABUSE.value], Literal[PACategory.GEOPOLITICS_FACILITY.value]
57
+ ]
39
58
  PBA_WITH_IMAGES_INST = (PBA_DomainAbuse, PBA_GeopoliticsFacility)
59
+
60
+ ALERTS_PER_PAGE = 50
61
+
62
+ BULK_LOOKUP_BATCH_SIZE = 200
@@ -31,5 +31,9 @@ class PlaybookAlertFetchError(PlaybookAlertError):
31
31
  """Error raised when playbook alert fetch fails."""
32
32
 
33
33
 
34
+ class PlaybookAlertBulkFetchError(PlaybookAlertError):
35
+ """Error raised when playbook alert bulk fetch fails."""
36
+
37
+
34
38
  class PlaybookAlertRetrieveImageError(PlaybookAlertError):
35
39
  """Error raised when playbook alert image fetch fails."""
@@ -16,7 +16,7 @@ from typing import Union
16
16
 
17
17
  from ..errors import WriteFileError
18
18
  from ..helpers import OSHelpers, debug_call
19
- from .constants import DEFAULT_PBA_OUTPUT_DIR
19
+ from .constants import DEFAULT_ALERTS_OUTPUT_DIR
20
20
  from .playbook_alerts import PBA_DomainAbuse
21
21
 
22
22
  LOG = logging.getLogger('psengine.playbook_alerts.helpers')
@@ -25,7 +25,7 @@ LOG = logging.getLogger('psengine.playbook_alerts.helpers')
25
25
  @debug_call
26
26
  def save_pba_images(
27
27
  playbook_alerts: Union[PBA_DomainAbuse, list[PBA_DomainAbuse]],
28
- output_directory: str = DEFAULT_PBA_OUTPUT_DIR,
28
+ output_directory: str = DEFAULT_ALERTS_OUTPUT_DIR,
29
29
  ) -> None:
30
30
  """Save Domain Abuse images/screenshots to disk as a .png file.
31
31
 
@@ -56,7 +56,9 @@ def save_pba_images(
56
56
 
57
57
 
58
58
  def _save_image(
59
- file_name: str, image_bytes: bytes, output_directory: Union[str, Path] = DEFAULT_PBA_OUTPUT_DIR
59
+ file_name: str,
60
+ image_bytes: bytes,
61
+ output_directory: Union[str, Path] = DEFAULT_ALERTS_OUTPUT_DIR,
60
62
  ) -> None:
61
63
  """Save image to disk as a .png file.
62
64
 
@@ -17,13 +17,13 @@ from ...constants import TIMESTAMP_STR, TRUNCATE_COMMENT
17
17
  from ...markdown import (
18
18
  MarkdownMaker,
19
19
  )
20
- from ..models.common_models import ResolvedEntity
21
20
  from ..pa_category import PACategory
22
21
  from .markdown_code_repo import _code_repo_markdown
23
22
  from .markdown_cyber_vulnerability import _cyber_vulnerability_markdown
24
23
  from .markdown_domain_abuse import _domain_abuse_markdown
25
24
  from .markdown_geopolitics_facility import _geopolitics_facility_markdown
26
25
  from .markdown_identity_exposure import _identity_exposure_markdown
26
+ from .markdown_malware_report import _malware_report_markdown
27
27
  from .markdown_third_party_risk import _third_party_risk_markdown
28
28
 
29
29
  PORTAL_URL = 'https://app.recordedfuture.com/portal/playbook-alerts/{}'
@@ -37,6 +37,7 @@ MARKDOWN_BY_PBA_TYPE = {
37
37
  PACategory.THIRD_PARTY_RISK.value: _third_party_risk_markdown,
38
38
  PACategory.GEOPOLITICS_FACILITY.value: _geopolitics_facility_markdown,
39
39
  PACategory.CYBER_VULNERABILITY.value: _cyber_vulnerability_markdown,
40
+ PACategory.MALWARE_REPORT.value: _malware_report_markdown,
40
41
  }
41
42
 
42
43
 
@@ -56,27 +57,8 @@ def _generic_pba_summary(pba, md_maker: MarkdownMaker):
56
57
  md_maker.add_section('Summary', general_info)
57
58
 
58
59
 
59
- def _unmapped_pba(pba, md_maker: MarkdownMaker, *args, **kwargs) -> str: # noqa: ARG001
60
- md_maker.sections.insert(
61
- 0,
62
- md_maker.validate_section(
63
- 'Warning',
64
- [
65
- (
66
- '> 🚨 The markdown of this alert is not fully supported yet. '
67
- 'For full details check the portal 🚨'
68
- )
69
- ],
70
- ),
71
- )
72
-
73
- if targets := pba.panel_status.targets:
74
- if any(isinstance(t, str) for t in targets):
75
- md_maker.add_section('Targets', ', '.join(t for t in targets))
76
- elif any(isinstance(t, ResolvedEntity) for t in targets):
77
- md_maker.add_section('Targets', [t.name for t in targets])
78
-
79
- return md_maker.format_output()
60
+ def _unsupported_pba(pba, *args, **kwargs) -> str: # noqa: ARG001
61
+ raise NotImplementedError(f'No markdown function for playbook alert category: {pba.category}')
80
62
 
81
63
 
82
64
  def _markdown_playbook_alert(
@@ -97,7 +79,7 @@ def _markdown_playbook_alert(
97
79
  )
98
80
  _generic_pba_summary(playbook_alert, md_maker)
99
81
 
100
- if markdown := MARKDOWN_BY_PBA_TYPE.get(playbook_alert.category, _unmapped_pba)(
82
+ if markdown := MARKDOWN_BY_PBA_TYPE.get(playbook_alert.category, _unsupported_pba)(
101
83
  playbook_alert, md_maker, html_tags, extra_context
102
84
  ):
103
85
  return markdown
@@ -20,7 +20,7 @@ from ...constants import TIMESTAMP_STR
20
20
  from ...markdown import MarkdownMaker, clean_text, divider, html_textarea
21
21
 
22
22
  if TYPE_CHECKING:
23
- from ..playbook_alerts.playbook_alerts import PBA_CodeRepoLeakage
23
+ from ...playbook_alerts.playbook_alerts import PBA_CodeRepoLeakage
24
24
 
25
25
 
26
26
  def _add_repository(pba, md_maker: MarkdownMaker):
@@ -0,0 +1,66 @@
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 typing import TYPE_CHECKING
15
+
16
+ from markdown_strings import bold, header
17
+
18
+ from ...markdown import MarkdownMaker, divider
19
+ from ...markdown.markdown import html_collapsible
20
+
21
+ if TYPE_CHECKING:
22
+ from ...playbook_alerts.playbook_alerts import PBA_MalwareReport
23
+
24
+
25
+ def _add_hashes(pba: 'PBA_MalwareReport', md_maker: MarkdownMaker, html_tags: bool):
26
+ matched_hashes = []
27
+
28
+ detected_malwares = ', '.join(
29
+ [f'{m.name.upper()} ({m.count})' for m in pba.panel_evidence_summary.detected_malwares]
30
+ )
31
+ if detected_malwares:
32
+ matched_hashes.append(bold('Detected Malwares') + f' {detected_malwares} ')
33
+
34
+ sorted_hashes = sorted(
35
+ pba.panel_evidence_summary.matched_hashes, key=lambda h: h.risk_score, reverse=True
36
+ )
37
+
38
+ for _, _hash in enumerate(sorted_hashes):
39
+ matched_hashes.append(divider())
40
+ hash_header = f'{header((f"{_hash.sha256} ({_hash.risk_score})"), 5)}'
41
+ matched_hashes.append(hash_header)
42
+
43
+ sandbox_reports = []
44
+ for report in _hash.report_overviews:
45
+ report_header = f'{header(report.report_id.split("-", 1)[1], 6)} '
46
+ score = f'{bold("Sandbox Score:")} {report.sandbox_score} '
47
+ tags = f'{bold("Tags:")} {", ".join(report.tags).upper()} '
48
+ sandbox_reports.extend([report_header, score, tags])
49
+
50
+ if html_tags:
51
+ html = html_collapsible('Sandbox Reports', '\n\n' + '\n'.join(sandbox_reports) + '\n')
52
+ matched_hashes.append(html)
53
+ else:
54
+ matched_hashes.extend(sandbox_reports)
55
+
56
+ md_maker.add_section(f'Matched Hashes ({len(sorted_hashes)})', matched_hashes)
57
+
58
+
59
+ def _malware_report_markdown(
60
+ pba: 'PBA_MalwareReport',
61
+ md_maker: MarkdownMaker,
62
+ html_tags: bool,
63
+ *args, # noqa: ARG001
64
+ ) -> str:
65
+ _add_hashes(pba, md_maker, html_tags)
66
+ return md_maker.format_output()
@@ -47,9 +47,9 @@ class SandboxScore(RFBaseModel):
47
47
 
48
48
 
49
49
  class MalwareReportPanelEvidence(RFBaseModel):
50
- notification_title: str
51
- report_limit_reached: bool
52
- number_of_reports: int
53
- matched_hashes: list[MatchedHash]
54
- detected_malwares: list[DetectedMalware]
55
- sandbox_scores: list[SandboxScore]
50
+ notification_title: Optional[str] = None
51
+ report_limit_reached: Optional[bool] = None
52
+ number_of_reports: Optional[int] = None
53
+ matched_hashes: Optional[list[MatchedHash]] = []
54
+ detected_malwares: Optional[list[DetectedMalware]] = []
55
+ sandbox_scores: Optional[list[SandboxScore]] = []
@@ -36,4 +36,3 @@ class PACategory(Enum):
36
36
  IDENTITY_NOVEL_EXPOSURES = 'identity_novel_exposures'
37
37
  GEOPOLITICS_FACILITY = 'geopolitics_facility'
38
38
  MALWARE_REPORT = 'malware_report'
39
- UNMAPPED_ALERT = 'unmapped_alert'