voidaccess 1.8.1__tar.gz → 1.8.2__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 (225) hide show
  1. {voidaccess-1.8.1/voidaccess.egg-info → voidaccess-1.8.2}/PKG-INFO +1 -1
  2. {voidaccess-1.8.1 → voidaccess-1.8.2}/export/ioc_package.py +1 -1
  3. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/pipeline.py +17 -0
  4. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/relationship_extract.py +85 -1
  5. {voidaccess-1.8.1 → voidaccess-1.8.2}/pyproject.toml +1 -1
  6. voidaccess-1.8.2/tests/test_relationship_extract.py +74 -0
  7. {voidaccess-1.8.1 → voidaccess-1.8.2/voidaccess.egg-info}/PKG-INFO +1 -1
  8. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/SOURCES.txt +1 -0
  9. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/__init__.py +1 -1
  10. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/investigate.py +23 -1
  11. {voidaccess-1.8.1 → voidaccess-1.8.2}/LICENSE +0 -0
  12. {voidaccess-1.8.1 → voidaccess-1.8.2}/README.md +0 -0
  13. {voidaccess-1.8.1 → voidaccess-1.8.2}/analysis/__init__.py +0 -0
  14. {voidaccess-1.8.1 → voidaccess-1.8.2}/analysis/opsec.py +0 -0
  15. {voidaccess-1.8.1 → voidaccess-1.8.2}/analysis/patterns.py +0 -0
  16. {voidaccess-1.8.1 → voidaccess-1.8.2}/analysis/temporal.py +0 -0
  17. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/__init__.py +0 -0
  18. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/auth.py +0 -0
  19. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/errors.py +0 -0
  20. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/main.py +0 -0
  21. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/__init__.py +0 -0
  22. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/actors.py +0 -0
  23. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/admin.py +0 -0
  24. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/auth.py +0 -0
  25. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/entities.py +0 -0
  26. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/export.py +0 -0
  27. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/investigations.py +0 -0
  28. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/monitors.py +0 -0
  29. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/search.py +0 -0
  30. {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/settings.py +0 -0
  31. {voidaccess-1.8.1 → voidaccess-1.8.2}/auth/__init__.py +0 -0
  32. {voidaccess-1.8.1 → voidaccess-1.8.2}/auth/token_blacklist.py +0 -0
  33. {voidaccess-1.8.1 → voidaccess-1.8.2}/config.py +0 -0
  34. {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/__init__.py +0 -0
  35. {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/dedup.py +0 -0
  36. {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/frontier.py +0 -0
  37. {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/spider.py +0 -0
  38. {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/utils.py +0 -0
  39. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/__init__.py +0 -0
  40. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/__init__.py +0 -0
  41. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/env.py +0 -0
  42. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0001_initial_schema.py +0 -0
  43. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0002_add_investigation_status_column.py +0 -0
  44. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0002_add_missing_tables.py +0 -0
  45. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0003_add_canonical_value_and_entity_links.py +0 -0
  46. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0004_add_page_posted_at.py +0 -0
  47. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0005_add_extraction_method.py +0 -0
  48. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0006_add_monitor_alerts.py +0 -0
  49. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0007_add_actor_style_profiles.py +0 -0
  50. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0008_add_users_table.py +0 -0
  51. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0009_add_investigation_id_to_relationships.py +0 -0
  52. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0010_add_composite_index_entity_relationships.py +0 -0
  53. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0011_add_page_extraction_cache.py +0 -0
  54. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0013_add_graph_status.py +0 -0
  55. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0015_add_progress_fields.py +0 -0
  56. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0016_backfill_graph_status.py +0 -0
  57. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0017_add_user_api_keys.py +0 -0
  58. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0018_add_user_id_to_investigations.py +0 -0
  59. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0019_add_content_safety_log.py +0 -0
  60. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0020_add_entity_source_tracking.py +0 -0
  61. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0021_add_search_engine_stats.py +0 -0
  62. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0022_add_actor_profiles.py +0 -0
  63. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0023_add_investigation_metadata.py +0 -0
  64. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0024_add_investigation_step_metrics.py +0 -0
  65. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0025_add_cancellation_requested.py +0 -0
  66. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/models.py +0 -0
  67. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/queries.py +0 -0
  68. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/search_engine_stats.py +0 -0
  69. {voidaccess-1.8.1 → voidaccess-1.8.2}/db/session.py +0 -0
  70. {voidaccess-1.8.1 → voidaccess-1.8.2}/export/__init__.py +0 -0
  71. {voidaccess-1.8.1 → voidaccess-1.8.2}/export/misp.py +0 -0
  72. {voidaccess-1.8.1 → voidaccess-1.8.2}/export/sigma.py +0 -0
  73. {voidaccess-1.8.1 → voidaccess-1.8.2}/export/snort_export.py +0 -0
  74. {voidaccess-1.8.1 → voidaccess-1.8.2}/export/stix.py +0 -0
  75. {voidaccess-1.8.1 → voidaccess-1.8.2}/export/yara_export.py +0 -0
  76. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/__init__.py +0 -0
  77. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/confidence.py +0 -0
  78. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/entity_shape.py +0 -0
  79. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/gazetteer.py +0 -0
  80. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/llm_extract.py +0 -0
  81. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/ner.py +0 -0
  82. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/normalizer.py +0 -0
  83. {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/regex_patterns.py +0 -0
  84. {voidaccess-1.8.1 → voidaccess-1.8.2}/fingerprint/__init__.py +0 -0
  85. {voidaccess-1.8.1 → voidaccess-1.8.2}/fingerprint/calibration.py +0 -0
  86. {voidaccess-1.8.1 → voidaccess-1.8.2}/fingerprint/profiler.py +0 -0
  87. {voidaccess-1.8.1 → voidaccess-1.8.2}/fingerprint/stylometry.py +0 -0
  88. {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/__init__.py +0 -0
  89. {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/builder.py +0 -0
  90. {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/export.py +0 -0
  91. {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/model.py +0 -0
  92. {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/queries.py +0 -0
  93. {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/visualize.py +0 -0
  94. {voidaccess-1.8.1 → voidaccess-1.8.2}/i18n/__init__.py +0 -0
  95. {voidaccess-1.8.1 → voidaccess-1.8.2}/i18n/detect.py +0 -0
  96. {voidaccess-1.8.1 → voidaccess-1.8.2}/i18n/query_expand.py +0 -0
  97. {voidaccess-1.8.1 → voidaccess-1.8.2}/i18n/translate.py +0 -0
  98. {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/__init__.py +0 -0
  99. {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/_db.py +0 -0
  100. {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/alerts.py +0 -0
  101. {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/config.py +0 -0
  102. {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/diff.py +0 -0
  103. {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/jobs.py +0 -0
  104. {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/scheduler.py +0 -0
  105. {voidaccess-1.8.1 → voidaccess-1.8.2}/scraper/__init__.py +0 -0
  106. {voidaccess-1.8.1 → voidaccess-1.8.2}/scraper/scrape.py +0 -0
  107. {voidaccess-1.8.1 → voidaccess-1.8.2}/scraper/scrape_js.py +0 -0
  108. {voidaccess-1.8.1 → voidaccess-1.8.2}/search/__init__.py +0 -0
  109. {voidaccess-1.8.1 → voidaccess-1.8.2}/search/circuit_breaker.py +0 -0
  110. {voidaccess-1.8.1 → voidaccess-1.8.2}/search/engine_catalog.py +0 -0
  111. {voidaccess-1.8.1 → voidaccess-1.8.2}/search/query_builder.py +0 -0
  112. {voidaccess-1.8.1 → voidaccess-1.8.2}/search/search.py +0 -0
  113. {voidaccess-1.8.1 → voidaccess-1.8.2}/setup.cfg +0 -0
  114. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/__init__.py +0 -0
  115. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/actor_profiles.py +0 -0
  116. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/blockchain.py +0 -0
  117. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/breach_lookup.py +0 -0
  118. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/cache.py +0 -0
  119. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/cisa.py +0 -0
  120. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/data/__init__.py +0 -0
  121. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/data/onion_seeds.json +0 -0
  122. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/dns_enrichment.py +0 -0
  123. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/domain_reputation.py +0 -0
  124. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/email_reputation.py +0 -0
  125. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/engines.py +0 -0
  126. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/enrichment.py +0 -0
  127. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/github_scraper.py +0 -0
  128. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/gitlab_scraper.py +0 -0
  129. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/hash_reputation.py +0 -0
  130. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/historical_intel.py +0 -0
  131. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/infostealer.py +0 -0
  132. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/ip_reputation.py +0 -0
  133. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/nvd.py +0 -0
  134. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/paste_scraper.py +0 -0
  135. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/pastes.py +0 -0
  136. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/proxy_client.py +0 -0
  137. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/rss_scraper.py +0 -0
  138. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/seed_manager.py +0 -0
  139. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/seeds.py +0 -0
  140. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/shodan.py +0 -0
  141. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/telegram.py +0 -0
  142. {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/virustotal.py +0 -0
  143. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_analysis_opsec.py +0 -0
  144. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_analysis_stylometry.py +0 -0
  145. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_analysis_temporal.py +0 -0
  146. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_api.py +0 -0
  147. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_api_monitors.py +0 -0
  148. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_blockchain.py +0 -0
  149. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_breach_lookup.py +0 -0
  150. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_cli_proxy_config.py +0 -0
  151. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_confidence_calibration.py +0 -0
  152. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_config.py +0 -0
  153. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_crawler.py +0 -0
  154. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_db.py +0 -0
  155. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_dns_enrichment.py +0 -0
  156. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_domain_reputation.py +0 -0
  157. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_email_reputation.py +0 -0
  158. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_extractor_llm_pipeline.py +0 -0
  159. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_filter_e2e_manual.py +0 -0
  160. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_filter_parser_manual.py +0 -0
  161. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_fingerprint.py +0 -0
  162. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_github_scraper.py +0 -0
  163. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_gitlab_scraper.py +0 -0
  164. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_graph.py +0 -0
  165. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_graph_integration.py +0 -0
  166. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_hash_reputation.py +0 -0
  167. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_i18n.py +0 -0
  168. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_infostealer.py +0 -0
  169. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_ip_reputation.py +0 -0
  170. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_llm.py +0 -0
  171. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_llm_utils.py +0 -0
  172. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_model_singleton.py +0 -0
  173. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_monitor.py +0 -0
  174. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_monitor_integration.py +0 -0
  175. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_nvd.py +0 -0
  176. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_pagination.py +0 -0
  177. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_paste_scraper.py +0 -0
  178. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_proxy_client.py +0 -0
  179. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_ransomlook.py +0 -0
  180. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_regex_patterns.py +0 -0
  181. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_rss_scraper.py +0 -0
  182. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_scrape_js.py +0 -0
  183. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_settings.py +0 -0
  184. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_snort_export.py +0 -0
  185. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_sources.py +0 -0
  186. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_sources_enrichment_new.py +0 -0
  187. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_stix_export.py +0 -0
  188. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_vector.py +0 -0
  189. {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_yara_export.py +0 -0
  190. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/__init__.py +0 -0
  191. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/async_utils.py +0 -0
  192. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/content_dedup.py +0 -0
  193. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/content_safety.py +0 -0
  194. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/defang.py +0 -0
  195. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/encryption.py +0 -0
  196. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/enrichment_cache.py +0 -0
  197. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/investigation_metrics.py +0 -0
  198. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/ioc_freshness.py +0 -0
  199. {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/user_keys.py +0 -0
  200. {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/__init__.py +0 -0
  201. {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/embedder.py +0 -0
  202. {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/model_singleton.py +0 -0
  203. {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/search.py +0 -0
  204. {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/store.py +0 -0
  205. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess/__init__.py +0 -0
  206. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess/config.py +0 -0
  207. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess/llm.py +0 -0
  208. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess/llm_utils.py +0 -0
  209. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/dependency_links.txt +0 -0
  210. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/entry_points.txt +0 -0
  211. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/requires.txt +0 -0
  212. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/top_level.txt +0 -0
  213. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/adapters/__init__.py +0 -0
  214. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/adapters/sqlite.py +0 -0
  215. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/browser.py +0 -0
  216. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/__init__.py +0 -0
  217. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/actors.py +0 -0
  218. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/configure.py +0 -0
  219. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/enrich.py +0 -0
  220. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/export.py +0 -0
  221. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/show.py +0 -0
  222. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/config.py +0 -0
  223. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/display.py +0 -0
  224. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/main.py +0 -0
  225. {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/tor_detect.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidaccess
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: Dark web OSINT CLI — automated threat intelligence from query to report
5
5
  Author: VoidAccess
6
6
  License-Expression: MIT
@@ -54,7 +54,7 @@ logger = logging.getLogger(__name__)
54
54
  # ---------------------------------------------------------------------------
55
55
 
56
56
  PACKAGE_FORMAT = "voidaccess-ioc-v1"
57
- PACKAGE_VERSION = "1.8.1"
57
+ PACKAGE_VERSION = "1.8.2"
58
58
  SNORT_SID_BASE = 9000001 # 9xxxxxx reserved for VoidAccess-generated rules
59
59
 
60
60
  # Map extractor upper-case entity types to the per-file bucket they belong to.
@@ -574,6 +574,23 @@ async def extract_entities_from_pages(
574
574
  )
575
575
  page_id_by_url[_url] = _p.get("page_id")
576
576
 
577
+ # Some clearnet/side-source pages are materialized in the DB
578
+ # during entity persistence, after the CLI's initial page-id
579
+ # lookup. Resolve those URLs again before relationship
580
+ # extraction so provenance is never lost to that ordering.
581
+ missing_urls = [url for url, page_id in page_id_by_url.items() if page_id is None]
582
+ if missing_urls:
583
+ from db.models import Page # noqa: PLC0415
584
+
585
+ with get_session() as _page_session:
586
+ persisted_pages = (
587
+ _page_session.query(Page)
588
+ .filter(Page.url.in_(missing_urls))
589
+ .all()
590
+ )
591
+ for _page in persisted_pages:
592
+ page_id_by_url[_page.url] = _page.id
593
+
577
594
  max_rel_pages = int(
578
595
  getattr(_config, "MAX_REL_PAGES_PER_INV", 10) or 10
579
596
  )
@@ -76,6 +76,77 @@ _PASSIVE_REL_VOCAB: dict[str, str] = {
76
76
  "controlled_by": "CONTROLS",
77
77
  }
78
78
 
79
+ # Endpoint-type validation is deliberately kept in the relationship extractor
80
+ # so the prompt and the independent post-LLM gate share one source of truth.
81
+ # The sets are intentionally permissive for the project's current normalized
82
+ # entity vocabulary; unknown types are not accepted for typed edges.
83
+ _RELATIONSHIP_TYPE_COMPATIBILITY: dict[str, tuple[set[str], set[str]]] = {
84
+ "USES": (
85
+ {"RANSOMWARE_GROUP", "THREAT_ACTOR", "THREAT_ACTOR_HANDLE"},
86
+ {"MALWARE_FAMILY", "MALWARE", "TOOL", "SOFTWARE"},
87
+ ),
88
+ "DROPS": (
89
+ {"MALWARE_FAMILY", "MALWARE"},
90
+ {
91
+ "MALWARE_FAMILY", "MALWARE", "FILE_HASH_MD5", "FILE_HASH_SHA1",
92
+ "FILE_HASH_SHA256",
93
+ },
94
+ ),
95
+ "CONTROLS": (
96
+ {"RANSOMWARE_GROUP", "THREAT_ACTOR", "THREAT_ACTOR_HANDLE"},
97
+ {
98
+ "CRYPTO_WALLET", "BITCOIN_ADDRESS", "MONERO_ADDRESS", "ETH_ADDRESS",
99
+ "ETHEREUM_ADDRESS", "LITECOIN_ADDRESS", "ZCASH_ADDRESS",
100
+ "DOGECOIN_ADDRESS", "XRP_ADDRESS", "SOLANA_ADDRESS", "TRON_ADDRESS",
101
+ "BITCOIN_CASH_ADDRESS", "DASH_ADDRESS", "IP_ADDRESS", "IPV6_ADDRESS",
102
+ "DOMAIN", "ENS_DOMAIN", "ONION_URL",
103
+ },
104
+ ),
105
+ "TARGETS": (
106
+ {"RANSOMWARE_GROUP", "THREAT_ACTOR", "THREAT_ACTOR_HANDLE", "MALWARE_FAMILY", "MALWARE"},
107
+ {"ORGANIZATION_NAME"},
108
+ ),
109
+ "EXPLOITS": (
110
+ {"RANSOMWARE_GROUP", "THREAT_ACTOR", "THREAT_ACTOR_HANDLE", "MALWARE_FAMILY", "MALWARE"},
111
+ {"CVE", "CVE_NUMBER", "EXPLOIT_DB_ID"},
112
+ ),
113
+ "COMMUNICATES_WITH": (
114
+ {"RANSOMWARE_GROUP", "THREAT_ACTOR", "THREAT_ACTOR_HANDLE", "MALWARE_FAMILY", "MALWARE"},
115
+ {
116
+ "IP_ADDRESS", "IPV6_ADDRESS", "DOMAIN", "ENS_DOMAIN", "ONION_URL",
117
+ "TELEGRAM_HANDLE", "DISCORD_HANDLE", "XMPP_JID", "TOX_ID",
118
+ "MATRIX_HANDLE", "WIRE_HANDLE", "ICQ_NUMBER", "WICKR_ID",
119
+ "PHONE_NUMBER", "EMAIL_ADDRESS",
120
+ },
121
+ ),
122
+ }
123
+
124
+
125
+ def _entity_type(entity: dict) -> str:
126
+ return str(entity.get("type", "") or "").strip().upper()
127
+
128
+
129
+ def _is_compatible_relationship(rel_type: str, source: dict, target: dict) -> bool:
130
+ """Validate endpoint types independently of the LLM's claimed verb."""
131
+ allowed = _RELATIONSHIP_TYPE_COMPATIBILITY.get(rel_type)
132
+ if allowed is None:
133
+ return False
134
+ source_types, target_types = allowed
135
+ return _entity_type(source) in source_types and _entity_type(target) in target_types
136
+
137
+
138
+ def _compatibility_guide() -> str:
139
+ lines = []
140
+ for rel_type, (sources, targets) in _RELATIONSHIP_TYPE_COMPATIBILITY.items():
141
+ lines.append(
142
+ f"- {rel_type}: source {{{', '.join(sorted(sources))}}} -> "
143
+ f"target {{{', '.join(sorted(targets))}}}"
144
+ )
145
+ return "\n".join(lines)
146
+
147
+
148
+ _COMPATIBILITY_GUIDE = _compatibility_guide()
149
+
79
150
  _PROMPT_TEMPLATE = (
80
151
  "You are a threat intelligence analyst. Below is a numbered list of "
81
152
  "entities that were already extracted from a single dark-web page, "
@@ -100,6 +171,9 @@ _PROMPT_TEMPLATE = (
100
171
  "Plain co-occurrence is captured elsewhere — omit pairs that have no "
101
172
  "specific relationship.\n"
102
173
  "- Do not output any text outside the JSON object.\n\n"
174
+ "Entity-type compatibility guide (source -> target):\n"
175
+ "{compatibility_guide}\n"
176
+ "Only emit a relationship when BOTH endpoint types match this guide.\n\n"
103
177
  "Entities:\n{entities}\n\n"
104
178
  "Content:\n{content}"
105
179
  )
@@ -216,7 +290,11 @@ async def extract_relationships_for_page(
216
290
  for i, e in enumerate(trimmed)
217
291
  )
218
292
  content = page_text[:max_chunk_chars]
219
- prompt = _PROMPT_TEMPLATE.format(entities=entity_lines, content=content)
293
+ prompt = _PROMPT_TEMPLATE.format(
294
+ entities=entity_lines,
295
+ content=content,
296
+ compatibility_guide=_COMPATIBILITY_GUIDE,
297
+ )
220
298
 
221
299
  try:
222
300
  raw = await _invoke_llm(prompt, llm)
@@ -263,6 +341,12 @@ async def extract_relationships_for_page(
263
341
  # its plain co-occurrence edge, generated elsewhere.
264
342
  continue
265
343
 
344
+ if not _is_compatible_relationship(rel_type, a_ent, b_ent):
345
+ # Structural validation is independent of the model's claim. Do
346
+ # not downgrade invalid pairs to co-occurrence: the pair may be
347
+ # spurious even though both entities appeared on the page.
348
+ continue
349
+
266
350
  a_id = a_ent.get("id")
267
351
  b_id = b_ent.get("id")
268
352
  if not a_id or not b_id or a_id == b_id:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "voidaccess"
7
- version = "1.8.1"
7
+ version = "1.8.2"
8
8
  description = "Dark web OSINT CLI — automated threat intelligence from query to report"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,74 @@
1
+ import pytest
2
+
3
+ from extractor.relationship_extract import (
4
+ _COMPATIBILITY_GUIDE,
5
+ extract_relationships_for_page,
6
+ extract_relationships_from_results,
7
+ )
8
+
9
+
10
+ class _Response:
11
+ content = '{"relationships": [{"source": 0, "target": 1, "type": "uses", "confidence": 0.9}, {"source": 0, "target": 2, "type": "uses", "confidence": 0.9}]}'
12
+
13
+
14
+ class _LLM:
15
+ def __init__(self):
16
+ self.prompt = ""
17
+
18
+ def with_config(self, _config):
19
+ return self
20
+
21
+ async def ainvoke(self, prompt):
22
+ self.prompt = prompt
23
+ return _Response()
24
+
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_prompt_contains_shared_compatibility_guide_and_invalid_pair_is_dropped():
28
+ llm = _LLM()
29
+ entities = [
30
+ {"id": "actor", "type": "THREAT_ACTOR_HANDLE", "value": "@actor"},
31
+ {"id": "tool", "type": "TOOL", "value": "Rclone"},
32
+ {"id": "hash", "type": "FILE_HASH_SHA256", "value": "abc"},
33
+ ]
34
+
35
+ relationships = await extract_relationships_for_page("actor uses rclone", entities, llm)
36
+
37
+ assert relationships == [
38
+ {
39
+ "entity_a_id": "actor",
40
+ "entity_b_id": "tool",
41
+ "relationship_type": "USES",
42
+ "confidence": 0.9,
43
+ }
44
+ ]
45
+ assert _COMPATIBILITY_GUIDE in llm.prompt
46
+
47
+
48
+ @pytest.mark.asyncio
49
+ async def test_relationship_provenance_uses_page_manifest_id():
50
+ class _Result:
51
+ page_url = "https://example.test/page"
52
+ entity_ids = ["actor", "org"]
53
+ entities = [
54
+ type("Entity", (), {"entity_type": "THREAT_ACTOR_HANDLE", "value": "@actor", "confidence": 1.0})(),
55
+ type("Entity", (), {"entity_type": "ORGANIZATION_NAME", "value": "Acme", "confidence": 1.0})(),
56
+ ]
57
+
58
+ class _TargetLLM(_LLM):
59
+ async def ainvoke(self, prompt):
60
+ self.prompt = prompt
61
+ return type(
62
+ "Response",
63
+ (),
64
+ {"content": '{"relationships": [{"source": 0, "target": 1, "type": "targets", "confidence": 0.8}]}'},
65
+ )()
66
+
67
+ relationships = await extract_relationships_from_results(
68
+ [_Result()],
69
+ {"https://example.test/page": "actor targets Acme"},
70
+ {"https://example.test/page": "page-123"},
71
+ _TargetLLM(),
72
+ )
73
+
74
+ assert relationships[0]["source_page_id"] == "page-123"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidaccess
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: Dark web OSINT CLI — automated threat intelligence from query to report
5
5
  Author: VoidAccess
6
6
  License-Expression: MIT
@@ -171,6 +171,7 @@ tests/test_paste_scraper.py
171
171
  tests/test_proxy_client.py
172
172
  tests/test_ransomlook.py
173
173
  tests/test_regex_patterns.py
174
+ tests/test_relationship_extract.py
174
175
  tests/test_rss_scraper.py
175
176
  tests/test_scrape_js.py
176
177
  tests/test_settings.py
@@ -1,3 +1,3 @@
1
1
  """voidaccess CLI — dark-web OSINT command-line interface."""
2
2
 
3
- __version__ = "1.8.1"
3
+ __version__ = "1.8.2"
@@ -915,6 +915,17 @@ async def _run_investigation(
915
915
  display.update_step("Finalizing results", "active")
916
916
  final_entities = sqlite_adapter.get_entities(investigation_id)
917
917
  final_relationships = sqlite_adapter.get_relationships(investigation_id)
918
+ # Entity persistence may create Page rows for side-source pages after the
919
+ # initial pre-extraction lookup. Refresh the lightweight manifest so it
920
+ # agrees with relationship provenance and the persisted DB state.
921
+ refreshed_page_ids = await asyncio.to_thread(
922
+ _lookup_page_ids, [p["url"] for p in scraped_pages]
923
+ )
924
+ for page in scraped_pages:
925
+ if page.get("page_id") is None:
926
+ page_id = refreshed_page_ids.get(page["url"])
927
+ if page_id is not None:
928
+ page["page_id"] = page_id
918
929
  sqlite_adapter.update_investigation(
919
930
  investigation_id,
920
931
  {
@@ -958,7 +969,18 @@ async def _run_investigation(
958
969
  "sources_used": sources_used,
959
970
  "entities": final_entities,
960
971
  "relationships": final_relationships,
961
- "pages_scraped": [{"url": p["url"], "source": p.get("source", "")} for p in scraped_pages],
972
+ # Keep the lightweight output manifest needed to resolve relationship
973
+ # provenance. Text is intentionally omitted; the DB remains the
974
+ # source of page content while each relationship can point to a real
975
+ # page in this investigation.
976
+ "pages_scraped": [
977
+ {
978
+ "page_id": p.get("page_id"),
979
+ "url": p["url"],
980
+ "source": p.get("source", ""),
981
+ }
982
+ for p in scraped_pages
983
+ ],
962
984
  "communities": communities,
963
985
  "community_count": len(set(communities.values())) if communities else 0,
964
986
  "seeds_discovered": _discovered_count,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes