PR

prowler-attack-paths-query

Generates openCypher queries for Prowler Attack Paths to identify cloud security risks.

Install

mkdir -p .claude/skills/prowler-attack-paths-query && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/5163" && unzip -o skill.zip -d .claude/skills/prowler-attack-paths-query && rm skill.zip

Installs to .claude/skills/prowler-attack-paths-query

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Creates Prowler Attack Paths openCypher queries for graph analysis (compatible with Neo4j and Neptune). Trigger: When creating or updating Attack Paths queries that detect privilege escalation paths, network exposure, or security misconfigurations in cloud environments.
270 chars · catalog description✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Write openCypher queries for infrastructure graph analysis
  • Isolate nodes using AWSAccount anchor or automated labels
  • Chain graph traversals for privilege escalation detection
  • Validate query syntax for Neo4j and Amazon Neptune

How it works

It constructs graph traversal patterns by applying appropriate isolation labels or root anchors based on whether the query is predefined or custom.

Inputs & outputs

You give it
Natural language security requirement or pathfinding goal
You get back
OpenCypher query string

When to use prowler-attack-paths-query

  • Detect privilege escalation paths
  • Identify network exposure in AWS
  • Draft custom security misconfiguration queries
  • Verify query compatibility with Neo4j or Amazon Neptune

About this skill

Overview

Attack Paths queries are read-only openCypher queries over a Cartography-ingested cloud graph that detect privilege escalation chains, network exposure, and other graph-shaped security risks. Queries are written in openCypher Version 9 so they run on both Neo4j and Amazon Neptune sinks.

This skill is the concise, action-oriented reference for building queries. For the complete human-readable reference (graph model, list-typed and JSON-encoded properties, compatibility, and worked examples), see docs/developer-guide/attack-paths-queries.mdx.


Two query audiences

Predefined queriesCustom queries
Where they liveapi/src/backend/api/attack_paths/queries/{provider}.pyUser-supplied via the custom query API endpoint
Provider isolationAWSAccount {id: $provider_uid} anchor + path connectivityAutomatic _Provider_{uuid} label injection by cypher_sanitizer.py
What to writeChain every MATCH from the aws variablePlain Cypher, no isolation boilerplate
Internal labelsNever useNever use (system-injected)

Predefined queries: every node must be reachable from the AWSAccount root via graph traversal. That is the isolation boundary.

Custom queries: write natural Cypher. The runner injects a _Provider_{uuid} label into every node pattern, and a post-query filter handles edge cases.


Input sources

Two sources for new queries:

  1. pathfinding.cloud ID (e.g. ECS-001, GLUE-001), the Datadog research catalogue. The aggregated paths.json is too large for 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)"'
    

    If jq is unavailable, use python3 -c "import json,sys; ...".

  2. Natural language description from the requester.


Query structure

Provider scoping parameter

ParameterPropertyUsed onPurpose
$provider_uididAWSAccountScopes the query to a specific account

The runner binds $provider_uid automatically. Every other node is isolated by path connectivity from the AWSAccount anchor.

Imports

from api.attack_paths.queries.types import (
    AttackPathsQueryAttribution,
    AttackPathsQueryDefinition,
    AttackPathsQueryParameterDefinition,
)
from tasks.jobs.attack_paths.config import PROWLER_FINDING_LABEL

Always use PROWLER_FINDING_LABEL via f-string interpolation, never hardcode "ProwlerFinding".

Definition fields

  • id: kebab-case {provider}-{description}, e.g. aws-ec2-privesc-passrole-iam.
  • name: short, human-friendly label. Sourced queries append the reference ID: "EC2 Instance Launch with Privileged Role (EC2-001)".
  • short_description: one sentence, no technical permissions.
  • description: full technical explanation, plain text.
  • provider: aws, azure, gcp, kubernetes, or github.
  • cypher: f-string Cypher body. Literal { / } are escaped as {{ / }}.
  • parameters: parameters=[] if none.
  • attribution: optional AttackPathsQueryAttribution(text, link) for sourced queries. link uses the lowercase ID.

Append the constant to the {PROVIDER}_QUERIES list at the bottom of the provider file.


Predefined query template

The canonical shape combines a principal walk, an optional target walk, deduplicated nodes, and a typed finding overlay:

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=[],
)

Key points:

  • The principal walk types the POLICY and STATEMENT hops. Both are low-fan-out (each principal has a handful of policies; each policy a handful of statements), so the typed edge lets the planner cost a cheap inline filter.
  • The (aws)-- hub hops stay anonymous. AWSAccount is a high-degree node that fans out to every principal, role, policy, and resource in the account; typing those edges forces the planner to enumerate from the hub and collapses performance on multi-tenant Neptune.
  • Other relationship types appear only where the file's existing queries already use one (TRUSTS_AWS_PRINCIPAL, STS_ASSUMEROLE_ALLOW, MEMBER_AWS_GROUP, HAS_EXECUTION_ROLE).
  • The finding probe is typed :HAS_FINDING and left undirected. The type lets Neptune apply an inline edge filter; the lack of direction matches the convention of the rest of the file.
  • Collapse duplicate rows after each permission gate with WITH DISTINCT, carrying only the variables needed by later clauses.
  • Each HAS_* traversal is its own MATCH clause with a WHERE on the child item node. WITH DISTINCT path_principal, path_target precedes collect(path...) to dedupe the row multiplication produced by the joins.
  • The RETURN shape paths, dpf, dpfr is the contract the serializer and visualiser depend on. Do not change it.

Avoiding cartesian products

Matching a target set (AWSRole, AWSUser, AWSGroup) and then filtering each target against a statement's HAS_RESOURCE items in a separate, unconnected MATCH builds a cartesian product: every target is paired with every resource item before the filter runs. On accounts with many principals this errors or times out. Pre-aggregate the resource values into a list, then match each target once:

// 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
  • Aggregate resources before matching targets; cost becomes targets + resources, not targets × resources. This is a pure rewrite, the result set is identical.
  • ('*' IN res_values) short-circuits the wildcard grant so the list scan runs only when needed.
  • Bind target.name / target.arn to locals so the list comprehension reads them once per target, not once per resource value.
  • size([...]) > 0 is the Neptune-compatible form of any() (see "openCypher compatibility").
  • Two-statement queries aggregate each statement's resources into its own list (res_values, res2_values) and combine the two size([...]) > 0 checks with AND.
  • Targets already constrained by a relationship (STS_ASSUMEROLE_ALLOW, TRUSTS_AWS_PRINCIPAL) need no aggregation: the relationship already bounds the set.

Privilege escalation sub-patterns

Four path_target shapes cover the common attack types. Each shares the canonical template's path_principal, deduplication tail, and RETURN; only the path_target MATCH and it


Content truncated.

When not to use it

  • Non-graph based security analysis
  • General SQL database queries

Prerequisites

Cartography schemaOpenCypher 9 environment

Limitations

  • Requires graph data to be in Cartography format
  • Complex traversals must explicitly respect the AWSAccount root

How it compares

It automates the inclusion of required isolation boilerplate and schema-specific labels for cloud infrastructure analysis.

Compared to similar skills

prowler-attack-paths-query side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
prowler-attack-paths-query (this skill)12moReviewAdvanced
supabase-rls-policy-generator119moNo flagsAdvanced
sqlmap-database-penetration-testing46moReviewAdvanced
data-safety-auditor37moNo flagsAdvanced

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

supabase-rls-policy-generator

hopeoverture

This skill should be used when the user requests to generate, create, or add Row-Level Security (RLS) policies for Supabase databases in multi-tenant or role-based applications. It generates comprehensive RLS policies using auth.uid(), auth.jwt() claims, and role-based access patterns. Trigger terms include RLS, row level security, supabase security, generate policies, auth policies, multi-tenant security, role-based access, database security policies, supabase permissions, tenant isolation.

11109

sqlmap-database-penetration-testing

davila7

This skill should be used when the user asks to "automate SQL injection testing," "enumerate database structure," "extract database credentials using sqlmap," "dump tables and columns from a vulnerable database," or "perform automated database penetration testing." It provides comprehensive guidance for using SQLMap to detect and exploit SQL injection vulnerabilities.

449

data-safety-auditor

ananddtyagi

Comprehensive data safety auditor for Vue 3 + Pinia + IndexedDB + PouchDB applications. Detects data loss risks, sync issues, race conditions, and browser-specific vulnerabilities with actionable remediation guidance.

38

detecting-sql-injection-vulnerabilities

jeremylongshore

Detect and analyze SQL injection vulnerabilities in application code and database queries. Use when you need to scan code for SQL injection risks, review query construction, validate input sanitization, or implement secure query patterns. Trigger with phrases like "detect SQL injection", "scan for SQLi vulnerabilities", "review database queries", or "check SQL security".

35

row-level-security

dadbodgeoff

Implement PostgreSQL Row Level Security (RLS) for multi-tenant SaaS applications. Use when building apps where users should only see their own data, or when implementing organization-based data isolation.

15

supabase-data-handling

jeremylongshore

Implement Supabase PII handling, data retention, and GDPR/CCPA compliance patterns. Use when handling sensitive data, implementing data redaction, configuring retention policies, or ensuring compliance with privacy regulations for Supabase integrations. Trigger with phrases like "supabase data", "supabase PII", "supabase GDPR", "supabase data retention", "supabase privacy", "supabase CCPA".

13

Search skills

Search the agent skills registry