tcex 4.0.11.dev2__tar.gz → 4.0.11.dev4__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 (389) hide show
  1. {tcex-4.0.11.dev2/tcex.egg-info → tcex-4.0.11.dev4}/PKG-INFO +1 -1
  2. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/pyproject.toml +1 -1
  3. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/__metadata__.py +1 -1
  4. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/__init__.py +2 -1
  5. tcex-4.0.11.dev4/tcex/api/tc/v2/batch/batch_cleaner.py +451 -0
  6. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/batch_submit.py +50 -131
  7. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_object_abc.py +47 -4
  8. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attribute_types/attribute_type.py +10 -0
  9. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/cases/case.py +10 -2
  10. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/groups/group_filter.py +10 -0
  11. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/intel_requirement.py +3 -0
  12. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owner_roles/owner_role.py +10 -0
  13. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owners/owner.py +8 -0
  14. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/system_roles/system_role.py +10 -0
  15. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/user_groups/user_group.py +10 -0
  16. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/users/user.py +10 -0
  17. tcex-4.0.11.dev4/tcex/api/tc/v3/tags/mitre_tags.py +160 -0
  18. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/naics_tags.py +11 -3
  19. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tasks/task.py +10 -2
  20. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/threat_intelligence/threat_intelligence.py +6 -18
  21. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/app_spec_yml.py +6 -2
  22. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/install_json.py +1 -1
  23. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/install_json_update.py +3 -3
  24. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/install_json_validate.py +2 -2
  25. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/app_spec_yml_model.py +13 -7
  26. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/install_json_model.py +18 -18
  27. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/job_json_model.py +1 -1
  28. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/permutation.py +1 -1
  29. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/mqtt_message_broker.py +4 -6
  30. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/input.py +19 -0
  31. tcex-4.0.11.dev4/tcex/pleb/cached_property_filesystem.py +276 -0
  32. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/code_operation.py +18 -5
  33. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4/tcex.egg-info}/PKG-INFO +1 -1
  34. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex.egg-info/SOURCES.txt +2 -0
  35. tcex-4.0.11.dev2/tcex/api/tc/v3/tags/mitre_tags.py +0 -79
  36. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/LICENSE +0 -0
  37. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/README.md +0 -0
  38. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/setup.cfg +0 -0
  39. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/__init__.py +0 -0
  40. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/__init__.py +0 -0
  41. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/api.py +0 -0
  42. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/__init__.py +0 -0
  43. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/tc.py +0 -0
  44. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/__init__.py +0 -0
  45. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/model/__init__.py +0 -0
  46. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/model/transform_model.py +0 -0
  47. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/ti_predefined_functions.py +0 -0
  48. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/ti_transform.py +0 -0
  49. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/transform_abc.py +0 -0
  50. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/util/__init__.py +0 -0
  51. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/util/threat_intel_util.py +0 -0
  52. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/__init__.py +0 -0
  53. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/association.py +0 -0
  54. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/attribute.py +0 -0
  55. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/batch.py +0 -0
  56. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/batch_writer.py +0 -0
  57. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/group.py +0 -0
  58. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/indicator.py +0 -0
  59. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/security_label.py +0 -0
  60. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/tag.py +0 -0
  61. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/datastore/__init__.py +0 -0
  62. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/datastore/cache.py +0 -0
  63. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/datastore/datastore.py +0 -0
  64. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/metric/__init__.py +0 -0
  65. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/metric/metric.py +0 -0
  66. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/notification/__init__.py +0 -0
  67. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/notification/notification.py +0 -0
  68. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/__init__.py +0 -0
  69. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/__init__.py +0 -0
  70. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/filters.py +0 -0
  71. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/__init__.py +0 -0
  72. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group.py +0 -0
  73. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/__init__.py +0 -0
  74. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/adversary.py +0 -0
  75. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/attack_pattern.py +0 -0
  76. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/campaign.py +0 -0
  77. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/course_of_action.py +0 -0
  78. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/document.py +0 -0
  79. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/email.py +0 -0
  80. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/event.py +0 -0
  81. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/incident.py +0 -0
  82. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/intrusion_set.py +0 -0
  83. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/malware.py +0 -0
  84. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/report.py +0 -0
  85. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/signature.py +0 -0
  86. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/tactic.py +0 -0
  87. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/threat.py +0 -0
  88. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/tool.py +0 -0
  89. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/vulnerability.py +0 -0
  90. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/__init__.py +0 -0
  91. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator.py +0 -0
  92. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/__init__.py +0 -0
  93. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/address.py +0 -0
  94. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/email_address.py +0 -0
  95. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/file.py +0 -0
  96. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/host.py +0 -0
  97. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/url.py +0 -0
  98. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/mapping.py +0 -0
  99. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/owner.py +0 -0
  100. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/security_label.py +0 -0
  101. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/tag.py +0 -0
  102. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/tags.py +0 -0
  103. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/task.py +0 -0
  104. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/victim.py +0 -0
  105. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/tcex_ti_tc_request.py +0 -0
  106. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/threat_intelligence.py +0 -0
  107. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/v2.py +0 -0
  108. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/__init__.py +0 -0
  109. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/__init__.py +0 -0
  110. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen.py +0 -0
  111. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_abc.py +0 -0
  112. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_args_abc.py +0 -0
  113. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_filter_abc.py +0 -0
  114. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_model_abc.py +0 -0
  115. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_options_abc.py +0 -0
  116. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_options_data_filter.py +0 -0
  117. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_options_data_object.py +0 -0
  118. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/model/__init__.py +0 -0
  119. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/model/_filter_model.py +0 -0
  120. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/model/_property_model.py +0 -0
  121. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/adversary_assets/__init__.py +0 -0
  122. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/api_endpoints.py +0 -0
  123. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifact_types/__init__.py +0 -0
  124. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifact_types/artifact_type.py +0 -0
  125. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifact_types/artifact_type_filter.py +0 -0
  126. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifact_types/artifact_type_model.py +0 -0
  127. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifacts/__init__.py +0 -0
  128. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifacts/artifact.py +0 -0
  129. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifacts/artifact_filter.py +0 -0
  130. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifacts/artifact_model.py +0 -0
  131. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attribute_types/__init__.py +0 -0
  132. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attribute_types/attribute_type_filter.py +0 -0
  133. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attribute_types/attribute_type_model.py +0 -0
  134. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attributes/__init__.py +0 -0
  135. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attributes/attribute_model.py +0 -0
  136. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_attributes/__init__.py +0 -0
  137. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_attributes/case_attribute.py +0 -0
  138. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_attributes/case_attribute_filter.py +0 -0
  139. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_attributes/case_attribute_model.py +0 -0
  140. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_management/__init__.py +0 -0
  141. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_management/case_management.py +0 -0
  142. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/cases/__init__.py +0 -0
  143. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/cases/case_filter.py +0 -0
  144. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/cases/case_model.py +0 -0
  145. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/file_actions/__init__.py +0 -0
  146. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/file_actions/file_action_model.py +0 -0
  147. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/file_occurrences/__init__.py +0 -0
  148. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/file_occurrences/file_occurrence_model.py +0 -0
  149. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/filter_abc.py +0 -0
  150. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/group_attributes/__init__.py +0 -0
  151. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/group_attributes/group_attribute.py +0 -0
  152. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/group_attributes/group_attribute_filter.py +0 -0
  153. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/group_attributes/group_attribute_model.py +0 -0
  154. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/groups/__init__.py +0 -0
  155. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/groups/group.py +0 -0
  156. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/groups/group_model.py +0 -0
  157. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicator_attributes/__init__.py +0 -0
  158. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicator_attributes/indicator_attribute.py +0 -0
  159. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicator_attributes/indicator_attribute_filter.py +0 -0
  160. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicator_attributes/indicator_attribute_model.py +0 -0
  161. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicators/__init__.py +0 -0
  162. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicators/indicator.py +0 -0
  163. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicators/indicator_filter.py +0 -0
  164. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicators/indicator_model.py +0 -0
  165. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirement/__init__.py +0 -0
  166. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirement/ir.py +0 -0
  167. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/__init__.py +0 -0
  168. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/categories/__init__.py +0 -0
  169. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/categories/category.py +0 -0
  170. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/categories/category_filter.py +0 -0
  171. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/categories/category_model.py +0 -0
  172. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/intel_req_type_model.py +0 -0
  173. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/intel_requirement_filter.py +0 -0
  174. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/intel_requirement_model.py +0 -0
  175. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/keyword_sections/__init__.py +0 -0
  176. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/keyword_sections/keyword_section_model.py +0 -0
  177. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/results/__init__.py +0 -0
  178. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/results/result.py +0 -0
  179. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/results/result_filter.py +0 -0
  180. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/results/result_model.py +0 -0
  181. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/subtypes/__init__.py +0 -0
  182. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/subtypes/subtype.py +0 -0
  183. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/subtypes/subtype_filter.py +0 -0
  184. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/subtypes/subtype_model.py +0 -0
  185. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/notes/__init__.py +0 -0
  186. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/notes/note.py +0 -0
  187. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/notes/note_filter.py +0 -0
  188. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/notes/note_model.py +0 -0
  189. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/object_abc.py +0 -0
  190. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/object_collection_abc.py +0 -0
  191. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/__init__.py +0 -0
  192. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/assignee_model.py +0 -0
  193. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/assignee_user_group_model.py +0 -0
  194. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/assignee_user_model.py +0 -0
  195. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/exclusion_lists/__init__.py +0 -0
  196. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list.py +0 -0
  197. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list_filter.py +0 -0
  198. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list_model.py +0 -0
  199. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owner_roles/__init__.py +0 -0
  200. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owner_roles/owner_role_filter.py +0 -0
  201. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owner_roles/owner_role_model.py +0 -0
  202. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owners/__init__.py +0 -0
  203. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owners/owner_filter.py +0 -0
  204. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owners/owner_model.py +0 -0
  205. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/security.py +0 -0
  206. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/system_roles/__init__.py +0 -0
  207. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/system_roles/system_role_filter.py +0 -0
  208. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/system_roles/system_role_model.py +0 -0
  209. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/task_assignee_model.py +0 -0
  210. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/user_groups/__init__.py +0 -0
  211. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/user_groups/user_group_filter.py +0 -0
  212. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/user_groups/user_group_model.py +0 -0
  213. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/users/__init__.py +0 -0
  214. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/users/user_filter.py +0 -0
  215. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/users/user_model.py +0 -0
  216. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security_labels/__init__.py +0 -0
  217. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security_labels/security_label.py +0 -0
  218. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security_labels/security_label_filter.py +0 -0
  219. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security_labels/security_label_model.py +0 -0
  220. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/__init__.py +0 -0
  221. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/default_naics_tags.py +0 -0
  222. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/tag.py +0 -0
  223. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/tag_filter.py +0 -0
  224. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/tag_model.py +0 -0
  225. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tasks/__init__.py +0 -0
  226. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tasks/task_filter.py +0 -0
  227. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tasks/task_model.py +0 -0
  228. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/threat_intelligence/__init__.py +0 -0
  229. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tql/__init__.py +0 -0
  230. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tql/tql.py +0 -0
  231. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tql/tql_operator.py +0 -0
  232. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tql/tql_type.py +0 -0
  233. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/v3.py +0 -0
  234. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/v3_model_abc.py +0 -0
  235. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/v3_types.py +0 -0
  236. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_assets/__init__.py +0 -0
  237. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_assets/victim_asset.py +0 -0
  238. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_assets/victim_asset_filter.py +0 -0
  239. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_assets/victim_asset_model.py +0 -0
  240. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_attributes/__init__.py +0 -0
  241. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_attributes/victim_attribute.py +0 -0
  242. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_attributes/victim_attribute_filter.py +0 -0
  243. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_attributes/victim_attribute_model.py +0 -0
  244. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victims/__init__.py +0 -0
  245. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victims/victim.py +0 -0
  246. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victims/victim_filter.py +0 -0
  247. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victims/victim_model.py +0 -0
  248. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_events/__init__.py +0 -0
  249. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_events/workflow_event.py +0 -0
  250. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_events/workflow_event_filter.py +0 -0
  251. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_events/workflow_event_model.py +0 -0
  252. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_templates/__init__.py +0 -0
  253. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_templates/workflow_template.py +0 -0
  254. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_templates/workflow_template_filter.py +0 -0
  255. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_templates/workflow_template_model.py +0 -0
  256. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/__init__.py +0 -0
  257. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/app.py +0 -0
  258. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/__init__.py +0 -0
  259. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/job_json.py +0 -0
  260. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/layout_json.py +0 -0
  261. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/__init__.py +0 -0
  262. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/layout_json_model.py +0 -0
  263. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/tcex_json_model.py +0 -0
  264. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/tcex_json.py +0 -0
  265. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/tcex_json_update.py +0 -0
  266. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/__init__.py +0 -0
  267. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/benchmark.py +0 -0
  268. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/debug.py +0 -0
  269. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/fail_on_output.py +0 -0
  270. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/on_exception.py +0 -0
  271. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/on_success.py +0 -0
  272. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/output.py +0 -0
  273. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/__init__.py +0 -0
  274. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_abc.py +0 -0
  275. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_api.py +0 -0
  276. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_mock.py +0 -0
  277. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_redis.py +0 -0
  278. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_store.py +0 -0
  279. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/redis_client.py +0 -0
  280. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/redis_client_ssl.py +0 -0
  281. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/__init__.py +0 -0
  282. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/advanced_request.py +0 -0
  283. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook.py +0 -0
  284. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook_create.py +0 -0
  285. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook_delete.py +0 -0
  286. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook_output.py +0 -0
  287. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook_read.py +0 -0
  288. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/__init__.py +0 -0
  289. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/api_service.py +0 -0
  290. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/common_service.py +0 -0
  291. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/common_service_trigger.py +0 -0
  292. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/webhook_trigger_service.py +0 -0
  293. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/token/__init__.py +0 -0
  294. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/token/token.py +0 -0
  295. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/exit/__init__.py +0 -0
  296. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/exit/error_code.py +0 -0
  297. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/exit/exit.py +0 -0
  298. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/__init__.py +0 -0
  299. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/__init__.py +0 -0
  300. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/binary.py +0 -0
  301. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/case_management_entity.py +0 -0
  302. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/choice.py +0 -0
  303. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/datetime.py +0 -0
  304. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/edit_choice.py +0 -0
  305. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/exception.py +0 -0
  306. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/group_entity.py +0 -0
  307. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/indicator_entity.py +0 -0
  308. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/integer.py +0 -0
  309. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/ip_address.py +0 -0
  310. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/key_value.py +0 -0
  311. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/sensitive.py +0 -0
  312. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/string.py +0 -0
  313. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/tc_entity.py +0 -0
  314. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/validator.py +0 -0
  315. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/__init__.py +0 -0
  316. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/advanced_request_model.py +0 -0
  317. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/api_model.py +0 -0
  318. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_api_service_model.py +0 -0
  319. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_external_model.py +0 -0
  320. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_feed_api_service_model.py +0 -0
  321. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_organization_model.py +0 -0
  322. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_playbook_model.py +0 -0
  323. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_system_model.py +0 -0
  324. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_trigger_service_model.py +0 -0
  325. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_webhook_trigger_service_model.py +0 -0
  326. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/batch_model.py +0 -0
  327. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/cal_setting_model.py +0 -0
  328. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/cert_model.py +0 -0
  329. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/common_advanced_model.py +0 -0
  330. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/common_model.py +0 -0
  331. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/create_config_model.py +0 -0
  332. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/logging_model.py +0 -0
  333. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/model_map.py +0 -0
  334. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/module_app_model.py +0 -0
  335. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/module_requests_session_model.py +0 -0
  336. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/organization_model.py +0 -0
  337. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/path_model.py +0 -0
  338. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/playbook_common_model.py +0 -0
  339. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/playbook_model.py +0 -0
  340. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/proxy_model.py +0 -0
  341. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/service_model.py +0 -0
  342. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/smtp_setting_model.py +0 -0
  343. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/__init__.py +0 -0
  344. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/api_handler.py +0 -0
  345. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/cache_handler.py +0 -0
  346. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/logger.py +0 -0
  347. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/rotating_file_handler_custom.py +0 -0
  348. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/sensitive_filter.py +0 -0
  349. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/trace_logger.py +0 -0
  350. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/__init__.py +0 -0
  351. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/cached_property.py +0 -0
  352. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/event.py +0 -0
  353. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/exception_thread.py +0 -0
  354. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/jmespath_custom.py +0 -0
  355. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/none_model.py +0 -0
  356. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/proxies.py +0 -0
  357. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/scoped_property.py +0 -0
  358. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/singleton.py +0 -0
  359. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/registry.py +0 -0
  360. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_external/__init__.py +0 -0
  361. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_external/external_session.py +0 -0
  362. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_external/rate_limit_handler.py +0 -0
  363. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_external/requests_external.py +0 -0
  364. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/__init__.py +0 -0
  365. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/auth/__init__.py +0 -0
  366. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/auth/hmac_auth.py +0 -0
  367. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/auth/tc_auth.py +0 -0
  368. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/auth/token_auth.py +0 -0
  369. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/requests_tc.py +0 -0
  370. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/tc_session.py +0 -0
  371. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/tcex.py +0 -0
  372. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/__init__.py +0 -0
  373. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/aes_operation.py +0 -0
  374. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/datetime_operation.py +0 -0
  375. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/file_operation.py +0 -0
  376. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/model/__init__.py +0 -0
  377. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/model/playbook_variable_model.py +0 -0
  378. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/__init__.py +0 -0
  379. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/render.py +0 -0
  380. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/render_panel.py +0 -0
  381. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/render_prompt.py +0 -0
  382. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/render_table.py +0 -0
  383. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/requests_to_curl.py +0 -0
  384. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/string_operation.py +0 -0
  385. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/util.py +0 -0
  386. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/variable.py +0 -0
  387. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex.egg-info/dependency_links.txt +0 -0
  388. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex.egg-info/requires.txt +0 -0
  389. {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tcex
3
- Version: 4.0.11.dev2
3
+ Version: 4.0.11.dev4
4
4
  Summary: ThreatConnect Exchange App Framework
5
5
  Author-email: ThreatConnect <support@threatconnect.com>
6
6
  License-Expression: Apache-2.0
@@ -108,7 +108,7 @@ commit = false
108
108
  # Additional arguments to pass to the git commit command
109
109
  commit_args = "--no-verify"
110
110
  # The current version of the project
111
- current_version = "4.0.11-dev2"
111
+ current_version = "4.0.11-dev4"
112
112
  # The commit message template used when committing version bumps
113
113
  message = "Version updated from {current_version} to {new_version}"
114
114
  # Regular expression for parsing version strings
@@ -1,4 +1,4 @@
1
1
  """TcEx Framework Module"""
2
2
 
3
3
  __license__ = 'Apache-2.0'
4
- __version__ = '4.0.11-dev2'
4
+ __version__ = '4.0.11-dev4'
@@ -2,7 +2,8 @@
2
2
 
3
3
  # first-party
4
4
  from tcex.api.tc.v2.batch.batch import Batch
5
+ from tcex.api.tc.v2.batch.batch_cleaner import BatchCleaner
5
6
  from tcex.api.tc.v2.batch.batch_submit import BatchSubmit
6
7
  from tcex.api.tc.v2.batch.batch_writer import BatchWriter, GroupType, IndicatorType
7
8
 
8
- __all__ = ['Batch', 'BatchSubmit', 'BatchWriter', 'GroupType', 'IndicatorType']
9
+ __all__ = ['Batch', 'BatchCleaner', 'BatchSubmit', 'BatchWriter', 'GroupType', 'IndicatorType']
@@ -0,0 +1,451 @@
1
+ """TcEx Framework Module"""
2
+
3
+ # standard library
4
+ import json
5
+ import logging
6
+ from collections.abc import Callable
7
+ from itertools import chain
8
+
9
+ # first-party
10
+ from tcex.api.tc.v3.tags.mitre_tags import MitreTags
11
+ from tcex.api.tc.v3.tags.naics_tags import NAICSTags
12
+ from tcex.logger.trace_logger import TraceLogger
13
+ from tcex.pleb.cached_property import cached_property
14
+ from tcex.util.datetime_operation import DatetimeOperation
15
+
16
+ # get tcex logger
17
+ _logger: TraceLogger = logging.getLogger(__name__.split('.', maxsplit=1)[0]) # type: ignore
18
+
19
+
20
+ class BatchCleaner:
21
+ """Batch content cleaning configuration and logic.
22
+
23
+ All flags default to False. Enable the desired cleaning steps before calling clean().
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ attribute_types: dict[str, dict],
29
+ mitre_tags: MitreTags,
30
+ *,
31
+ combine_on_filename: bool = False,
32
+ convert_to_mitre_tags: bool = False,
33
+ convert_to_naics_tags: bool = False,
34
+ deduplicate_indicators: bool = False,
35
+ deduplicate_groups: bool = False,
36
+ deduplicate_attributes: bool = False,
37
+ truncate_attributes: bool = False,
38
+ ):
39
+ """Initialize instance properties.
40
+
41
+ Args:
42
+ attribute_types: A dictionary of attribute type config.
43
+ mitre_tags: A MitreTags instance.
44
+ combine_on_filename: Whether to combine items based on filename.
45
+ convert_to_mitre_tags: Whether to convert Mitre tags.
46
+ convert_to_naics_tags: Whether to convert NAICS tags.
47
+ deduplicate_indicators: Whether to deduplicate indicators.
48
+ deduplicate_groups: Whether to deduplicate groups.
49
+ deduplicate_attributes: Whether to deduplicate attributes.
50
+ truncate_attributes: Whether to truncate attributes.
51
+ """
52
+ self.attribute_types = attribute_types
53
+ self.mitre_tags = mitre_tags
54
+
55
+ self.combine_on_filename = combine_on_filename
56
+ self.convert_to_mitre_tags = convert_to_mitre_tags
57
+ self.convert_to_naics_tags = convert_to_naics_tags
58
+ self.deduplicate_indicators = deduplicate_indicators
59
+ self.deduplicate_groups = deduplicate_groups
60
+ self.deduplicate_attributes = deduplicate_attributes
61
+ self.truncate_attributes = truncate_attributes
62
+
63
+ def clean(self, content: dict | str) -> dict:
64
+ """Run enabled cleaning steps on content.
65
+
66
+ Pass 1: Deduplicate and combine indicators/groups.
67
+ Pass 2: Clean attributes (truncate/deduplicate) on the result.
68
+
69
+ Args:
70
+ content: The batch content dictionary or JSON string.
71
+
72
+ Returns:
73
+ The cleaned content dictionary.
74
+ """
75
+ content_dict = json.loads(content) if isinstance(content, str) else content
76
+
77
+ # Pass 1: dedup/combine
78
+ content_dict['indicator'] = self._dedup_indicators(content_dict.get('indicator', []))
79
+ if self.deduplicate_groups:
80
+ content_dict['group'] = self._dedup(content_dict.get('group', []), self._group_keys)
81
+
82
+ # Pass 2: attribute cleaning
83
+ if self.deduplicate_attributes or self.truncate_attributes:
84
+ truncated_types: set[str] = set()
85
+ for item in chain(
86
+ content_dict.get('indicator') or [],
87
+ content_dict.get('group') or [],
88
+ ):
89
+ self.clean_attributes(
90
+ item,
91
+ self.attribute_types,
92
+ deduplicate=self.deduplicate_attributes,
93
+ truncate=self.truncate_attributes,
94
+ truncated_types=truncated_types,
95
+ )
96
+
97
+ return content_dict
98
+
99
+ def _dedup_indicators(self, indicators: list[dict]) -> list[dict]:
100
+ """Deduplicate and combine indicators.
101
+
102
+ When deduplicate_indicators is enabled, indicators are matched by
103
+ summary (hash-overlap for File indicators, exact match for others).
104
+
105
+ When combine_on_filename is enabled, File indicators with matching
106
+ fileOccurrence fileName values are also merged.
107
+
108
+ Args:
109
+ indicators: The list of indicator dictionaries.
110
+
111
+ Returns:
112
+ The deduplicated list of indicator dictionaries.
113
+ """
114
+ if not self.deduplicate_indicators and not self.combine_on_filename:
115
+ return indicators
116
+
117
+ return self._dedup(indicators, self._indicator_keys)
118
+
119
+ def _dedup(
120
+ self,
121
+ items: list[dict],
122
+ key_fn: Callable[[dict], set[str]],
123
+ ) -> list[dict]:
124
+ """Deduplicate items using a key function that returns a set of lookup keys.
125
+
126
+ Each item is matched against previously seen keys. If any key from the
127
+ incoming item is already in the index, the item is merged into the
128
+ existing one. After a merge, keys are re-indexed to account for new
129
+ keys added during the merge (e.g. File hash combination).
130
+
131
+ Args:
132
+ items: The list of items to deduplicate.
133
+ key_fn: A callable that takes an item dict and returns a set of
134
+ strings to use as lookup keys. Return an empty set for items
135
+ that should never be deduplicated.
136
+
137
+ Returns:
138
+ The deduplicated list of items.
139
+ """
140
+ result: list[dict] = []
141
+ seen: dict[str, int] = {}
142
+
143
+ for item in items:
144
+ keys = key_fn(item)
145
+ match_idx = next((seen[k] for k in keys if k in seen), None)
146
+ if match_idx is not None:
147
+ self.deduplicate(item, result[match_idx])
148
+ # re-index: merged item may have new keys (e.g. combined file hashes)
149
+ for k in key_fn(result[match_idx]):
150
+ seen[k] = match_idx
151
+ else:
152
+ idx = len(result)
153
+ for k in keys:
154
+ seen[k] = idx
155
+ result.append(item)
156
+
157
+ return result
158
+
159
+ def _indicator_keys(self, item: dict) -> set[str]:
160
+ """Return the set of dedup keys for an indicator.
161
+
162
+ File indicators return individual hashes so that any hash overlap
163
+ triggers a merge. When combine_on_filename is enabled, a prefixed
164
+ filename key is also included for File indicators with a fileOccurrence.
165
+
166
+ All other indicators return the summary as a single-element set.
167
+
168
+ Args:
169
+ item: An indicator dictionary.
170
+
171
+ Returns:
172
+ A set of key strings for dedup lookup.
173
+ """
174
+ if item.get('type') == 'File':
175
+ keys: set[str] = set()
176
+ if self.deduplicate_indicators:
177
+ keys = BatchCleaner._file_hash_set(item.get('summary', ''))
178
+ if self.combine_on_filename:
179
+ file_occ = item.get('fileOccurrence') or []
180
+ if file_occ:
181
+ fname = file_occ[0].get('fileName')
182
+ if fname:
183
+ keys.add(f'filename:{fname.lower()}')
184
+ return keys
185
+ if self.deduplicate_indicators:
186
+ summary = item.get('summary')
187
+ return {summary} if summary else set()
188
+ return set()
189
+
190
+ @staticmethod
191
+ def _group_keys(item: dict) -> set[str]:
192
+ """Return the set of dedup keys for a group.
193
+
194
+ Groups are deduplicated by xid.
195
+
196
+ Args:
197
+ item: A group dictionary.
198
+
199
+ Returns:
200
+ A single-element set containing the xid, or empty set if no xid.
201
+ """
202
+ xid = item.get('xid')
203
+ return {xid} if xid else set()
204
+
205
+ def _normalize_tags(self, tags: list) -> None:
206
+ """Convert tag names to formatted MITRE/NAICS format where applicable.
207
+
208
+ Args:
209
+ tags: A list of tags
210
+ """
211
+ for tag in tags:
212
+ name = tag.get('name')
213
+ if not name:
214
+ continue
215
+
216
+ # try MITRE conversion first
217
+ if self.convert_to_mitre_tags:
218
+ formatted = self.mitre_tags.get_by_id_regex(name)
219
+ if formatted:
220
+ tag['name'] = formatted
221
+ continue
222
+
223
+ # try NAICS conversion
224
+ if self.convert_to_naics_tags:
225
+ formatted = self.naics_tags.get_by_id(name)
226
+ if formatted:
227
+ tag['name'] = formatted
228
+
229
+ @staticmethod
230
+ def auto_truncate_attribute(
231
+ attribute_type: str,
232
+ attribute_value: str,
233
+ attribute_config: dict,
234
+ ellipsis: str = '...',
235
+ ) -> str:
236
+ """Truncate attribute value if it exceeds the maximum length for its type.
237
+
238
+ Args:
239
+ attribute_type: The name of the attribute type.
240
+ attribute_value: The attribute value to potentially truncate.
241
+ attribute_config: Attribute type config dict keyed by name.
242
+ ellipsis: The string to append when truncating. Defaults to '...'.
243
+
244
+ Returns:
245
+ The original value if within limits, or the truncated value with ellipsis.
246
+ """
247
+ config = attribute_config.get(attribute_type)
248
+ if not config:
249
+ return attribute_value
250
+
251
+ max_length = config.get('maxSize')
252
+ if not max_length:
253
+ return attribute_value
254
+
255
+ if not isinstance(attribute_value, str):
256
+ return attribute_value
257
+
258
+ if len(attribute_value) <= max_length:
259
+ return attribute_value
260
+
261
+ if max_length <= len(ellipsis):
262
+ return attribute_value[:max_length]
263
+
264
+ return attribute_value[: max_length - len(ellipsis)] + ellipsis
265
+
266
+ @staticmethod
267
+ def clean_attributes(
268
+ item: dict,
269
+ attribute_types: dict,
270
+ deduplicate: bool = False,
271
+ truncate: bool = False,
272
+ truncated_types: set[str] | None = None,
273
+ ) -> dict:
274
+ """Truncate and/or deduplicate attributes on a single item.
275
+
276
+ Args:
277
+ item: A single group or indicator dictionary.
278
+ attribute_types: Attribute type config dict keyed by name.
279
+ deduplicate: Whether to remove duplicate attributes.
280
+ truncate: Whether to truncate attributes based on maxSize.
281
+ truncated_types: A shared set tracking attribute types that have already
282
+ been logged as truncated, to avoid duplicate log warnings. If None,
283
+ warnings are logged for every truncation.
284
+
285
+ Returns:
286
+ The item with cleaned attributes.
287
+ """
288
+ original_attrs = item.get('attribute') or []
289
+ cleaned_attrs = []
290
+ seen: set[tuple] = set()
291
+
292
+ for attr in original_attrs:
293
+ type_ = attr.get('type')
294
+ value = attr.get('value')
295
+
296
+ if not type_ or value is None or value == '':
297
+ continue
298
+
299
+ if truncate:
300
+ truncated = BatchCleaner.auto_truncate_attribute(type_, value, attribute_types)
301
+ if truncated != value and (truncated_types is None or type_ not in truncated_types):
302
+ _logger.warning(
303
+ f'feature=batch, event=attribute-truncated, attribute-type={type_}'
304
+ )
305
+ if truncated_types is not None:
306
+ truncated_types.add(type_)
307
+ else:
308
+ truncated = value
309
+
310
+ if deduplicate:
311
+ normalized = tuple(
312
+ (k, truncated if k == 'value' else attr.get(k)) for k in sorted(attr.keys())
313
+ )
314
+ if normalized in seen:
315
+ continue
316
+ seen.add(normalized)
317
+
318
+ new_attr = dict(attr)
319
+ new_attr['value'] = truncated
320
+ cleaned_attrs.append(new_attr)
321
+
322
+ item['attribute'] = cleaned_attrs
323
+ return item
324
+
325
+ @staticmethod
326
+ def merge_list_field(existing: dict, incoming: dict, field: str) -> None:
327
+ """Merge a list-of-dicts field from incoming into existing, removing duplicates.
328
+
329
+ Args:
330
+ existing: The target item (modified in place).
331
+ incoming: The source item to merge from.
332
+ field: The field name (e.g. 'tag', 'securityLabel').
333
+ """
334
+ incoming_items = incoming.get(field) or []
335
+ if not incoming_items:
336
+ return
337
+
338
+ existing_items = existing.setdefault(field, [])
339
+ seen = {frozenset(item.items()) for item in existing_items}
340
+ for item in incoming_items:
341
+ key = frozenset(item.items())
342
+ if key not in seen:
343
+ seen.add(key)
344
+ existing_items.append(item)
345
+
346
+ def deduplicate(self, incoming: dict, existing: dict) -> dict:
347
+ """Merge incoming item into existing.
348
+
349
+ For File indicators, summaries are combined via hash merging.
350
+
351
+ Args:
352
+ incoming: The new item.
353
+ existing: The previously seen item to merge into.
354
+
355
+ Returns:
356
+ The merged item (same reference as existing).
357
+ """
358
+ # merge File hash summaries
359
+ if existing.get('type') == 'File' and incoming.get('type') == 'File':
360
+ existing['summary'] = BatchCleaner.combine_file_hashes(
361
+ existing.get('summary', ''), incoming.get('summary', '')
362
+ )
363
+
364
+ # merge associated group xids
365
+ incoming_xids = incoming.get('associatedGroupXid') or []
366
+ if incoming_xids:
367
+ existing_xids = existing.get('associatedGroupXid') or []
368
+ existing['associatedGroupXid'] = list(set(existing_xids + incoming_xids))
369
+
370
+ # normalize existing tags
371
+ existing_tags = existing.get('tag') or []
372
+ if existing_tags:
373
+ self._normalize_tags(existing_tags)
374
+
375
+ # normalize incoming tags
376
+ incoming_tags = incoming.get('tag') or []
377
+ if incoming_tags:
378
+ self._normalize_tags(incoming_tags)
379
+
380
+ # merge metadata
381
+ BatchCleaner.merge_list_field(existing, incoming, 'associatedGroups')
382
+ BatchCleaner.merge_list_field(existing, incoming, 'fileOccurrence')
383
+ BatchCleaner.merge_list_field(existing, incoming, 'securityLabel')
384
+ BatchCleaner.merge_list_field(existing, incoming, 'tag')
385
+
386
+ # keep highest rating/confidence
387
+ for field in ('rating', 'confidence'):
388
+ if field in incoming:
389
+ existing[field] = max(existing.get(field, 0), incoming.get(field, 0))
390
+
391
+ # keep earliest externalDateAdded and latest externalLastModified
392
+ to_dt = DatetimeOperation.any_to_datetime
393
+ date_merge_rules = (
394
+ ('externalDateAdded', min),
395
+ ('externalDateLastModified', max),
396
+ )
397
+ for field, resolve in date_merge_rules:
398
+ if field in incoming:
399
+ existing_val = existing.get(field, incoming[field])
400
+ try:
401
+ existing[field] = resolve(existing_val, incoming[field], key=to_dt)
402
+ except Exception:
403
+ _logger.exception(f'Invalid date specified for {field}')
404
+
405
+ # combine attributes and later the cleaning pass will dedup/truncate
406
+ incoming_attrs = incoming.get('attribute') or []
407
+ if incoming_attrs:
408
+ existing['attribute'] = (existing.get('attribute') or []) + incoming_attrs
409
+
410
+ return existing
411
+
412
+ @staticmethod
413
+ def combine_file_hashes(existing_summary: str, new_summary: str) -> str:
414
+ """Combine two File indicator summaries into a single multi-hash summary.
415
+
416
+ Args:
417
+ existing_summary: The summary from the already-collected indicator.
418
+ new_summary: The summary from the incoming indicator.
419
+
420
+ Returns:
421
+ A combined summary with hashes in md5 : sha1 : sha256 order.
422
+ """
423
+ hash_type_by_len = {32: 'md5', 40: 'sha1', 64: 'sha256'}
424
+
425
+ all_hashes = [h.strip() for h in new_summary.split(' : ')]
426
+ all_hashes.extend(h.strip() for h in existing_summary.split(' : '))
427
+
428
+ hash_values: dict[str, str] = {}
429
+ for hash_value in all_hashes:
430
+ hash_type = hash_type_by_len.get(len(hash_value))
431
+ if hash_type:
432
+ hash_values[hash_type] = hash_value
433
+
434
+ return ' : '.join(hash_values[ht] for ht in ['md5', 'sha1', 'sha256'] if ht in hash_values)
435
+
436
+ @staticmethod
437
+ def _file_hash_set(summary: str) -> set[str]:
438
+ """Extract individual hashes from a File indicator summary.
439
+
440
+ Args:
441
+ summary: A File indicator summary string (e.g. 'md5 : sha1 : sha256').
442
+
443
+ Returns:
444
+ A set of individual hash strings with whitespace stripped.
445
+ """
446
+ return {h.strip() for h in summary.split(' : ') if h.strip()}
447
+
448
+ @cached_property
449
+ def naics_tags(self) -> NAICSTags:
450
+ """NAICS Tags"""
451
+ return NAICSTags()