Self-hosted service
Ostorlab/oxo avatar
Ostorlab/oxo

OXO: an orchestrator that runs a dozen security scanners as one job

OXO is a security scanning orchestrator for the modern age.

583 stars60 forksPythonApache-2.0

At a glance

What is it?
OXO is Ostorlab's open source scanning engine, and its idea is that the interesting part of security scanning is not the individual tool but the orchestration, so it runs Nmap, Nuclei, ZAP, WhatWeb, Subfinder, VirusTotal and others as isolated agents in Docker, unifies their output into one vulnerability list, and exposes everything through a GraphQL API for CI. The cost of that design is that Docker is not optional.
Who is it for?
Adopt OXO if you are running several scanners against the same targets and want one job, one result list and a GraphQL API instead of a shell script and several output formats, especially if you scan mobile packages or API schemas that individual tools do not cover.
Can I use it commercially?
Yes. Apache-2.0 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 2 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 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The idea is orchestration, not scanning

OXO is described as a security scanning framework built for modularity, scalability and simplicity, and it is open source and maintained by Ostorlab. The description in the repository is that OXO Engine combines specialized tools to work cohesively to find vulnerabilities and perform actions like recon, enumeration and fingerprinting.

That is the whole thesis. Any individual scanner already exists and is well understood. What is tedious is running five of them against the same host, correlating their output, keeping track of which one found what, and getting the result into a pipeline. OXO moves that work into a framework.

The store makes the point concrete. OXO ships with a store that boasts dozens of agents, from network scanning agents like Nmap, Nuclei and Tsunami, web scanners like ZAP, web fingerprinting tools like WhatWeb and Wappalyzer, DNS brute-forcing tools like Subfinder and Dnsx, and malware file scanning like VirusTotal. Each of these is an agent, and an agent is a standard Docker image, so the orchestrator's job is to start the right containers, feed them the target, and normalise what they produce.

The other key features follow from that. Modular and scalable means you can combine multiple specialised agents for comprehensive scans. Broad asset support means you can scan anything from IP addresses and domains to mobile applications for Android, iOS and HarmonyOS, and API schemas. Extensible means there is built-in support for creating and publishing your own agents using a simple Python-based framework. And API first means there is a GraphQL API for integration into CI/CD pipelines and other automated workflows.

The primary language is Python, the licence is Apache-2.0, the homepage is oxo.ostorlab.co, and the recent releases are v2.18.0 from 2026-10-20, v2.17.0 from 2026-10-14 and v2.16.0 from 2026-10-14. The last push was on 2026-10-20 and the project is not archived, with the release cadence showing several releases in a single week.

Installing it, and the Docker requirement that shapes everything

The requirements section is one line and it is the one that decides whether this tool fits your environment: Docker is required to run scans locally.

The installation itself is a single pip command, because OXO ships as a Python package on PyPI:

shell
pip install -U ostorlab

The interesting detail is that the package name on PyPI is ostorlab, not oxo. The command line is reachable as either oxo or ostorlab, so the install name and the binary name differ, which is a small trap for scripts and CI configurations.

Docker being mandatory rather than optional is worth understanding, because it is not an implementation detail. Each agent is a Docker image, and the orchestration is the act of running those images against a target and collecting results. There is no path where OXO runs a scanner in-process, and the Docker requirement section says as much.

That has a knock-on effect. The README shows the image-based invocation, and it is instructive:

shell
docker run -v /var/run/docker.sock:/var/run/docker.sock ostorlab/oxo:latest scan run --install --agent agent/ostorlab/nmap ip 8.8.8.8

Two notes accompany it. The command starts directly with scan run because the ostorlab/oxo image has oxo as its entrypoint. And it is important to mount the Docker socket so OXO can create agents on the host machine, because the orchestrator is not running the scanners itself, it is asking the host's Docker daemon to run them.

That last point is the one to sit with before deploying this anywhere. Mounting the Docker socket into a container is functionally equivalent to giving it root on the host, and a scanner orchestrator that runs community-supplied agent images is a container that can do anything the Docker daemon can do. On a workstation that is a considered risk. On a shared build agent, it is a question for whoever owns that machine.

Running a scan and reading the results

The getting started section shows the shape of a real invocation, and it is a single command with one flag per agent and the target at the end:

shell
oxo scan run --install --agent agent/ostorlab/nmap --agent agent/ostorlab/tsunami --agent agent/ostorlab/nuclei ip 8.8.8.8

The --install flag is what makes that one line work. Without it you would have to have the agent images already present; with it, OXO downloads and installs the three named agents, and the README lists them afterwards with links to their store pages. The target is given as ip 8.8.8.8, which is the asset type followed by the value.

The asset vocabulary is the first thing to get your head around, and the table in the README is the reference. The Network category has ip for an address or range, both v4 and v6, and domain-name. The Web category has link, which accepts a URL along with a method, headers and request body, and api-schema for OpenAPI or GraphQL schemas. The Mobile category is the widest: android-apk and android-aab for package files, android-store for an app in the Google Play Store, ios-ipa for a package file, ios-store for the App Store, ios-testflight, and harmonyos-apk and harmonyos-hap. Then Other has file for a generic file, phone-number, and agent, which is meta-scanning of an agent.

The scanning commands are then simple. To check the scan status:

shell
oxo scan list

To read the findings, two commands, one to list and one to describe a specific vulnerability:

shell
oxo vulnz list --scan-id <scan-id>
oxo vulnz describe --vuln-id <vuln-id>

And to stop a scan that is going the wrong way:

shell
oxo scan stop --scan-id <scan-id>

That is a small command surface, and the reason is that the orchestration happens server-side. The scan runs, the agents do their work in containers, and the client talks to the service about the job. The results are addressed by identifiers, which is what makes the GraphQL API useful for a pipeline that wants to consume findings programmatically rather than scrape a terminal.

Writing and publishing an agent

The store is where OXO turns from a tool into a platform, and the publishing process is described in five steps.

You clone a template agent with all files already set up. You change the template_agent.py file to add your logic. You change the Dockerfile by adding any extra building steps. You change the ostorlab.yaml by adding selectors, documentation, image and license. And then you publish it on the store.

That last part is what makes the model work, and the README explains the mechanics: the store handles agent building and will automatically pick up new releases from your Git repo. So publishing an agent means pointing the store at a repository, and every commit to that repository becomes a new agent version. There is no release process to run by hand.

The three files to change map cleanly onto the three concerns. template_agent.py is where your scanning logic goes, the Dockerfile is where you add the system dependencies your tool needs, and ostorlab.yaml is the manifest where you declare the metadata that makes the agent discoverable, including the selectors that decide which assets the agent applies to.

The README also lists ideas for agents to build, which is a good indication of the intended scope. Several are struck through, meaning implemented or abandoned, and the remaining ones are concrete: nbtscan, which scans for open NetBIOS name servers on the target's network, onesixtyone, a fast scanner to find publicly exposed SNMP services, Retire.js, which detects the use of JavaScript libraries with known vulnerabilities, and cve-bin-tool, which scans binaries for vulnerable components. The struck-through entries are semgrep for source code scanning, snallygaster, testssl.sh for TLS weaknesses including Heartbleed, CRIME and ROBOT, and TruffleHog for searching Git repositories for high-entropy strings and secrets.

For a team, that list is a good template for deciding what to build. A grep for secrets in a repository, an audit of exposed SNMP, a check for outdated JavaScript libraries in a frontend build, those are all small wrappers around existing tools, and the framework is a way to publish them once rather than maintaining a shell script per scan.

Running it as a service, and the log options that matter on-prem

The on-premise scanner mode has its own command and its own log handling, and the log options are the part worth reading.

The command is oxo scanner with a scanner id, and the key flag is --persist-logs:

shell
oxo scanner --scanner-id <scanner-uuid> --persist-logs

By default the logs are written to ~/.ostorlab/scanner.log, and to choose another file you add the log-file option:

shell
oxo scanner --scanner-id <scanner-uuid> --persist-logs --log-file /var/log/ostorlab/scanner.log

And to change the verbosity of the persisted log:

shell
oxo scanner --scanner-id <scanner-uuid> --persist-logs --log-level DEBUG

The distinction between the on-premise scanner mode and the ordinary scan-and-read workflow is worth noting. A scanner is a long-running service that holds the agent infrastructure, and --persist-logs is what makes it diagnosable after the fact. A scan that fails halfway through with no log is the failure mode you will actually hit, and the fact that the log location and level are both explicit flags rather than buried in a config file is a small kindness for an operator.

The Dockerfile for the project is worth a look for a different reason: it installs the same package four times with different extras, google-cloud-logging, agent, serve and scanner, each into a prefix, and then the runtime stage copies the install prefix into the image. The four extras are the four modes the product runs in, and the Dockerfile makes that structure explicit rather than hiding it behind a single entry point.

The base image is python:3.14-alpine, which tells you the project is tracking a recent Python rather than pinning an old one.

How to evaluate it, and the questions to ask first

There are three questions, and the first two are answerable from the documentation while the third is a policy decision rather than a technical one.

The first is whether Docker is available in your environment and acceptable in it. OXO requires it, the agents are Docker images, and the orchestrator needs the Docker socket to create them. If your scanning happens on a laptop, that is fine. If it happens inside a locked-down CI runner that deliberately withholds the Docker socket, then OXO is not deployable there as documented, and the workarounds are yours to design.

The second is which assets you actually need. The table is broad, and the mobile and API schema support is genuinely more than most orchestrators attempt, but broad coverage is not the same as good coverage of your case. The store listing, and the list of agents you would install for a given target, tell you what the combination will and will not find.

The third is the supply chain question, and it is the one to raise with whoever owns the machine. Agents are third-party Docker images pulled at scan time, and a community-published agent is code someone else wrote that will run with the privileges of the Docker daemon. The store does the building and the publishing, and the template makes writing one straightforward, which is exactly why the review step matters. A curated internal store of agents you have read is a very different proposition from a public one.

What you get for that is real convenience. One command starts a coordinated multi-scanner job, the results are unified into a single vulnerability list addressed by scan id and vulnerability id, the GraphQL API lets a pipeline consume that list without parsing tool-specific output, and the Docker image lets you run the same thing in a container. For a team that today runs four scanners in a shell script and pastes three output formats into a spreadsheet, that is a clear improvement, and the price is the Docker socket and the trust you place in the agent store.

Editorial conclusion

Adopt OXO if you are running several scanners against the same targets and want one job, one result list and a GraphQL API instead of a shell script and several output formats, especially if you scan mobile packages or API schemas that individual tools do not cover. Do not adopt it in a locked-down CI runner that withholds the Docker socket, because OXO needs that socket to start agent containers on the host, and treat the public agent store as untrusted code that runs with host privileges. Before you commit, decide whether you will curate your own agents rather than pulling from the public store, test one scan with the --install flag against an asset you own, and check that the GraphQL API covers the result fields your pipeline needs before you rewrite the existing scripts around it.

Frequently asked questions

What is OXO?

It is a security scanning framework built for modularity, scalability and simplicity, open source and maintained by Ostorlab. It combines specialized tools to work cohesively to find vulnerabilities and perform actions like recon, enumeration and fingerprinting, with a GraphQL API, an agent store and per-vendor packages.

How do I install and run an OXO scan?

Install the package with pip install -U ostorlab, then run a scan such as oxo scan run --install with one --agent flag per agent and the target at the end, for example against an ip asset. Docker is required to run scans locally.

Which asset types can OXO scan?

Network assets such as an ip address or range in v4 and v6 and a domain name, web assets including a link with method, headers and body and an api-schema, mobile assets covering Android, iOS and HarmonyOS packages and store listings including TestFlight, and other assets such as a generic file, a phone number and meta-scanning of an agent.

How do I write and publish an OXO agent?

Clone a template agent, change template_agent.py to add your logic, change the Dockerfile for extra build steps, change the ostorlab.yaml with selectors, documentation, image and licence, and publish it on the store. The store handles agent building and automatically picks up new releases from your Git repository.

How do I see the results of an OXO scan?

Check the status with oxo scan list, then read the findings with oxo vulnz list for a scan id and oxo vulnz describe for a specific vulnerability id. A running scan can be stopped with oxo scan stop for a given scan id.

Official sources

  1. License: Apache-2.0
  2. Ostorlab/oxo on GitHub
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ostorlab-oxo.svg)](https://hysenlabs.com/projects/ostorlab-oxo)