首页
/ attack-tree-construction 实战:用 Python 数据模型构建与量化分析攻击树(Agentic Security 技能包)

attack-tree-construction 实战:用 Python 数据模型构建与量化分析攻击树(Agentic Security 技能包)

2026-09-09 19:48:17作者:瞿蔚英Wynne

攻击树(Attack Tree)是威胁建模中最直观的可视化工具:以攻击者目标为根节点,逐层分解出子目标与具体攻击步骤,让安全团队一眼看清"系统可能被怎样攻破"。本篇文章以 agents24/agents 仓库中 security-scanning 插件的 attack-tree-construction 技能为骨架,完整讲解该技能配套的四套可运行模板——数据模型、流式构建器、Mermaid/PlantUML 导出器与攻击路径分析器,并结合仓库中 threat-modeling-expert 等配套资源,说明如何在安全架构评审、渗透测试规划与防御投资决策中落地使用。读完本文,你将获得一套可直接复制运行的攻击树建模代码,掌握 OR/AND/叶节点聚合规则、最易/最省/最隐蔽路径搜索,以及基于路径覆盖率的缓解措施优先级排序方法。

技能定位:attack-tree-construction 在仓库中的角色

在 agents24/agents 这个 Multi-harness agentic plugin marketplace 中,security-scanning 插件是 SAST 分析与漏洞扫描领域的核心模块,位于 plugins/security-scanning。该插件由以下组件构成:

  • agentssecurity-auditor(DevSecOps 与合规审计专家)、threat-modeling-expert(精通 STRIDE、PASTA、攻击树与安全需求提取);
  • commandssecurity-dependenciessecurity-hardeningsecurity-sast 三个斜杠命令;
  • skillsattack-tree-constructionsast-configurationsecurity-requirement-extractionstride-analysis-patternsthreat-mitigation-mapping 五个渐进式技能。

技能目录遵循渐进式披露(progressive disclosure)约定:SKILL.md 提供核心概念与使用时机,详细模板放在 references/details.md 中按需加载。正如 docs/agent-skills.md 中描述的那样,attack-tree-construction 的职责是"构建将威胁场景映射到漏洞的攻击树"。本技能与仓库内其他威胁建模技能构成完整链路:先用 stride-analysis-patterns 系统识别威胁,再用 attack-tree-construction 对关键路径建树,继而由 security-requirement-extraction 提炼安全需求,最后通过 threat-mitigation-mapping 映射缓解控制。

核心概念回顾:攻击树结构与节点类型

SKILL.md(见 SKILL.md)定义了攻击树的三要素:

                    [Root Goal]
                         |
            ┌────────────┴────────────┐
            │                         │
       [Sub-goal 1]              [Sub-goal 2]
       (OR node)                 (AND node)
            │                         │
      ┌─────┴─────┐             ┌─────┴─────┐
      │           │             │           │
   [Attack]   [Attack]      [Attack]   [Attack]
    (leaf)     (leaf)        (leaf)     (leaf)
类型 图形符号 语义
OR 椭圆 任一子节点达成即可实现目标
AND 矩形 所有子节点都必须达成
Leaf 方框 原子攻击步骤(不可再分解)

每个叶节点需要标注攻击属性:成本(Cost)、耗时(Time)、技能要求(Skill)、被发现可能性(Detection)。SKILL.md 同时给出最佳实践:从明确目标出发、穷举所有攻击向量、为攻击标注属性、随威胁演进持续更新、用红队评审校验——同时避免过度简化、忽略 AND 依赖、遗漏内部威胁、跳过缓解设计、把威胁模型做成静态文档。

模板一:Attack Tree 数据模型

references/details.md 的第一个模板定义了攻击树的完整数据模型,包含四个枚举与三个数据类,是后续所有模板的基础。

属性枚举:难度、成本、检测风险

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

注意两个细节:其一,Difficulty(攻击者所需技能)与 SKILL.md 中的文字化"Low/Medium/High"相比,细化为五档(TRIVIAL 到 EXPERT);Cost 细化为五档、DetectionRisk 细化为五档,数值上从低到高递增,为后续量化计算提供了可比较的标度。其二,这些 Enum 的数值成员会在路径聚合算法中直接参与数学运算,因此顺序与数值必须稳定。

AttackAttributes:单步攻击的属性载体

@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 = False

AttackAttributes 除了三个枚举型属性外,还包含 time_hours(预计执行小时数)、requires_insider(是否需要内部人员配合)、requires_physical(是否需要物理接触)三个扩展字段,为内部威胁与物理攻击场景提供建模能力——这与 SKILL.md "Don't forget insider threats" 的最佳实践一一对应。

AttackNode:树的节点单元

@dataclass
class AttackNode:
    id: str
    name: str
    description: str
    node_type: NodeType
    attributes: AttackAttributes = field(default_factory=AttackAttributes)
    children: List['AttackNode'] = field(default_factory=list)
    mitigations: List[str] = field(default_factory=list)
    cve_refs: List[str] = field(default_factory=list)

    def add_child(self, child: 'AttackNode') -> None:
        self.children.append(child)

    def calculate_path_difficulty(self) -> float:
        """Calculate aggregate difficulty for this path."""
        if self.node_type == NodeType.LEAF:
            return self.attributes.difficulty.value

        if not self.children:
            return 0

        child_difficulties = [c.calculate_path_difficulty() for c in self.children]

        if self.node_type == NodeType.OR:
            return min(child_difficulties)
        else:  # AND
            return max(child_difficulties)

    def calculate_path_cost(self) -> float:
        """Calculate aggregate cost for this path."""
        if self.node_type == NodeType.LEAF:
            return self.attributes.cost.value

        if not self.children:
            return 0

        child_costs = [c.calculate_path_cost() for c in self.children]

        if self.node_type == NodeType.OR:
            return min(child_costs)
        else:  # AND
            return sum(child_costs)

    def to_dict(self) -> Dict:
        """Convert to dictionary for serialization."""
        return {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "type": self.node_type.value,
            "attributes": {
                "difficulty": self.attributes.difficulty.name,
                "cost": self.attributes.cost.name,
                "detection_risk": self.attributes.detection_risk.name,
                "time_hours": self.attributes.time_hours,
            },
            "mitigations": self.mitigations,
            "children": [c.to_dict() for c in self.children]
        }

节点携带 mitigations(缓解措施列表)与 cve_refs(关联 CVE 编号列表)两个关键字段,让攻击树不止于"威胁地图",还能直接服务防御规划与漏洞关联分析。

节点上的聚合算法是理解整个数据模型的核心,两条规则贯穿全文:

  • OR 节点聚合:难度取子节点最小值(攻击者会选最省力的分支),成本取子节点最小值;
  • AND 节点聚合:难度取子节点最大值(木桶效应,最难的步骤决定整条路径),成本取子节点之和(所有步骤都要花钱)。

to_dict() 提供了无递归依赖的 JSON 序列化接口,供导出与持久化使用。

AttackTree:树级分析 API

@dataclass
class AttackTree:
    name: str
    description: str
    root: AttackNode
    version: str = "1.0"

    def find_easiest_path(self) -> List[AttackNode]:
        """Find the path with lowest difficulty."""
        return self._find_path(self.root, minimize="difficulty")

    def find_cheapest_path(self) -> List[AttackNode]:
        """Find the path with lowest cost."""
        return self._find_path(self.root, minimize="cost")

    def find_stealthiest_path(self) -> List[AttackNode]:
        """Find the path with lowest detection risk."""
        return self._find_path(self.root, minimize="detection")

    def _find_path(
        self,
        node: AttackNode,
        minimize: str
    ) -> List[AttackNode]:
        """Recursive path finding."""
        if node.node_type == NodeType.LEAF:
            return [node]

        if not node.children:
            return [node]

        if node.node_type == NodeType.OR:
            # Pick the best child path
            best_path = None
            best_score = float('inf')

            for child in node.children:
                child_path = self._find_path(child, minimize)
                score = self._path_score(child_path, minimize)
                if score < best_score:
                    best_score = score
                    best_path = child_path

            return [node] + (best_path or [])
        else:  # AND
            # Must traverse all children
            path = [node]
            for child in node.children:
                path.extend(self._find_path(child, minimize))
            return path

    def _path_score(self, path: List[AttackNode], metric: str) -> float:
        """Calculate score for a path."""
        if metric == "difficulty":
            return sum(n.attributes.difficulty.value for n in path if n.node_type == NodeType.LEAF)
        elif metric == "cost":
            return sum(n.attributes.cost.value for n in path if n.node_type == NodeType.LEAF)
        elif metric == "detection":
            return sum(n.attributes.detection_risk.value for n in path if n.node_type == NodeType.LEAF)
        return 0

    def get_all_leaf_attacks(self) -> List[AttackNode]:
        """Get all leaf attack nodes."""
        leaves = []
        self._collect_leaves(self.root, leaves)
        return leaves

    def _collect_leaves(self, node: AttackNode, leaves: List[AttackNode]) -> None:
        if node.node_type == NodeType.LEAF:
            leaves.append(node)
        for child in node.children:
            self._collect_leaves(child, leaves)

    def get_unmitigated_attacks(self) -> List[AttackNode]:
        """Find attacks without mitigations."""
        return [n for n in self.get_all_leaf_attacks() if not n.mitigations]

    def export_json(self) -> str:
        """Export tree to JSON."""
        return json.dumps({
            "name": self.name,
            "description": self.description,
            "version": self.version,
            "root": self.root.to_dict()
        }, indent=2)

AttackTree 提供的分析 API 值得逐一点评:

  • find_easiest_path / find_cheapest_path / find_stealthiest_path:回答"攻击者最可能走哪条路"这一核心问题。实现中,OR 节点选取子路径得分最低者(_path_score 只对叶节点累加对应属性值),AND 节点则必须串联全部子路径。这三个指标恰好对应 SKILL.md 属性表中的 Skill(难度)、Cost、Detection 三维评估。
  • get_unmitigated_attacks:直接返回所有"没有缓解措施"的叶节点,是防御缺口审计的即查即用接口,可以推断它应与 threat-mitigation-mapping 技能的"Critical Gaps reporting"配合产出。
  • export_json:输出带缩进的 JSON 文档,便于存入安全知识库或交给下游工具。

模板二:AttackTreeBuilder 流式构建器与账户接管实战示例

手写嵌套 AttackNode 容易出错,第二个模板提供了流式(fluent)构建器,用链式调用把树的"深度优先构造"变得直观:

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)
        self._node_stack.append(node)
        return self

    def and_node(self, id: str, name: str, description: str = "") -> 'AttackTreeBuilder':
        """Add an AND sub-goal (all children required)."""
        node = AttackNode(
            id=id,
            name=name,
            description=description,
            node_type=NodeType.AND
        )
        self._current().add_child(node)
        self._node_stack.append(node)
        return self

    def attack(
        self,
        id: str,
        name: str,
        description: str = "",
        difficulty: Difficulty = Difficulty.MEDIUM,
        cost: Cost = Cost.MEDIUM,
        detection: DetectionRisk = DetectionRisk.MEDIUM,
        time_hours: float = 8.0,
        mitigations: List[str] = None
    ) -> 'AttackTreeBuilder':
        """Add a leaf attack node."""
        node = AttackNode(
            id=id,
            name=name,
            description=description,
            node_type=NodeType.LEAF,
            attributes=AttackAttributes(
                difficulty=difficulty,
                cost=cost,
                detection_risk=detection,
                time_hours=time_hours
            ),
            mitigations=mitigations or []
        )
        self._current().add_child(node)
        return self

    def end(self) -> 'AttackTreeBuilder':
        """Close current node, return to parent."""
        if len(self._node_stack) > 1:
            self._node_stack.pop()
        return self

    def build(self) -> AttackTree:
        """Build the attack tree."""
        if not self._root:
            raise ValueError("No root goal defined")
        return AttackTree(
            name=self.name,
            description=self.description,
            root=self._root
        )

    def _current(self) -> AttackNode:
        if not self._node_stack:
            raise ValueError("No current node")
        return self._node_stack[-1]

构建器的设计要点:

  • 维护一个 _node_stack 栈模拟遍历状态,goal() 建立根节点,or_node() / and_node() 入栈,end() 弹栈返回父节点,attack() 只在当前栈顶挂叶节点——这保证任何时刻新增的节点都精确挂到"当前位置";
  • attack() 的参数默认值与 AttackAttributes 保持一致(难度/成本/检测风险默认 MEDIUM,耗时默认 8 小时),实际建模时可以只覆盖关心的维度;
  • build() 在缺少根目标时抛出 ValueError,防止构造出无意义的不完整树。

实战:账户接管攻击树(Account Takeover)

模板随后给出了完整的账户接管场景示例,演示了 OR(凭据窃取、认证绕过、社会工程三条互为替代的路径)、AND(S3.1 账号恢复攻击需"收集个人信息 + 致电支持台"两步骤齐备)、以及每条攻击的难度/成本/检测风险和缓解列表:

# Example usage
def build_account_takeover_tree() -> AttackTree:
    """Build attack tree for account takeover scenario."""
    return (
        AttackTreeBuilder("Account Takeover", "Gain unauthorized access to user account")
        .goal("G1", "Take Over User Account")

        .or_node("S1", "Steal Credentials")
            .attack(
                "A1", "Phishing Attack",
                difficulty=Difficulty.LOW,
                cost=Cost.LOW,
                detection=DetectionRisk.MEDIUM,
                mitigations=["Security awareness training", "Email filtering"]
            )
            .attack(
                "A2", "Credential Stuffing",
                difficulty=Difficulty.TRIVIAL,
                cost=Cost.LOW,
                detection=DetectionRisk.HIGH,
                mitigations=["Rate limiting", "MFA", "Password breach monitoring"]
            )
            .attack(
                "A3", "Keylogger Malware",
                difficulty=Difficulty.MEDIUM,
                cost=Cost.MEDIUM,
                detection=DetectionRisk.MEDIUM,
                mitigations=["Endpoint protection", "MFA"]
            )
        .end()

        .or_node("S2", "Bypass Authentication")
            .attack(
                "A4", "Session Hijacking",
                difficulty=Difficulty.MEDIUM,
                cost=Cost.LOW,
                detection=DetectionRisk.LOW,
                mitigations=["Secure session management", "HTTPS only"]
            )
            .attack(
                "A5", "Authentication Bypass Vulnerability",
                difficulty=Difficulty.HIGH,
                cost=Cost.LOW,
                detection=DetectionRisk.LOW,
                mitigations=["Security testing", "Code review", "WAF"]
            )
        .end()

        .or_node("S3", "Social Engineering")
            .and_node("S3.1", "Account Recovery Attack")
                .attack(
                    "A6", "Gather Personal Information",
                    difficulty=Difficulty.LOW,
                    cost=Cost.FREE,
                    detection=DetectionRisk.NONE
                )
                .attack(
                    "A7", "Call Support Desk",
                    difficulty=Difficulty.MEDIUM,
                    cost=Cost.FREE,
                    detection=DetectionRisk.MEDIUM,
                    mitigations=["Support verification procedures", "Security questions"]
                )
            .end()
        .end()

        .build()
    )

该示例在信息密度上覆盖了模型几乎全部能力:Cost.FREE(成本为零的公开信息收集)、DetectionRisk.NONE(无法被检测的被动步骤)、AND 组合、以及逐条映射的缓解措施。安全团队可直接复用此模式为任意系统(认证、支付、云基础设施等)建立类似的攻击树。

模板三:Mermaid 与 PlantUML 可视化导出

攻击树的价值在于沟通,模板三提供两个导出器,把内存中的树转换为团队熟悉的可视化格式。

MermaidExporter:按难度着色

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

        # Node shape based on type
        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:  # LEAF
            # Color based on difficulty
            style = self._get_leaf_style(node)
            shape = f"{node_id}[/{node.name}/]"
            self._lines.append(f"    style {node_id} {style}")

        self._lines.append(f"    {shape}")

        if parent_id:
            connector = "-->" if node.node_type != NodeType.AND else "==>"
            self._lines.append(f"    {parent_id} {connector} {node_id}")

        for child in node.children:
            self._export_node(child, node_id)

        return node_id

    def _get_leaf_style(self, node: AttackNode) -> str:
        """Get style based on attack attributes."""
        colors = {
            Difficulty.TRIVIAL: "fill:#ff6b6b",  # Red - easy attack
            Difficulty.LOW: "fill:#ffa06b",
            Difficulty.MEDIUM: "fill:#ffd93d",
            Difficulty.HIGH: "fill:#6bcb77",
            Difficulty.EXPERT: "fill:#4d96ff",  # Blue - hard attack
        }
        color = colors.get(node.attributes.difficulty, "fill:#gray")
        return color

导出器的视觉编码设计专业且自洽:

  • 形状区分类型:OR 用圆角双圆 (( )),AND 用矩形 [ ],叶节点用斜边六边形 / /,与 SKILL.md 的符号约定一致;
  • 连接符区分语义:普通分支用 -->,AND 组合用粗箭头 ==>,强化"所有子节点必须同时成立"的视觉暗示;
  • 颜色区分风险:按难度从红(TRIVIAL,最易被利用、风险最高)渐变到蓝(EXPERT,最难),即"越红越危险",方便管理者一眼锁定最紧迫的攻击面。

PlantUMLExporter:思维导图输出

class PlantUMLExporter:
    """Export attack trees to PlantUML format."""

    def __init__(self, tree: AttackTree):
        self.tree = tree

    def export(self) -> str:
        """Export tree to PlantUML."""
        lines = [
            "@startmindmap",
            f"* {self.tree.name}",
        ]
        self._export_node(self.tree.root, lines, 1)
        lines.append("@endmindmap")
        return "\n".join(lines)

    def _export_node(self, node: AttackNode, lines: List[str], depth: int) -> None:
        """Recursively export nodes."""
        prefix = "*" * (depth + 1)

        if node.node_type == NodeType.OR:
            marker = "[OR]"
        elif node.node_type == NodeType.AND:
            marker = "[AND]"
        else:
            diff = node.attributes.difficulty.name
            marker = f"<<{diff}>>"

        lines.append(f"{prefix} {marker} {node.name}")

        for child in node.children:
            self._export_node(child, lines, depth + 1)

PlantUML 版输出 @startmindmap 思维导图,用文本标记 [OR][AND]<<DIFFICULTY>> 标注节点类型与难度,适合直接粘贴到支持 PlantUML 的文档平台。

模板四:AttackPathAnalyzer 路径枚举与量化分析

最后一个模板把攻击树从"静态图"升级为"可计算的决策模型":

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:
            # Each child is a separate path
            for child in node.children:
                self._collect_paths(child, current_path, all_paths)
        else:  # AND
            # Must combine all children
            child_paths = []
            for child in node.children:
                child_sub_paths = []
                self._collect_paths(child, [], child_sub_paths)
                child_paths.append(child_sub_paths)

            # Combine paths from all AND children
            combined = self._combine_and_paths(child_paths)
            for combo in combined:
                all_paths.append(current_path + combo)

    def _combine_and_paths(
        self,
        child_paths: List[List[List[AttackNode]]]
    ) -> List[List[AttackNode]]:
        """Combine paths from AND node children."""
        if not child_paths:
            return [[]]

        if len(child_paths) == 1:
            return [path for paths in child_paths for path in paths]

        # Cartesian product of all child path combinations
        result = [[]]
        for paths in child_paths:
            new_result = []
            for existing in result:
                for path in paths:
                    new_result.append(existing + path)
            result = new_result
        return result

    def calculate_path_metrics(self, path: List[AttackNode]) -> Dict:
        """Calculate metrics for a specific path."""
        leaves = [n for n in path if n.node_type == NodeType.LEAF]

        total_difficulty = sum(n.attributes.difficulty.value for n in leaves)
        total_cost = sum(n.attributes.cost.value for n in leaves)
        total_time = sum(n.attributes.time_hours for n in leaves)
        max_detection = max((n.attributes.detection_risk.value for n in leaves), default=0)

        return {
            "steps": len(leaves),
            "total_difficulty": total_difficulty,
            "avg_difficulty": total_difficulty / len(leaves) if leaves else 0,
            "total_cost": total_cost,
            "total_time_hours": total_time,
            "max_detection_risk": max_detection,
            "requires_insider": any(n.attributes.requires_insider for n in leaves),
            "requires_physical": any(n.attributes.requires_physical for n in leaves),
        }

    def identify_critical_nodes(self) -> List[Tuple[AttackNode, int]]:
        """Find nodes that appear in the most paths."""
        paths = self.get_all_paths()
        node_counts: Dict[str, Tuple[AttackNode, int]] = {}

        for path in paths:
            for node in path:
                if node.id not in node_counts:
                    node_counts[node.id] = (node, 0)
                node_counts[node.id] = (node, node_counts[node.id][1] + 1)

        return sorted(
            node_counts.values(),
            key=lambda x: x[1],
            reverse=True
        )

    def coverage_analysis(self, mitigated_attacks: Set[str]) -> Dict:
        """Analyze how mitigations affect attack coverage."""
        all_paths = self.get_all_paths()
        blocked_paths = []
        open_paths = []

        for path in all_paths:
            path_attacks = {n.id for n in path if n.node_type == NodeType.LEAF}
            if path_attacks & mitigated_attacks:
                blocked_paths.append(path)
            else:
                open_paths.append(path)

        return {
            "total_paths": len(all_paths),
            "blocked_paths": len(blocked_paths),
            "open_paths": len(open_paths),
            "coverage_percentage": len(blocked_paths) / len(all_paths) * 100 if all_paths else 0,
            "open_path_details": [
                {"path": [n.name for n in p], "metrics": self.calculate_path_metrics(p)}
                for p in open_paths[:5]  # Top 5 open paths
            ]
        }

    def prioritize_mitigations(self) -> List[Dict]:
        """Prioritize mitigations by impact."""
        critical_nodes = self.identify_critical_nodes()
        paths = self.get_all_paths()
        total_paths = len(paths)

        recommendations = []
        for node, count in critical_nodes:
            if node.node_type == NodeType.LEAF and node.mitigations:
                recommendations.append({
                    "attack": node.name,
                    "attack_id": node.id,
                    "paths_blocked": count,
                    "coverage_impact": count / total_paths * 100,
                    "difficulty": node.attributes.difficulty.name,
                    "mitigations": node.mitigations,
                })

        return sorted(recommendations, key=lambda x: x["coverage_impact"], reverse=True)

该分析器提供的四类能力分别解决安全规划中的四个问题:

  1. get_all_paths():枚举全部攻击路径。实现要点是 AND 节点子路径的笛卡尔积组合(_combine_and_paths),即 AND 分支的每一种子路径组合都是一条完整攻击路径。注意路径数量随 AND 分支指数增长,从源码实现可以推断它更适合中小规模树。
  2. calculate_path_metrics():对单条路径输出步骤数、总难度/平均难度、总成本、总耗时、最大检测风险、是否涉及内部人员/物理接触等量化指标,输出可直接用于风险评分卡。
  3. identify_critical_nodes():统计每个节点出现在多少条路径中,按出现次数降序返回——出现在路径最多的叶节点,就是缓解价值最高的"关键节点"。
  4. coverage_analysis(mitigated_attacks):输入已缓解攻击的 id 集合,输出总路径数、已阻断路径数、剩余开放路径数、覆盖率百分比(blocked/total×100),并附上前 5 条未覆盖路径的名称与指标,天然对接 threat-mitigation-mapping 的"Critical Gaps 报告"。
  5. prioritize_mitigations():综合关键节点统计与缓解信息,输出按覆盖率影响降序排列的缓解建议,每个建议包含攻击名、阻断路径数、覆盖率影响百分比、难度与对应缓解措施——这直接支撑 SKILL.md 中"Planning defensive investments"的使用场景,也呼应 threat-modeling-expert 工作流的"Score and prioritize threats"步骤。

把攻击树接入完整威胁建模工作流

在 agents24/agents 仓库中,attack-tree-construction 不是孤立工具,而是 security-scanning 插件威胁建模链路的一环。推荐的组合方式是:

  1. stride-analysis-patterns 对系统各组件执行 STRIDE 分类(Spoofing/Tampering/Repudiation/Info Disclosure/DoS/Elevation),找出需要深入的高风险面;
  2. 对高风险面调用本技能的四套模板建树:AttackTreeBuilder 建模 → AttackPathAnalyzer 找最易/最省/最隐蔽路径与未缓解攻击 → MermaidExporter/PlantUMLExporter 出图汇报;
  3. security-requirement-extraction 把树中未缓解的攻击转化为带验收标准的安全需求;
  4. threat-mitigation-mapping 将威胁映射到预防性/检测性/纠正性控制,形成修复路线图。

这也与 threat-modeling-expert 的八步工作流(界定范围与信任边界 → 数据流图 → 识别资产与入口 → STRIDE 逐组件分析 → 对关键路径构建攻击树 → 打分排序 → 设计缓解 → 记录残余风险)完全吻合。若需要执行 SAST、依赖扫描与加固等后续动作,可配合该插件的 security-sastsecurity-dependenciessecurity-hardening 三个命令使用(详见 docs/usage.md)。

小结

attack-tree-construction 技能用约 600 行 Python 把攻击树方法论落成了四套可直接运行、可直接扩展的工程模板:数据模型负责表达与聚合,流式构建器负责快速建模,双格式导出器负责沟通汇报,路径分析器负责量化决策。配合仓库中 security-scanning 插件其余四个技能,即可在 Agent 工作流中完成从"威胁识别 → 攻击树建模 → 路径量化 → 需求提取 → 缓解映射"的完整威胁建模闭环。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525