Nemesis: a centralised pipeline for the files an offensive assessment leaves behind
An offensive data enrichment pipeline
At a glance
- What is it?
- Nemesis is a SpecterOps platform that ingests, enriches and stores files collected during offensive security assessments, running on Docker with Dapr sidecars so each processing service is independently deployable.
- Who is it for?
- Nemesis fits a red team or a security operations group that collects many files across many engagements and needs one place to enrich them, index them and search them later, including with AI assistance. It is a poor fit for a single operator with one laptop and a handful of files, because the platform brings a service mesh, a message broker and a database with it, and it is a poor fit if you cannot run containers in a network that reaches your targets.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The idea: a VirusTotal for collected files
Nemesis is an open-source, centralised data processing platform that ingests, enriches, and allows collaborative analysis, with humans and AI, of files collected during offensive security assessments. The hero line in the README puts it as a centralised file processing platform that functions as an offensive VirusTotal.
That analogy is doing real work. VirusTotal works because many people submit the same file to one service and the accumulated submissions make the aggregate more valuable than any single submission. The same logic applied to offensive work means that when a red team collects a script, a binary or a configuration file during an engagement, that file goes into a shared system where it can be enriched, correlated and searched against everything collected before.
The project comes from SpecterOps, the creators of BloodHound, and the repository header says so directly. The topic tags are data-analysis and offensive, and the licence is BSD-3-Clause with a LICENSE file at the root. The last push was on 2026-09-23, and the releases are v2.2.2 from 2026-03-04, v2.2.1 from 2026-01-27 and v2.0.0 from 2025-06-28.
Version 2.0 was a rewrite rather than an increment, and the README says so plainly. Nemesis 2.0 is built on Docker with heavy Dapr integration, and the goal was to create a centralised file processing platform. The previous Nemesis 1.0.1 code base has been preserved as a branch, so the 1.x line is still there to read if you want to see the shape of the problem before the rewrite. The acknowledgements also credit OpenAI and Claude for helping with the rewrite, which tells you that the AI-assisted analysis capability is part of the 2.0 design rather than an afterthought.
Why Dapr is the load-bearing choice
Dapr is the unusual decision in this project, and the compose file shows exactly how it is used.
Each service gets a sidecar. The web-api service is paired with web-api-dapr, running the daprio/daprd image at version 1.17.7, and the sidecar is configured with the app id, the app port and the Dapr HTTP and gRPC ports:
web-api-dapr:
image: "daprio/daprd:1.17.7"
command:
[
"./daprd",
"--app-id",
"web-api",
"--max-body-size",
"1Gi",
"--app-port",
"8000",
"--dapr-http-port",
"3500",
"--dapr-grpc-port",
"50001",The effect is that services talk to each other through the sidecar rather than through direct service discovery, which is what lets a processing service be split out, scaled and replaced independently. For a pipeline that ingests a file, runs several enrichment modules over it, indexes the result and stores it, that decoupling is the difference between a monolith you rewrite and a set of services you deploy.
The project runs on Docker, and the compose file is a full platform definition rather than a single container. The named volumes list the stateful pieces: postgres_data, rabbitmq_data, seaweedfs_data, prometheus_data, grafana_data, loki_data, jaeger_data, phoenix_data and scheduler_data. So the state is in PostgreSQL, the queue is RabbitMQ, the object storage for the files themselves is SeaweedFS, and the observability stack is Prometheus with Grafana, Loki for logs and Jaeger for traces. The scheduling piece has its own service named scheduler in the volumes list, which is the component that decides what gets processed.
There is also a placement service in the depends_on block, and the web-api service waits for postgres, placement and rabbitmq, with rabbitmq required to be healthy rather than merely started.
The compose file is where the architecture is written down
The repository top level is a set of compose files rather than one, and the split tells you the deployment stories. There is compose.yaml, which defines the whole platform, plus compose.base.yaml, compose.override.yaml and compose.prod.build.yaml, so a development run and a production build are assembled from the same pieces with different overlays. The k8s directory holds the Kubernetes manifests, and infra/ holds the rest of the infrastructure definition.
The service definitions in compose.yaml carry a few details that are worth reading, because they show how the platform treats a file it has collected.
The web-api service mounts a host directory into the container at /mounted-containers, with the host path coming from an environment variable and a fallback of empty-mounted-containers. That is the ingestion path, where collected files land on the host and the platform picks them up. There is also a dedicated volume literally named empty, which is the placeholder for a path that is not set.
Two settings define the platform's behaviour around time. DEFAULT_EXPIRATION_DAYS has a default of 100, and LOG_LEVEL has a default of INFO.
Then there is the traffic configuration, which uses Traefik rather than exposing ports directly. The web-api service carries a set of Traefik labels that route on the path prefix /api, load balance to port 8000, terminate TLS on the websecure entrypoint, and apply an auth middleware. The healthcheck is a wget spider against http://localhost:8000/healthz every 10 seconds with a 5 second timeout and 5 retries, which is a real health check rather than a process check.
So the shape is: a Traefik front door with TLS and auth, an API service, a Dapr sidecar beside it, and behind them a set of processing services, all coordinated over RabbitMQ.
Enrichment modules, projects, and the code layout
The directory listing shows how the project is factored, and two of the entries are the important ones.
projects/ holds the individual services, the things the compose file refers to. libs/ holds the shared code, and one path inside it is instructive: libs/file_enrichment_modules/file_enrichment_modules/office_doc/office2john.py, which appears in the ruff exclude list. The library is a set of enrichment modules, one per file type or per analysis, and office2john is the module that turns an Office document into a hashcat-compatible format, a John the Ripper format or similar.
The other two directories are tools/ and docs/. There is a mkdocs.yml, so the documentation site is built from the docs directory, and the tools directory holds the project's own tooling.
The Python project configuration is in pyproject.toml, and it is worth reading because the lint policy is unusually explicit. Ruff is configured with a line length of 120, an indent width of 4 and a target version of py313. The select list names the ruleset in full: pycodestyle errors and warnings, pyflakes, isort, flake8-comprehensions, flake8-bugbear, pyupgrade, numpy-specific rules, flake8-builtins, and three flake8-type-checking rules for moving imports into a TYPE_CHECKING block.
The ignore list is where the honest parts are. E501 is ignored because line too long is handled by black, and B008 is ignored for function calls in argument defaults, which is the FastAPI dependency-injection idiom. C901 is ignored for cyclomatic complexity with a comment saying TODO enable later, and the ruff lint mccabe section at the bottom of the file is commented out entirely with max-complexity set to 20 and the same TODO.
Two entries in the exclude list are individual files rather than directories: office2john.py, which is presumably adapted from an upstream project with its own formatting, and projects/jupyter/jupyter_config.py. Excluding a specific file from linting is a signal that the file is vendored or generated, and both of these look like that.
What the platform costs you to run
The first limitation is scale. The compose file defines a platform with a reverse proxy, an API, a placement service, a scheduler, RabbitMQ, PostgreSQL, SeaweedFS, Prometheus, Grafana, Loki and Jaeger, each with its own volume. That is a substantial deployment for a tool whose input is a folder of collected files, and a single operator with one laptop and twenty files is not the case this was designed for. The project is aimed at a group running engagements and accumulating material over time.
The second limitation is the data retention question, which the default of 100 days for DEFAULT_EXPIRATION_DAYS raises but does not settle. Collected files from an offensive engagement are exactly the kind of material with legal and contractual implications, and a platform that centralises them across engagements needs a deliberate answer about how long they are kept, who can query them, and what happens when an engagement ends and its data should be destroyed.
The third is that the AI-assisted analysis capability depends on an external model, which means the collected data may be sent to a third party. The README credits OpenAI and Claude for helping with the rewrite, and collaborative analysis with humans and AI is part of the stated design, so a deployment has to decide where that processing happens.
The fourth is the dependency on the target being reachable from the platform. Nemesis is a central service, and the agents or collection tooling that produce the files have to be able to reach it, which in a locked-down environment is a real constraint.
On maintenance, the repository is not archived, the last push was on 2026-09-23, and the version is 2.2.x with the 2.0 rewrite having landed in mid-2025. The project keeps a CHANGELOG.md, an AGENTS.md and a CLAUDE.md, and the documentation site is at specterops.github.io/Nemesis with the quickstart and usage guides on docs.specterops.io. The upstream project also maintains a series of blog posts on the 2.x development, with the 2.x development guide dated March 2026.
Editorial conclusion
Nemesis fits a red team or a security operations group that collects many files across many engagements and needs one place to enrich them, index them and search them later, including with AI assistance. It is a poor fit for a single operator with one laptop and a handful of files, because the platform brings a service mesh, a message broker and a database with it, and it is a poor fit if you cannot run containers in a network that reaches your targets. Before adopting, read the quickstart guide in the documentation site, and plan for the data retention question, since the default expiration setting in the compose file is 100 days.
Frequently asked questions
What is Nemesis?
Nemesis is an open-source, centralized data processing platform that ingests, enriches, and allows collaborative analysis, with humans and AI, of files collected during offensive security assessments. The project describes the goal as a centralized file processing platform that functions as an offensive VirusTotal, built on Docker with heavy Dapr integration.
What does Dapr do in the Nemesis architecture?
Each service runs beside a Dapr sidecar, so services communicate through the sidecar rather than direct service discovery. In the compose file the web-api service is paired with a daprio/daprd sidecar at version 1.17.7, configured with the app id, the app port and the Dapr HTTP and gRPC ports.
What infrastructure does the Nemesis compose file require?
The volumes list shows the stateful pieces: PostgreSQL for data, RabbitMQ for the queue, SeaweedFS for file storage, Prometheus with Grafana, Loki and Jaeger for observability, plus Phoenix and scheduler volumes. The web-api service is exposed through Traefik with TLS on the websecure entrypoint and an auth middleware, and healthchecked on /healthz.
How do I install Nemesis?
The README points at the quickstart guide on the SpecterOps documentation site for setup, and at the Nemesis Usage Guide for usage. The repository itself holds compose.yaml plus compose.base.yaml, compose.override.yaml and compose.prod.build.yaml for local and production assembly, a k8s directory for Kubernetes manifests, and an env.example file for configuration.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/specterops-nemesis)