Model or dataset
center-for-threat-informed-defense/attack-flow avatar
center-for-threat-informed-defense/attack-flow

Attack Flow is a language, a schema and a builder for how attacks compose

Attack Flow helps executives, SOC managers, and defenders easily understand how attackers compose ATT&CK techniques into attacks by developing a representation of attack flows, modeling attack flows for a small corpus of incidents, and creating visualization tools to display attack flows.

787 stars132 forksTypeScriptApache-2.0

At a glance

What is it?
Attack Flow is an Apache-2.0 project from the MITRE Center for Threat-Informed Defense that describes how adversaries sequence ATT&CK techniques, with the language defined as a JSON Schema and rendered through a browser builder. It models incidents rather than collecting telemetry, so it is a reasoning tool, not a detection pipeline.
Who is it for?
Adopt Attack Flow when you need a shared, reviewable representation of how techniques chain into an incident, especially if your audience includes executives and SOC managers who need the story rather than the raw evidence, and if you are willing to write flows by hand against the JSON Schema.
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 10 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A language for composition, not a tool that runs attacks

Attack Flow is a language for describing how cyber adversaries combine and sequence offensive techniques to reach a goal. The stated audience is defenders and leaders: executives, SOC managers, and people who need to understand how an intrusion unfolded rather than re-derive it from logs. The project exists so that a compromise can be written down as an ordered story of techniques, reviewed by other people, and reused as a model.

It is created and maintained by the MITRE Center for Threat-Informed Defense, and the funding comes from the research participants rather than from a commercial sponsor. The repository is Apache-2.0 licensed, with copyright 2021 MITRE and an approved for public release document number CT0040. The README also states that the project makes use of MITRE ATT&CK, so the ATT&CK terms of use sit alongside the Apache licence when you redistribute work built on it.

Two facts frame everything else. The repository is written in TypeScript and in Python, the language is specified as a JSON Schema, and the version of ATT&CK the project is aligned with is shown as 19.1 in the README badges. This is a modelling and visualisation project. It does not collect telemetry, it does not detect anything, and it does not touch your estate.

The specification is a JSON Schema and the corpus is the argument

The language itself is delivered as a JSON Schema, version 2.0.0, and the repository path for it is stix/attack-flow-schema-2.0.0.json. That choice matters for adoption: a flow is a structured document that you can validate against a published schema, diff in version control, and generate tooling for. The Python side depends on jsonschema at a pinned 4.17.3, which is how validation happens in the tooling that ships with the project.

Alongside the schema sits a corpus of example flows under corpus/, and the README describes the ambition as modeling attack flows for a small corpus of incidents. That is an honest limit rather than a promise. The corpus exists so that people can see the language applied to real incidents, and the project explicitly asks readers to help prioritise which examples should be turned into flows next, which is a way of saying the coverage is deliberately partial.

The Python dependencies explain the data model in practice: stix2 at version 3.0.1 or later for STIX objects, networkx for graph handling, and graphviz for rendering. Flow documents are therefore graph-shaped data, and the choice of a graph library rather than an ad hoc tree is a design decision with consequences for how you query a flow later.

The builder is a TypeScript app compiled to static files

Attack Flow Builder is the browser GUI for creating flows, and the README points newcomers at it as the easy way in, with the schema and the example flows waiting for people who want to go deeper. The build is a two stage Docker image. The first stage compiles the TypeScript builder, and the second serves the finished assets from nginx:

dockerfile
FROM node:20 AS builder

COPY src/attack_flow_builder /attack_flow_builder
WORKDIR /attack_flow_builder
RUN npm ci

The full Dockerfile copies the built dist directory into an nginx:1.21-alpine image and puts it under the web root, so what ships is static files served by a small web server rather than a long running application server. The type checking step is currently disabled in the build, with a note in the file that this is temporary, and the build runs npm run build-only.

That architecture has a consequence worth naming. A static builder is easy to host and easy to cache, but the interesting behaviour in this project sits in the API, not the page, and the online builder at the documentation site is described as one route into it rather than the only one.

Self-hosting splits into an API, a UI and a proxy

The repository documents deployment as separate pieces. There is a Dockerfile for the UI, a Dockerfile for the API and a Dockerfile for a proxy, with matching compose files named docker-compose.api.yml, docker-compose.ui.yml, docker-compose.proxy.yml and a combined docker-compose.yml. The guidance for running the whole thing yourself lives in docs/deployment-self-hosting.md, and the API behaviour, including worker fusion notes, is described separately in src/attack_flow_api/README.md.

The combined compose file is the simplest starting point:

yaml
version: '3.8'

services:
  attack-flow:
    image: ghcr.io/center-for-threat-informed-defense/attack-flow:latest
    ## Uncomment this to use the local Dockerfile instead of the official image
    #build:
    #  context: .
    #  dockerfile: ./Dockerfile
    environment:
      LOG_LEVEL: ${LOG_LEVEL:-INFO}
    ports:
      - 8080:80
    restart: always

The image comes from the GitHub Container Registry with a latest tag, and the service publishes host port 8080 to container port 80, with the container restarted always. Commenting out the build section and uncommenting the local one switches from the published image to a build from the checkout, which is what you want when you are changing the builder.

The Python package sits on the same repository, with a poetry configuration that names two packages, attack_flow and attack_flow_api, both taken from the src directory, and it exposes a single command line entry point named af, wired to attack_flow.cli:main.

The .env file decides what the API is allowed to do

The configuration surface is a single .env.example file, and it is more revealing about the project's shape than the prose documentation. The API runtime block sets APP_NAME, APP_ENV in production mode, API_HOST on 0.0.0.0, API_PORT and API_BIND_PORT on 8000, API_PREFIX to /api/v1, and LOG_LEVEL to INFO. Cross origin requests are off by default, with CORS_ENABLED false and empty allowed origins.

Storage is file based and hardened by default. DATA_DIR points at /var/lib/attack-flow/data, with a SQLite database, an upload directory and an artifact directory beneath it, and FILE_STORAGE_STRICT_MODE turned on with FILE_STORAGE_MAX_BYTES set to 10485760, so uploads and artifacts are capped at ten megabytes.

The provider block is where this stops being only a visualisation tool. PROVIDERS_CONFIG_PATH points at /app/config/providers.yml, and there are placeholders for OPENAI_API_KEY, COMPATIBLE_PROVIDER_API_KEY, AZURE_OPENAI_API_KEY, ANTHROPIC_API_KEY and GEMINI_API_KEY, with LITELLM_LOCAL_MODEL_COST_MAP set to true. Runtime overrides for request supplied provider settings exist but are switched off by default through ALLOW_RUNTIME_PROVIDER_OVERRIDE, and the allowed provider types are listed as openai, openai_compatible and azure_openai. There is also a runtime timeout of 180 seconds and a certs/ directory for placing a corporate root certificate that the API container imports at startup.

A Python 3.12 floor and a pinned dependency set

The service side is a poetry project, and its dependency pins are specific enough to plan around:

toml
python = ">=3.12,<3.15"
jsonschema = "4.17.3"
stix2 = "^3.0.1"
networkx = "^2.8.6"
graphviz = "^0.20.1"

The Python range is narrow on purpose, and it means an environment on 3.11 cannot run the API without an interpreter upgrade. The pins are the sort that protect reproducibility at the cost of upgrade friction: jsonschema is fixed to one exact version, while stix2, networkx and graphviz are held to compatible release ranges.

The API extras are optional and live in their own dependency group, which means the core package installs without them. They pull in FastAPI, uvicorn with the standard extras, pydantic and pydantic-settings, pyyaml, python-multipart, pypdf and litellm. The presence of litellm and the provider key placeholders is the clearest statement of intent: the API can call language models, and the model provider configuration is read from a YAML file you supply rather than being hard coded.

For development, the dev group includes pytest with coverage, flake8 and mock plugins, bandit for security linting, safety for dependency checks, black for formatting, bumpver for version bumps and Sphinx for the documentation build, with the docs group adding a Sphinx theme and the wagtail based design package.

Where the modelling stops and the work starts

The central limitation is that Attack Flow does not tell you anything about your own systems. It describes attacks, so producing a flow for a real incident still means someone has read the telemetry, mapped the activity to techniques and written the document. The tooling then makes that document reviewable, which is a real gain, but it does not remove the analysis.

The second limitation is corpus coverage. The project describes itself as modelling flows for a small corpus of incidents, and it asks the community to prioritise further examples. A team whose intrusions do not resemble the published flows will get the language and the builder, and will have to author their own corpus before the tool becomes useful to anyone else.

The third is the schema version. The specification is at version 2.0.0 while the package version in the poetry configuration reads 4.0, and releases v4.0.0 and v4.1.0 were both published on 2026-10-14, with v3.2.0 before that on 2026-05-15. Two major versions on the same day is a signal that the data model is still moving, so flows written today may need to be checked against a later schema.

Editorial conclusion

Adopt Attack Flow when you need a shared, reviewable representation of how techniques chain into an incident, especially if your audience includes executives and SOC managers who need the story rather than the raw evidence, and if you are willing to write flows by hand against the JSON Schema. Do not adopt it as a detection or telemetry system, since nothing in it reads logs, and do not expect the corpus to cover your incidents, because the project describes it as a small corpus. Before you commit, read stix/attack-flow-schema-2.0.0.json to see what the language actually permits, review the ATT&CK terms of use alongside the Apache-2.0 licence, and decide whether you want the hosted builder or the Docker stack in docs/deployment-self-hosting.md, since the self-hosted path adds an API, a proxy and your own provider keys.

Frequently asked questions

What is Attack Flow and who maintains it?

Attack Flow is a language for describing how adversaries combine and sequence offensive techniques into attacks. It is created and maintained by the MITRE Center for Threat-Informed Defense and is funded by the project's research participants.

How is the Attack Flow language specified?

The specification is expressed as a JSON Schema, version 2.0.0, published in the repository at stix/attack-flow-schema-2.0.0.json, with a corpus of example flows under corpus/. The toolchain depends on jsonschema and stix2 to work with those documents.

Can I self-host the Attack Flow Builder and API?

Yes. The repository ships Dockerfile.api, Dockerfile.ui and Dockerfile.proxy files with matching compose files, and docs/deployment-self-hosting.md describes deploying the API and UI separately with Docker. The combined docker-compose.yml publishes port 8080 to container port 80 using the published image.

What configuration does the Attack Flow API expect?

The .env.example file sets the API host and port, the /api/v1 prefix, a data directory under /var/lib/attack-flow/data, and file storage limits with strict mode enabled. Model provider settings are read from config/providers.yml and the placeholders for OPENAI_API_KEY, AZURE_OPENAI_API_KEY, ANTHROPIC_API_KEY and GEMINI_API_KEY are set there.

Official sources

  1. center-for-threat-informed-defense/attack-flow on GitHub
  2. License: Apache-2.0
  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/center-for-threat-informed-defense-attack-flow.svg)](https://hysenlabs.com/projects/center-for-threat-informed-defense-attack-flow)

Was this useful?