txt2stix 1.1.12__tar.gz → 1.1.13__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.
- {txt2stix-1.1.12 → txt2stix-1.1.13}/PKG-INFO +1 -1
- {txt2stix-1.1.12 → txt2stix-1.1.13}/pyproject.toml +1 -1
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_main.py +131 -58
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_run_txt2stix.py +9 -9
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/__init__.py +0 -1
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/base.py +4 -1
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/utils.py +3 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/attack_flow.py +2 -1
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/bundler.py +1 -4
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/txt2stix.py +277 -81
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/utils.py +2 -2
- {txt2stix-1.1.12 → txt2stix-1.1.13}/.env.example +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/.env.markdown +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/.github/workflows/create-release.yml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/.github/workflows/run-tests.yml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/.gitignore +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/LICENSE +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/docs/README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/docs/stix-mapping.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/docs/txt2stix.png +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/extractions/ai/config.yaml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/extractions/lookup/config.yaml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/extractions/pattern/config.yaml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/helpers/mimetype_filename_extension_list.csv +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/helpers/stix_relationship_types.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/helpers/tlds.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/helpers/windows_registry_key_prefix.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/_README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/_generate_lookups.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/attack_pattern.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/campaign.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/country_iso3166_alpha2.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/course_of_action.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/disarm_id_v1_6.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/disarm_name_v1_6.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/extensions.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/identity.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/infrastructure.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/intrusion_set.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/malware.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_atlas_id_v4_9_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_atlas_name_v4_9_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_enterprise_aliases_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_enterprise_id_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_enterprise_name_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_ics_aliases_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_ics_id_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_ics_name_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_mobile_aliases_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_mobile_id_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_attack_mobile_name_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_capec_id_v3_9.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_capec_name_v3_9.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_cwe_id_v4_18.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/mitre_cwe_name_v4_18.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/sector_aliases_v1_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/threat_actor.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/tld.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/lookups/tool.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/includes/tests/test_cases.yaml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/requirements.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/ai_country.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/ai_mitre_attack_enterprise.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/ai_mitre_attack_ics.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/ai_mitre_attack_mobile.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/ai_mitre_capec.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/ai_mitre_cwe.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/all_cases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_autonomous_system_number.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_bank_card_all.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_bank_card_amex.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_bank_card_diners.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_bank_card_discover.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_bank_card_jcb.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_bank_card_mastercard.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_bank_card_union_pay.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_bank_card_visa.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_country_alpha2.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_cpe_uri.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_cryptocurrency_btc_transaction.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_cryptocurrency_btc_wallet.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_cryptocurrency_eth_transaction.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_cryptocurrency_eth_wallet.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_cryptocurrency_xmr_transaction.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_cryptocurrency_xmr_wallet.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_cve_id.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_directory_unix.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_directory_unix_file.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_directory_windows.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_directory_windows_with_file.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_disarm.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_disarm_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_domain_name_only.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_domain_name_subdomain.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_email_address.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_file_hash_md5.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_file_hash_sha_1.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_file_hash_sha_224.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_file_hash_sha_256.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_file_hash_sha_384.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_file_hash_sha_512.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_file_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_host_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_host_name_file.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_host_name_path.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_host_name_subdomain.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_host_name_url.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_iban_number.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_ipv4_address_cidr.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_ipv4_address_only.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_ipv4_address_port.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_ipv6_address_cidr.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_ipv6_address_only.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_ipv6_address_port.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mac_address.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_atlas.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_atlas_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_enterprise.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_enterprise_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_enterprise_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_ics.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_ics_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_ics_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_mobile.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_mobile_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_attack_mobile_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_capec.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_capec_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_cwe.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_mitre_cwe_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_phone_number.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_sector_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_url.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_url_file.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_url_path.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_user_agent.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/generic_windows_registry_key.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_attack_pattern.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_campaign.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_course_of_action.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_identity.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_infrastructure.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_intrusion_set.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_malware.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_threat_actor.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/extraction_types/lookup_tool.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/ai_index_position.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/attack_flow_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/attack_navigator_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/bad_vulmatch_lookups.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/basic_relationship.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/char_length_too_long.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/descriptive_for_ai_relationships_1.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/disarm_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/embedded_img_ignore.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/embedded_link_ignore.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/ip1.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/ip2.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/known_whitelist_match.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/mitre_attack_enterprise_ai_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/mitre_attack_enterprise_lookup_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/mixed_extractions.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/not_security_content.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/test_ai_hash_error_with_stix2_lib.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/test_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/test_extraction_boundary.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/manually_generated_reports/test_extraction_escapes.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/APT28-Center-of-Storm-2017.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/Bitdefender-Labs-Report-X-creat6958-en-EN.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/FireEyeAPT39.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/France_CERT_APT31_Pakdoor_TLPWHITE.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/Group-IB_Ransomware_Uncovered_whitepaper_eng.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/JOINT_CSA_HUNTING_RU_INTEL_SNAKE_MALWARE_20230509.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/TA22-0126-QAKBOT-analysis-TLP-GREEN.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/dinners_card.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/mandiant-apt1.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/data/real_intel_reports/mykings_report_final.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/manual-tests/cases-ai-relationships.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/manual-tests/cases-extraction-type-ai.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/manual-tests/cases-extraction-type-lookup.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/manual-tests/cases-extraction-type-pattern.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/manual-tests/cases-standard-tests.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/scripts/generate_simple_extraction_test_cases_txt_files.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_attack_flow.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_bundler.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_extractors.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_indicator.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_lookups.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_retriever.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/test_utils.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/tests/src/utils.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/anthropic.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/deepseek.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/gemini.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/openai.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/openrouter.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/ai_extractor/prompts.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/common.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/credential_checker.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/extractions.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/indicator.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/lookups.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/base_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/amex_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/diners_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/discover_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/jcb_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/master_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/union_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/card/visa_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/crypto/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/crypto/btc_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/directory/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/directory/unix_directory_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/directory/unix_file_path_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/directory/windows_directory_path_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/directory/windows_file_path_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/domain/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/domain/domain_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/domain/hostname_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/domain/sub_domain_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/hashes/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/hashes/md5_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/hashes/sha1_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/hashes/sha224_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/hashes/sha2_256_exactor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/hashes/sha2_512_exactor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/hashes/sha3_256_exactor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/hashes/sha3_512_exactor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/helper.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/ip/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/ip/ipv4_cidr_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/ip/ipv4_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/ip/ipv4_port_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/ip/ipv6_cidr_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/ip/ipv6_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/ip/ipv6_port_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/asn_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/cpe_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/cve_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/email_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/filename_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/iban_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/mac_address_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/phonenumber_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/user_agent_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/others/windows_registry_key_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/url/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/url/url_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/url/url_file_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/pattern/extractors/url/url_path_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/retriever.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix/stix.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.13}/txt2stix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: txt2stix
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.13
|
|
4
4
|
Summary: txt2stix is a Python script that is designed to identify and extract IoCs and TTPs from text files, identify the relationships between them, convert them to STIX 2.1 objects, and output as a STIX 2.1 bundle.
|
|
5
5
|
Project-URL: Homepage, https://github.com/muchdogesec/txt2stix
|
|
6
6
|
Project-URL: Issues, https://github.com/muchdogesec/txt2stix/issues
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "txt2stix"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.13"
|
|
8
8
|
authors = [{ name = "dogesec" }]
|
|
9
9
|
maintainers = [{ name = "dogesec" }]
|
|
10
10
|
description = "txt2stix is a Python script that is designed to identify and extract IoCs and TTPs from text files, identify the relationships between them, convert them to STIX 2.1 objects, and output as a STIX 2.1 bundle."
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
import tempfile
|
|
3
2
|
from types import SimpleNamespace
|
|
4
|
-
import uuid
|
|
5
3
|
import pytest
|
|
6
4
|
from unittest import mock
|
|
7
5
|
from unittest.mock import MagicMock, patch
|
|
@@ -10,12 +8,8 @@ import sys
|
|
|
10
8
|
import os
|
|
11
9
|
|
|
12
10
|
from txt2stix.utils import RELATIONSHIP_TYPES, remove_links
|
|
13
|
-
from . import utils
|
|
14
11
|
|
|
15
|
-
from txt2stix import get_all_extractors
|
|
16
12
|
from txt2stix.ai_extractor.openai import OpenAIExtractor
|
|
17
|
-
from txt2stix.ai_extractor.utils import DescribesIncident
|
|
18
|
-
from txt2stix.bundler import txt2stixBundler
|
|
19
13
|
from txt2stix.txt2stix import (
|
|
20
14
|
main,
|
|
21
15
|
newLogger,
|
|
@@ -24,15 +18,18 @@ from txt2stix.txt2stix import (
|
|
|
24
18
|
parse_extractors_globbed,
|
|
25
19
|
parse_model,
|
|
26
20
|
parse_ref,
|
|
27
|
-
run_txt2stix,
|
|
28
21
|
setLogFile,
|
|
29
22
|
split_comma,
|
|
30
23
|
range_type,
|
|
31
24
|
parse_labels,
|
|
32
25
|
load_env,
|
|
33
26
|
# run_txt2stix,
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
# process_extracts,
|
|
28
|
+
process_extracts,
|
|
29
|
+
extraction_phase,
|
|
30
|
+
processing_phase,
|
|
31
|
+
run_extractors,
|
|
32
|
+
extract_relationships,
|
|
36
33
|
)
|
|
37
34
|
from txt2stix.common import FatalException
|
|
38
35
|
import argparse
|
|
@@ -240,23 +237,6 @@ def test_parse_extractors_globbed():
|
|
|
240
237
|
def test_parse_bool(string, expected):
|
|
241
238
|
assert parse_bool(string) == expected
|
|
242
239
|
|
|
243
|
-
def test_extract_all():
|
|
244
|
-
"""Test the extract_all function"""
|
|
245
|
-
mock_bundler = MagicMock()
|
|
246
|
-
mock_extractors_map = {"lookup": {}, "pattern": {}, "ai": {}}
|
|
247
|
-
text_content = "some sample text"
|
|
248
|
-
ai_extractors = [MagicMock()]
|
|
249
|
-
|
|
250
|
-
with mock.patch("txt2stix.txt2stix.extract_all") as mock_lookups:
|
|
251
|
-
mock_lookups.return_value = {}
|
|
252
|
-
|
|
253
|
-
result = mock_lookups(
|
|
254
|
-
mock_bundler, mock_extractors_map, text_content, ai_extractors=ai_extractors
|
|
255
|
-
)
|
|
256
|
-
assert isinstance(result, dict)
|
|
257
|
-
mock_lookups.assert_called_once()
|
|
258
|
-
|
|
259
|
-
|
|
260
240
|
def test_main_func():
|
|
261
241
|
input_text = "fake input text"
|
|
262
242
|
processed_text = "processed input text"
|
|
@@ -302,53 +282,64 @@ def named_ai_extractor_mock(name, retval):
|
|
|
302
282
|
m.extract_objects.return_value = retval
|
|
303
283
|
return m
|
|
304
284
|
|
|
305
|
-
def
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
patch(
|
|
309
|
-
patch('txt2stix.pattern.extract_all') as mock_pattern__extract_all,
|
|
285
|
+
def test_run_extractors():
|
|
286
|
+
with (
|
|
287
|
+
patch("txt2stix.lookups.extract_all") as mock_lookup__extract_all,
|
|
288
|
+
patch("txt2stix.pattern.extract_all") as mock_pattern__extract_all,
|
|
310
289
|
):
|
|
311
|
-
mock_lookup__extract_all.return_value = [
|
|
312
|
-
mock_pattern__extract_all.return_value = [
|
|
290
|
+
mock_lookup__extract_all.return_value = [dict(value="lookup1"), dict(value="lookup2")]
|
|
291
|
+
mock_pattern__extract_all.return_value = [dict(value="pattern1"), dict(value="pattern2")]
|
|
292
|
+
|
|
293
|
+
# test pattern and lookup (no bundler processing in run_extractors)
|
|
294
|
+
all_extracts = run_extractors(dict(lookup=dict(a=1), pattern=dict(b=2)), "")
|
|
313
295
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
296
|
+
# ensure returned structure contains the lists and each item got an id
|
|
297
|
+
assert "lookup" in all_extracts and "pattern" in all_extracts
|
|
298
|
+
for lst in all_extracts.values():
|
|
299
|
+
for item in lst:
|
|
300
|
+
assert isinstance(item, dict)
|
|
301
|
+
assert "id" in item and item["id"].startswith("ex-")
|
|
320
302
|
|
|
321
|
-
# test pattern and ai
|
|
303
|
+
# test pattern and ai with one failing extractor
|
|
322
304
|
ai_extractors = [
|
|
323
|
-
named_ai_extractor_mock(
|
|
324
|
-
named_ai_extractor_mock(
|
|
325
|
-
MagicMock()
|
|
305
|
+
named_ai_extractor_mock("llm:model2", [dict(value="value 0")]),
|
|
306
|
+
named_ai_extractor_mock("openai:model1", [dict(value="ai3"), dict(value="ai9")]),
|
|
307
|
+
MagicMock(),
|
|
326
308
|
]
|
|
327
309
|
ai_extractors[-1].extract_objects.side_effect = Exception
|
|
328
|
-
all_extracts = extract_all(bundler, dict(lookup=dict(a=1), pattern=dict(b=2), ai=dict(c=1)), '', ai_extractors=ai_extractors)
|
|
329
|
-
bundler.process_observables.assert_called()
|
|
330
|
-
bundler.process_observables.assert_any_call(['lookup1', 'lookup2'])
|
|
331
|
-
bundler.process_observables.assert_any_call(['pattern1', 'pattern2'])
|
|
332
|
-
bundler.process_observables.assert_any_call(['ai0'])
|
|
333
|
-
bundler.process_observables.assert_any_call(['ai3', 'ai9'])
|
|
334
310
|
|
|
311
|
+
all_extracts = run_extractors(
|
|
312
|
+
dict(lookup=dict(a=1), pattern=dict(b=2), ai=dict(c=1)), "", ai_extractors=ai_extractors
|
|
313
|
+
)
|
|
335
314
|
|
|
315
|
+
# succeeded AI extractors should appear
|
|
316
|
+
assert any(k.startswith("ai-") for k in all_extracts.keys())
|
|
317
|
+
assert set(all_extracts) == {"lookup", "pattern", "ai-llm:model2", "ai-openai:model1"}
|
|
318
|
+
# failing extractor should not break others; items should still have ids
|
|
319
|
+
for lst in all_extracts.values():
|
|
320
|
+
for item in lst:
|
|
321
|
+
assert "id" in item
|
|
336
322
|
|
|
337
323
|
|
|
338
|
-
def
|
|
339
|
-
mock_bundler = MagicMock()
|
|
324
|
+
def test_extract_relationships():
|
|
340
325
|
text = "TEXT_CONTENT"
|
|
341
|
-
all_extracts = {
|
|
326
|
+
all_extracts = {"lookup": [1, 2], "ai": [3, 4]}
|
|
342
327
|
mock_ai_session = MagicMock()
|
|
343
|
-
mock_ai_session.extract_relationships.return_value.model_dump.return_value = {
|
|
344
|
-
|
|
345
|
-
|
|
328
|
+
mock_ai_session.extract_relationships.return_value.model_dump.return_value = {
|
|
329
|
+
"relationships": [1, 2]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
relationships = extract_relationships(text, all_extracts, mock_ai_session)
|
|
333
|
+
|
|
334
|
+
mock_ai_session.extract_relationships.assert_called_once_with(
|
|
335
|
+
text, [1, 2, 3, 4], RELATIONSHIP_TYPES
|
|
336
|
+
)
|
|
346
337
|
mock_ai_session.extract_relationships.return_value.model_dump.assert_called()
|
|
347
338
|
assert relationships == mock_ai_session.extract_relationships.return_value.model_dump.return_value
|
|
348
|
-
mock_bundler.process_relationships.assert_called_once_with([1, 2])
|
|
349
339
|
|
|
340
|
+
# exception path returns None
|
|
350
341
|
mock_ai_session.extract_relationships.side_effect = Exception
|
|
351
|
-
assert
|
|
342
|
+
assert extract_relationships(text, all_extracts, mock_ai_session) is None
|
|
352
343
|
|
|
353
344
|
|
|
354
345
|
def test_check_credentials(monkeypatch):
|
|
@@ -357,4 +348,86 @@ def test_check_credentials(monkeypatch):
|
|
|
357
348
|
"--check_credentials"
|
|
358
349
|
])
|
|
359
350
|
with pytest.raises(SystemExit):
|
|
360
|
-
parse_args()
|
|
351
|
+
parse_args()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def test_process_extracts_normal_and_none():
|
|
355
|
+
"""Ensure process_extracts calls bundler.process_observables for each key,
|
|
356
|
+
and handles None/empty input without error."""
|
|
357
|
+
bundler = MagicMock()
|
|
358
|
+
all_extracts = {
|
|
359
|
+
'lookup': ['l1', 'l2'],
|
|
360
|
+
'pattern': ['p1'],
|
|
361
|
+
'ai-ex1': ['a1', 'a2']
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
# Normal case
|
|
365
|
+
process_extracts(bundler, all_extracts)
|
|
366
|
+
assert bundler.process_observables.call_count == len(all_extracts)
|
|
367
|
+
bundler.process_observables.assert_any_call(['l1', 'l2'])
|
|
368
|
+
bundler.process_observables.assert_any_call(['p1'])
|
|
369
|
+
bundler.process_observables.assert_any_call(['a1', 'a2'])
|
|
370
|
+
|
|
371
|
+
# None or empty should not raise and should not call further
|
|
372
|
+
bundler.reset_mock()
|
|
373
|
+
process_extracts(bundler, {})
|
|
374
|
+
process_extracts(bundler, None)
|
|
375
|
+
bundler.process_observables.assert_not_called()
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def test_process_extracts_handles_exceptions():
|
|
379
|
+
"""If bundler.process_observables raises for one key, process_extracts should continue."""
|
|
380
|
+
def side_effect(extracts):
|
|
381
|
+
if extracts == ['bad']:
|
|
382
|
+
raise Exception('boom')
|
|
383
|
+
|
|
384
|
+
bundler = MagicMock()
|
|
385
|
+
bundler.process_observables.side_effect = side_effect
|
|
386
|
+
|
|
387
|
+
all_extracts = {
|
|
388
|
+
'good': ['ok'],
|
|
389
|
+
'badkey': ['bad'],
|
|
390
|
+
'also_good': ['ok2']
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# Should not raise
|
|
394
|
+
process_extracts(bundler, all_extracts)
|
|
395
|
+
|
|
396
|
+
# All three attempted
|
|
397
|
+
assert bundler.process_observables.call_count == 3
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def test_extraction_phase_runs_extractors_and_relationships():
|
|
401
|
+
preprocessed_text = "SOME TEXT"
|
|
402
|
+
extractors_map = {'lookup': {'l': 1}, 'pattern': {'p': 1}, 'ai': {'a': 1}}
|
|
403
|
+
|
|
404
|
+
with patch('txt2stix.txt2stix.run_extractors') as mock_run_extractors, \
|
|
405
|
+
patch('txt2stix.txt2stix.extract_relationships') as mock_extract_relationships, \
|
|
406
|
+
patch('txt2stix.txt2stix.validate_token_count') as mock_validate_token_limit:
|
|
407
|
+
mock_run_extractors.return_value = {'lookup': ['l1'], 'pattern': ['p1']}
|
|
408
|
+
mock_extract_relationships.return_value = {'relationships': ['r1']}
|
|
409
|
+
|
|
410
|
+
data = extraction_phase(preprocessed_text, extractors_map, ai_content_check_provider=None, input_token_limit=10, ai_settings_extractions=["ai_1", "ai_2"], ai_settings_relationships=None, relationship_mode='ai')
|
|
411
|
+
|
|
412
|
+
mock_run_extractors.assert_called_once()
|
|
413
|
+
mock_extract_relationships.assert_called_once()
|
|
414
|
+
assert data.extractions == {'lookup': ['l1'], 'pattern': ['p1']}
|
|
415
|
+
assert data.relationships == {'relationships': ['r1']}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def test_processing_phase_applies_extracts_and_relationships():
|
|
419
|
+
preprocessed_text = "SOME TEXT"
|
|
420
|
+
# prepare data object similar to Txt2StixData
|
|
421
|
+
data = SimpleNamespace()
|
|
422
|
+
data.extractions = {'lookup': ['l1'], 'pattern': ['p1']}
|
|
423
|
+
data.relationships = {'relationships': ['r1']}
|
|
424
|
+
data.content_check = None
|
|
425
|
+
|
|
426
|
+
bundler = MagicMock()
|
|
427
|
+
bundler.report = SimpleNamespace(external_references=[], labels=[])
|
|
428
|
+
|
|
429
|
+
processing_phase(bundler, preprocessed_text, data, ai_create_attack_flow=False, ai_create_attack_navigator_layer=False)
|
|
430
|
+
|
|
431
|
+
bundler.process_observables.assert_any_call(['l1'])
|
|
432
|
+
bundler.process_observables.assert_any_call(['p1'])
|
|
433
|
+
bundler.process_relationships.assert_called_once_with(['r1'])
|
|
@@ -160,7 +160,7 @@ def test_content_check_param(mock_validate_token_count, subtests):
|
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
@mock.patch('txt2stix.txt2stix.attack_flow.extract_attack_flow_and_navigator')
|
|
163
|
-
def
|
|
163
|
+
def test_attack_flow_or_nav__no_preset_flow(mock_extract_attack_flow, subtests):
|
|
164
164
|
preprocessed_text = "192.168.0.1"
|
|
165
165
|
mock_extractors_map = parse_extractors_globbed("extractor", all_extractors, "pattern_ipv4_address_only,pattern_domain_name_only")
|
|
166
166
|
extractor = parse_model(TEST_AI_MODEL)
|
|
@@ -175,7 +175,7 @@ def test_attack_flow_or_nav(mock_extract_attack_flow, subtests):
|
|
|
175
175
|
|
|
176
176
|
with subtests.test("both true", ai_create_attack_flow=True, ai_create_attack_navigator_layer=True):
|
|
177
177
|
retval = run_txt2stix(mock_bundler, preprocessed_text, mock_extractors_map, ai_settings_relationships=extractor, ai_create_attack_flow=True, ai_create_attack_navigator_layer=True)
|
|
178
|
-
mock_extract_attack_flow.assert_called_once_with(mock_bundler, preprocessed_text, True, True, extractor)
|
|
178
|
+
mock_extract_attack_flow.assert_called_once_with(mock_bundler, preprocessed_text, True, True, extractor, flow=None)
|
|
179
179
|
assert retval.attack_flow == "a"
|
|
180
180
|
assert retval.navigator_layer == "b"
|
|
181
181
|
|
|
@@ -184,7 +184,7 @@ def test_attack_flow_or_nav(mock_extract_attack_flow, subtests):
|
|
|
184
184
|
|
|
185
185
|
with subtests.test("only flow", ai_create_attack_flow=True, ai_create_attack_navigator_layer=False):
|
|
186
186
|
retval = run_txt2stix(mock_bundler, preprocessed_text, mock_extractors_map, ai_settings_relationships=extractor, ai_create_attack_flow=True, ai_create_attack_navigator_layer=False)
|
|
187
|
-
mock_extract_attack_flow.assert_called_once_with(mock_bundler, preprocessed_text, True, False, extractor)
|
|
187
|
+
mock_extract_attack_flow.assert_called_once_with(mock_bundler, preprocessed_text, True, False, extractor, flow=None)
|
|
188
188
|
assert retval.attack_flow == "a"
|
|
189
189
|
assert retval.navigator_layer == "b"
|
|
190
190
|
|
|
@@ -193,24 +193,24 @@ def test_attack_flow_or_nav(mock_extract_attack_flow, subtests):
|
|
|
193
193
|
|
|
194
194
|
with subtests.test("only nav", ai_create_attack_flow=False, ai_create_attack_navigator_layer=True):
|
|
195
195
|
retval = run_txt2stix(mock_bundler, preprocessed_text, mock_extractors_map, ai_settings_relationships=extractor, ai_create_attack_flow=False, ai_create_attack_navigator_layer=True)
|
|
196
|
-
mock_extract_attack_flow.assert_called_once_with(mock_bundler, preprocessed_text, False, True, extractor)
|
|
196
|
+
mock_extract_attack_flow.assert_called_once_with(mock_bundler, preprocessed_text, False, True, extractor, flow=None)
|
|
197
197
|
assert retval.attack_flow == "a"
|
|
198
198
|
assert retval.navigator_layer == "b"
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
|
|
202
|
-
@mock.patch('txt2stix.txt2stix.
|
|
203
|
-
def test_relationship_mode(
|
|
202
|
+
@mock.patch('txt2stix.txt2stix.extract_relationships')
|
|
203
|
+
def test_relationship_mode(mock_extract_relationships, subtests):
|
|
204
204
|
mock_extractors_map = parse_extractors_globbed("extractor", all_extractors, "pattern_ipv4_address_only,pattern_domain_name_only")
|
|
205
205
|
preprocessed_text = "192.168.0.1"
|
|
206
|
-
|
|
206
|
+
mock_extract_relationships.return_value = []
|
|
207
207
|
|
|
208
208
|
with subtests.test('mode_standard'):
|
|
209
209
|
retval = run_txt2stix(mock_bundler, preprocessed_text, mock_extractors_map, ai_settings_relationships=parse_model(TEST_AI_MODEL), relationship_mode='standard')
|
|
210
|
-
|
|
210
|
+
mock_extract_relationships.assert_not_called()
|
|
211
211
|
assert retval.relationships == None, "extract_relationships_with_ai should not run"
|
|
212
212
|
|
|
213
213
|
with subtests.test('mode_ai'):
|
|
214
214
|
retval = run_txt2stix(mock_bundler, preprocessed_text, mock_extractors_map, ai_settings_relationships=parse_model(TEST_AI_MODEL), relationship_mode='ai')
|
|
215
|
-
|
|
215
|
+
mock_extract_relationships.assert_called_once()
|
|
216
216
|
assert retval.relationships != None, "extract_relationships_with_ai should run"
|
|
@@ -33,6 +33,9 @@ class RelationshipList(BaseModel):
|
|
|
33
33
|
relationships: list[Relationship] = Field(default_factory=list)
|
|
34
34
|
success: bool
|
|
35
35
|
|
|
36
|
+
def get(self, key, default=None):
|
|
37
|
+
return getattr(self, key, default)
|
|
38
|
+
|
|
36
39
|
class DescribesIncident(BaseModel):
|
|
37
40
|
describes_incident: bool = Field(description="does the <document> include malware analysis, APT group reports, data breaches and vulnerabilities?")
|
|
38
41
|
explanation: str = Field(description="Two or three sentence summary of the incidents it describes OR summary of what it describes instead of an incident")
|
|
@@ -213,6 +213,7 @@ def extract_attack_flow_and_navigator(
|
|
|
213
213
|
ai_create_attack_flow,
|
|
214
214
|
ai_create_attack_navigator_layer,
|
|
215
215
|
ai_settings_relationships,
|
|
216
|
+
flow=None
|
|
216
217
|
):
|
|
217
218
|
ex: BaseAIExtractor = ai_settings_relationships
|
|
218
219
|
tactics = get_all_tactics()
|
|
@@ -225,7 +226,7 @@ def extract_attack_flow_and_navigator(
|
|
|
225
226
|
]
|
|
226
227
|
logging.debug(f"parsed techniques: {json.dumps(logged_techniques, indent=4)}")
|
|
227
228
|
|
|
228
|
-
flow = ex.extract_attack_flow(preprocessed_text, techniques)
|
|
229
|
+
flow = flow or ex.extract_attack_flow(preprocessed_text, techniques)
|
|
229
230
|
navigator = None
|
|
230
231
|
if ai_create_attack_flow:
|
|
231
232
|
logging.info("creating attack-flow bundle")
|
|
@@ -422,10 +422,6 @@ class txt2stixBundler:
|
|
|
422
422
|
def process_observables(self, extractions, add_standard_relationship=False):
|
|
423
423
|
for ex in extractions:
|
|
424
424
|
try:
|
|
425
|
-
if ex.get("id", "").startswith(
|
|
426
|
-
"ai"
|
|
427
|
-
): # so id is distinct across multiple AIExtractors
|
|
428
|
-
ex["id"] = f'{ex["id"]}_{self.observables_processed}'
|
|
429
425
|
ex["id"] = ex.get("id", f"ex_{self.observables_processed}")
|
|
430
426
|
self.observables_processed += 1
|
|
431
427
|
self.add_indicator(ex, add_standard_relationship)
|
|
@@ -437,6 +433,7 @@ class txt2stixBundler:
|
|
|
437
433
|
ex["error"] = str(e)
|
|
438
434
|
|
|
439
435
|
def process_relationships(self, observables):
|
|
436
|
+
print(observables)
|
|
440
437
|
for relationship in observables:
|
|
441
438
|
try:
|
|
442
439
|
self.add_ai_relationship(relationship)
|