Continuous threat exposure management platform. Seed it with a domain, IP, or ASN and it maps your full attack surface as a graph — discovering subdomains, open ports, services, certificates, and CVEs by orchestrating open-source recon tools as isolated microservice workers.
Every finding is a node. Every causal relationship is a directed edge. The result is a queryable graph you can traverse from seed to CVE in a single query.
Early-stage implementation. The core architecture, worker model, and repository layout are established. Additional connectors, processing stages, and production hardening are still in development. Read what follows as the design it is being built to, not a description of a finished system.
- You POST a scan with a seed (e.g.
example.com, typeDomain) - The engine dispatches jobs to workers over NATS JetStream
- Workers run recon tools, publish findings back to the engine
- The engine merges nodes and edges into Neo4j, deduplicates, and dispatches downstream jobs for newly discovered nodes
- The cycle continues until no new nodes are produced
- Query the graph or view results via the API / Neo4j Browser
Domain ──RESOLVES_TO──► IP ──HAS_PORT──► Port ──RUNS_SERVICE──► Service
│ │
└──HAS_MX──► Hostname VULNERABLE_TO──► CVE
│
└──HAS_NS──► Hostname ──RESOLVES_TO──► IP ──HAS_PORT──► Port ...
| Component | Technology |
|---|---|
| API + engine | Go |
| Graph database | Neo4j 5 Community |
| Message bus | NATS JetStream |
| Workers | Go (one binary per tool) |
| Frontend | React + Vite (Phase 4) |
| Deployment | Docker Compose |
Prerequisites: Docker + Docker Compose
git clone https://github.com/DevelopSolutionsLLC/mycelium
cd mycelium
cp config.example.yaml config.yamlEdit config.yaml — set a strong admin_key, change the Neo4j password if needed.
make up # starts neo4j, nats, api, worker-dns
make logs # tail all service logsThe API is live at http://localhost:8080. Neo4j Browser is at http://localhost:7474.
Create a tenant and get an API key:
curl -X POST http://localhost:8080/api/tenants \
-H "Authorization: Bearer <your-admin-key>" \
-H "Content-Type: application/json" \
-d '{"name": "acme", "plan": "pro"}'
# Response includes api_key — save it, it's shown onceStart a scan:
curl -X POST http://localhost:8080/api/scans \
-H "Authorization: Bearer <tenant-api-key>" \
-H "Content-Type: application/json" \
-d '{"seed": "example.com", "seed_type": "Domain"}'Check results:
curl http://localhost:8080/api/scans/<scan-id>/graph \
-H "Authorization: Bearer <tenant-api-key>"Or open Neo4j Browser at http://localhost:7474 and run:
MATCH path = (d:Domain)-[*1..6]->(n)
WHERE d.value = 'example.com'
RETURN path LIMIT 100Each worker type is a separate container with independent resource limits. Scale any of them without touching the others:
make scale svc=worker-dns n=4
make scale svc=worker-nuclei n=2| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/health |
none | Health check |
POST |
/api/tenants |
admin key | Create tenant, returns API key |
POST |
/api/tenants/keys |
admin key | Add API key to existing tenant |
POST |
/api/scans |
tenant key | Start a scan |
GET |
/api/scans |
tenant key | List scans |
GET |
/api/scans/:id |
tenant key | Scan status + node count |
GET |
/api/scans/:id/graph |
tenant key | Full node/edge graph for the scan |
Seed types: Domain, Hostname, IP, URL, ASN, Email
| Worker | Status | Triggers | Produces |
|---|---|---|---|
dns |
✅ Built | Domain, Hostname | IP, Hostname (MX/NS) |
http |
🔜 Phase 2 | Hostname, IP | URL, Certificate, Service |
certs |
🔜 Phase 2 | Domain | Hostname, Certificate |
subfinder |
🔜 Phase 2 | Domain | Hostname |
nmap |
🔜 Phase 3 | IP | Port, Service |
nuclei |
🔜 Phase 3 | URL, Port | CVE |
shodan |
🔜 Phase 3 | IP | Port, Service, CVE |
Every worker implements a single interface:
type Worker interface {
Name() string
Subject() string
Process(ctx context.Context, job model.JobMsg) ([]model.Finding, error)
}Create workers/<name>/main.go, call base.Run(), add a Dockerfile, add the service to docker-compose.yml. The engine routes jobs to it automatically based on the NATS subject.
cmd/myco/ — API server + engine entrypoint
internal/
model/ — shared types (Node, Edge, Finding, Job, Result, Tenant, Scan)
graph/ — Neo4j client (tenant-scoped queries, MERGE helpers)
natsconn/ — NATS JetStream client (streams, pub/sub)
engine/ — result consumer, deduplication, job dispatch
api/ — Chi router, auth middleware, REST handlers
config/ — YAML config loader
workers/
base/ — Worker interface + Run() harness
dns/ — DNS resolution worker
ui/ — React + Vite frontend (Phase 4)
correlations/ — YAML correlation rules (Phase 3)
docker-compose.yml
Makefile
config.example.yaml
All data is namespace-isolated by tenant_id. Every node in Neo4j carries a tenant_id property and every query filters by it — tenants cannot see each other's data. API keys are hashed (SHA-256) at rest; the plain key is returned once at creation and never stored.
Plans: free (5 scans, 1K nodes), pro (50 scans, 50K nodes), enterprise (500 scans, 500K nodes).
# Run tests
go test ./...
# Build everything
go build ./...
# Local dev (requires neo4j + nats running separately)
cp config.example.yaml config.yaml
go run ./cmd/myco -config config.yaml
# UI dev server (proxies /api → localhost:8080)
cd ui && npm install && npm run dev