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