txt2stix 1.1.12__tar.gz → 1.1.14__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.14}/PKG-INFO +1 -1
- {txt2stix-1.1.12 → txt2stix-1.1.14}/pyproject.toml +1 -1
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_indicator.py +55 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_main.py +131 -58
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_run_txt2stix.py +9 -9
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/__init__.py +0 -1
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/base.py +4 -1
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/utils.py +3 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/attack_flow.py +2 -1
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/bundler.py +1 -4
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/indicator.py +21 -3
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/txt2stix.py +277 -81
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/utils.py +2 -2
- {txt2stix-1.1.12 → txt2stix-1.1.14}/.env.example +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/.env.markdown +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/.github/workflows/create-release.yml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/.github/workflows/run-tests.yml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/.gitignore +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/LICENSE +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/docs/README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/docs/stix-mapping.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/docs/txt2stix.png +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/extractions/ai/config.yaml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/extractions/lookup/config.yaml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/extractions/pattern/config.yaml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/helpers/mimetype_filename_extension_list.csv +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/helpers/stix_relationship_types.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/helpers/tlds.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/helpers/windows_registry_key_prefix.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/_README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/_generate_lookups.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/attack_pattern.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/campaign.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/country_iso3166_alpha2.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/course_of_action.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/disarm_id_v1_6.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/disarm_name_v1_6.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/extensions.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/identity.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/infrastructure.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/intrusion_set.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/malware.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_atlas_id_v4_9_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_atlas_name_v4_9_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_enterprise_aliases_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_enterprise_id_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_enterprise_name_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_ics_aliases_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_ics_id_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_ics_name_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_mobile_aliases_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_mobile_id_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_mobile_name_v18_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_capec_id_v3_9.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_capec_name_v3_9.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_cwe_id_v4_18.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_cwe_name_v4_18.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/sector_aliases_v1_0.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/threat_actor.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/tld.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/tool.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/tests/test_cases.yaml +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/requirements.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_country.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_attack_enterprise.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_attack_ics.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_attack_mobile.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_capec.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_cwe.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/all_cases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_autonomous_system_number.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_all.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_amex.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_diners.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_discover.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_jcb.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_mastercard.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_union_pay.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_visa.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_country_alpha2.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cpe_uri.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_btc_transaction.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_btc_wallet.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_eth_transaction.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_eth_wallet.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_xmr_transaction.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_xmr_wallet.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cve_id.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_directory_unix.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_directory_unix_file.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_directory_windows.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_directory_windows_with_file.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_disarm.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_disarm_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_domain_name_only.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_domain_name_subdomain.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_email_address.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_md5.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_1.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_224.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_256.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_384.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_512.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name_file.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name_path.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name_subdomain.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name_url.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_iban_number.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv4_address_cidr.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv4_address_only.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv4_address_port.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv6_address_cidr.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv6_address_only.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv6_address_port.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mac_address.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_atlas.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_atlas_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_enterprise.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_enterprise_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_enterprise_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_ics.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_ics_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_ics_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_mobile.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_mobile_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_mobile_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_capec.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_capec_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_cwe.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_cwe_name.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_phone_number.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_sector_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_url.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_url_file.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_url_path.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_user_agent.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_windows_registry_key.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_attack_pattern.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_campaign.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_course_of_action.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_identity.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_infrastructure.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_intrusion_set.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_malware.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_threat_actor.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_tool.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/ai_index_position.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/attack_flow_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/attack_navigator_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/bad_vulmatch_lookups.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/basic_relationship.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/char_length_too_long.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/descriptive_for_ai_relationships_1.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/disarm_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/embedded_img_ignore.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/embedded_link_ignore.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/ip1.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/ip2.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/known_whitelist_match.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/mitre_attack_enterprise_ai_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/mitre_attack_enterprise_lookup_demo.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/mixed_extractions.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/not_security_content.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/test_ai_hash_error_with_stix2_lib.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/test_aliases.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/test_extraction_boundary.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/test_extraction_escapes.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/APT28-Center-of-Storm-2017.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/Bitdefender-Labs-Report-X-creat6958-en-EN.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/FireEyeAPT39.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/France_CERT_APT31_Pakdoor_TLPWHITE.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/Group-IB_Ransomware_Uncovered_whitepaper_eng.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/JOINT_CSA_HUNTING_RU_INTEL_SNAKE_MALWARE_20230509.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/TA22-0126-QAKBOT-analysis-TLP-GREEN.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/dinners_card.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/mandiant-apt1.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/mykings_report_final.txt +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-ai-relationships.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-extraction-type-ai.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-extraction-type-lookup.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-extraction-type-pattern.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-standard-tests.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/scripts/generate_simple_extraction_test_cases_txt_files.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_attack_flow.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_bundler.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_extractors.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_lookups.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_retriever.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_utils.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/utils.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/anthropic.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/deepseek.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/gemini.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/openai.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/openrouter.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/prompts.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/common.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/credential_checker.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/extractions.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/lookups.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/base_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/README.md +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/amex_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/diners_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/discover_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/jcb_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/master_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/union_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/visa_card_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/crypto/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/crypto/btc_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/unix_directory_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/unix_file_path_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/windows_directory_path_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/windows_file_path_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/domain/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/domain/domain_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/domain/hostname_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/domain/sub_domain_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/md5_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha1_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha224_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha2_256_exactor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha2_512_exactor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha3_256_exactor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha3_512_exactor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/helper.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv4_cidr_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv4_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv4_port_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv6_cidr_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv6_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv6_port_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/asn_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/cpe_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/cve_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/email_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/filename_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/iban_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/mac_address_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/phonenumber_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/user_agent_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/windows_registry_key_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/url/__init__.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/url/url_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/url/url_file_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/url/url_path_extractor.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/retriever.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/stix.py +0 -0
- {txt2stix-1.1.12 → txt2stix-1.1.14}/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.14
|
|
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.14"
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from pathlib import PurePosixPath, PureWindowsPath
|
|
2
3
|
import pytest
|
|
3
4
|
from unittest import mock
|
|
@@ -12,6 +13,7 @@ from txt2stix.indicator import (
|
|
|
12
13
|
BadDataException,
|
|
13
14
|
)
|
|
14
15
|
from stix2 import HashConstant
|
|
16
|
+
from stix2.serialization import serialize as serialize_stix
|
|
15
17
|
|
|
16
18
|
from txt2stix.bundler import txt2stixBundler
|
|
17
19
|
from datetime import datetime
|
|
@@ -379,6 +381,21 @@ all_extractors = get_all_extractors()
|
|
|
379
381
|
},
|
|
380
382
|
id="payment-card, with issuer-name",
|
|
381
383
|
),
|
|
384
|
+
## identity
|
|
385
|
+
pytest.param(
|
|
386
|
+
"My Identity 1",
|
|
387
|
+
"ai_identity",
|
|
388
|
+
{"identity--919283f8-8498-542b-a532-6bb2003282aa"},
|
|
389
|
+
{"identity--919283f8-8498-542b-a532-6bb2003282aa"},
|
|
390
|
+
id="identity-i",
|
|
391
|
+
),
|
|
392
|
+
pytest.param(
|
|
393
|
+
"My Identity 2",
|
|
394
|
+
"ai_identity",
|
|
395
|
+
{"identity--2e0aadad-9b58-5c8c-bef6-4c258b35f319"},
|
|
396
|
+
{"identity--2e0aadad-9b58-5c8c-bef6-4c258b35f319"},
|
|
397
|
+
id="identity-ii",
|
|
398
|
+
),
|
|
382
399
|
],
|
|
383
400
|
)
|
|
384
401
|
def test_build_observables(value, extractor_name, expected_objects, expected_rels):
|
|
@@ -391,6 +408,44 @@ def test_build_observables(value, extractor_name, expected_objects, expected_rel
|
|
|
391
408
|
assert {id for id in relationships} == set(expected_rels)
|
|
392
409
|
|
|
393
410
|
|
|
411
|
+
def test_identity_deterministic():
|
|
412
|
+
value = "My Identity 1"
|
|
413
|
+
extractor = all_extractors["ai_identity"]
|
|
414
|
+
indicator = mock_bundler.new_indicator(extractor, extractor.stix_mapping, value)
|
|
415
|
+
objects, relationships = build_observables(
|
|
416
|
+
mock_bundler, extractor.stix_mapping, indicator, value, extractor
|
|
417
|
+
)
|
|
418
|
+
assert len(objects) == 1
|
|
419
|
+
assert len(relationships) == 1
|
|
420
|
+
obj = objects[0]
|
|
421
|
+
assert obj["id"] == relationships[0]
|
|
422
|
+
obj = json.loads(serialize_stix(obj))
|
|
423
|
+
assert obj == {
|
|
424
|
+
"type": "identity",
|
|
425
|
+
"spec_version": "2.1",
|
|
426
|
+
"id": "identity--919283f8-8498-542b-a532-6bb2003282aa",
|
|
427
|
+
"created_by_ref": "identity--f92e15d9-6afc-5ae2-bb3e-85a1fd83a3b5",
|
|
428
|
+
"created": "2020-01-01T00:00:00.000Z",
|
|
429
|
+
"modified": "2020-01-01T00:00:00.000Z",
|
|
430
|
+
"name": "My Identity 1",
|
|
431
|
+
"identity_class": "unspecified",
|
|
432
|
+
"external_references": [
|
|
433
|
+
{
|
|
434
|
+
"source_name": "txt2stix_report_id",
|
|
435
|
+
"external_id": "4719f590-f54e-5589-88b0-cdeeac908a51",
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"source_name": "txt2stix_extraction_type",
|
|
439
|
+
"description": "ai_identity_1.0.0",
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
"object_marking_refs": [
|
|
443
|
+
"marking-definition--e828b379-4e03-4974-9ac4-e53a884c97c1",
|
|
444
|
+
"marking-definition--f92e15d9-6afc-5ae2-bb3e-85a1fd83a3b5",
|
|
445
|
+
],
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
394
449
|
@pytest.mark.parametrize(
|
|
395
450
|
"extractor_name",
|
|
396
451
|
{
|
|
@@ -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)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
from datetime import UTC, datetime
|
|
2
3
|
import os
|
|
3
4
|
import re
|
|
5
|
+
import uuid
|
|
4
6
|
from stix2.parsing import dict_to_stix2
|
|
5
7
|
from stix2 import HashConstant, File
|
|
6
8
|
from stix2.v21.vocab import HASHING_ALGORITHM
|
|
@@ -24,7 +26,7 @@ if TYPE_CHECKING:
|
|
|
24
26
|
|
|
25
27
|
# from schwifty import IBAN
|
|
26
28
|
|
|
27
|
-
from .common import MinorException
|
|
29
|
+
from .common import UUID_NAMESPACE, MinorException
|
|
28
30
|
|
|
29
31
|
from .retriever import retrieve_stix_objects
|
|
30
32
|
|
|
@@ -675,11 +677,19 @@ def _build_observables(
|
|
|
675
677
|
)
|
|
676
678
|
)
|
|
677
679
|
|
|
680
|
+
_id_part = str(
|
|
681
|
+
uuid.uuid5(
|
|
682
|
+
UUID_NAMESPACE,
|
|
683
|
+
f"txt2stix+{extracted_value}",
|
|
684
|
+
)
|
|
685
|
+
)
|
|
686
|
+
|
|
678
687
|
if stix_mapping == "attack-pattern":
|
|
679
688
|
stix_objects = [
|
|
680
689
|
dict_to_stix2(
|
|
681
690
|
{
|
|
682
691
|
"type": "attack-pattern",
|
|
692
|
+
# "id": stix_mapping + "--" + _id_part,
|
|
683
693
|
"spec_version": "2.1",
|
|
684
694
|
"created_by_ref": indicator["created_by_ref"],
|
|
685
695
|
"created": indicator["created"],
|
|
@@ -695,6 +705,7 @@ def _build_observables(
|
|
|
695
705
|
dict_to_stix2(
|
|
696
706
|
{
|
|
697
707
|
"type": "campaign",
|
|
708
|
+
# "id": stix_mapping + "--" + _id_part,
|
|
698
709
|
"spec_version": "2.1",
|
|
699
710
|
"created_by_ref": indicator["created_by_ref"],
|
|
700
711
|
"created": indicator["created"],
|
|
@@ -711,6 +722,7 @@ def _build_observables(
|
|
|
711
722
|
dict_to_stix2(
|
|
712
723
|
{
|
|
713
724
|
"type": "course-of-action",
|
|
725
|
+
# "id": stix_mapping + "--" + _id_part,
|
|
714
726
|
"spec_version": "2.1",
|
|
715
727
|
"created_by_ref": indicator["created_by_ref"],
|
|
716
728
|
"created": indicator["created"],
|
|
@@ -727,6 +739,7 @@ def _build_observables(
|
|
|
727
739
|
dict_to_stix2(
|
|
728
740
|
{
|
|
729
741
|
"type": "infrastructure",
|
|
742
|
+
# "id": stix_mapping + "--" + _id_part,
|
|
730
743
|
"spec_version": "2.1",
|
|
731
744
|
"created_by_ref": indicator["created_by_ref"],
|
|
732
745
|
"created": indicator["created"],
|
|
@@ -744,6 +757,7 @@ def _build_observables(
|
|
|
744
757
|
dict_to_stix2(
|
|
745
758
|
{
|
|
746
759
|
"type": "intrusion-set",
|
|
760
|
+
# "id": stix_mapping + "--" + _id_part,
|
|
747
761
|
"spec_version": "2.1",
|
|
748
762
|
"created_by_ref": indicator["created_by_ref"],
|
|
749
763
|
"created": indicator["created"],
|
|
@@ -760,6 +774,7 @@ def _build_observables(
|
|
|
760
774
|
dict_to_stix2(
|
|
761
775
|
{
|
|
762
776
|
"type": "malware",
|
|
777
|
+
# "id": stix_mapping + "--" + _id_part,
|
|
763
778
|
"spec_version": "2.1",
|
|
764
779
|
"created_by_ref": indicator["created_by_ref"],
|
|
765
780
|
"created": indicator["created"],
|
|
@@ -778,6 +793,7 @@ def _build_observables(
|
|
|
778
793
|
dict_to_stix2(
|
|
779
794
|
{
|
|
780
795
|
"type": "threat-actor",
|
|
796
|
+
# "id": stix_mapping + "--" + _id_part,
|
|
781
797
|
"spec_version": "2.1",
|
|
782
798
|
"created_by_ref": indicator["created_by_ref"],
|
|
783
799
|
"created": indicator["created"],
|
|
@@ -795,6 +811,7 @@ def _build_observables(
|
|
|
795
811
|
dict_to_stix2(
|
|
796
812
|
{
|
|
797
813
|
"type": "tool",
|
|
814
|
+
# "id": stix_mapping + "--" + _id_part,
|
|
798
815
|
"spec_version": "2.1",
|
|
799
816
|
"created_by_ref": indicator["created_by_ref"],
|
|
800
817
|
"created": indicator["created"],
|
|
@@ -814,8 +831,9 @@ def _build_observables(
|
|
|
814
831
|
"type": "identity",
|
|
815
832
|
"spec_version": "2.1",
|
|
816
833
|
"created_by_ref": indicator["created_by_ref"],
|
|
817
|
-
"created":
|
|
818
|
-
"modified":
|
|
834
|
+
"created": datetime(2020, 1, 1, tzinfo=UTC),
|
|
835
|
+
"modified": datetime(2020, 1, 1, tzinfo=UTC),
|
|
836
|
+
"id": "identity--" + _id_part,
|
|
819
837
|
"name": extracted_value,
|
|
820
838
|
"identity_class": "unspecified",
|
|
821
839
|
"object_marking_refs": indicator["object_marking_refs"],
|