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.
Files changed (264) hide show
  1. {txt2stix-1.1.12 → txt2stix-1.1.14}/PKG-INFO +1 -1
  2. {txt2stix-1.1.12 → txt2stix-1.1.14}/pyproject.toml +1 -1
  3. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_indicator.py +55 -0
  4. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_main.py +131 -58
  5. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_run_txt2stix.py +9 -9
  6. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/__init__.py +0 -1
  7. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/base.py +4 -1
  8. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/utils.py +3 -0
  9. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/attack_flow.py +2 -1
  10. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/bundler.py +1 -4
  11. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/indicator.py +21 -3
  12. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/txt2stix.py +277 -81
  13. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/utils.py +2 -2
  14. {txt2stix-1.1.12 → txt2stix-1.1.14}/.env.example +0 -0
  15. {txt2stix-1.1.12 → txt2stix-1.1.14}/.env.markdown +0 -0
  16. {txt2stix-1.1.12 → txt2stix-1.1.14}/.github/workflows/create-release.yml +0 -0
  17. {txt2stix-1.1.12 → txt2stix-1.1.14}/.github/workflows/run-tests.yml +0 -0
  18. {txt2stix-1.1.12 → txt2stix-1.1.14}/.gitignore +0 -0
  19. {txt2stix-1.1.12 → txt2stix-1.1.14}/LICENSE +0 -0
  20. {txt2stix-1.1.12 → txt2stix-1.1.14}/README.md +0 -0
  21. {txt2stix-1.1.12 → txt2stix-1.1.14}/docs/README.md +0 -0
  22. {txt2stix-1.1.12 → txt2stix-1.1.14}/docs/stix-mapping.md +0 -0
  23. {txt2stix-1.1.12 → txt2stix-1.1.14}/docs/txt2stix.png +0 -0
  24. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/__init__.py +0 -0
  25. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/extractions/ai/config.yaml +0 -0
  26. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/extractions/lookup/config.yaml +0 -0
  27. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/extractions/pattern/config.yaml +0 -0
  28. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/helpers/mimetype_filename_extension_list.csv +0 -0
  29. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/helpers/stix_relationship_types.txt +0 -0
  30. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/helpers/tlds.txt +0 -0
  31. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/helpers/windows_registry_key_prefix.txt +0 -0
  32. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/_README.md +0 -0
  33. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/_generate_lookups.py +0 -0
  34. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/attack_pattern.txt +0 -0
  35. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/campaign.txt +0 -0
  36. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/country_iso3166_alpha2.txt +0 -0
  37. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/course_of_action.txt +0 -0
  38. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/disarm_id_v1_6.txt +0 -0
  39. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/disarm_name_v1_6.txt +0 -0
  40. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/extensions.txt +0 -0
  41. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/identity.txt +0 -0
  42. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/infrastructure.txt +0 -0
  43. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/intrusion_set.txt +0 -0
  44. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/malware.txt +0 -0
  45. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_atlas_id_v4_9_0.txt +0 -0
  46. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_atlas_name_v4_9_0.txt +0 -0
  47. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_enterprise_aliases_v18_0.txt +0 -0
  48. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_enterprise_id_v18_0.txt +0 -0
  49. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_enterprise_name_v18_0.txt +0 -0
  50. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_ics_aliases_v18_0.txt +0 -0
  51. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_ics_id_v18_0.txt +0 -0
  52. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_ics_name_v18_0.txt +0 -0
  53. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_mobile_aliases_v18_0.txt +0 -0
  54. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_mobile_id_v18_0.txt +0 -0
  55. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_attack_mobile_name_v18_0.txt +0 -0
  56. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_capec_id_v3_9.txt +0 -0
  57. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_capec_name_v3_9.txt +0 -0
  58. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_cwe_id_v4_18.txt +0 -0
  59. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/mitre_cwe_name_v4_18.txt +0 -0
  60. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/sector_aliases_v1_0.txt +0 -0
  61. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/threat_actor.txt +0 -0
  62. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/tld.txt +0 -0
  63. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/lookups/tool.txt +0 -0
  64. {txt2stix-1.1.12 → txt2stix-1.1.14}/includes/tests/test_cases.yaml +0 -0
  65. {txt2stix-1.1.12 → txt2stix-1.1.14}/requirements.txt +0 -0
  66. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/README.md +0 -0
  67. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_country.txt +0 -0
  68. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_attack_enterprise.txt +0 -0
  69. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_attack_ics.txt +0 -0
  70. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_attack_mobile.txt +0 -0
  71. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_capec.txt +0 -0
  72. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/ai_mitre_cwe.txt +0 -0
  73. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/all_cases.txt +0 -0
  74. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_autonomous_system_number.txt +0 -0
  75. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_all.txt +0 -0
  76. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_amex.txt +0 -0
  77. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_diners.txt +0 -0
  78. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_discover.txt +0 -0
  79. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_jcb.txt +0 -0
  80. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_mastercard.txt +0 -0
  81. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_union_pay.txt +0 -0
  82. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_bank_card_visa.txt +0 -0
  83. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_country_alpha2.txt +0 -0
  84. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cpe_uri.txt +0 -0
  85. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_btc_transaction.txt +0 -0
  86. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_btc_wallet.txt +0 -0
  87. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_eth_transaction.txt +0 -0
  88. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_eth_wallet.txt +0 -0
  89. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_xmr_transaction.txt +0 -0
  90. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cryptocurrency_xmr_wallet.txt +0 -0
  91. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_cve_id.txt +0 -0
  92. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_directory_unix.txt +0 -0
  93. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_directory_unix_file.txt +0 -0
  94. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_directory_windows.txt +0 -0
  95. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_directory_windows_with_file.txt +0 -0
  96. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_disarm.txt +0 -0
  97. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_disarm_name.txt +0 -0
  98. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_domain_name_only.txt +0 -0
  99. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_domain_name_subdomain.txt +0 -0
  100. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_email_address.txt +0 -0
  101. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_md5.txt +0 -0
  102. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_1.txt +0 -0
  103. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_224.txt +0 -0
  104. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_256.txt +0 -0
  105. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_384.txt +0 -0
  106. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_hash_sha_512.txt +0 -0
  107. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_file_name.txt +0 -0
  108. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name.txt +0 -0
  109. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name_file.txt +0 -0
  110. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name_path.txt +0 -0
  111. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name_subdomain.txt +0 -0
  112. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_host_name_url.txt +0 -0
  113. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_iban_number.txt +0 -0
  114. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv4_address_cidr.txt +0 -0
  115. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv4_address_only.txt +0 -0
  116. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv4_address_port.txt +0 -0
  117. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv6_address_cidr.txt +0 -0
  118. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv6_address_only.txt +0 -0
  119. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_ipv6_address_port.txt +0 -0
  120. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mac_address.txt +0 -0
  121. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_atlas.txt +0 -0
  122. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_atlas_name.txt +0 -0
  123. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_enterprise.txt +0 -0
  124. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_enterprise_aliases.txt +0 -0
  125. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_enterprise_name.txt +0 -0
  126. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_ics.txt +0 -0
  127. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_ics_aliases.txt +0 -0
  128. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_ics_name.txt +0 -0
  129. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_mobile.txt +0 -0
  130. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_mobile_aliases.txt +0 -0
  131. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_attack_mobile_name.txt +0 -0
  132. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_capec.txt +0 -0
  133. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_capec_name.txt +0 -0
  134. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_cwe.txt +0 -0
  135. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_mitre_cwe_name.txt +0 -0
  136. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_phone_number.txt +0 -0
  137. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_sector_aliases.txt +0 -0
  138. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_url.txt +0 -0
  139. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_url_file.txt +0 -0
  140. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_url_path.txt +0 -0
  141. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_user_agent.txt +0 -0
  142. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/generic_windows_registry_key.txt +0 -0
  143. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_attack_pattern.txt +0 -0
  144. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_campaign.txt +0 -0
  145. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_course_of_action.txt +0 -0
  146. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_identity.txt +0 -0
  147. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_infrastructure.txt +0 -0
  148. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_intrusion_set.txt +0 -0
  149. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_malware.txt +0 -0
  150. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_threat_actor.txt +0 -0
  151. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/extraction_types/lookup_tool.txt +0 -0
  152. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/ai_index_position.txt +0 -0
  153. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/attack_flow_demo.txt +0 -0
  154. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/attack_navigator_demo.txt +0 -0
  155. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/bad_vulmatch_lookups.txt +0 -0
  156. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/basic_relationship.txt +0 -0
  157. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/char_length_too_long.txt +0 -0
  158. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/descriptive_for_ai_relationships_1.txt +0 -0
  159. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/disarm_demo.txt +0 -0
  160. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/embedded_img_ignore.txt +0 -0
  161. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/embedded_link_ignore.txt +0 -0
  162. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/ip1.txt +0 -0
  163. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/ip2.txt +0 -0
  164. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/known_whitelist_match.txt +0 -0
  165. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/mitre_attack_enterprise_ai_demo.txt +0 -0
  166. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/mitre_attack_enterprise_lookup_demo.txt +0 -0
  167. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/mixed_extractions.txt +0 -0
  168. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/not_security_content.txt +0 -0
  169. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/test_ai_hash_error_with_stix2_lib.txt +0 -0
  170. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/test_aliases.txt +0 -0
  171. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/test_extraction_boundary.txt +0 -0
  172. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/manually_generated_reports/test_extraction_escapes.txt +0 -0
  173. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/APT28-Center-of-Storm-2017.txt +0 -0
  174. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/Bitdefender-Labs-Report-X-creat6958-en-EN.txt +0 -0
  175. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/FireEyeAPT39.txt +0 -0
  176. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/France_CERT_APT31_Pakdoor_TLPWHITE.txt +0 -0
  177. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/Group-IB_Ransomware_Uncovered_whitepaper_eng.txt +0 -0
  178. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/JOINT_CSA_HUNTING_RU_INTEL_SNAKE_MALWARE_20230509.txt +0 -0
  179. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/TA22-0126-QAKBOT-analysis-TLP-GREEN.txt +0 -0
  180. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/dinners_card.txt +0 -0
  181. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/mandiant-apt1.txt +0 -0
  182. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/data/real_intel_reports/mykings_report_final.txt +0 -0
  183. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-ai-relationships.md +0 -0
  184. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-extraction-type-ai.md +0 -0
  185. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-extraction-type-lookup.md +0 -0
  186. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-extraction-type-pattern.md +0 -0
  187. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/manual-tests/cases-standard-tests.md +0 -0
  188. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/scripts/generate_simple_extraction_test_cases_txt_files.py +0 -0
  189. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/__init__.py +0 -0
  190. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_attack_flow.py +0 -0
  191. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_bundler.py +0 -0
  192. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_extractors.py +0 -0
  193. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_lookups.py +0 -0
  194. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_retriever.py +0 -0
  195. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/test_utils.py +0 -0
  196. {txt2stix-1.1.12 → txt2stix-1.1.14}/tests/src/utils.py +0 -0
  197. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/__init__.py +0 -0
  198. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/anthropic.py +0 -0
  199. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/deepseek.py +0 -0
  200. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/gemini.py +0 -0
  201. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/openai.py +0 -0
  202. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/openrouter.py +0 -0
  203. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/ai_extractor/prompts.py +0 -0
  204. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/common.py +0 -0
  205. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/credential_checker.py +0 -0
  206. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/extractions.py +0 -0
  207. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/lookups.py +0 -0
  208. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/__init__.py +0 -0
  209. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/__init__.py +0 -0
  210. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/base_extractor.py +0 -0
  211. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/README.md +0 -0
  212. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/__init__.py +0 -0
  213. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/amex_card_extractor.py +0 -0
  214. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/diners_card_extractor.py +0 -0
  215. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/discover_card_extractor.py +0 -0
  216. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/jcb_card_extractor.py +0 -0
  217. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/master_card_extractor.py +0 -0
  218. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/union_card_extractor.py +0 -0
  219. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/card/visa_card_extractor.py +0 -0
  220. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/crypto/__init__.py +0 -0
  221. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/crypto/btc_extractor.py +0 -0
  222. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/__init__.py +0 -0
  223. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/unix_directory_extractor.py +0 -0
  224. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/unix_file_path_extractor.py +0 -0
  225. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/windows_directory_path_extractor.py +0 -0
  226. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/directory/windows_file_path_extractor.py +0 -0
  227. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/domain/__init__.py +0 -0
  228. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/domain/domain_extractor.py +0 -0
  229. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/domain/hostname_extractor.py +0 -0
  230. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/domain/sub_domain_extractor.py +0 -0
  231. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/__init__.py +0 -0
  232. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/md5_extractor.py +0 -0
  233. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha1_extractor.py +0 -0
  234. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha224_extractor.py +0 -0
  235. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha2_256_exactor.py +0 -0
  236. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha2_512_exactor.py +0 -0
  237. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha3_256_exactor.py +0 -0
  238. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/hashes/sha3_512_exactor.py +0 -0
  239. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/helper.py +0 -0
  240. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/__init__.py +0 -0
  241. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv4_cidr_extractor.py +0 -0
  242. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv4_extractor.py +0 -0
  243. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv4_port_extractor.py +0 -0
  244. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv6_cidr_extractor.py +0 -0
  245. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv6_extractor.py +0 -0
  246. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/ip/ipv6_port_extractor.py +0 -0
  247. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/__init__.py +0 -0
  248. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/asn_extractor.py +0 -0
  249. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/cpe_extractor.py +0 -0
  250. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/cve_extractor.py +0 -0
  251. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/email_extractor.py +0 -0
  252. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/filename_extractor.py +0 -0
  253. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/iban_extractor.py +0 -0
  254. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/mac_address_extractor.py +0 -0
  255. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/phonenumber_extractor.py +0 -0
  256. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/user_agent_extractor.py +0 -0
  257. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/others/windows_registry_key_extractor.py +0 -0
  258. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/url/__init__.py +0 -0
  259. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/url/url_extractor.py +0 -0
  260. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/url/url_file_extractor.py +0 -0
  261. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/pattern/extractors/url/url_path_extractor.py +0 -0
  262. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/retriever.py +0 -0
  263. {txt2stix-1.1.12 → txt2stix-1.1.14}/txt2stix/stix.py +0 -0
  264. {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.12
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.12"
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
- extract_all,
35
- extract_relationships_with_ai,
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 test_extract_all():
306
- bundler = MagicMock()
307
- with(
308
- patch('txt2stix.lookups.extract_all') as mock_lookup__extract_all,
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 = ['lookup1', 'lookup2']
312
- mock_pattern__extract_all.return_value = ['pattern1', 'pattern2']
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
- ## test pattern and lookup
315
- all_extracts = extract_all(bundler, dict(lookup=dict(a=1), pattern=dict(b=2)), '')
316
- bundler.process_observables.assert_called()
317
- bundler.process_observables.assert_any_call(['lookup1', 'lookup2'])
318
- bundler.process_observables.assert_any_call(['pattern1', 'pattern2'])
319
- assert all_extracts == dict(lookup=['lookup1', 'lookup2'], pattern=['pattern1', 'pattern2'])
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('ex1', ['ai0']),
324
- named_ai_extractor_mock('ai2', ['ai3', 'ai9']),
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 test_extract_relationships_with_ai():
339
- mock_bundler = MagicMock()
324
+ def test_extract_relationships():
340
325
  text = "TEXT_CONTENT"
341
- all_extracts = {'lookup': [1, 2], 'ai': [3, 4]}
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 = {'relationships': [1, 2]}
344
- relationships = extract_relationships_with_ai(mock_bundler, text, all_extracts, mock_ai_session)
345
- mock_ai_session.extract_relationships.assert_called_once_with(text, [1, 2, 3, 4], RELATIONSHIP_TYPES)
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 extract_relationships_with_ai(mock_bundler, text, all_extracts, mock_ai_session) == None
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 test_attack_flow_or_nav(mock_extract_attack_flow, subtests):
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.extract_relationships_with_ai')
203
- def test_relationship_mode(mock_extract_relationships_with_ai, subtests):
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
- mock_extract_relationships_with_ai.return_value = []
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
- mock_extract_relationships_with_ai.assert_not_called()
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
- mock_extract_relationships_with_ai.assert_called_once()
215
+ mock_extract_relationships.assert_called_once()
216
216
  assert retval.relationships != None, "extract_relationships_with_ai should run"
@@ -1,6 +1,5 @@
1
1
  from txt2stix import extractions
2
2
  from .bundler import txt2stixBundler
3
- from .txt2stix import extract_all
4
3
  from pathlib import Path
5
4
 
6
5
  INCLUDES_PATH = None
@@ -104,4 +104,7 @@ class BaseAIExtractor():
104
104
 
105
105
  def _check_credential(self):
106
106
  self.llm.complete("say 'hi'")
107
- return True
107
+ return True
108
+
109
+ def __str__(self):
110
+ return self.extractor_name
@@ -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": indicator["created"],
818
- "modified": indicator["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"],