关于 prowler-attack-paths-query
prowler-attack-paths-query 是用于编写 Prowler Attack Paths 查询的开发技能,面向在 Cartography 云图中识别权限提升链和网络暴露等风险的开发者。它提供 openCypher 查询结构、兼容性及性能编写规范。
主要功能
- 01说明预定义与自定义查询
- 02提供 openCypher 查询模板
- 03涵盖权限提升与网络暴露模式
- 04指导避免笛卡尔积
- 05说明列表属性的图结构映射
适用场景
- 01为云图新增攻击路径查询
- 02编写权限提升检测查询
- 03优化查询以避免性能问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ prowler-cloud/ prowler --skill prowler-attack-paths-query安装后在 Claude Code 中输入/prowler-attack-paths-query确认已加载。
详细介绍
概述
攻击路径查询是针对 Cartography 摄取的云图谱执行的只读 openCypher 查询,用于检测权限提升链、网络暴露以及其他图结构安全风险。查询使用 openCypher Version 9 编写,因此可在 Neo4j 和 Amazon Neptune 存储端运行。
本技能是用于构建查询的简明、面向行动的参考指南。完整的可读参考(图模型、列表类型和 JSON 编码属性、兼容性及示例)请参阅 docs/developer-guide/attack-paths-queries.mdx。
两类查询受众
| 预定义查询 | 自定义查询 | |
|---|---|---|
| 存放位置 | api/src/backend/api/attack_paths/queries/{provider}.py | 用户通过自定义查询 API 端点提供 |
| 提供商隔离 | AWSAccount {id: $provider_uid} 锚点 + 路径连通性 | 由 cypher_sanitizer.py 自动注入 _Provider_{uuid} 标签 |
| 编写内容 | 从 aws 变量开始,将每个 MATCH 串联起来 | 普通 Cypher,无需编写隔离样板代码 |
| 内部标签 | 切勿使用 | 切勿使用(由系统注入) |
预定义查询:每个节点都必须能通过图遍历从 AWSAccount 根节点到达。这就是隔离边界。
自定义查询:编写自然的 Cypher。运行器会向每个节点模式注入 _Provider_{uuid} 标签,并通过查询后过滤器处理边缘情况。
输入来源
新查询有两个来源:
-
pathfinding.cloud ID(例如
ECS-001、GLUE-001),即 Datadog 研究目录。汇总后的paths.json对 WebFetch 来说太大:# Fetch a single path by ID curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \ | jq '.[] | select(.id == "ecs-002")' # List all path IDs and names curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \ | jq -r '.[] | "\(.id): \(.name)"' # Filter by service prefix curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \ | jq -r '.[] | select(.id | startswith("ecs")) | "\(.id): \(.name)"'如果没有
jq,请使用python3 -c "import json,sys; ..."。 -
请求者提供的自然语言描述。
查询结构
提供商范围参数
| 参数 | 属性 | 用于 | 目的 |
|---|---|---|---|
$provider_uid | id | AWSAccount | 将查询范围限定到特定账户 |
运行器会自动绑定 $provider_uid。其他所有节点都通过与 AWSAccount 锚点的路径连通性进行隔离。
导入
from api.attack_paths.queries.types import (
AttackPathsQueryAttribution,
AttackPathsQueryDefinition,
AttackPathsQueryParameterDefinition,
)
from tasks.jobs.attack_paths.config import PROWLER_FINDING_LABEL
始终通过 f 字符串插值使用 PROWLER_FINDING_LABEL,切勿将 "ProwlerFinding" 硬编码。
定义字段
- id:kebab-case 格式的
{provider}-{description},例如aws-ec2-privesc-passrole-iam。 - name:简短、易懂的标签。来源查询会附加参考 ID:
"EC2 Instance Launch with Privileged Role (EC2-001)"。 - short_description:一句话,不包含技术权限。
- description:完整的技术说明,使用纯文本。
- provider:
aws、azure、gcp、kubernetes或github。 - cypher:f 字符串 Cypher 主体。字面量
{/}要转义为{{/}}。 - parameters:如果没有参数,则使用
parameters=[]。 - attribution:来源查询可选的
AttackPathsQueryAttribution(text, link)。link使用小写 ID。
将常量追加到提供商文件底部的 {PROVIDER}_QUERIES 列表中。
预定义查询模板
规范形态结合了主体遍历、可选的目标遍历、去重后的节点,以及类型化的发现项叠加:
AWS_{QUERY_NAME} = AttackPathsQueryDefinition(
id="aws-{kebab-case-name}",
name="{Label} ({REFERENCE_ID})",
short_description="{One sentence.}",
description="{Full technical explanation.}",
attribution=AttackPathsQueryAttribution(
text="pathfinding.cloud - {REFERENCE_ID} - {permission}",
link="https://pathfinding.cloud/paths/{reference_id_lowercase}",
),
provider="aws",
cypher=f"""
// Find principals with {permission}
MATCH path_principal = (aws:AWSAccount {{id: $provider_uid}})--(principal:AWSPrincipal)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement {{effect: 'Allow'}})
MATCH (stmt)-[:HAS_ACTION]->(act:AWSPolicyStatementActionItem)
WHERE toLower(act.value) IN ['{permission_lowercase}', '{service}:*']
OR act.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
// Pre-aggregate the statement's resource values (see "Avoiding cartesian products")
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WITH aws, principal, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, principal, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Target policies attached to the principal, matched once against the resource list
MATCH path_target = (aws)--(target_policy:AWSPolicy)--(principal)
WITH path_principal, path_target, res_values, res_wildcard, target_policy.arn AS parn
WHERE parn CONTAINS $provider_uid
AND (res_wildcard OR size([rv IN res_values WHERE parn CONTAINS rv]) > 0)
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
UNWIND paths AS p
UNWIND nodes(p) AS n
WITH paths, collect(DISTINCT n) AS unique_nodes
UNWIND unique_nodes AS n
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})
RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr
""",
parameters=[],
)
要点:
- 主体遍历为
POLICY和STATEMENT跳转指定类型。两者的扇出都很低(每个主体只有少量策略,每个策略只有少量语句),因此,类型化边可让规划器将其作为低成本的内联筛选条件。 (aws)--枢纽跳转保持匿名。AWSAccount是一个高度节点,会扇出到帐户中的每个主体、角色、策略和资源;为这些边指定类型会迫使规划器从枢纽开始枚举,并导致多租户 Neptune 上的性能骤降。- 其他关系类型仅在文件现有查询已使用的地方出现(
TRUSTS_AWS_PRINCIPAL、STS_ASSUMEROLE_ALLOW、MEMBER_AWS_GROUP、HAS_EXECUTION_ROLE)。 - 发现项探测使用类型
:HAS_FINDING,且不指定方向。类型可让 Neptune 应用内联边筛选;不指定方向则符合文件其余部分的惯例。 - 每次权限检查后都使用
WITH DISTINCT折叠重复行,仅保留后续子句所需的变量。 - 每次
HAS_*遍历都使用独立的MATCH子句,并通过WHERE筛选子项节点。在collect(path...)前使用WITH DISTINCT path_principal, path_target,以消除联接产生的行倍增。 RETURN形态paths, dpf, dpfr是序列化器和可视化工具所依赖的契约。请勿更改。
避免笛卡尔积
匹配目标集合(AWSRole、AWSUser、AWSGroup),随后在另一个不相关联的 MATCH 中将每个目标与语句的 HAS_RESOURCE 项逐一筛选,会构建笛卡尔积:筛选执行前,每个目标都会与每个资源项配对。在主体较多的帐户上,这会导致报错或超时。请先将资源值预聚合为列表,再对每个目标只进行一次匹配:
// Pre-aggregate the statement's resource values into a list
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Match each target once; bind name/arn to locals so the predicate reads them once
MATCH path_target = (aws)--(target_role:AWSRole)
WITH path_principal, path_target, res_values, res_wildcard,
target_role.name AS rname, target_role.arn AS rarn
WHERE res_wildcard
OR size([rv IN res_values WHERE rv CONTAINS rname OR rarn CONTAINS rv]) > 0
- 在匹配目标前聚合资源;成本变为
targets + resources,而不是targets × resources。这只是纯粹的改写,结果集保持不变。 ('*' IN res_values)会短路通配符授权,只有需要时才扫描列表。- 将
target.name/target.arn绑定到局部变量,使列表推导式对每个目标只读取一次,而不是对每个资源值都读取一次。 size([...]) > 0是与 Neptune 兼容的any()写法(参见“openCypher 兼容性”)。- 双语句查询会将每条语句的资源分别聚合到各自的列表(
res_values、res2_values),并用AND组合两个size([...]) > 0检查。 - 已由关系(
STS_ASSUMEROLE_ALLOW、TRUSTS_AWS_PRINCIPAL)限定的目标无需聚合:该关系已限定了集合范围。
权限提升子模式
四种 path_target 形状涵盖常见的攻击类型。每种都沿用规范模板的 path_principal、去重尾部和 RETURN,只有 path_target 的 MATCH 和资源谓词不同。
| 子模式 | 目标 | path_target 形状 | 示例 |
|---|---|---|---|
| 自我提权 | 主体自身的策略 | (aws)--(target_policy:AWSPolicy)--(principal) | IAM-001 |
| 横向访问用户 | 其他 IAM 用户 | (aws)--(target_user:AWSUser) | IAM-002 |
| Assume-role 横向访问 | 可被代入的角色 | (aws)--(target_role:AWSRole)-[:STS_ASSUMEROLE_ALLOW]-(principal) | IAM-014 |
| PassRole + 服务 | 信任服务的角色 | (aws)--(target_role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]-(:AWSPrincipal {arn: '{service}.amazonaws.com'}) | EC2-001 |
多权限查询(例如 PassRole 加上创建服务的操作)会在 path_target 之前添加权限门控。新变量(act2、policy2、stmt2)沿用每个查询的计数器,并在每个门控之后合并行:
MATCH (principal)-[:POLICY]->(policy2:AWSPolicy)-[:STATEMENT]->(stmt2:AWSPolicyStatement {effect: 'Allow'})
MATCH (stmt2)-[:HAS_ACTION]->(act2:AWSPolicyStatementActionItem)
WHERE toLower(act2.value) IN ['service:*', 'service:createsomething']
OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
如果某个权限仅作为存在性门控,且其语句资源之后不会再检查,则将策略和语句设为匿名,只保留仍需使用的变量:
MATCH (principal)-[:POLICY]->(:AWSPolicy)-[:STATEMENT]->(:AWSPolicyStatement {effect: 'Allow'})-[:HAS_ACTION]->(act3:AWSPolicyStatementActionItem)
WHERE toLower(act3.value) IN ['service:*', 'service:othersomething']
OR act3.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
如果所有匹配的主体都能以相同的独立资源集为目标,则应先收集主体路径,再展开目标,而不是为每个主体与目标的组合创建一行:
WITH aws, collect(DISTINCT path_principal) AS principal_paths
MATCH path_target = (aws)--(target)
WITH principal_paths, collect(DISTINCT path_target) AS target_paths
WITH principal_paths + target_paths AS paths
对目标有限制的语句仍会通过 HAS_RESOURCE 遍历(res、res2)进行检查。参见 aws.py 中的 IAM-015 或 EC2-001。
网络暴露模式
通过已限定范围的资源沿 CAN_ACCESS 边到达 Internet 节点,绝不将其作为独立查询对象:
cypher=f"""
// Resource scoped through the account anchor
MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(resource:EC2Instance)
WHERE resource.exposed_internet = true
// Internet node reached via path connectivity through the resource
OPTIONAL MATCH (internet:Internet)-[can_access:CAN_ACCESS]->(resource)
WITH collect(path) AS paths, head(collect(internet)) AS internet, collect(can_access) AS can_access
UNWIND paths AS p
UNWIND nodes(p) AS n
WITH paths, internet, can_access, collect(DISTINCT n) AS unique_nodes
UNWIND unique_nodes AS n
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})
RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr,
internet, can_access
"""
CAN_ACCESS 边保持类型明确且有向(-[:CAN_ACCESS]->);这是其规范的同步时方向。
将列表类型属性建模为子节点
某些 Cartography 节点属性包含值列表:AWSPolicyStatement.action、AWSPolicyStatement.resource、KMSKey.encryption_algorithms、CloudFrontDistribution.aliases,以及许多其他属性。图会将每个此类属性建模为一组子项节点,并通过带类型的边连接到父节点。查询通过遍历边来获取这些值;父节点不会将列表作为单个字段保存。
命名约定
对于列表类型的父属性,sink 会存储:
- 子节点标签:
<ParentLabel><PropertyPascal>Item。例如:AWSPolicyStatement.resource→AWSPolicyStatementResourceItem。 - 边类型:
HAS_<PROPERTY_UPPER>。例如:resource→HAS_RESOURCE。 - 子节点属性:对于标量列表属性,使用
value(单个标量字符串)。对于字典列表属性(较少见,例如SecretsManagerSecretVersion.tags),子节点会根据目录中的field_map,将字典键作为具名字段保存。
子项匹配的变量命名
aws.py 对每次 HAS_* 遍历使用每个查询独立的计数器,确保链式匹配清晰无歧义:
| 边 | 第一次 | 第二次 | 第三次 |
|---|---|---|---|
HAS_ACTION | act | act2 | act3 |
HAS_RESOURCE | res | res2 | res3 |
HAS_NOTACTION | nact | nact2 | nact3 |
HAS_NOTRESOURCE | nres | nres2 | nres3 |
每个查询开始时,计数器都会重置。
示例 - action 匹配
查找授予 iam:PassRole、iam:* 或 * 的语句。通过单独的 MATCH 子句遍历 HAS_ACTION 边,并在附加的 WHERE 中应用谓词:
MATCH (stmt:AWSPolicyStatement {effect: 'Allow'})
MATCH (stmt)-[:HAS_ACTION]->(act:AWSPolicyStatementActionItem)
WHERE toLower(act.value) IN ['iam:passrole', 'iam:*']
OR act.value = '*'
字面 action 列表使用 toLower(act.value) 进行大小写折叠,因为 IAM 编写者会混用大小写(iam:PassRole、iam:passrole);* 通配符不会转换为小写。
示例 - 资源 ARN 匹配
要查找资源可指向特定角色的语句,请先聚合资源值,然后针对列表测试目标一次(参见“避免笛卡尔积”)。不要在单独的 MATCH 中将目标集合与 HAS_RESOURCE 项配对;这会生成笛卡尔积。
三个谓词涵盖了资源的各种情况:完整通配符(*)、包含目标名称的模式(arn:aws:iam::*:role/admin*),以及作为实际 ARN 前缀或组成部分的模式。
列表属性目录
提供程序目录位于 api/src/backend/tasks/jobs/attack_paths/provider_config.py(AWS_NORMALIZED_LISTS)。除策略语句外,它还包括 KMS 算法、ECS 容器定义列表(entry_point、command、links、dns_servers 等)、CloudFront 别名、Inspector 调查结果 URL 和漏洞列表、RDS 事件订阅类别等。要查询目录中尚未包含的列表属性,请先在其中添加条目,以便同步层将其具体化。
常见 openCypher 模式
账户/主体匹配以及服务信任和 assume-role 目标形态都出现在上述模板和子模式中。其他可复用模式:
JSON 编码属性
对象类型的 Cartography 属性(最值得注意的是 AWSPolicyStatement 和 S3PolicyStatement 上的 condition)以 JSON 编码字符串形式存储,例如 '{"StringEquals":{"aws:SourceAccount":"123456789012"}}'。查询时没有 JSON 解析器,因此请使用 CONTAINS 进行子字符串检查:
WHERE stmt.condition CONTAINS '"aws:SourceAccount"'
如需结构化检查,请获取行并在 Python 中解析。Cypher 无法遍历 JSON 对象键。
通过路径连通性查询 Internet 节点
OPTIONAL MATCH (internet:Internet)-[can_access:CAN_ACCESS]->(resource)
resource 必须已由上面的账户锚定模式绑定。
多标签 OR(多个资源类型)
MATCH path = (aws:AWSAccount {id: $provider_uid})-[r]-(x)-[q]-(y)
WHERE (x:EC2PrivateIp AND x.public_ip = $ip)
OR (x:EC2Instance AND x.publicipaddress = $ip)
OR (x:NetworkInterface AND x.public_ip = $ip)
OR (x:ElasticIPAddress AND x.public_ip = $ip)
包含 Prowler 调查结果
在进行类型化调查结果探测前先对节点去重,避免对同一节点的每条路径出现都执行一次 OPTIONAL MATCH:
WITH collect(path_principal) + collect(path_target) AS paths
UNWIND paths AS p
UNWIND nodes(p) AS n
WITH paths, collect(DISTINCT n) AS unique_nodes
UNWIND unique_nodes AS n
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})
RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr
对于网络暴露查询,请将 Internet 节点及其边与路径一同聚合:
WITH collect(path) AS paths, head(collect(internet)) AS internet, collect(can_access) AS can_access
UNWIND paths AS p
UNWIND nodes(p) AS n
WITH paths, internet, can_access, collect(DISTINCT n) AS unique_nodes
UNWIND unique_nodes AS n
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})
RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr,
internet, can_access
Prowler 专属标签和关系
这些由同步任务添加,不属于 Cartography 架构的一部分。其他内容请查阅固定版本的 Cartography 架构(参见“创建步骤”)。
| 标签 / 关系 | 说明 |
|---|---|
ProwlerFinding | 调查结果节点(status、severity、check_id) |
Internet | Internet 哨兵节点 |
CAN_ACCESS | (Internet)-[:CAN_ACCESS]->(resource) 暴露边 |
HAS_FINDING | (resource)-[:HAS_FINDING]->(:ProwlerFinding) 调查结果链接 |
TRUSTS_AWS_PRINCIPAL | 角色信任关系 |
STS_ASSUMEROLE_ALLOW | 可以 assume-role |
参数
对于接受用户输入的查询:
parameters=[
AttackPathsQueryParameterDefinition(
name="ip",
label="IP address",
# data_type defaults to "string", cast defaults to str.
# For non-string params, set both: data_type="integer", cast=int
description="Public IP address, e.g. 192.0.2.0.",
placeholder="192.0.2.0",
),
],
openCypher 兼容性
查询必须同时适用于 Neo4j 和 Amazon Neptune。请避免使用以下结构:
| 功能 | 替代方案 |
|---|---|
APOC 过程(apoc.*) | 图中的实际节点和关系 |
| Neptune 扩展 | 标准 openCypher |
reduce() | UNWIND + collect() |
FOREACH | WITH + UNWIND + SET |
正则表达式 =~ | toLower() + 精确匹配,或 STARTS WITH / CONTAINS |
CALL () { UNION } | 在 WHERE 中使用多标签 OR(参见上方模式) |
| 保留值与聚合表达式 | 先投影聚合结果:WITH principal_paths, collect(...) AS target_paths,然后在下一个 WITH 中合并列表 |
any(x IN list ...) | size([x IN list WHERE pred]) > 0 |
all(x IN list ...) | size([x IN list WHERE pred]) = size(list) |
none(x IN list ...) | size([x IN list WHERE pred]) = 0 |
EXISTS { MATCH (pattern) WHERE pred } | 独立使用 MATCH (pattern) + WHERE pred;在下游 collect(path...) 前添加 WITH DISTINCT <path-vars>,以去重连接结果 |
对于目录中列表类型的属性(如 action、resource 等),请按照“将列表类型属性作为子节点”中展示的多条 MATCH 结构,通过 HAS_* 边遍历到子项节点。父节点不会将列表作为单个字段保存,因此 split(...) 和逗号分隔字符串谓词不适用。
最佳实践
- 每个 MATCH 都从账户锚点开始链式匹配。 未锚定的
MATCH (role:AWSRole)会返回图中所有提供商的角色;MATCH (aws)--(role:AWSRole)则限定了范围。第二个权限 MATCH,例如MATCH (principal)--(policy2:AWSPolicy)--(stmt2:AWSPolicyStatement),是安全的,因为principal已绑定到账户的子图。 - 为发现项探测指定类型。 始终使用
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})。类型可让 Neptune 应用内联边过滤器;不指定类型的探测会扫描高连接度节点的每条关联边。 - 为每个 MATCH 添加注释。 每个子句用一行内联
// ...说明其作用。 - 绝不使用内部标签。
_ProviderResource、_AWSResource、_Tenant_*、_Provider_*是系统隔离标签,不得出现在查询文本中(无论是预定义查询还是自定义查询)。 - 通过路径连通性抵达 Internet 节点,使用
(internet:Internet)-[:CAN_ACCESS]->(resource),绝不要将其作为独立匹配项。 - 保留
RETURN约定。 标准形式使用paths, dpf, dpfr;网络暴露查询则添加internet, can_access。序列化器和可视化器依赖这些名称。
命名约定
- ID:kebab-case
{provider}-{category}-{description},例如aws-ec2-privesc-passrole-iam。 - 常量:SHOUTING*SNAKE_CASE
{PROVIDER}*{CATEGORY}\_{DESCRIPTION},例如AWS_EC2_PRIVESC_PASSROLE_IAM。
创建步骤
-
先阅读 queries 模块,以遵循现有风格:
api/src/backend/api/attack_paths/queries/ ├── __init__.py ├── types.py # dataclass definitions ├── registry.py └── {provider}.py -
获取固定版本对应的 Cartography 架构。 不要猜测标签、属性或关系。读取依赖项固定版本:
grep cartography api/pyproject.toml然后获取该确切标签对应的架构:
# Git pin (prowler-cloud/cartography@<TAG>): https://raw.githubusercontent.com/prowler-cloud/cartography/refs/tags/<TAG>/docs/root/modules/{provider}/schema.md # PyPI pin (cartography==<TAG>): https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/<TAG>/docs/root/modules/{provider}/schema.md -
构建查询,使用规范的预定义模板以及适当的子模式(权限提升或网络暴露)。对于列表类型的属性(action/resource 等),通过展开的子节点进行遍历,例如
[:HAS_ACTION]->(:AWSPolicyStatementActionItem)(参见“作为子节点的列表类型属性”和AWS_NORMALIZED_LISTS目录)。 -
注册该常量,将其添加到提供商文件底部的
{PROVIDER}_QUERIES列表中。
参考资料
- pathfinding.cloud:https://github.com/DataDog/pathfinding.cloud(使用
curl | jq;聚合后的paths.json对 WebFetch 来说太大)。 - Cartography 架构(按固定标签版本):
https://raw.githubusercontent.com/{org}/cartography/refs/tags/{tag}/docs/root/modules/{provider}/schema.md。 - Neptune openCypher 兼容性:https://docs.aws.amazon.com/neptune/latest/userguide/feature-opencypher-compliance.html。
- openCypher 规范:https://github.com/opencypher/openCypher。
- 同步转换器(
tasks/jobs/attack_paths/sync.py):tasks/jobs/attack_paths/provider_config.py::AWS_NORMALIZED_LISTS中列出的列表类型节点属性会被物化为子项节点和HAS_*边。不在目录中的属性会序列化为逗号分隔的字符串,并仅发出一次警告。字典类型的属性会变为 JSON 字符串。两个数据接收端的结构相同。