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