| name | attack-tree-construction |
| description | Build comprehensive attack trees to visualize threat paths. Use when mapping attack scenarios, identifying defense gaps, or communicating security risks to stakeholders. |
Attack Tree Construction
Systematic attack path visualization and analysis.
When to Use This Skill
- Visualizing complex attack scenarios
- Identifying defense gaps and priorities
- Communicating risks to stakeholders
- Planning defensive investments
- Penetration test planning
- Security architecture review
Core Concepts
1. Attack Tree Structure
[Root Goal]
|
┌────────────┴────────────┐
│ │
[Sub-goal 1] [Sub-goal 2]
(OR node) (AND node)
│ │
┌─────┴─────┐ ┌─────┴─────┐
│ │ │ │
[Attack] [Attack] [Attack] [Attack]
(leaf) (leaf) (leaf) (leaf)
2. Node Types
| Type | Symbol | Description |
|---|
| OR | Oval | Any child achieves goal |
| AND | Rectangle | All children required |
| Leaf | Box | Atomic attack step |
3. Attack Attributes
| Attribute | Description | Values |
|---|
| Cost | Resources needed | $, $$, $$$ |
| Time | Duration to execute | Hours, Days, Weeks |
| Skill | Expertise required | Low, Medium, High |
| Detection | Likelihood of detection | Low, Medium, High |
Templates
Template 1: Attack Tree Data Model
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Dict, Optional, Union
import json
class NodeType(Enum):
OR = "or"
AND = "and"
LEAF = "leaf"
class Difficulty(Enum):
TRIVIAL = 1
LOW = 2
MEDIUM = 3
HIGH = 4
EXPERT = 5
class Cost(Enum):
FREE = 0
LOW = 1
MEDIUM = 2
HIGH = 3
VERY_HIGH = 4
class DetectionRisk(Enum):
NONE = 0
LOW = 1
MEDIUM = 2
HIGH = 3
CERTAIN = 4
@dataclass
class AttackAttributes:
difficulty: Difficulty = Difficulty.MEDIUM
cost: Cost = Cost.MEDIUM
detection_risk: DetectionRisk = DetectionRisk.MEDIUM
time_hours: float = 8.0
requires_insider: bool = False
requires_physical: bool =
:
:
name:
description:
node_type: NodeType
attributes: AttackAttributes = field(default_factory=AttackAttributes)
children: [] = field(default_factory=)
mitigations: [] = field(default_factory=)
cve_refs: [] = field(default_factory=)
() -> :
.children.append(child)
() -> :
.node_type == NodeType.LEAF:
.attributes.difficulty.value
.children:
child_difficulties = [c.calculate_path_difficulty() c .children]
.node_type == NodeType.OR:
(child_difficulties)
:
(child_difficulties)
() -> :
.node_type == NodeType.LEAF:
.attributes.cost.value
.children:
child_costs = [c.calculate_path_cost() c .children]
.node_type == NodeType.OR:
(child_costs)
:
(child_costs)
() -> :
{
: .,
: .name,
: .description,
: .node_type.value,
: {
: .attributes.difficulty.name,
: .attributes.cost.name,
: .attributes.detection_risk.name,
: .attributes.time_hours,
},
: .mitigations,
: [c.to_dict() c .children]
}
:
name:
description:
root: AttackNode
version: =
() -> [AttackNode]:
._find_path(.root, minimize=)
() -> [AttackNode]:
._find_path(.root, minimize=)
() -> [AttackNode]:
._find_path(.root, minimize=)
() -> [AttackNode]:
node.node_type == NodeType.LEAF:
[node]
node.children:
[node]
node.node_type == NodeType.OR:
best_path =
best_score = ()
child node.children:
child_path = ._find_path(child, minimize)
score = ._path_score(child_path, minimize)
score < best_score:
best_score = score
best_path = child_path
[node] + (best_path [])
:
path = [node]
child node.children:
path.extend(._find_path(child, minimize))
path
() -> :
metric == :
(n.attributes.difficulty.value n path n.node_type == NodeType.LEAF)
metric == :
(n.attributes.cost.value n path n.node_type == NodeType.LEAF)
metric == :
(n.attributes.detection_risk.value n path n.node_type == NodeType.LEAF)
() -> [AttackNode]:
leaves = []
._collect_leaves(.root, leaves)
leaves
() -> :
node.node_type == NodeType.LEAF:
leaves.append(node)
child node.children:
._collect_leaves(child, leaves)
() -> [AttackNode]:
[n n .get_all_leaf_attacks() n.mitigations]
() -> :
json.dumps({
: .name,
: .description,
: .version,
: .root.to_dict()
}, indent=)
Template 2: Attack Tree Builder
class AttackTreeBuilder:
"""Fluent builder for attack trees."""
def __init__(self, name: str, description: str):
self.name = name
self.description = description
self._node_stack: List[AttackNode] = []
self._root: Optional[AttackNode] = None
def goal(self, id: str, name: str, description: str = "") -> 'AttackTreeBuilder':
"""Set the root goal (OR node by default)."""
self._root = AttackNode(
id=id,
name=name,
description=description,
node_type=NodeType.OR
)
self._node_stack = [self._root]
return self
def or_node(self, id: str, name: str, description: str = "") -> 'AttackTreeBuilder':
"""Add an OR sub-goal."""
node = AttackNode(
id=id,
name=name,
description=description,
node_type=NodeType.OR
)
self._current().add_child(node)
._node_stack.append(node)
() -> :
node = AttackNode(
=,
name=name,
description=description,
node_type=NodeType.AND
)
._current().add_child(node)
._node_stack.append(node)
() -> :
node = AttackNode(
=,
name=name,
description=description,
node_type=NodeType.LEAF,
attributes=AttackAttributes(
difficulty=difficulty,
cost=cost,
detection_risk=detection,
time_hours=time_hours
),
mitigations=mitigations []
)
._current().add_child(node)
() -> :
(._node_stack) > :
._node_stack.pop()
() -> AttackTree:
._root:
ValueError()
AttackTree(
name=.name,
description=.description,
root=._root
)
() -> AttackNode:
._node_stack:
ValueError()
._node_stack[-]
() -> AttackTree:
(
AttackTreeBuilder(, )
.goal(, )
.or_node(, )
.attack(
, ,
difficulty=Difficulty.LOW,
cost=Cost.LOW,
detection=DetectionRisk.MEDIUM,
mitigations=[, ]
)
.attack(
, ,
difficulty=Difficulty.TRIVIAL,
cost=Cost.LOW,
detection=DetectionRisk.HIGH,
mitigations=[, , ]
)
.attack(
, ,
difficulty=Difficulty.MEDIUM,
cost=Cost.MEDIUM,
detection=DetectionRisk.MEDIUM,
mitigations=[, ]
)
.end()
.or_node(, )
.attack(
, ,
difficulty=Difficulty.MEDIUM,
cost=Cost.LOW,
detection=DetectionRisk.LOW,
mitigations=[, ]
)
.attack(
, ,
difficulty=Difficulty.HIGH,
cost=Cost.LOW,
detection=DetectionRisk.LOW,
mitigations=[, , ]
)
.end()
.or_node(, )
.and_node(, )
.attack(
, ,
difficulty=Difficulty.LOW,
cost=Cost.FREE,
detection=DetectionRisk.NONE
)
.attack(
, ,
difficulty=Difficulty.MEDIUM,
cost=Cost.FREE,
detection=DetectionRisk.MEDIUM,
mitigations=[, ]
)
.end()
.end()
.build()
)
Template 3: Mermaid Diagram Generator
class MermaidExporter:
"""Export attack trees to Mermaid diagram format."""
def __init__(self, tree: AttackTree):
self.tree = tree
self._lines: List[str] = []
self._node_count = 0
def export(self) -> str:
"""Export tree to Mermaid flowchart."""
self._lines = ["flowchart TD"]
self._export_node(self.tree.root, None)
return "\n".join(self._lines)
def _export_node(self, node: AttackNode, parent_id: Optional[str]) -> str:
"""Recursively export nodes."""
node_id = f"N{self._node_count}"
self._node_count += 1
if node.node_type == NodeType.OR:
shape = f"{node_id}(({node.name}))"
elif node.node_type == NodeType.AND:
shape = f"{node_id}[{node.name}]"
else:
style = ._get_leaf_style(node)
shape =
._lines.append()
._lines.append()
parent_id:
connector = node.node_type != NodeType.AND
._lines.append()
child node.children:
._export_node(child, node_id)
node_id
() -> :
colors = {
Difficulty.TRIVIAL: ,
Difficulty.LOW: ,
Difficulty.MEDIUM: ,
Difficulty.HIGH: ,
Difficulty.EXPERT: ,
}
color = colors.get(node.attributes.difficulty, )
color
:
():
.tree = tree
() -> :
lines = [
,
,
]
._export_node(.tree.root, lines, )
lines.append()
.join(lines)
() -> :
prefix = * (depth + )
node.node_type == NodeType.OR:
marker =
node.node_type == NodeType.AND:
marker =
:
diff = node.attributes.difficulty.name
marker =
lines.append()
child node.children:
._export_node(child, lines, depth + )
Template 4: Attack Path Analysis
from typing import Set, Tuple
class AttackPathAnalyzer:
"""Analyze attack paths and coverage."""
def __init__(self, tree: AttackTree):
self.tree = tree
def get_all_paths(self) -> List[List[AttackNode]]:
"""Get all possible attack paths."""
paths = []
self._collect_paths(self.tree.root, [], paths)
return paths
def _collect_paths(
self,
node: AttackNode,
current_path: List[AttackNode],
all_paths: List[List[AttackNode]]
) -> None:
"""Recursively collect all paths."""
current_path = current_path + [node]
if node.node_type == NodeType.LEAF:
all_paths.append(current_path)
return
if not node.children:
all_paths.append(current_path)
return
if node.node_type == NodeType.OR:
for child in node.children:
self._collect_paths(child, current_path, all_paths)
else:
child_paths = []
for child node.children:
child_sub_paths = []
._collect_paths(child, [], child_sub_paths)
child_paths.append(child_sub_paths)
combined = ._combine_and_paths(child_paths)
combo combined:
all_paths.append(current_path + combo)
() -> [[AttackNode]]:
child_paths:
[[]]
(child_paths) == :
[path paths child_paths path paths]
result = [[]]
paths child_paths:
new_result = []
existing result:
path paths:
new_result.append(existing + path)
result = new_result
result
() -> :
leaves = [n n path n.node_type == NodeType.LEAF]
total_difficulty = (n.attributes.difficulty.value n leaves)
total_cost = (n.attributes.cost.value n leaves)
total_time = (n.attributes.time_hours n leaves)
max_detection = ((n.attributes.detection_risk.value n leaves), default=)
{
: (leaves),
: total_difficulty,
: total_difficulty / (leaves) leaves ,
: total_cost,
: total_time,
: max_detection,
: (n.attributes.requires_insider n leaves),
: (n.attributes.requires_physical n leaves),
}
() -> [[AttackNode, ]]:
paths = .get_all_paths()
node_counts: [, [AttackNode, ]] = {}
path paths:
node path:
node. node_counts:
node_counts[node.] = (node, )
node_counts[node.] = (node, node_counts[node.][] + )
(
node_counts.values(),
key= x: x[],
reverse=
)
() -> :
all_paths = .get_all_paths()
blocked_paths = []
open_paths = []
path all_paths:
path_attacks = {n. n path n.node_type == NodeType.LEAF}
path_attacks & mitigated_attacks:
blocked_paths.append(path)
:
open_paths.append(path)
{
: (all_paths),
: (blocked_paths),
: (open_paths),
: (blocked_paths) / (all_paths) * all_paths ,
: [
{: [n.name n p], : .calculate_path_metrics(p)}
p open_paths[:]
]
}
() -> []:
critical_nodes = .identify_critical_nodes()
paths = .get_all_paths()
total_paths = (paths)
recommendations = []
node, count critical_nodes:
node.node_type == NodeType.LEAF node.mitigations:
recommendations.append({
: node.name,
: node.,
: count,
: count / total_paths * ,
: node.attributes.difficulty.name,
: node.mitigations,
})
(recommendations, key= x: x[], reverse=)
Best Practices
Do's
- Start with clear goals - Define what attacker wants
- Be exhaustive - Consider all attack vectors
- Attribute attacks - Cost, skill, and detection
- Update regularly - New threats emerge
- Validate with experts - Red team review
Don'ts
- Don't oversimplify - Real attacks are complex
- Don't ignore dependencies - AND nodes matter
- Don't forget insider threats - Not all attackers are external
- Don't skip mitigations - Trees are for defense planning
- Don't make it static - Threat landscape evolves
Resources