chore(attack-pahts): improve attack paths queries attribution (#9983)

This commit is contained in:
Josema Camacho
2026-02-09 11:07:12 +01:00
committed by GitHub
parent fa189e7eb9
commit 5cbbceb3be
13 changed files with 701 additions and 340 deletions

View File

@@ -2,6 +2,14 @@
All notable changes to the **Prowler API** are documented in this file.
## [1.20.0] (Prowler UNRELEASED)
### 🔄 Changed
- Attack Paths: Queries definition now has short description and attribution [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
---
## [1.19.0] (Prowler v5.18.0)
### 🚀 Added

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,14 @@
from dataclasses import dataclass, field
@dataclass
class AttackPathsQueryAttribution:
"""Source attribution for an Attack Path query."""
text: str
link: str
@dataclass
class AttackPathsQueryParameterDefinition:
"""
@@ -23,7 +31,9 @@ class AttackPathsQueryDefinition:
id: str
name: str
short_description: str
description: str
provider: str
cypher: str
attribution: AttackPathsQueryAttribution | None = None
parameters: list[AttackPathsQueryParameterDefinition] = field(default_factory=list)

View File

@@ -616,7 +616,7 @@ paths:
operationId: attack_paths_scans_queries_retrieve
description: Retrieve the catalog of Attack Paths queries available for this
Attack Paths scan.
summary: List attack paths queries
summary: List Attack Paths queries
parameters:
- in: query
name: fields[attack-paths-scans]
@@ -714,7 +714,7 @@ paths:
description: Bad request (e.g., Unknown Attack Paths query for the selected
provider)
'404':
description: No attack paths found for the given query and parameters
description: No Attack Paths found for the given query and parameters
'500':
description: Attack Paths query execution failed due to a database error
/api/v1/compliance-overviews:
@@ -12438,6 +12438,8 @@ components:
type: string
name:
type: string
short_description:
type: string
description:
type: string
provider:
@@ -12446,12 +12448,42 @@ components:
type: array
items:
$ref: '#/components/schemas/AttackPathsQueryParameter'
attribution:
allOf:
- $ref: '#/components/schemas/AttackPathsQueryAttribution'
nullable: true
required:
- id
- name
- short_description
- description
- provider
- parameters
AttackPathsQueryAttribution:
type: object
required:
- type
- id
additionalProperties: false
properties:
type:
type: string
description: The [type](https://jsonapi.org/format/#document-resource-object-identification)
member is used to describe resource objects that share common attributes
and relationships.
enum:
- attack-paths-query-attributions
id: {}
attributes:
type: object
properties:
text:
type: string
link:
type: string
required:
- text
- link
AttackPathsQueryParameter:
type: object
required:

View File

@@ -83,6 +83,7 @@ def test_execute_attack_paths_query_serializes_graph(
definition = attack_paths_query_definition_factory(
id="aws-rds",
name="RDS",
short_description="Short desc",
description="",
cypher="MATCH (n) RETURN n",
parameters=[],
@@ -143,6 +144,7 @@ def test_execute_attack_paths_query_wraps_graph_errors(
definition = attack_paths_query_definition_factory(
id="aws-rds",
name="RDS",
short_description="Short desc",
description="",
cypher="MATCH (n) RETURN n",
parameters=[],

View File

@@ -3830,6 +3830,7 @@ class TestAttackPathsScanViewSet:
AttackPathsQueryDefinition(
id="aws-rds",
name="RDS inventory",
short_description="List account RDS assets.",
description="List account RDS assets",
provider=provider.provider,
cypher="MATCH (n) RETURN n",
@@ -3892,6 +3893,7 @@ class TestAttackPathsScanViewSet:
query_definition = AttackPathsQueryDefinition(
id="aws-rds",
name="RDS inventory",
short_description="List account RDS assets.",
description="List account RDS assets",
provider=provider.provider,
cypher="MATCH (n) RETURN n",
@@ -4049,6 +4051,7 @@ class TestAttackPathsScanViewSet:
query_definition = AttackPathsQueryDefinition(
id="aws-empty",
name="empty",
short_description="",
description="",
provider=provider.provider,
cypher="MATCH (n) RETURN n",

View File

@@ -1176,6 +1176,14 @@ class AttackPathsScanSerializer(RLSSerializer):
return provider.uid if provider else None
class AttackPathsQueryAttributionSerializer(BaseSerializerV1):
text = serializers.CharField()
link = serializers.CharField()
class JSONAPIMeta:
resource_name = "attack-paths-query-attributions"
class AttackPathsQueryParameterSerializer(BaseSerializerV1):
name = serializers.CharField()
label = serializers.CharField()
@@ -1190,7 +1198,9 @@ class AttackPathsQueryParameterSerializer(BaseSerializerV1):
class AttackPathsQuerySerializer(BaseSerializerV1):
id = serializers.CharField()
name = serializers.CharField()
short_description = serializers.CharField()
description = serializers.CharField()
attribution = AttackPathsQueryAttributionSerializer(allow_null=True, required=False)
provider = serializers.CharField()
parameters = AttackPathsQueryParameterSerializer(many=True)

View File

@@ -1663,6 +1663,7 @@ def attack_paths_query_definition_factory():
definition_payload = {
"id": "aws-test",
"name": "Attack Paths Test Query",
"short_description": "Synthetic short description for tests.",
"description": "Synthetic Attack Paths definition for tests.",
"provider": "aws",
"cypher": "RETURN 1",

View File

@@ -18,9 +18,7 @@ allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, Task
## Overview
Attack Paths queries are openCypher queries that analyze cloud infrastructure graphs
(ingested via Cartography) to detect security risks like privilege escalation paths,
network exposure, and misconfigurations.
Attack Paths queries are openCypher queries that analyze cloud infrastructure graphs (ingested via Cartography) to detect security risks like privilege escalation paths, network exposure, and misconfigurations.
Queries are written in **openCypher Version 9** to ensure compatibility with both Neo4j and Amazon Neptune.
@@ -34,10 +32,11 @@ Queries can be created from:
- The JSON index contains: `id`, `name`, `description`, `services`, `permissions`, `exploitationSteps`, `prerequisites`, etc.
- Reference: https://github.com/DataDog/pathfinding.cloud
**Fetching a single path by ID** The aggregated `paths.json` is too large for WebFetch
**Fetching a single path by ID** - The aggregated `paths.json` is too large for WebFetch
(content gets truncated). Use Bash with `curl` and a JSON parser instead:
Prefer `jq` (concise), fall back to `python3` (guaranteed in this Python project):
```bash
# With jq
curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \
@@ -50,6 +49,7 @@ Queries can be created from:
2. **Listing Available Attack Paths**
- Use Bash to list available paths from the JSON index:
```bash
# List all path IDs and names (jq)
curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \
@@ -84,6 +84,7 @@ Example: `api/src/backend/api/attack_paths/queries/aws.py`
```python
from api.attack_paths.queries.types import (
AttackPathsQueryAttribution,
AttackPathsQueryDefinition,
AttackPathsQueryParameterDefinition,
)
@@ -92,8 +93,13 @@ from tasks.jobs.attack_paths.config import PROWLER_FINDING_LABEL
# {REFERENCE_ID} (e.g., EC2-001, GLUE-001)
AWS_{QUERY_NAME} = AttackPathsQueryDefinition(
id="aws-{kebab-case-name}",
name="Privilege Escalation: {permission1} + {permission2}",
description="{Detailed description of the Attack Paths}.",
name="{Human-friendly label} ({REFERENCE_ID})",
short_description="{Brief explanation of the attack, no technical permissions.}",
description="{Detailed description of the attack vector and impact.}",
attribution=AttackPathsQueryAttribution(
text="pathfinding.cloud - {REFERENCE_ID} - {permission1} + {permission2}",
link="https://pathfinding.cloud/paths/{reference_id_lowercase}",
),
provider="aws",
cypher=f"""
// Find principals with {permission1}
@@ -114,7 +120,7 @@ AWS_{QUERY_NAME} = AttackPathsQueryDefinition(
OR action = '*'
)
// Find target resources
// Find target resources (MUST chain from `aws` for provider isolation)
MATCH path_target = (aws)--(target_role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]->(:AWSPrincipal {{arn: '{service}.amazonaws.com'}})
WHERE any(resource IN stmt.resource WHERE
resource = '*'
@@ -231,11 +237,13 @@ This informs query design by showing what data is actually available to query.
Use the standard pattern (see above) with:
- **id**: Auto-generated as `{provider}-{kebab-case-description}`
- **name**: Human-readable, e.g., "Privilege Escalation: {perm1} + {perm2}"
- **description**: Explain the attack vector and impact
- **name**: Short, human-friendly label. No raw IAM permissions. For sourced queries (e.g., pathfinding.cloud), append the reference ID in parentheses: `"EC2 Instance Launch with Privileged Role (EC2-001)"`. If the name already has parentheses, prepend the ID inside them: `"ECS Service Creation with Privileged Role (ECS-003 - Existing Cluster)"`.
- **short_description**: Brief explanation of the attack, no technical permissions. E.g., "Launch EC2 instances with privileged IAM roles to gain their permissions via IMDS."
- **description**: Full technical explanation of the attack vector and impact. Plain text only, no HTML or technical permissions here.
- **provider**: Provider identifier (aws, azure, gcp, kubernetes, github)
- **cypher**: The openCypher query with proper escaping
- **parameters**: Optional list of user-provided parameters (use `parameters=[]` if none needed)
- **attribution**: Optional `AttackPathsQueryAttribution(text, link)` for sourced queries. The `text` includes the source, reference ID, and technical permissions (e.g., `"pathfinding.cloud - EC2-001 - iam:PassRole + ec2:RunInstances"`). The `link` is the URL with a lowercase ID (e.g., `"https://pathfinding.cloud/paths/ec2-001"`). Omit (defaults to `None`) for non-sourced queries.
### 5. Add Query to Provider List
@@ -397,6 +405,16 @@ parameters=[
6. **Validate schema first**: Ensure all node labels and properties exist in Cartography schema
7. **Chain all MATCHes from the root account node**: Every `MATCH` clause must connect to the `aws` variable (or another variable already bound to the account's subgraph). The tenant database contains data from multiple providers — an unanchored `MATCH` would return nodes from all providers, breaking provider isolation.
```cypher
// WRONG: matches ALL AWSRoles across all providers in the tenant DB
MATCH (role:AWSRole) WHERE role.name = 'admin'
// CORRECT: scoped to the specific account's subgraph
MATCH (aws)--(role:AWSRole) WHERE role.name = 'admin'
```
---
## openCypher Compatibility

View File

@@ -2,6 +2,14 @@
All notable changes to the **Prowler UI** are documented in this file.
## [1.19.0] (Prowler UNRELEASED)
### 🔄 Changed
- Attack Paths: Query list now shows their name and short description, when one is selected it also shows a longer description and an attribution if it has it [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
---
## [1.18.1] (Prowler UNRELEASED)
### 🐞 Fixed

View File

@@ -35,7 +35,7 @@ export const QuerySelector = ({
<div className="flex flex-col gap-1">
<span className="font-medium">{query.attributes.name}</span>
<span className="text-xs text-gray-500">
{query.attributes.description}
{query.attributes.short_description}
</span>
</div>
</SelectItem>

View File

@@ -340,6 +340,33 @@ export default function AttackPathAnalysisPage() {
onQueryChange={handleQueryChange}
/>
{queryBuilder.selectedQueryData && (
<div className="bg-bg-neutral-tertiary text-text-neutral-secondary dark:text-text-neutral-secondary rounded-md p-3 text-sm">
<p className="whitespace-pre-line">
{queryBuilder.selectedQueryData.attributes.description}
</p>
{queryBuilder.selectedQueryData.attributes.attribution && (
<p className="mt-2 text-xs">
Source:{" "}
<a
href={
queryBuilder.selectedQueryData.attributes
.attribution.link
}
target="_blank"
rel="noopener noreferrer"
className="underline"
>
{
queryBuilder.selectedQueryData.attributes
.attribution.text
}
</a>
</p>
)}
</div>
)}
{queryBuilder.selectedQuery && (
<QueryParametersForm
selectedQuery={queryBuilder.selectedQueryData}

View File

@@ -90,11 +90,18 @@ export interface AttackPathQueryParameter {
required?: boolean;
}
export interface AttackPathQueryAttribution {
text: string;
link: string;
}
export interface AttackPathQueryAttributes {
name: string;
short_description: string;
description: string;
provider: string;
parameters: AttackPathQueryParameter[];
attribution: AttackPathQueryAttribution | null;
}
export interface AttackPathQuery {