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.
Files changed (86) hide show
  1. estorides-1.3.0.dist-info/METADATA +758 -0
  2. estorides-1.3.0.dist-info/RECORD +86 -0
  3. estorides-1.3.0.dist-info/WHEEL +5 -0
  4. estorides-1.3.0.dist-info/entry_points.txt +2 -0
  5. estorides-1.3.0.dist-info/licenses/LICENSE +661 -0
  6. estorides-1.3.0.dist-info/top_level.txt +3 -0
  7. estorides_core/__init__.py +4 -0
  8. estorides_core/active_recon.py +359 -0
  9. estorides_core/alerter.py +323 -0
  10. estorides_core/async_client.py +454 -0
  11. estorides_core/audit.py +250 -0
  12. estorides_core/case_crypto.py +82 -0
  13. estorides_core/cases.py +491 -0
  14. estorides_core/change_detection.py +522 -0
  15. estorides_core/cloud_asset_discovery.py +94 -0
  16. estorides_core/code_exposure.py +212 -0
  17. estorides_core/config.py +674 -0
  18. estorides_core/discoverer.py +369 -0
  19. estorides_core/entity_extraction.py +550 -0
  20. estorides_core/entity_resolution.py +733 -0
  21. estorides_core/entity_store.py +165 -0
  22. estorides_core/envutil.py +68 -0
  23. estorides_core/event_bus.py +90 -0
  24. estorides_core/feeds.py +347 -0
  25. estorides_core/fusion_analytics.py +550 -0
  26. estorides_core/fusion_store.py +734 -0
  27. estorides_core/graph_bundle.py +519 -0
  28. estorides_core/graph_force.py +455 -0
  29. estorides_core/graph_kuzu.py +444 -0
  30. estorides_core/graph_rag_search.py +930 -0
  31. estorides_core/hypothesis_engine.py +687 -0
  32. estorides_core/ids.py +29 -0
  33. estorides_core/intel_resolver.py +809 -0
  34. estorides_core/job_registry.py +126 -0
  35. estorides_core/knowledge_graph.py +407 -0
  36. estorides_core/lan_bundle.py +415 -0
  37. estorides_core/lan_inventory.py +357 -0
  38. estorides_core/lan_policy.py +178 -0
  39. estorides_core/lan_runner.py +249 -0
  40. estorides_core/lan_scope.py +113 -0
  41. estorides_core/lan_web.py +165 -0
  42. estorides_core/mitre_attack.py +237 -0
  43. estorides_core/monitoring.py +410 -0
  44. estorides_core/observation_models.py +191 -0
  45. estorides_core/ontology.py +397 -0
  46. estorides_core/openapi.py +41 -0
  47. estorides_core/ops_observability.py +146 -0
  48. estorides_core/orchestrator.py +981 -0
  49. estorides_core/osiris_sources.py +525 -0
  50. estorides_core/pagination.py +124 -0
  51. estorides_core/parsers.py +1293 -0
  52. estorides_core/pdns_monitor.py +91 -0
  53. estorides_core/people_intel.py +209 -0
  54. estorides_core/pivot_engine.py +502 -0
  55. estorides_core/recon_fusion.py +432 -0
  56. estorides_core/recon_pipeline.py +86 -0
  57. estorides_core/relationship_inference.py +214 -0
  58. estorides_core/reliability_scoring.py +482 -0
  59. estorides_core/scope.py +397 -0
  60. estorides_core/search_telemetry.py +358 -0
  61. estorides_core/socmint.py +372 -0
  62. estorides_core/source_health_monitoring.py +359 -0
  63. estorides_core/source_loader.py +371 -0
  64. estorides_core/sqlite_store.py +119 -0
  65. estorides_core/ssrf_guard.py +270 -0
  66. estorides_core/supply_chain.py +141 -0
  67. estorides_core/system_app_sources.py +499 -0
  68. estorides_core/target_management.py +394 -0
  69. estorides_core/target_scoring.py +246 -0
  70. estorides_core/tech_fingerprint.py +214 -0
  71. estorides_core/tool_install.py +542 -0
  72. estorides_core/tool_runner.py +321 -0
  73. estorides_core/transforms.py +541 -0
  74. estorides_core/transliteration.py +146 -0
  75. estorides_core/validation.py +135 -0
  76. estorides_core/vuln_correlation.py +237 -0
  77. estorides_core/web_security.py +446 -0
  78. estorides_export/__init__.py +30 -0
  79. estorides_export/encryption.py +143 -0
  80. estorides_export/misp.py +85 -0
  81. estorides_export/recon_report.py +192 -0
  82. estorides_export/report.py +233 -0
  83. estorides_export/stix.py +151 -0
  84. estorides_llm/__init__.py +6 -0
  85. estorides_llm/intelligence_prompts.py +161 -0
  86. 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
+ ![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) ![Shell Script](https://img.shields.io/badge/shell_script-%23121011.svg?style=for-the-badge&logo=gnu-bash&logoColor=white) ![Flask](https://img.shields.io/badge/flask-%23000.svg?style=for-the-badge&logo=flask&logoColor=white) [![License: AGPL v3](https://img.shields.io/badge/License-AGPLv3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
693
+
694
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](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 -->