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.
- {voidaccess-1.8.1/voidaccess.egg-info → voidaccess-1.8.2}/PKG-INFO +1 -1
- {voidaccess-1.8.1 → voidaccess-1.8.2}/export/ioc_package.py +1 -1
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/pipeline.py +17 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/relationship_extract.py +85 -1
- {voidaccess-1.8.1 → voidaccess-1.8.2}/pyproject.toml +1 -1
- voidaccess-1.8.2/tests/test_relationship_extract.py +74 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2/voidaccess.egg-info}/PKG-INFO +1 -1
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/SOURCES.txt +1 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/__init__.py +1 -1
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/investigate.py +23 -1
- {voidaccess-1.8.1 → voidaccess-1.8.2}/LICENSE +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/README.md +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/analysis/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/analysis/opsec.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/analysis/patterns.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/analysis/temporal.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/auth.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/errors.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/main.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/actors.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/admin.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/auth.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/entities.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/export.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/investigations.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/monitors.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/search.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/api/routes/settings.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/auth/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/auth/token_blacklist.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/config.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/dedup.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/frontier.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/spider.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/crawler/utils.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/env.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0001_initial_schema.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0002_add_investigation_status_column.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0002_add_missing_tables.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0003_add_canonical_value_and_entity_links.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0004_add_page_posted_at.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0005_add_extraction_method.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0006_add_monitor_alerts.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0007_add_actor_style_profiles.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0008_add_users_table.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0009_add_investigation_id_to_relationships.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0010_add_composite_index_entity_relationships.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0011_add_page_extraction_cache.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0013_add_graph_status.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0015_add_progress_fields.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0016_backfill_graph_status.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0017_add_user_api_keys.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0018_add_user_id_to_investigations.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0019_add_content_safety_log.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0020_add_entity_source_tracking.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0021_add_search_engine_stats.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0022_add_actor_profiles.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0023_add_investigation_metadata.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0024_add_investigation_step_metrics.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0025_add_cancellation_requested.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/models.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/queries.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/search_engine_stats.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/db/session.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/export/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/export/misp.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/export/sigma.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/export/snort_export.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/export/stix.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/export/yara_export.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/confidence.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/entity_shape.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/gazetteer.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/llm_extract.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/ner.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/normalizer.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/extractor/regex_patterns.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/fingerprint/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/fingerprint/calibration.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/fingerprint/profiler.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/fingerprint/stylometry.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/builder.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/export.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/model.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/queries.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/graph/visualize.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/i18n/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/i18n/detect.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/i18n/query_expand.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/i18n/translate.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/_db.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/alerts.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/config.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/diff.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/jobs.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/monitor/scheduler.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/scraper/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/scraper/scrape.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/scraper/scrape_js.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/search/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/search/circuit_breaker.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/search/engine_catalog.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/search/query_builder.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/search/search.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/setup.cfg +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/actor_profiles.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/blockchain.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/breach_lookup.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/cache.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/cisa.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/data/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/data/onion_seeds.json +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/dns_enrichment.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/domain_reputation.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/email_reputation.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/engines.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/enrichment.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/github_scraper.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/gitlab_scraper.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/hash_reputation.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/historical_intel.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/infostealer.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/ip_reputation.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/nvd.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/paste_scraper.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/pastes.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/proxy_client.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/rss_scraper.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/seed_manager.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/seeds.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/shodan.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/telegram.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/sources/virustotal.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_analysis_opsec.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_analysis_stylometry.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_analysis_temporal.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_api.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_api_monitors.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_blockchain.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_breach_lookup.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_cli_proxy_config.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_confidence_calibration.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_config.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_crawler.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_db.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_dns_enrichment.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_domain_reputation.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_email_reputation.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_extractor_llm_pipeline.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_filter_e2e_manual.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_filter_parser_manual.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_fingerprint.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_github_scraper.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_gitlab_scraper.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_graph.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_graph_integration.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_hash_reputation.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_i18n.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_infostealer.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_ip_reputation.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_llm.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_llm_utils.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_model_singleton.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_monitor.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_monitor_integration.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_nvd.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_pagination.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_paste_scraper.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_proxy_client.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_ransomlook.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_regex_patterns.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_rss_scraper.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_scrape_js.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_settings.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_snort_export.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_sources.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_sources_enrichment_new.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_stix_export.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_vector.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/tests/test_yara_export.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/async_utils.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/content_dedup.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/content_safety.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/defang.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/encryption.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/enrichment_cache.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/investigation_metrics.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/ioc_freshness.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/utils/user_keys.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/embedder.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/model_singleton.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/search.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/vector/store.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess/config.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess/llm.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess/llm_utils.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/dependency_links.txt +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/entry_points.txt +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/requires.txt +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess.egg-info/top_level.txt +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/adapters/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/adapters/sqlite.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/browser.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/__init__.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/actors.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/configure.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/enrich.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/export.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/commands/show.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/config.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/display.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/main.py +0 -0
- {voidaccess-1.8.1 → voidaccess-1.8.2}/voidaccess_cli/tor_detect.py +0 -0
|
@@ -54,7 +54,7 @@ logger = logging.getLogger(__name__)
|
|
|
54
54
|
# ---------------------------------------------------------------------------
|
|
55
55
|
|
|
56
56
|
PACKAGE_FORMAT = "voidaccess-ioc-v1"
|
|
57
|
-
PACKAGE_VERSION = "1.8.
|
|
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(
|
|
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:
|
|
@@ -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"
|
|
@@ -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
|
-
|
|
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
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0002_add_investigation_status_column.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0007_add_actor_style_profiles.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0011_add_page_extraction_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0018_add_user_id_to_investigations.py
RENAMED
|
File without changes
|
|
File without changes
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0020_add_entity_source_tracking.py
RENAMED
|
File without changes
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0021_add_search_engine_stats.py
RENAMED
|
File without changes
|
|
File without changes
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0023_add_investigation_metadata.py
RENAMED
|
File without changes
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0024_add_investigation_step_metrics.py
RENAMED
|
File without changes
|
{voidaccess-1.8.1 → voidaccess-1.8.2}/db/migrations/versions/0025_add_cancellation_requested.py
RENAMED
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|