estorides 1.3.0__py3-none-any.whl
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.
- estorides-1.3.0.dist-info/METADATA +758 -0
- estorides-1.3.0.dist-info/RECORD +86 -0
- estorides-1.3.0.dist-info/WHEEL +5 -0
- estorides-1.3.0.dist-info/entry_points.txt +2 -0
- estorides-1.3.0.dist-info/licenses/LICENSE +661 -0
- estorides-1.3.0.dist-info/top_level.txt +3 -0
- estorides_core/__init__.py +4 -0
- estorides_core/active_recon.py +359 -0
- estorides_core/alerter.py +323 -0
- estorides_core/async_client.py +454 -0
- estorides_core/audit.py +250 -0
- estorides_core/case_crypto.py +82 -0
- estorides_core/cases.py +491 -0
- estorides_core/change_detection.py +522 -0
- estorides_core/cloud_asset_discovery.py +94 -0
- estorides_core/code_exposure.py +212 -0
- estorides_core/config.py +674 -0
- estorides_core/discoverer.py +369 -0
- estorides_core/entity_extraction.py +550 -0
- estorides_core/entity_resolution.py +733 -0
- estorides_core/entity_store.py +165 -0
- estorides_core/envutil.py +68 -0
- estorides_core/event_bus.py +90 -0
- estorides_core/feeds.py +347 -0
- estorides_core/fusion_analytics.py +550 -0
- estorides_core/fusion_store.py +734 -0
- estorides_core/graph_bundle.py +519 -0
- estorides_core/graph_force.py +455 -0
- estorides_core/graph_kuzu.py +444 -0
- estorides_core/graph_rag_search.py +930 -0
- estorides_core/hypothesis_engine.py +687 -0
- estorides_core/ids.py +29 -0
- estorides_core/intel_resolver.py +809 -0
- estorides_core/job_registry.py +126 -0
- estorides_core/knowledge_graph.py +407 -0
- estorides_core/lan_bundle.py +415 -0
- estorides_core/lan_inventory.py +357 -0
- estorides_core/lan_policy.py +178 -0
- estorides_core/lan_runner.py +249 -0
- estorides_core/lan_scope.py +113 -0
- estorides_core/lan_web.py +165 -0
- estorides_core/mitre_attack.py +237 -0
- estorides_core/monitoring.py +410 -0
- estorides_core/observation_models.py +191 -0
- estorides_core/ontology.py +397 -0
- estorides_core/openapi.py +41 -0
- estorides_core/ops_observability.py +146 -0
- estorides_core/orchestrator.py +981 -0
- estorides_core/osiris_sources.py +525 -0
- estorides_core/pagination.py +124 -0
- estorides_core/parsers.py +1293 -0
- estorides_core/pdns_monitor.py +91 -0
- estorides_core/people_intel.py +209 -0
- estorides_core/pivot_engine.py +502 -0
- estorides_core/recon_fusion.py +432 -0
- estorides_core/recon_pipeline.py +86 -0
- estorides_core/relationship_inference.py +214 -0
- estorides_core/reliability_scoring.py +482 -0
- estorides_core/scope.py +397 -0
- estorides_core/search_telemetry.py +358 -0
- estorides_core/socmint.py +372 -0
- estorides_core/source_health_monitoring.py +359 -0
- estorides_core/source_loader.py +371 -0
- estorides_core/sqlite_store.py +119 -0
- estorides_core/ssrf_guard.py +270 -0
- estorides_core/supply_chain.py +141 -0
- estorides_core/system_app_sources.py +499 -0
- estorides_core/target_management.py +394 -0
- estorides_core/target_scoring.py +246 -0
- estorides_core/tech_fingerprint.py +214 -0
- estorides_core/tool_install.py +542 -0
- estorides_core/tool_runner.py +321 -0
- estorides_core/transforms.py +541 -0
- estorides_core/transliteration.py +146 -0
- estorides_core/validation.py +135 -0
- estorides_core/vuln_correlation.py +237 -0
- estorides_core/web_security.py +446 -0
- estorides_export/__init__.py +30 -0
- estorides_export/encryption.py +143 -0
- estorides_export/misp.py +85 -0
- estorides_export/recon_report.py +192 -0
- estorides_export/report.py +233 -0
- estorides_export/stix.py +151 -0
- estorides_llm/__init__.py +6 -0
- estorides_llm/intelligence_prompts.py +161 -0
- estorides_llm/manager.py +474 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: estorides
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: State-level OSINT orchestrator. 99+ free public sources, async fanout, knowledge graph, multi-LLM analyst.
|
|
5
|
+
Author-email: Gris Iscomeback <grisiscomeback@gmail.com>
|
|
6
|
+
License: GPL-3.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/grisuno/estorides
|
|
8
|
+
Project-URL: Issues, https://github.com/grisuno/estorides/issues
|
|
9
|
+
Keywords: osint,intelligence,reconnaissance,threat-intel,stix,misp
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: flask<4.0,>=3.0
|
|
23
|
+
Requires-Dist: networkx<4.0,>=3.0
|
|
24
|
+
Requires-Dist: requests<3.0,>=2.32.0
|
|
25
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
26
|
+
Requires-Dist: aiohttp<4.0,>=3.10.11
|
|
27
|
+
Requires-Dist: gunicorn<27.0,>=22.0
|
|
28
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
29
|
+
Requires-Dist: defusedxml<1.0,>=0.7
|
|
30
|
+
Provides-Extra: kuzu
|
|
31
|
+
Requires-Dist: kuzu>=0.11; extra == "kuzu"
|
|
32
|
+
Provides-Extra: tor
|
|
33
|
+
Requires-Dist: aiohttp_socks>=0.8; extra == "tor"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
39
|
+
Requires-Dist: bandit>=1.7; extra == "dev"
|
|
40
|
+
Requires-Dist: hypothesis>=6.100; extra == "dev"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: estorides[dev,kuzu,tor]; extra == "all"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# Estorides — OSINT Platform
|
|
46
|
+
|
|
47
|
+
## E.S.T.E.R.O.I.D.E.S. (acronym)
|
|
48
|
+
|
|
49
|
+
- E — Entities (identification of entities, aliases, emails, IPs).
|
|
50
|
+
- S — Signals (capture of digital footprints and metadata).
|
|
51
|
+
- T — Targeted (focused on specific objectives).
|
|
52
|
+
- E — Extraction (automated extraction from web sources).
|
|
53
|
+
- R — Reconnaissance (recon and footprinting).
|
|
54
|
+
- O — Open-source (the nature of the OSINT engine).
|
|
55
|
+
- I — Intelligence (processing and correlation of data).
|
|
56
|
+
- D — Data (massive ingestion of unstructured records).
|
|
57
|
+
- E — Engine (the central engine that orchestrates queries).
|
|
58
|
+
- S — Scraper (automated and persistent collection).
|
|
59
|
+
|
|
60
|
+
From the creators of LazyOwn Redteam Framework comes a free and open-source
|
|
61
|
+
intelligence (OSINT) aggregator and correlation engine
|
|
62
|
+
inspired by Palantir, Bellingcat, Maltego, and Citizen Lab workflows.
|
|
63
|
+
A pure open-source re-imagining of the original `fucklantir` /
|
|
64
|
+
`osint_palantir` toolchain, with a much bigger source catalogue, a
|
|
65
|
+
proper knowledge graph, structured parsers, and a multi-backend LLM
|
|
66
|
+
analyst.
|
|
67
|
+
|
|
68
|
+
No payloads. No active scanning. Just 99+ free public OSINT sources,
|
|
69
|
+
fanned out in parallel, fused into a single intelligence picture.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
+--------------------------+
|
|
73
|
+
query "example.com" | Estorides Orchestrator | -> STIX 2.1 bundle
|
|
74
|
+
---------------------> | - async fanout | -> MISP event JSON
|
|
75
|
+
| - 99+ free sources | -> GraphML for Gephi
|
|
76
|
+
| - structured parsers | -> JSONL for training
|
|
77
|
+
| - entity resolution |
|
|
78
|
+
| - knowledge graph |
|
|
79
|
+
| - ontology engine | <- OFAC SDN cross-check
|
|
80
|
+
| - MITRE ATT&CK mapper | <- technique auto-tagging
|
|
81
|
+
| - SSRF guard | <- blocklist at egress
|
|
82
|
+
| - audit log + RL | <- per-IP trail
|
|
83
|
+
| - multi-LLM analyst | <- BLUF / tactical / system
|
|
84
|
+
+--------------------------+
|
|
85
|
+
|
|
|
86
|
+
v
|
|
87
|
+
Web UI: map / graph / bundles / timeline / results
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Architecture highlights (state-level)
|
|
91
|
+
|
|
92
|
+
Estorides is structured around small, single-responsibility registries
|
|
93
|
+
so adding a new source, backend, inferer, or feed never requires
|
|
94
|
+
touching the central orchestrator. The five plug-in surfaces are:
|
|
95
|
+
|
|
96
|
+
| Surface | Decorator | File | Used for |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| Source parsers | `@register_parser("name")` | `estorides_core/parsers.py` | Translate raw HTTP into structured dicts |
|
|
99
|
+
| LLM backends | `@register("name")` | `estorides_llm/manager.py` | Add an LLM provider (ollama, openai, …) |
|
|
100
|
+
| Relationship inferers | `@register_inferer("source")` | `estorides_core/relationship_inference.py` | Source -> graph edges |
|
|
101
|
+
| Real-time feeds | subclass `Feed` | `estorides_core/feeds.py` | Map layers (quakes, fires, news) |
|
|
102
|
+
| Encrypted exporters | `estorides_export.encryption` | `estorides_export/encryption.py` | STIX/MISP + age encryption |
|
|
103
|
+
|
|
104
|
+
## What you get that the original does not
|
|
105
|
+
|
|
106
|
+
| Capability | Original | Estorides |
|
|
107
|
+
| --------------------------------------- | ----------- | --------- |
|
|
108
|
+
| Number of free OSINT sources | ~20 | **99** |
|
|
109
|
+
| Intelligence categories | 6 | **12** |
|
|
110
|
+
| HTTP fanout model | sequential | **async** |
|
|
111
|
+
| Retries + backoff + circuit breaker | basic | **yes** |
|
|
112
|
+
| Response cache (SQLite) | none | **yes** |
|
|
113
|
+
| Per-source parsers | none | **50+** |
|
|
114
|
+
| Entity extraction (IP, domain, CVE…) | regex only | **structured** + dedup |
|
|
115
|
+
| Knowledge graph | none | **NetworkX** + GraphML |
|
|
116
|
+
| STIX 2.1 / MISP export | none | **yes** |
|
|
117
|
+
| Multi-LLM (Ollama / OpenAI / Anthropic) | Ollama only | **4 backends** + stub |
|
|
118
|
+
| Map (geolocation results) | PyVista 3D | **Leaflet 2D** |
|
|
119
|
+
| Force-directed graph view | none | **D3.js** |
|
|
120
|
+
| API key handling | none | **per-source env vars** |
|
|
121
|
+
| Paid source support | none | **flag-based opt-in** |
|
|
122
|
+
| OFAC SDN sanctions cross-check | none | **ontology engine** |
|
|
123
|
+
| MITRE ATT&CK technique auto-tagging | none | **~40 techniques** |
|
|
124
|
+
| SSRF / private-NW egress guard | none | **RFC1918 + cloud IMDS blocked** |
|
|
125
|
+
| Audit log (per request, append-only) | none | **JSONL with IP+query+latency** |
|
|
126
|
+
| Per-IP rate limit (sliding window) | none | **default 30/min, env-tunable** |
|
|
127
|
+
| Encrypted export (age) | none | **opt-in via `?key=age1…`** |
|
|
128
|
+
| Real-time feed layers | none | **earthquakes + fires + news** |
|
|
129
|
+
| Encrypted export (age) | none | **opt-in via `?key=age1…`** |
|
|
130
|
+
|
|
131
|
+
## What v1.1 adds on top
|
|
132
|
+
|
|
133
|
+
| Capability | v1.0 | v1.1 (this) |
|
|
134
|
+
| --------------------------------------- | ----------- | ----------- |
|
|
135
|
+
| Persistent graph (Cypher queries) | NetworkX dump | **Kùzu embedded DB**, cross-run joins |
|
|
136
|
+
| Run persistence | JSONL append | **SQLite cases** with FK observations/entities |
|
|
137
|
+
| Cross-feed entity resolver | none | **Wikidata + OFAC + IP-API + NVD** via `intel_resolver` |
|
|
138
|
+
| Fuzzy entity clustering | exact dedup | **`difflib` SequenceMatcher**, 0.85 threshold, aliases surfaced |
|
|
139
|
+
| Extra OSINT endpoints (keyless) | 99 YAML sources | **+7** (BGP, MAC, phone, GitHub, leaks, CISA KEV, malware C2) |
|
|
140
|
+
| Read-only Cypher endpoint | none | **`/api/intel/graph?q=...`** with write-keyword guard |
|
|
141
|
+
| Case history UI | none | **Cases tab** + full-entity inspector |
|
|
142
|
+
|
|
143
|
+
## What the data→information pipeline adds (v1.4)
|
|
144
|
+
|
|
145
|
+
The original engine was a **data** collector: a fan-out, parsers, a graph, an
|
|
146
|
+
LLM that summarises. The v1.4 sprint introduces the **information** layer:
|
|
147
|
+
every observation now carries a *grounded* confidence score; the orchestrator
|
|
148
|
+
and downstream consumers (hypothesis engine, change detection, LLM analyst)
|
|
149
|
+
stop using their own ad-hoc heuristics and use this single source of truth.
|
|
150
|
+
|
|
151
|
+
| Module | Spec | Status | What it does |
|
|
152
|
+
| --- | --- | --- | --- |
|
|
153
|
+
| `reliability_scoring` (2a) | [`spec/reliability_scoring.md`](spec/reliability_scoring.md) | closed 2026-06-27 | NATO Admiralty (reliability A-F × credibility 1-6) × corroboration × freshness decay. Replaces `+0.1` heuristic and `MAX()` SQL. 45 BDD tests + 9 hypothesis properties. |
|
|
154
|
+
| `source_hierarchy` (1) | [`spec/reliability_scoring.md`](spec/reliability_scoring.md) | closed 2026-07-10 | SourceType enum (PRIMARY/SECONDARY/TERTIARY), SOURCE_TYPE_MAP (42 curated sources), SOURCE_TYPE_WEIGHT. Extends confidence computation with source-type weight. 64 BDD tests + 12 properties. |
|
|
155
|
+
| `source_health_monitoring` (2) | [`spec/source_health_monitoring.md`](spec/source_health_monitoring.md) | closed 2026-07-10 | Source health dashboard: compute_health() with success/latency/freshness weights, tiered stats. 37 BDD + 5 properties. |
|
|
156
|
+
| `entity_resolution` (3) | [`spec/entity_resolution.md`](spec/entity_resolution.md) | closed 2026-07-10 | Proper spec + 48 BDD pytest tests for the existing entity_resolution. Transliteration, Jaro-Winkler, normalisation, canonical ids, cross-script fusion. |
|
|
157
|
+
| `probabilistic_fusion` (4) | [`spec/probabilistic_fusion.md`](spec/probabilistic_fusion.md) | closed 2026-07-10 | Bayesian merge_confidence() replaces MAX() in FusionStore.fuse_entity and fuse_relationship. Source-weighted merging prevents tertiary inflation. 17 BDD tests. |
|
|
158
|
+
| `paged_results` (5) | [`spec/paged_results.md`](spec/paged_results.md) | closed 2026-07-10 | Pagination module with three strategies (page, offset, cursor). PaginationConfig, build_page_params, extract_cursor, count_results. Integrated into orchestrator._execute_source. 31 BDD tests. |
|
|
159
|
+
| `hypothesis_engine` (2b) | [`spec/hypothesis_engine.md`](spec/hypothesis_engine.md) | closed 2026-06-27 | Capa "data → information". 4 generadores tipados (domain-belongsto-actor, email-aliasto-person, ip-shared-infra, asn-shared-infra). Ids deterministas (sha1), audit trail con evidence items. 26 BDD tests + 9 hypothesis properties. |
|
|
160
|
+
| `change_detection` (2c) | [`spec/change_detection.md`](spec/change_detection.md) | closed 2026-06-27 | Capa "temporal": diff entre dos `Snapshot` del mismo target. 8 kinds tipados (`new`/`disappeared`/`property_changed`/`source_added`/`source_removed`/`edge_added`/`edge_removed`/`confidence_shifted`), score reliability-weighted, ids deterministas (sha1), audit trail completo. Puro: sin I/O, sin logging del payload, acotado por `max_changes`. 30 BDD tests (S1-S15) + 8 hypothesis properties (1000 ejemplos c/u). |
|
|
161
|
+
| `observation_models` (2n) | [`spec/observation_models.md`](spec/observation_models.md) | closed 2026-08-23 | Strict Pydantic v2 data contracts (`ObservationMeta`, `Observation`, `ObservedEntity`, `RunResult`) with `strict=True` + `extra="forbid"` and recursive JSON-safe validation. Bounds centralised in `SchemaConfig` (env-tunable). First module closed with **100% mutation coverage (79/79)**. 24 BDD tests + 4 hypothesis properties (1000 examples each). |
|
|
162
|
+
|
|
163
|
+
The full development doctrine (SDD + TDD + BDD + boy-scout + visual review
|
|
164
|
+
+ fuzzing) lives in [`CLAUDE.md`](CLAUDE.md). Every module follows the
|
|
165
|
+
same cycle: spec → red test → green code → refactor → validate → fuzz →
|
|
166
|
+
document.
|
|
167
|
+
|
|
168
|
+
### v1.1 architecture
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
+--------------------------+
|
|
172
|
+
query "example.com" | Estorides Orchestrator | -> STIX 2.1 / MISP / GraphML / JSON
|
|
173
|
+
---------------------> | + async fanout |
|
|
174
|
+
| + 99 free sources |
|
|
175
|
+
| + 7 Osiris-style probes |
|
|
176
|
+
| + SSRF guard + audit |
|
|
177
|
+
| + ontology engine |
|
|
178
|
+
| + MITRE ATT&CK mapper |
|
|
179
|
+
| + multi-LLM analyst |
|
|
180
|
+
| + cross-feed resolver | <- Wikidata SPARQL + OFAC + IP-API + NVD
|
|
181
|
+
| + fuzzy entity cluster | <- difflib SequenceMatcher
|
|
182
|
+
+-----------+--------------+
|
|
183
|
+
|
|
|
184
|
+
+----------------+-----------------+--------------------+
|
|
185
|
+
v v v
|
|
186
|
+
+------------------+ +------------------+ +------------------+
|
|
187
|
+
| Kùzu graph DB | | SQLite case store| | In-memory NX |
|
|
188
|
+
| (Cypher queries) | | (FK observations)| | (per-run working)|
|
|
189
|
+
| 99 node labels | | search by entity | | per-run edges |
|
|
190
|
+
| 9 REL types | | search by query | | |
|
|
191
|
+
+------------------+ +------------------+ +------------------+
|
|
192
|
+
^ ^
|
|
193
|
+
+---------/api/intel/resolve-------+
|
|
194
|
+
+---------/api/cases/...-----------+
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### v1.1 API additions
|
|
198
|
+
|
|
199
|
+
| Endpoint | Purpose |
|
|
200
|
+
| ---------------------------------------------- | ------- |
|
|
201
|
+
| `GET /api/cases?q=<substr>&type=<qtype>` | List past runs. Searchable by query substring. |
|
|
202
|
+
| `GET /api/cases/<id>?full=1` | Replay a case. `full=1` includes observations + entities. |
|
|
203
|
+
| `DELETE /api/cases/<id>` | Drop a case. |
|
|
204
|
+
| `GET /api/intel/resolve?type=<t>&id=<v>` | Cross-feed resolution. `type` is one of `ip`, `domain`, `company`, `person`, `country`, `cve`, `btc_address`, `eth_address`. |
|
|
205
|
+
| `GET /api/intel/graph?q=<cypher>` | Read-only Cypher against the Kùzu graph. Mutations (`CREATE`/`MERGE`/`SET`/`DELETE`) are rejected. |
|
|
206
|
+
| `GET /api/intel/stats` | One-glance dashboard: case count, Kùzu node/edge counts, resolver cache size. |
|
|
207
|
+
| `GET /api/osiris/bgp?query=<ip\|ASxxxxx>` | BGP / ASN lookup via `bgpview.io`. |
|
|
208
|
+
| `GET /api/osiris/mac?mac=00:1A:...` | MAC OUI vendor via `macvendors.co`. |
|
|
209
|
+
| `GET /api/osiris/phone?number=+14155552671` | Phone geolocation (NANP area code → lat/lng). |
|
|
210
|
+
| `GET /api/osiris/github?user=torvalds` | GitHub user + 5 most recent repos. |
|
|
211
|
+
| `GET /api/osiris/leaks?email=...` | XposedOrNot breach analytics (more detail than HIBP). |
|
|
212
|
+
| `GET /api/osiris/cisa-kev?limit=10&days=30` | CISA Known Exploited Vulnerabilities, recent window. |
|
|
213
|
+
| `GET /api/osiris/malware?limit=200` | Feodo Tracker + URLhaus active C2, geolocated. |
|
|
214
|
+
|
|
215
|
+
## Fusion datastore — the cross-run fused fact base
|
|
216
|
+
|
|
217
|
+
The case store keeps a *per-run silo*: each investigation writes its own
|
|
218
|
+
copy of what it saw, keyed by `case_id`. The same entity seen across fifty
|
|
219
|
+
runs becomes fifty rows, and the relational store cannot answer "everything
|
|
220
|
+
we know about X, from every source, across every case".
|
|
221
|
+
|
|
222
|
+
The **fusion datastore** (`estorides_core/fusion_store.py`) is the
|
|
223
|
+
data-fusion layer that closes that gap — the relational analogue of the
|
|
224
|
+
Kùzu graph. Every run feeds it, and it accumulates a normalised,
|
|
225
|
+
deduplicated, **source-attributed** fact base across runs:
|
|
226
|
+
|
|
227
|
+
- **Deterministic identity** — every entity gets `sha1(type:normalized)` as
|
|
228
|
+
its id, so the same real-world entity computed in two different runs lands
|
|
229
|
+
on the same row with no coordination. The resolver's `canonical_id` is
|
|
230
|
+
recorded alongside but never the dedup key.
|
|
231
|
+
- **Provenance survives the merge** — when N feeds corroborate an entity,
|
|
232
|
+
the record is merged but every contributing source is retained, so
|
|
233
|
+
`source_count` grounds confidence in how many independent feeds agree
|
|
234
|
+
(e.g. `1.1.1.1` corroborated by 20 sources).
|
|
235
|
+
- **Property fusion with conflict preserved** — each source's flat facts are
|
|
236
|
+
attributed to the target entity. Agreement is surfaced
|
|
237
|
+
(`country=Australia` by 2 sources); disagreement is **kept with its
|
|
238
|
+
provenance** (`region=New South Wales` vs `region=Queensland`) instead of
|
|
239
|
+
silently picking one — exactly what an intelligence fusion store must do.
|
|
240
|
+
- **Relationships fused** — analytic edges from the knowledge graph (skip
|
|
241
|
+
the `observed_by`/`co_occurs` plumbing) accumulate cross-run, with both
|
|
242
|
+
endpoints always materialised so the graph is navigable from either side.
|
|
243
|
+
|
|
244
|
+
It mirrors the rest of the persistence layer: WAL SQLite, one serialised
|
|
245
|
+
connection, and **fail-soft** — without a writable data dir a run still
|
|
246
|
+
returns, it just leaves nothing in the fused store. Toggle with
|
|
247
|
+
`ESTORIDES_FUSION_ENABLED=0`; relocate with `ESTORIDES_FUSION_DB=/path`.
|
|
248
|
+
|
|
249
|
+
### Fusion API + CLI
|
|
250
|
+
|
|
251
|
+
| Endpoint | Purpose |
|
|
252
|
+
| ---------------------------------------------------- | ------- |
|
|
253
|
+
| `GET /api/fusion/stats` | Size of the fused base: entities, multi-source count, observations, properties, relationships, by-type breakdown. |
|
|
254
|
+
| `GET /api/fusion/sources` | YAML source catalogue with accumulated fetch/ok counters. |
|
|
255
|
+
| `GET /api/fusion/entities?q=&type=&min_sources=2` | Search fused entities. `min_sources=N` is the fusion-native "only what ≥N feeds corroborate" filter. |
|
|
256
|
+
| `GET /api/fusion/entity/<id>?min_sources=2` | Full fused view of one entity: provenance, properties, edges, and the corroborated (multi-source-agreed) properties. |
|
|
257
|
+
|
|
258
|
+
The fused stats are also folded into `GET /api/intel/stats` under `fusion`.
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
python3 estorides_cli.py run 1.1.1.1 # fan out + fuse into the store
|
|
262
|
+
python3 estorides_cli.py fusion stats # how big is the fused base
|
|
263
|
+
python3 estorides_cli.py fusion entities --min-sources 2 # only corroborated entities
|
|
264
|
+
python3 estorides_cli.py fusion entity <id> # full provenance + properties
|
|
265
|
+
python3 estorides_cli.py fusion sources # per-source fetch history
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### v1.1 install
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
pip install -r requirements.txt
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The only new required dep is `kuzu>=0.11`. The orchestrator falls
|
|
275
|
+
back to in-memory NetworkX if Kùzu is not importable, but a persistent
|
|
276
|
+
cross-run graph only happens with Kùzu present.
|
|
277
|
+
|
|
278
|
+
## Quickstart
|
|
279
|
+
|
|
280
|
+
### 1. Install (no extra packages needed; the project uses Flask + NetworkX + requests)
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
cd estorides
|
|
284
|
+
python3 -m pip install flask networkx requests pyyaml
|
|
285
|
+
```
|
|
286
|
+
Optional, for a real LLM:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# pick one — local is preferred (async, no API cost)
|
|
290
|
+
ollama serve && ollama pull deepseek-r1:1.5b # fast local default
|
|
291
|
+
ollama pull qwen3.8:27b # bigger, higher-quality (slower)
|
|
292
|
+
export OPENAI_API_KEY=sk-...
|
|
293
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
294
|
+
export OPENROUTER_API_KEY=sk-or-...
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Estorides auto-detects a fast local ollama model and waits generously for it
|
|
298
|
+
(`LLM_REQUEST_TIMEOUT` default 600s, runs offloaded/async) — no more
|
|
299
|
+
`[Stub LLM — no backends available]` when a local model is present. Force a
|
|
300
|
+
specific model with `ESTORIDES_OLLAMA_MODEL` (e.g. `qwen3.8:27b`).
|
|
301
|
+
|
|
302
|
+
### 2. CLI
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# 99 sources, 12 categories
|
|
306
|
+
python3 estorides_cli.py status
|
|
307
|
+
|
|
308
|
+
# run a query (free sources only)
|
|
309
|
+
python3 estorides_cli.py run 8.8.8.8
|
|
310
|
+
|
|
311
|
+
# enable sources that need an API key
|
|
312
|
+
python3 estorides_cli.py run user@example.com --include-paid
|
|
313
|
+
|
|
314
|
+
# only a subset of sources
|
|
315
|
+
python3 estorides_cli.py run example.com \
|
|
316
|
+
--only-sources crt_sh_certificates,shodan_internetdb,ipapi_free
|
|
317
|
+
|
|
318
|
+
# export the latest run as STIX 2.1 or MISP
|
|
319
|
+
python3 estorides_cli.py stix --out my_bundle.json
|
|
320
|
+
python3 estorides_cli.py misp --out my_event.json
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 3. Web UI
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
python3 estorides_cli.py serve --port 5050
|
|
327
|
+
# open http://127.0.0.1:5050
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
<img width="1913" height="963" alt="image" src="https://github.com/user-attachments/assets/dcc2ff2a-bdbe-49ed-a803-61b7362877e0" />
|
|
331
|
+
|
|
332
|
+
<img width="1913" height="963" alt="image" src="https://github.com/user-attachments/assets/c2d0b501-1d34-40b0-a300-4a623a6c8cab" />
|
|
333
|
+
|
|
334
|
+
<img width="1913" height="963" alt="image" src="https://github.com/user-attachments/assets/760c4f10-961e-41ca-9f4b-ae8ab936a09c" />
|
|
335
|
+
|
|
336
|
+
<img width="1913" height="963" alt="image" src="https://github.com/user-attachments/assets/5050fb8f-1e96-43bd-a109-ffbec7845634" />
|
|
337
|
+
|
|
338
|
+
UI features:
|
|
339
|
+
|
|
340
|
+
- 2D map (Leaflet) of every geolocated result
|
|
341
|
+
- D3.js force-directed knowledge graph (drag, zoom, hover)
|
|
342
|
+
- Timeline of source acquisition
|
|
343
|
+
- Source results panel with per-source parsed output
|
|
344
|
+
- Filterable entity list
|
|
345
|
+
- LLM analysis with backend / model badge
|
|
346
|
+
- One-click export: STIX 2.1, MISP, GraphML, JSON
|
|
347
|
+
|
|
348
|
+
### Graph intelligence (Maltego-style)
|
|
349
|
+
|
|
350
|
+
The Graph canvas turns observations into an interactive intelligence
|
|
351
|
+
workbench:
|
|
352
|
+
|
|
353
|
+
- **Clusters** — nodes are grouped into communities (translucent hulls)
|
|
354
|
+
and coloured by cluster. **Inter-cluster links** are dashed/highlighted;
|
|
355
|
+
click one to see a **cross-reference tooltip** explaining how two
|
|
356
|
+
clusters relate (the bridge entities + relation).
|
|
357
|
+
- **Click to enrich** — left-click a node to resolve it (cross-feed +
|
|
358
|
+
VirusTotal relationships) and merge the new nodes/links into both the
|
|
359
|
+
graph **and** the map. Each new node is itself clickable, so exploration
|
|
360
|
+
is recursive.
|
|
361
|
+
- **Intelligence tiers** — every node carries an auto-computed level shown
|
|
362
|
+
as a coloured ring: `data` → `information` (≥2 corroborating sources) →
|
|
363
|
+
`intelligence` (cross-cluster / resolved) → `counter-intelligence`
|
|
364
|
+
(sanction / threat / VirusTotal-malicious). Override any node's level
|
|
365
|
+
from the right-click menu or the inspector (persists in the browser).
|
|
366
|
+
- **Transforms** — right-click a node (or use the side **inspector**
|
|
367
|
+
panel) for transforms grouped by tier: data → information →
|
|
368
|
+
intelligence → counter-intelligence.
|
|
369
|
+
|
|
370
|
+
### VirusTotal
|
|
371
|
+
|
|
372
|
+
VirusTotal is integrated both as a source (`vt_ip`, `vt_domain`,
|
|
373
|
+
`vt_file`) and as the relationship engine behind graph expansion
|
|
374
|
+
(resolved domains/IPs, communicating/dropped files, contacted infra).
|
|
375
|
+
It needs a free API key; without it VirusTotal stays inactive and the
|
|
376
|
+
rest of the platform is unaffected:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
export VT_API_KEY=... # https://www.virustotal.com/gui/my-apikey
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## 99 sources, 12 categories
|
|
383
|
+
|
|
384
|
+
01. DNS Intelligence (9) - Google DoH, Cloudflare DoH, HackerTarget,
|
|
385
|
+
crt.sh, Cert Spotter, RDAP, DNS Dumpster, host search
|
|
386
|
+
02. IP & Infrastructure (13) - ip-api, ipinfo, ipapi.co, ipwho.is, Shodan InternetDB,
|
|
387
|
+
GreyNoise, ipwhois, Robtex, RDAP, AS lookup,
|
|
388
|
+
AbuseIPDB, MAC OUI, RIPE Stat, BGPView IP/ASN
|
|
389
|
+
03. Web Intelligence (10) - urlscan, Wayback CDX, Wayback availability,
|
|
390
|
+
HTTP headers, whois, geoip, traceroute, nping,
|
|
391
|
+
Microlink, Google cache
|
|
392
|
+
04. Social Media (13) - GitHub, Reddit, Mastodon, Keybase,
|
|
393
|
+
HackerNews, Telegram, Pinterest, WordPress,
|
|
394
|
+
Medium, DEV.to
|
|
395
|
+
05. Threat Intelligence (13) - ThreatFox, URLhaus, payloads, PhishTank,
|
|
396
|
+
OpenPhish, OTX (+passive domain/IP, no key),
|
|
397
|
+
MalwareBazaar, Feodo, SSLBL,
|
|
398
|
+
Emerging Threats, blocklist.de, CISA KEV
|
|
399
|
+
06. Breach Intelligence (6) - HIBP breaches, HIBP pastes, Phonebook email,
|
|
400
|
+
Phonebook domain, DeHashed, IntelligenceX
|
|
401
|
+
07. Geolocation (5) - Nominatim search + reverse, OpenWeather
|
|
402
|
+
geocoding, TimeZoneDB, Wikidata
|
|
403
|
+
08. Knowledge (12) - Wikipedia, summary, DuckDuckGo IA, OpenAlex,
|
|
404
|
+
Crossref, arXiv, GitHub advisories, NVD CVE,
|
|
405
|
+
cve.circl, ExploitDB, Reddit subreddit search
|
|
406
|
+
09. Wireless (5) - WiGLE, IEEE OUI, OpenSky, MarineTraffic, N2YO
|
|
407
|
+
10. Blockchain (5) - blockchain.info (balance + tx), Blockstream,
|
|
408
|
+
Ethplorer, mempool.space
|
|
409
|
+
11. Paste & Leaks (4) - psbdmp, GitHub gist search, TGStat, LeakCheck
|
|
410
|
+
12. Visual (4) - ScreenshotMachine, Microlink, TinEye, EXIF
|
|
411
|
+
20. System Tools (Kali) (19) - local OSINT CLI tools as first-class sources
|
|
412
|
+
(`kind: system_app`): theHarvester, amass,
|
|
413
|
+
dnsrecon, dnsenum, fierce, sublist3r, dmitry,
|
|
414
|
+
urlcrazy, sherlock, maigret, holehe, usufy,
|
|
415
|
+
mailfy, phonefy, searchfy, metagoofil, whatweb,
|
|
416
|
+
wafw00f, phoneinfoga — executed through the
|
|
417
|
+
tool_runner sandbox and fused into the same
|
|
418
|
+
aggregation pipeline as HTTP sources.
|
|
419
|
+
|
|
420
|
+
Sources are addons: one YAML file per source, organised into category
|
|
421
|
+
subdirectories under `sources/` (lazyaddons-style). The loader recurses, so
|
|
422
|
+
add a new source by dropping `sources/<NN_category>/<name>.yaml` — no central
|
|
423
|
+
registry to edit. Grouped multi-document files still load if present. Point
|
|
424
|
+
`ESTORIDES_SOURCES_DIR` at another tree to use your own addon set. The schema
|
|
425
|
+
is documented at the top of `estorides_core/source_loader.py` and the
|
|
426
|
+
system_app schema in `spec/system_app_sources.md`:
|
|
427
|
+
|
|
428
|
+
```yaml
|
|
429
|
+
# sources/20_system_tools/kali_sherlock.yaml — a Kali binary as a source
|
|
430
|
+
name: kali_sherlock
|
|
431
|
+
enabled: true
|
|
432
|
+
category: 20. System Tools (Kali)
|
|
433
|
+
kind: system_app # local binary vs remote HTTP API
|
|
434
|
+
os: linux # platform gate; "any" allowed
|
|
435
|
+
contact: none # none|broker|active — feeds the passive-only guarantee
|
|
436
|
+
parser: sherlock_text
|
|
437
|
+
entity_hints: [username, url]
|
|
438
|
+
applies_to: [username]
|
|
439
|
+
tool:
|
|
440
|
+
binary: sherlock # must be in TOOL_ALLOWLIST and on the filesystem
|
|
441
|
+
args: ["{query}", "--print-found", "--no-color", "--timeout", "30"]
|
|
442
|
+
output_format: text # json | text | lines
|
|
443
|
+
timeout: 300
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
sources/
|
|
448
|
+
01_dns/
|
|
449
|
+
dns_google.yaml
|
|
450
|
+
crt_sh_certificates.yaml
|
|
451
|
+
02_ip_infra/
|
|
452
|
+
shodan_internetdb.yaml
|
|
453
|
+
...
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
`tools/split_sources.py` migrates legacy grouped files into this layout.
|
|
457
|
+
|
|
458
|
+
## Architecture
|
|
459
|
+
|
|
460
|
+
```
|
|
461
|
+
sources/ one YAML per addon, grouped by category dir (126 addons)
|
|
462
|
+
estorides_core/
|
|
463
|
+
config.py every tunable (env-overridable)
|
|
464
|
+
source_loader.py registry, validation, lookup
|
|
465
|
+
async_client.py aiohttp + circuit breaker + SQLite cache
|
|
466
|
+
parsers.py 50+ structured parsers (ipapi, dns_json, crtsh…)
|
|
467
|
+
system_app_sources.py Kali CLI tools as sources (tool_runner sandbox + parsers)
|
|
468
|
+
observation_models.py Strict Pydantic v2 data contracts for observations/entities
|
|
469
|
+
entity_extraction.py regex-based entity finder with dedup
|
|
470
|
+
knowledge_graph.py NetworkX MultiDiGraph + GraphML export
|
|
471
|
+
orchestrator.py glues everything, infers higher-level relations
|
|
472
|
+
estorides_llm/
|
|
473
|
+
manager.py multi-backend LLM (Ollama → OpenRouter → Anthropic → OpenAI → stub)
|
|
474
|
+
estorides_export/
|
|
475
|
+
stix.py STIX 2.1 bundle export
|
|
476
|
+
misp.py MISP event JSON export
|
|
477
|
+
estorides_cli.py argparse CLI
|
|
478
|
+
estorides_web.py Flask app
|
|
479
|
+
templates/index.html UI
|
|
480
|
+
static/{css,js}/estorides.* UI styles + D3 controller
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Tips for a real run
|
|
484
|
+
|
|
485
|
+
1. Start with the free-tier sources (default) — that is 80+ endpoints.
|
|
486
|
+
2. Set `ESTORIDES_PARALLEL=16` for faster fanout.
|
|
487
|
+
3. Set `ESTORIDES_TIMEOUT=20` if your network is slow.
|
|
488
|
+
4. Disable paid sources you don't have keys for by setting
|
|
489
|
+
`ESTORIDES_DISABLE_BACKENDS=openai,anthropic` (or by leaving
|
|
490
|
+
`--include-paid` off in the CLI).
|
|
491
|
+
5. The SQLite cache lives in `data/estorides_cache.sqlite` —
|
|
492
|
+
delete it to force fresh fetches.
|
|
493
|
+
6. The LLM stage needs a **generative** model. Ollama auto-selects a fast
|
|
494
|
+
installed model (prefers `deepseek-r1:1.5b`; override with
|
|
495
|
+
`ESTORIDES_OLLAMA_MODEL`) and retries with a larger token budget if a
|
|
496
|
+
reasoning model burns its preamble and returns empty. An embedding-only
|
|
497
|
+
model (`*:e2b`) still returns no text and falls back to the stub.
|
|
498
|
+
`ollama pull llama3.1:8b` for a real analysis.
|
|
499
|
+
|
|
500
|
+
### Missing CLI tools? Install from the GUI
|
|
501
|
+
|
|
502
|
+
When a `system_app` source fails with `TOOL_NOT_FOUND`, the result card
|
|
503
|
+
shows an **"Install tool"** button. Clicking it installs the binary via a
|
|
504
|
+
lazyaddon-style recipe in `tool_recipes/` (apt primary, git/pip fallback)
|
|
505
|
+
using **graphical `run0`** elevation — no terminal `sudo` password needed.
|
|
506
|
+
Re-run the query afterwards to collect. `GET /api/tools/doctor` reports
|
|
507
|
+
every Kali binary at once: installed/missing, recipe available, and which
|
|
508
|
+
sources depend on it.
|
|
509
|
+
|
|
510
|
+
### Performance knobs (bounds that keep a run from stalling)
|
|
511
|
+
|
|
512
|
+
| Env var | Default | What it caps |
|
|
513
|
+
|---|---|---|
|
|
514
|
+
| `ESTORIDES_DEADLINE` via `--deadline` | 30s | hard wall-clock cap for the whole fanout |
|
|
515
|
+
| `ESTORIDES_ENTITY_MAX_SCAN` | 120000 | chars scanned per response (huge crt.sh/wayback dumps) |
|
|
516
|
+
| `ESTORIDES_ENTITY_MAX_PER_TYPE` | 750 | entities kept per type per source |
|
|
517
|
+
| `ESTORIDES_KG_MAX_COOCCUR` | 30 | entities per source in the co-occurrence clique (O(n²) guard) |
|
|
518
|
+
| `ESTORIDES_LLM_REQUEST_TIMEOUT` | 600s | per-call LLM HTTP timeout (local models can take minutes; runs offloaded/async) |
|
|
519
|
+
|
|
520
|
+
## Passive recon & operator OPSEC (bug-bounty mode)
|
|
521
|
+
|
|
522
|
+
For attack-surface scoping the two things that matter are: never let the
|
|
523
|
+
target observe a probe attributable to your recon window, and never let a
|
|
524
|
+
queried broker tie the lookups back to your real IP. Estorides enforces
|
|
525
|
+
both at the engine level.
|
|
526
|
+
|
|
527
|
+
### Contact classification
|
|
528
|
+
|
|
529
|
+
Every source declares how its traffic reaches the target:
|
|
530
|
+
|
|
531
|
+
| `contact` | Meaning | In `--passive-only`? |
|
|
532
|
+
| --- | --- | --- |
|
|
533
|
+
| `none` (default) | Only a third-party DB / resolver / CT log is hit; the target sees nothing | kept |
|
|
534
|
+
| `broker` | A third party actively probes the target on your behalf (ping, traceroute, header fetch) | excluded |
|
|
535
|
+
| `active` | The engine connects to the target's own infrastructure directly | excluded |
|
|
536
|
+
|
|
537
|
+
An unknown/typo class is treated as `active`, so a passive-only run can
|
|
538
|
+
never be silently widened. `--passive-only` is enforced even for an
|
|
539
|
+
explicit `--only-sources` list. Sources that log your lookups also carry
|
|
540
|
+
`logs_queries: true` (surfaced in `status`).
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
# scope a domain without ever touching its infrastructure
|
|
544
|
+
python3 estorides_cli.py discover example.com --passive-only --out-json surface.json
|
|
545
|
+
python3 estorides_cli.py run example.com --passive-only
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Egress anonymisation
|
|
549
|
+
|
|
550
|
+
Route every outbound request through a proxy so brokers never see your
|
|
551
|
+
real IP. SOCKS (Tor) needs `aiohttp_socks`; HTTP/HTTPS proxies work with
|
|
552
|
+
stock aiohttp and a comma-separated pool rotates per request.
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
python3 estorides_cli.py run example.com --passive-only --tor
|
|
556
|
+
python3 estorides_cli.py run example.com --proxy socks5://127.0.0.1:9050
|
|
557
|
+
export ESTORIDES_HTTP_PROXY_POOL="http://p1:8080,http://p2:8080"
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Fail-closed: if a SOCKS proxy is requested without `aiohttp_socks`
|
|
561
|
+
installed, the client refuses to run rather than fall back to a
|
|
562
|
+
deanonymising direct connection. When proxying, the SSRF guard's local
|
|
563
|
+
DNS-resolution leg is skipped (`ESTORIDES_PROXY_REMOTE_DNS=1`, default) so
|
|
564
|
+
your resolver never learns which targets you are investigating — the
|
|
565
|
+
literal-host guard still runs and the exit node resolves the name.
|
|
566
|
+
|
|
567
|
+
### Scope classification
|
|
568
|
+
|
|
569
|
+
Turn a discovered surface into in/out-of-scope flat lists you can pipe
|
|
570
|
+
into the active phase. Out-of-scope always wins, so an excluded asset is
|
|
571
|
+
never targeted by accident.
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
python3 estorides_cli.py scope \
|
|
575
|
+
--assets surface.json \
|
|
576
|
+
--scope program_scope.txt \
|
|
577
|
+
--out scope_result.json \
|
|
578
|
+
--flat-dir ./scope_out
|
|
579
|
+
# -> scope_out/in_scope_hosts.txt, in_scope_ips.txt, unknown.txt
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Rules file grammar (one per line, `#` comments; a `## out-of-scope`
|
|
583
|
+
divider separates the two lists):
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
*.example.com wildcard host suffix (apex + subdomains)
|
|
587
|
+
api.example.com exact host
|
|
588
|
+
192.0.2.0/24 CIDR (IPv4 or IPv6)
|
|
589
|
+
re:^staging-[0-9]+\.ex regex (prefix re:)
|
|
590
|
+
|
|
591
|
+
## out-of-scope
|
|
592
|
+
blog.example.com
|
|
593
|
+
192.0.2.200/32
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## Hard rules
|
|
597
|
+
|
|
598
|
+
- This is a passive intelligence tool. It does not probe, exploit, or
|
|
599
|
+
interact with the target beyond what the public sources allow.
|
|
600
|
+
- All API keys stay in environment variables; they are never written
|
|
601
|
+
to disk.
|
|
602
|
+
- Respect the rate limits of the upstream services. The circuit
|
|
603
|
+
breaker will back off automatically when a host starts returning
|
|
604
|
+
errors.
|
|
605
|
+
- Output is for legitimate OSINT, threat intelligence, journalism,
|
|
606
|
+
academic research, and defensive security work.
|
|
607
|
+
|
|
608
|
+
## Security & operations
|
|
609
|
+
|
|
610
|
+
| Concern | Control | Where |
|
|
611
|
+
| --- | --- | --- |
|
|
612
|
+
| Outbound to RFC1918 / loopback / cloud IMDS | SSRF guard runs on every URL before fetch (allowlist override via `ESTORIDES_ALLOWED_HOSTS`) | `estorides_core/ssrf_guard.py` |
|
|
613
|
+
| Web DoS / scraping | Sliding-window per-IP rate limit (default 30/min; tune via `ESTORIDES_RATE_LIMIT`) | `estorides_core/audit.py` |
|
|
614
|
+
| Compliance trail | Append-only JSONL audit log of every API call (timestamp, IP, query, sources, status, latency) at `data/audit.jsonl` | `estorides_core/audit.py` |
|
|
615
|
+
| Adversarial input | `validate_query()` rejects empty, oversize, control-char, bidi-override, and unsupported-type queries; bidi is rejected outright rather than silently stripped | `estorides_core/validation.py` |
|
|
616
|
+
| API key leakage | Keys read from env at call time, never logged, never written to disk | `estorides_core/orchestrator.py` (`_resolve_auth`) |
|
|
617
|
+
| Encrypted report delivery | `age` (https://age-encryption.org) opt-in via `?key=age1…` on the export endpoint; graceful fallback to plaintext when `age` is missing | `estorides_export/encryption.py` |
|
|
618
|
+
| Trusting X-Forwarded-For | Only honoured when `ESTORIDES_TRUST_PROXY=1` is set explicitly | `estorides_web.py` |
|
|
619
|
+
| Target observing a recon probe | Per-source `contact` class; `--passive-only` (or `ESTORIDES_PASSIVE_ONLY=1`) keeps only `none` | `estorides_core/source_loader.py`, `orchestrator._select_sources` |
|
|
620
|
+
| Broker tying lookups to operator IP | Egress proxy/Tor (`--proxy`/`--tor`, `ESTORIDES_HTTP_PROXY[_POOL]`); fail-closed on missing SOCKS lib | `estorides_core/async_client.py` |
|
|
621
|
+
| DNS leak of investigated targets | Local resolution skipped when proxying (`ESTORIDES_PROXY_REMOTE_DNS=1`, default); literal-host guard still runs | `estorides_core/async_client.py` |
|
|
622
|
+
|
|
623
|
+
## Intelligence features
|
|
624
|
+
|
|
625
|
+
### Ontology engine — OFAC SDN cross-check
|
|
626
|
+
|
|
627
|
+
`estorides_core/ontology.py` loads the OpenSanctions OFAC SDN list
|
|
628
|
+
(CC-BY 4.0) once, indexes it by normalised name + alias, and stamps
|
|
629
|
+
every observation with `{sanctioned, hits, fields}`. The LLM analyst
|
|
630
|
+
stage then writes a "SANCTIONED — OFAC SDN match on …" line into the
|
|
631
|
+
brief so sanctions exposure is impossible to miss in the report.
|
|
632
|
+
|
|
633
|
+
Index characteristics:
|
|
634
|
+
|
|
635
|
+
- ~7 MB, low-tens-of-thousands of entries
|
|
636
|
+
- 24h lazy refresh
|
|
637
|
+
- Single-flight: concurrent first-loads share one fetch
|
|
638
|
+
- Best-effort disk cache at `data/ontology_sdn.json`
|
|
639
|
+
- Stale-on-error: keeps the previous snapshot if a refresh fails
|
|
640
|
+
|
|
641
|
+
### MITRE ATT&CK auto-tagging
|
|
642
|
+
|
|
643
|
+
`estorides_core/mitre_attack.py` maps every observation to the
|
|
644
|
+
ATT&CK techniques it might support, by both source-keyed table
|
|
645
|
+
(40+ techniques across the threat-intel, breach, and web sources)
|
|
646
|
+
and keyword scan (catches malware families: mimikatz, cobalt
|
|
647
|
+
strike, lockbit, …). Aggregated techniques are exposed at the top
|
|
648
|
+
of the orchestrator result as `result.mitre.techniques`.
|
|
649
|
+
|
|
650
|
+
### Real-time feeds
|
|
651
|
+
|
|
652
|
+
`estorides_core/feeds.py` ships three keyless feeds that the map
|
|
653
|
+
UI can layer on top of OSINT results:
|
|
654
|
+
|
|
655
|
+
| Feed | Source | Refresh | Notes |
|
|
656
|
+
| --- | --- | --- | --- |
|
|
657
|
+
| Earthquakes | USGS M2.5+ GeoJSON | 10 min | Always on |
|
|
658
|
+
| Fires | NASA FIRMS VIIRS_NOAA20_NRT CSV | 30 min | Requires `ESTORIDES_FIRMS_KEY` |
|
|
659
|
+
| News | GDELT 2.0 article list | 15 min | Coords unavailable; surfaces at (0,0) |
|
|
660
|
+
|
|
661
|
+
Endpoint: `GET /api/feeds?bbox=min_lon,min_lat,max_lon,max_lat&no_cache=1`.
|
|
662
|
+
|
|
663
|
+
### LLM prompt flavours
|
|
664
|
+
|
|
665
|
+
`estorides_llm/intelligence_prompts.py` ships three prompt styles:
|
|
666
|
+
|
|
667
|
+
- `system` — the default Palantir-grade analyst with BLUF + confidence-graded findings.
|
|
668
|
+
- `bluf` — single-paragraph BLUF only, for time-critical briefs.
|
|
669
|
+
- `tactical` — adds THREAT PICTURE + COA-1/2/3 + IMMEDIATE ACTION.
|
|
670
|
+
|
|
671
|
+
Backend priority is configurable: `ESTORIDES_BACKEND_PRIORITY=openai,ollama`
|
|
672
|
+
or via the `LLMManager` constructor.
|
|
673
|
+
|
|
674
|
+
## Tests
|
|
675
|
+
|
|
676
|
+
```bash
|
|
677
|
+
# All tests (pytest: BDD + ATDD + property-based fuzzing)
|
|
678
|
+
pytest -q
|
|
679
|
+
|
|
680
|
+
# Individual suites
|
|
681
|
+
pytest tests/test_security_remediation.py # SSRF, redirects, exception exposure
|
|
682
|
+
pytest tests/test_parsers.py # parser totality contract
|
|
683
|
+
pytest tests/properties # hypothesis fuzzing (1000 ex/property)
|
|
684
|
+
python3 _multi_test.sh # end-to-end: query types through the orchestrator
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
`pytest` is the single gate (also run by CI with `ruff`, `mypy --strict` and
|
|
688
|
+
`bandit`). The old root-level `_validate.py` runner and its `_test_*.py`
|
|
689
|
+
scripts were ported into `tests/` so every check runs under pytest/CI.
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
   [](https://www.gnu.org/licenses/agpl-3.0)
|
|
693
|
+
|
|
694
|
+
[](https://ko-fi.com/Y8Y2Z73AV)
|
|
695
|
+
|
|
696
|
+
<!-- readmenator-kb-link -->
|
|
697
|
+
## Knowledge Base
|
|
698
|
+
|
|
699
|
+
This project has been analyzed by [ReadMenator](https://github.com/grisuno/ReadMenator),
|
|
700
|
+
a zero-token polyglot static analysis tool. Regenerate with
|
|
701
|
+
`pip install readmenator && readmenator . --rebuild`. Analysis outputs:
|
|
702
|
+
|
|
703
|
+
- **[KNOWLEDGE_BASE.md](./KNOWLEDGE_BASE.md)** -- Full architecture reference with all
|
|
704
|
+
classes, functions, imports, dependency graphs, UML class diagrams, security
|
|
705
|
+
audit findings, community analysis, taint paths, hotspots, cycles, and more.
|
|
706
|
+
- **[readmenator-agent/](./readmenator-agent/)** -- Agent-friendly, grep-optimized index.
|
|
707
|
+
- `INDEX.md` -- Quick reference: what each file does
|
|
708
|
+
- `SYMBOLS.md` -- Every symbol with file:line
|
|
709
|
+
- `ARCHITECTURE.md` -- Internal dependency pairs + external imports
|
|
710
|
+
- `API.md` -- Public function contracts
|
|
711
|
+
- `GOTCHAS.md` -- Change warnings (blast radius, cycles, hotspots)
|
|
712
|
+
- `SECURITY.md` -- Findings by severity with fix hints
|
|
713
|
+
- `MEMORY.md` -- Rules, workflow, style, deliverables, session log
|
|
714
|
+
- `MANIFEST.json` -- Freshness (git_commit + source_fingerprint) + read order
|
|
715
|
+
- `KB_<subsystem>.md` -- Per-subsystem context
|
|
716
|
+
- `recipes/*.md` -- Grounded task guides (add-function, fix-cycle, ...)
|
|
717
|
+
- **[readmenator-wiki/](./readmenator-wiki/)** -- Navigable wiki (start here for the big picture).
|
|
718
|
+
- `index.md` -- Entry point: overview, reading order, god nodes, connections
|
|
719
|
+
- `community_*.md` -- One synthesis page per code community
|
|
720
|
+
- `connections.json` -- Typed bridges between communities
|
|
721
|
+
- `queries.md` -- Suggested questions + answer log
|
|
722
|
+
- `REPORT.md` -- Honest audit: coverage, confidence, limits
|
|
723
|
+
- **[readmenator-graphrag/](./readmenator-graphrag/)** -- Zero-token GraphRAG index.
|
|
724
|
+
- `index.json` + `REPORTS.md` -- Query with `readmenator . ask "<question>"`
|
|
725
|
+
- **[readmenator-apidocs/](./readmenator-apidocs/)** -- API reference (Sphinx/Doxygen style).
|
|
726
|
+
- `index.md` + `modules/*.md` -- Signatures, parameters, returns, raises, examples, call graphs
|
|
727
|
+
- `architecture.md` -- Tiered architecture + execution flows (control flow, call tree, sequence)
|
|
728
|
+
- `classes.md`, `genindex.md`, `COVERAGE.md` -- Hierarchy, A-Z index, doc coverage + warnings
|
|
729
|
+
- `index.html` + `api/` -- Offline searchable browser and machine-readable model (`api/index.json`, `api/modules/*.jsonl` one entity per line)
|
|
730
|
+
- **[readmenator-maps/](./readmenator-maps/)** -- Interactive gallery (`index.html`).
|
|
731
|
+
- 5 system maps: architecture, workflow, sequence, dataflow, lifecycle
|
|
732
|
+
- `graph-force.html` -- Force-graph explorer (2D/3D, reach, paths, lenses)
|
|
733
|
+
- `graph-bundle.html` -- Edge bundle explorer (circle + sphere)
|
|
734
|
+
- `components.html` -- Component diagrams
|
|
735
|
+
- **[docs/](./docs/)** -- Static site (GitHub Pages ready).
|
|
736
|
+
- Gallery copy + `llms.txt` (agent entry point) + `md/` docs + video
|
|
737
|
+
- **[readmenator-diagrams/](./readmenator-diagrams/)** -- Component diagrams.
|
|
738
|
+
- `analysis.json` + `graph LR` markdown + per-component pages + `index.html`
|
|
739
|
+
- **[readmenator-rules/](./readmenator-rules/)** -- Suggested Semgrep rules (`*.yml`),
|
|
740
|
+
`yara_rules.yar` (YARA-lite) + `exclusions.yaml` (false-positive blocklist)
|
|
741
|
+
- **[readmenator.mp4](./readmenator.mp4)** -- Cinematic synthwave overview video
|
|
742
|
+
- **[.cursorrules](./.cursorrules)** -- Generated AI assistant rules
|
|
743
|
+
- **[readmenator_audit.sarif](./readmenator_audit.sarif)** -- SARIF v2.1.0 audit (when security scan is on)
|
|
744
|
+
|
|
745
|
+
Cache (`.readmenator_cache/`), packaged agent skills (`.claude/skills/`), and the
|
|
746
|
+
GitHub wiki mirror are also produced on `run`/`--rebuild`.
|
|
747
|
+
|
|
748
|
+
AI agents: Read `readmenator-wiki/index.md` first for the big picture, then `readmenator-agent/INDEX.md` for grep-friendly lookup.
|
|
749
|
+
Developers: Read `KNOWLEDGE_BASE.md` for full architecture reference, open `readmenator-maps/index.html` for the visual gallery.
|
|
750
|
+
<!-- /readmenator-kb-link -->
|
|
751
|
+
|
|
752
|
+
<!-- deploynator-footer -->
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
Deployed with [deploynator](https://github.com/grisuno/deploynator)
|
|
757
|
+
|
|
758
|
+
<!-- /deploynator-footer -->
|