Open source
Cybersecurity tooling, built in the open
Three separate pieces of work with sFractal: a command and control system for security devices, a software bill of materials, plus a game that teaches security. All Elixir, all public.
- Collaboration
- sFractal and Podii
- Repositories
- 13, public
- Language
- Elixir
- My role
- Lead plus contributor
- Elixir
- Phoenix LiveView
- MQTT
- OpenC2
- SBOM
- CycloneDX
- OAuth
Everything here lives under github.com/sFractal-Podii, a long-running collaboration between sFractal and Podii. I lead the Podii team on it and write code on the codebases myself. On several of these repositories I am the largest human contributor.
These are three separate projects rather than one system. They share a language, a security bent and some people. Only the first is a single connected thing.
Project 1
Command and control for security devices
An implementation of OpenC2, the standard that lets one security system tell another what to do without a human in the middle.
OpenC2 is an OASIS standard for machine-to-machine command and control of cyber defence. When one system decides something must change, it says so to another system directly, in a form both understand, instead of a person copying instructions between two consoles.
It came out of work led by the NSA, which moved to OASIS as a technical committee in 2017. The framing it belongs to is Integrated Adaptive Cyber Defense, essentially the OODA loop applied to security operations and OpenC2 covers one phase of that loop: the acting. Observing and deciding already had standards. Carrying out the decision did not.
The problem it exists to solve is that attacks now happen faster than people can respond to them. If a detection system works out what is wrong in milliseconds, then a human reads an alert, opens a second vendor's console and types in a rule, the speed of the detection was wasted. OpenC2 is the vocabulary that lets the second step happen without the human, across products from different vendors written in different languages.
What the standard actually covers
Three roles, deliberately few
A Producer sends commands. A Consumer receives them. The Actuator is the Consumer that carries one out. That is the whole cast.
A command is an action and a target
Everything else is optional. A response, at minimum, is a status code. Keeping the required surface that small is what lets it run on constrained devices.
Actuator profiles say what a given device supports
The language describes what is expressible. A profile describes what a firewall, or an endpoint, or a log collector will actually accept. Only stateless packet filtering is finished.
Transfer is somebody else's problem
The language says nothing about how a message travels. Separate specifications cover HTTPS and MQTT, so the same command can cross either.
The design goals are stated plainly in the architecture specification: technology agnostic, concise, abstract, extensible. The first of those is the one that matters here. It is also the one a specification cannot achieve on its own.
Why write another implementation
A standard with one implementation is not really a standard. It is a specification with a reference build. Technology agnostic is a claim and the only thing that turns it into a fact is independent implementations, in unrelated languages, that interoperate without their authors having coordinated.
It is a plugfest implementation, not a demo
This codebase goes to OpenC2 interoperability plugfests, where implementations that have never met are pointed at each other. The repository still carries the artwork for the SBOM proof of concept plugfest, feeding the IACD community, CACAO playbooks, the Open Cybersecurity Alliance plus SCAP.
Elixir is not one of the usual suspects
Most reference work sits in the languages you would expect. An implementation on the BEAM tests whether the specification is genuinely portable or quietly shaped by the tools its authors happened to use.
It implements a profile that is still being written
The software bill of materials profile is in draft. Answering a query for one now, in a real device, is how a draft gets found wanting while it can still be changed.
Being strict is the contribution
A permissive implementation accepts things the standard does not allow and every one of those quietly makes the standard mean less. Refusing by name, with a reason, is what makes an interoperability test worth running.
The runtime suits the job
A consumer has to stay connected, survive the broker going away and handle messages arriving at once. That is the problem the BEAM was built for, so the interesting work stays in the validation rather than in the plumbing.
The specifications are public: the language specification, the architecture and the MQTT transfer specification.
Three pieces make up the working system. A library that understands the standard, a dashboard that issues commands, plus a device that receives them. The library is the part that matters, because it is what stands between a public message broker and a device doing something.
What a command looks like
A command is JSON with four fields. The device has to decide, for every message that arrives, whether this is something it recognises, something it recognises but cannot do, or something it should refuse outright.
The vocabulary is a list, not a guess
The permitted combinations are written down. An action nobody implemented, or a pairing that makes no sense for this device, is rejected by name rather than by something further down the call stack failing at runtime.
Two of those pairs are worth pointing at. query features asks the device what it can do and it answers with the profiles it implements against version 0.9.5 of the specification. query sbom asks the device what it is made of, which is the thread running into the second project below.
Every stage can refuse and refusal travels
Validation is eight stages long. Each one is narrow enough to name exactly what it rejects and each begins by checking whether an earlier stage already failed. If one did, the stage returns the command untouched.
The effect is that nothing raises. There is no error handling wrapped around the pipeline, because a malformed command from a public broker is not an exception, it is an ordinary result carrying the reason it was refused. The transport turns that into a reply.
The transport knows nothing about the standard
The library takes a chunk of text and hands back a result. It has no idea where that text came from. That separation is why the same library serves a device over MQTT here and could serve one over HTTP without changing.
Why the device is a twin
Blinky is a Raspberry Pi with an LED matrix, the usual hello world of IoT. Twinkly is the same device in the cloud, where an eight by eight grid of divs stands in for the LEDs. It subscribes to the same topic, runs the same library and validates the same way. Only the last step differs.
The talk stops depending on a suitcase
Demonstrating this at a conference otherwise means hardware in hand luggage, a bench to set it up on, plus a camera pointed at it so the back row can see.
A remote audience sees the real thing
Everyone watching gets the identical demo in their own browser rather than a video feed of somebody else's desk. Every connected browser updates at once.
The interesting half is unchanged
The command still crosses a public broker and still has to survive validation. Swapping diodes for divs changes nothing about the part worth showing.
Project 2
Knowing what your software is made of
A software bill of materials is an inventory of every dependency you ship, so that when a vulnerability lands in some package you can answer whether it affects you.
The question sounds simple until you try to answer it for something already running. Most inventories are produced by somebody auditing a system after the fact, which means they are correct on the day they were written and drifting from then on.
SbomPoc is the dashboard reporting the state of the proof of concept. The part I find more interesting is that the applications generate their own inventory as a build step, in CycloneDX, so the answer is a by-product of shipping rather than a task somebody remembers to run. That is also why a device will answer query sbom over the same channel used to command it. Asking a machine what it is made of becomes the same kind of operation as telling it what to do.
Project 3
A game that teaches security
Quadblock Quiz is a falling-blocks game, extended from Grox.io's quadblocks, where the security lesson is built into the board rather than bolted on as a quiz.
Plenty of security training is a slide deck followed by a multiple choice test. This takes a different route. Play long enough and vulnerable bricks start appearing on your board, with their own colour so you cannot miss them. They do not go away on their own.
Answering a security question correctly earns a power and the powers are named after what they remove: fix a vulnerability, fix a licence problem, or clear all of them at once. So the lesson arrives mechanically instead of being stated. An unfixed vulnerability keeps occupying space you need, licence problems behave the same way and the only route to clearing either is knowing the answer.
It gets taken to security conferences and run as a competition, with a contest layer holding state plus RSVPs on top of the game itself.
The question bank is content, not code
Questions live on disk in directories by category, found at runtime rather than compiled in and categories already answered are filtered out so a session does not repeat itself. Three build tasks validate the questions and the answers before anything ships, which means somebody writing questions never has to write Elixir and a malformed question fails in the pipeline rather than in front of a room.
Sign-in runs through GitHub, Google or LinkedIn, because at a conference stand nobody is going to create an account to try something for five minutes.
Think your organisation has outgrown its systems?
Let's figure out what is actually broken.