01
Your knowledge graph, your Postgres.
Findings, tech fingerprints, asset deltas — all in a Postgres 16 you administer. No multi-tenant SaaS holding your attack surface. No security vendor monetising your client list as a competitive moat.
Discovery · The intelligence graph for security work
Discovery is the layer that makes every Dhara scan smarter. A knowledge graph of tech fingerprints, CVEs, and recurring risk patterns — owned, in your Postgres, fed by every engagement. The fifth target is smarter than the first because the graph remembers.
Sovereign graph. Owned CVE feeds. The flywheel compounds inside your box.
Surface signal
Status
LIVE
Graph
Sovereign
API
discovery.eleven11.pro
Why this exists
Every security tool runs the same scan in isolation, throws an HTML report at you, and forgets the engagement. The next target starts from zero. The same django-CVE-2024-something fires across four clients and nobody notices the pattern. The intelligence is left on the floor.
Discovery is the layer that catches it. Every Dhara scan POSTs into the graph, every fingerprint upserts against an asset, every CVE links to the targets it touches. The fifth engagement is smarter than the first because the graph remembers.
Self-sustained by design
Sovereignty in security intelligence is not a checkbox — it's the difference between asking your graph a question and donating your client roster to a vendor's training pipeline.
01
Your knowledge graph, your Postgres.
Findings, tech fingerprints, asset deltas — all in a Postgres 16 you administer. No multi-tenant SaaS holding your attack surface. No security vendor monetising your client list as a competitive moat.
02
Owned CVE feed ingestion.
NVD, EPSS, OSV-PyPI flowing directly into the graph. KEV via tira proxy + RSS for the Hetzner-ASN block. No third-party threat-intel API charging per query for data that wants to be public.
03
No third-party model reading your targets.
The graph runs in your tenancy. Pattern queries, blast-radius rollups, surface-risk scores — all in your Postgres, executed by your worker. Nobody is uploading your client's subdomain list to enrich a global model.
04
The flywheel compounds locally.
Discovery → Dhara → /v1/hooks/dhara → graph update → smarter pattern queries on the next scan. The compounding happens inside the box. Cancel any external integration and the engine still turns.
05
Auditable ingest path.
Every dhara report lands as an idempotent upsert keyed by (target, template, host). Alert emits queue inside the transaction and flush on commit. A rolled-back ingest cannot leak a misleading alert into your channels.
The primitive
Discovery is built on a small, opinionated trio — an asset for what exists, a finding for what's wrong with it, a fingerprint for what it's built from. Every cross-target query is a join across these three. There is no fourth shape to learn.
δ.1 · Asset
assets / asset_changes
Subdomains, IPs, ports, certs, tech, leaks. Upserted by (target, asset_type, value, source). The asset_changes log records new / updated / disappeared / reappeared. Every Dhara host that the discovery sources missed gets added back here.
δ.2 · Finding
findings
Vulnerabilities ingested via /v1/hooks/dhara. Upserted by (target, template_id, host). OWASP field is normalized — dhara sometimes returns dict, sometimes bare string. CVE-linked, severity-graded, blast-radius queryable.
δ.3 · Fingerprint
tech_fingerprints
Technology inventory matched to assets by host. Upserted by (target, name, version, host). The grain that powers cross-target patterns — find every client running the django version that just got a CVE without re-scanning anyone.
How it fits the fleet
Discovery is rarely the surface customers think they're buying — but it's the layer that turns the rest of the fleet into a compounding asset. Every adjacent tool either feeds the graph or reads from it.
Dhara
Every Dhara scan POSTs report.json to /v1/hooks/dhara. Findings upsert, fingerprints upsert, missed hosts get added as assets, critical/high finding linked-asset severity escalates. The flywheel's drive train.
Outreach
Discovery surfaces a thin finding for an ABDM partner; outreach picks it up via /v1/targets/{id}/enrich-prospect and routes it into the campaign sequence. Surface intelligence becomes a sales motion without a human re-typing it.
Architect
/v1/intelligence/{patterns,surface-risk,cve/{id}} renders inside Architect's canvas as widgets. A matter page on a target shows live finding counts, the patterns view answers "what else runs this stack across our book."
Operator
operator.eleven11.pro/discovery and /discovery/intelligence are the human reads on top of the graph — target list, per-target findings + tech, the cross-target patterns view, surface-risk per target, blast radius per CVE.
Alerts
Four events wired: discovery.asset.new, discovery.asset.changed (only on fresh tech/vuln signal), discovery.finding.critical, discovery.scan.failed. Queued inside the ingest transaction and flushed after commit.
Sources
crt.sh, shodan, censys, dns, whois, builtwith, hibp, jobboards. Every source is optional — a missing key skips that source, never breaks the run. The ingest contract is the same shape regardless of who discovered the asset.
Surfaces & contracts
Discovery is API-first — one inbound webhook for the flywheel ingress, three intelligence reads, two per-target drill-downs. The smallest surface that does the job.
POST /v1/hooks/dhara
The flywheel ingress.
Inbound webhook from the dhara worker on scan completion. Hands off to ingest_dhara_report on the discovery.integrate queue. x-api-key auth, explicit User-Agent (dhara-worker/1.0) to clear the Cloudflare default-UA block.
GET /v1/intelligence/patterns
Cross-target patterns.
What recurring vulns / tech / CVE / exploited-in-the-wild signals span your book. The view that answers "what else runs this stack" without re-scanning anyone — straight off the graph.
GET /v1/intelligence/cve/{id}
Blast radius per CVE.
Pin a CVE, get every target and host that fingerprints into its match window. The query that turns a fresh KEV entry into a one-day customer call list.
GET /v1/intelligence/surface-risk
Per-target risk score.
Score + label per target, computed from the findings + assets graph. Two independent subqueries grouped by target_id, outer-joined to targets — the cartesian-product trap is documented and avoided.
GET /v1/targets/{id}/findings
Per-target vulnerability roll-up.
Every Dhara finding ever ingested for this target, with severity, OWASP class, CVE links, host. The drill-down behind the patterns view; the same shape Architect renders inside a matter page.
GET /v1/targets/{id}/tech
Per-target tech inventory.
Every fingerprint dhara has ever found on this target, by host. The substrate the patterns view reads from, and the join that lets you ask "is this client exposed to today's CVE?" without firing a new scan.
Senior engineering, visible
Five decisions visible in the upsert keys, the transaction shapes, the documented bug-traps, and the Celery registration discipline — not adjectives, design choices.
Idempotent upserts, not append-only logs.
Findings keyed by (target, template_id, host). Fingerprints keyed by (target, name, version, host). Assets keyed by (target, type, value, source). Re-ingesting the same report is a no-op. The graph stays clean across retries.
Alerts queue inside the transaction.
Critical-finding emits are staged during the ingest transaction and flushed only after db.commit(). A rolled-back ingest cannot leak a misleading alert. This is a deliberate ordering, not a side effect.
Cartesian-product trap, documented.
Surface-risk aggregation joining findings AND assets in the same SELECT explodes (18 × 29 = 522). The query uses two independent subqueries grouped by target_id, then outer-joins to targets. The bug is in the CLAUDE.md so it cannot be re-introduced.
Inconsistent upstream, normalized at the boundary.
Dhara's owasp field is sometimes a dict, sometimes a bare string, sometimes missing. The ingest task normalizes with isinstance checks at the parse boundary, so the rest of the graph never sees the variance.
Celery registration is a pull request, not a deploy gotcha.
Every new task module updates both imports=[...] and task_routes={...} in celery_app.py. Otherwise the task is silently unregistered and the webhook drops. The discipline is in the contributor checklist, not in tribal knowledge.
Who this is for
Discovery earns its keep when the cost of forgetting an engagement starts to exceed the cost of running a graph.
FAQ
Discovery maps surface — crt.sh, shodan, censys, dns, builtwith, hibp, job boards. Dhara validates. The split is deliberate: passive surface mapping is one job, opinionated active validation is another, and conflating them produces a worse version of both.
Discovery maps surface → Dhara validates → /v1/hooks/dhara POST → graph upserts findings + tech + missed assets → cross-target patterns query → next discovery cycle is smarter. CVE Intelligence slice 1 shipped 2026-05-08; matcher proven on django → 29 findings.
Because third-party threat-intel APIs charge per-query for data that wants to be public, and because your CVE feed should not be a vendor's growth metric. NVD, EPSS, OSV-PyPI flow directly into the graph. KEV is on the slice 1.5 path via tira proxy + RSS — the Hetzner ASN sits inside the CISA WAF block, so the workaround is engineered, not hidden.
operator.eleven11.pro/discovery/intelligence reads /v1/intelligence/{patterns,surface-risk,cve/{id}}. Cross-target patterns, per-target risk score + label, blast radius per CVE. The same routes Architect renders inside a matter page. Single graph, two human surfaces.
Discuss Discovery
Discovery is partner-deployed today — bundled with Dhara engagements or standalone for teams running their own scanners. Talk to us about ingest wiring, custom CVE feeds, or how to plug the graph into an existing security workflow.
Direct line
Consultation requests stay owned. We reply from e11 after reviewing fit and timing.