Skip to main content

adtree-viz

Intro

An Attack-Defense Tree modelling lib that allows user to model attack-defense scenarios using an internal DSL.

Project inspired by https://github.com/hyakuhei/attackTrees and https://github.com/tahti/ADTool2.

The main goals are:

  • add support for AND nodes
  • be able to break down a large tree into multiple subtrees.
  • keep it simple, only Attack and Defense nodes

Usage

Requirements:

  • Graphviz
  • Python 3.9

Install the library

pip install adtree-viz

Quick start

from adtree.models import Attack, Defence, AndGate, ADTree
from adtree.renderer import Renderer
from adtree.themes import RedBlueFillTheme

tree = ADTree("REFS.01", Attack("the goal", [
    Attack("path1", [
        Defence("defend path1", [
            Attack("path1 defence defeated")
        ])
    ]),
    Attack("path2", [
        Attack("path2.1"),
        AndGate([
            Attack("path3.1"),
            Attack("path3.2"),
        ]),
    ]),
]))

theme = RedBlueFillTheme()
renderer = Renderer(theme=theme, output_format="png", view=True)
renderer.render(tree=tree, filename="my-adtree")

The above should produce an attack-defence tree like this: attack-defence tree

Composing trees

Trees can be composed of multiple subtrees. Which of the subtrees get expanded is decided at render time based on the subtrees_to_expand variable.

from adtree.models import Attack, ADTree, ExternalADTree
from adtree.renderer import Renderer
from adtree.themes import NoFormatTheme

some_external_ref = ExternalADTree("EXT.01", "External resource covered by other docs")
some_internal_ref1 = ADTree("INT.01", root_node=Attack("internal path1", [
    Attack("path 1.1", [
        ADTree("INT.01.A", Attack("nested path 1.1A"))
    ])
]))
some_internal_ref2 = ADTree("INT.02", root_node=Attack("internal path2", [
    Attack("path 2.1")
]))
tree = ADTree("REFS.01", Attack("node1", [
    some_external_ref,
    some_internal_ref1,
    some_internal_ref2
]))

theme = NoFormatTheme()
renderer = Renderer(theme=theme, output_format="png", view=False)

# Default is to not expand
renderer.render(tree=tree, filename="default")

# Optionally expand some nodes
renderer.render(tree=tree, subtrees_to_expand=[some_internal_ref1], filename="partially_expanded")

The above will render two files.

One with all the subtrees collapsed (the default): attack-defence tree

And another file with one subtree expanded: attack-defence tree

Analysing trees

Currently, there is only one analyser available, the IsDefendedAnalyser. Traverse the tree and mark each nodes as either defended or undefended A node is considered defended if:

  1. is a Defence node and has no children
  2. is an Attack node and has a direct defended Defence node as child
  3. is an Attack or Defence node and all child nodes are defended nodes
  4. is an AndGate and at least one child node is defended

Example with custom rendering of the defended nodes

from adtree.models import NodeType, Node, Attack, ADTree, Defence, AndGate
from adtree.analysers import IsDefendedAnalyser
from adtree.renderer import Renderer
from adtree.themes import NoFormatTheme

class CustomIsDefendedTheme(NoFormatTheme):
    def get_node_attrs_for(self, node: Node):
        metadata_attrs = {
            "style": "filled"
        }
        if node.get_node_type() == NodeType.DEFENCE:
            metadata_attrs |= {
                "shape": "box",
            }
        if node.get_node_type() == NodeType.AND_GATE:
            metadata_attrs |= {
                "shape": "triangle",
            }
        if node.has_metadata(IsDefendedAnalyser.METADATA_KEY):
            fillcolor = "#C8FFCB" if node.get_metadata(IsDefendedAnalyser.METADATA_KEY) else "#FFD3D6"
            metadata_attrs |= {
                "fillcolor": fillcolor,
            }
        return metadata_attrs

tree = ADTree("REFS.01", Attack("the goal", [
    Attack("path1", [
        Defence("defend path1", [
            Attack("path1 defence defeated")
        ])
    ]),
    Attack("path2", [
        Attack("path2.1", [
            Defence("def2.1"),
            Attack("path2.1.1")
        ]),
        AndGate([
            Attack("path3.1"),
            Attack("path3.2", [
                Defence("defended")
            ]),
        ]),
    ]),
]))

analyser = IsDefendedAnalyser()
analyser.analyse_tree(tree)

theme = CustomIsDefendedTheme()
renderer = Renderer(theme=theme, output_format="png", view=False)

# Default is to not expand
renderer.render(tree=tree, filename="default")

The above should produce an attack-defence tree like this: attack-defence tree

Development

Create a venv

python3.9 -m venv venv

Activate

 . venv/bin/activate

Install deps

pip install -r requirements.txt

Run tests

PYTHONPATH=src python -m pytest

Run individual test file

PYTHONPATH=src python -m pytest ./test/adtree/test_theme.py

Run individual test methods

PYTHONPATH=src python  -m pytest --capture=no ./test/adtree/test_theme.py -k "metadata"

Release to Github and PyPi

Create tag and push

./release.sh

Manually build and release

Run the below to generate a distributable archive:

python3 -m build

The adtree-viz-x.xx.x.tar.gz archive can be found in the dist folder.

Deploy to PyPi

python3 -m twine upload -r pypi dist/*

# Use __token__ as username
# Use PyPi API TOKEN as password

Metadata

Release files for adtree-viz 0.0.10

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for adtree-viz 0.0.10
File Size Uploaded Provenance Actions
adtree-viz-0.0.10.tar.gz 11.2 kB Without provenance Details for adtree-viz-0.0.10.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for adtree-viz 0.0.10
File Interpreter ABI Platform Actions
adtree_viz-0.0.10-py3-none-any.whl Python 3 none any Details for adtree_viz-0.0.10-py3-none-any.whl

Total release size: 22.1 kB

Release files / adtree-viz-0.0.10.tar.gz

Download URL adtree-viz-0.0.10.tar.gz
Size 11.2 kB
Tags Source
SHA-256 checksum
How to use checksums
752ca90acdedf81faaeb218b5fecfed7dd11abc0091066c96ebd542696648643
BLAKE2b-256 checksum
How to use checksums
3766cb169f0f739369ffce799e7907b657495402f6a2ac78720952372f6edb92
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.11.1

Release files / adtree_viz-0.0.10-py3-none-any.whl

Download URL adtree_viz-0.0.10-py3-none-any.whl
Size 10.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f7aeacd303b2a2c43fd7a2099ebed74c0b66dad7c99f992a269d1e1c82712dba
BLAKE2b-256 checksum
How to use checksums
e1140a620a0e1dc0ecf347b717be1cb0b7d6808013759c8e2e32b4afb925c384
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.11.1

Security for adtree-viz 0.0.10

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page