MulVAL (as a Service) wraps MulVAL β a logic-based network security analyser β behind a gRPC/REST API to distribute its capabilities to third-party services.
Clients submit EDB facts and optional IDB interaction rules; the service runs MulVAL in the background and returns a Long-Running Operation (LRO) that resolves to the generated attack graph.
[!NOTE]
This project is research-grade software.
Changes can happen at any time in a non-backward-compatible way.
π What MulVal (as a Service) Does
- Simplify MulVal usage β Running MulVal can become quite complex depending on your infrastructure, so running it as a Service helps bootstrapping it fast to experiment;
- Manages multi-experiments β Every analysis is stored so can be shared among researchers and engineers in a lab, or used to iterate;
- Reusability β Designed as a microservice, MulVal (as a Service) provides a gRPC/HTTP REST API that can be used by third-party services to run their experiment;
- Visualization β Provide a UI that maps the API features for running in-browser experiments and visualize the results.
π§© Architecture
The service is stateless between requests; all durable state lives in PostgreSQL.
NATS JetStream is used only for completion notifications β WaitOperation subscribes to a per-operation subject and blocks until the executor publishes a state change, avoiding poll loops.
If NATS is unavailable the service degrades gracefully: WaitOperation returns after its timeout with done=false.
β‘ Quick start
# Write the configuration file config.yaml
# An example:
#
# logLevel: info
# events:
# url: nats://localhost:4222
# instanceID:
# from_env: HOSTNAME
# storage:
# dsn: postgres://user:secret@localhost:5432/mulval-backend
# schema: mulval
# migrate: true
# minConns: 4
# Start the MulVal (as a Service) Docker container.
# Add --ui for the web graphical interface.
docker run -p 8080:8080 -v config.yaml:/config.yaml cvewatcher/mulval:latest --config=/config.yaml --ui
# The service is now available at localhost:8080 (gRPC/HTTP REST)
# The web UI is at http://localhost:8080/ui/
Submitting an analysis via curl
curl -s -X POST http://localhost:8080/api/v1/analyses \
-H 'Content-Type: application/json' \
-d '{
"edbFacts": [
"attackerLocated(internet).",
"attackGoal(execCode(fileServer,_)).",
"hacl(internet, webServer, tcp, 80).",
"hacl(webServer, fileServer, tcp, 445).",
"hacl(H, H, _, _).",
"networkServiceInfo(webServer, httpd, tcp, 80, apache).",
"vulExists(webServer, '\''CVE-2021-44228'\'', httpd).",
"vulProperty('\''CVE-2021-44228'\'', remoteExploit, privEscalation).",
"networkServiceInfo(fileServer, smb, tcp, 445, root).",
"vulExists(fileServer, '\''CVE-2017-0144'\'', smb).",
"vulProperty('\''CVE-2017-0144'\'', remoteExploit, privEscalation)."
]
}'
This returns an LRO immediately:
{
"name": "operations/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"done": false
}
Poll until complete:
curl -s -X POST http://localhost:8080/api/v1/operations/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:wait \
-H 'Content-Type: application/json' \
-d '{"timeout": "30s"}'
When done: true, the response contains the full Analysis resource including verticesCsv, arcsCsv, and the parsed graph.
πΌοΈ User Interface

All analyses MulVal (as a Service) ran.

The details of an analysis, with a graph display of the results.

The creation form.
π¨ Development setup
OpenTelemetry
PostgreSQL
-
Setup:
docker run -d \
--name postgres \
-e POSTGRES_DB=mulval-backend \
-e POSTGRES_USER=user \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
postgres:16-alpine
-
Teardown:
docker rm -f postgres
-
Adminer, for debug purposes:
docker run -p 8082:8080 adminer
You can connect with:
- System:
PostgreSQL
- Server: The result of
echo "$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgres):5432"
- Username:
user
- Password:
secret
- Database:
mulval-backend
NATS JetStream