mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-05 03:12:14 +00:00
docs(attack-paths): link custom queries to Prowler docs (#10640)
This commit is contained in:
@@ -105,6 +105,87 @@ If a query requires no parameters, the form displays a message confirming that t
|
||||
width="700"
|
||||
/>
|
||||
|
||||
## Writing Custom openCypher Queries
|
||||
|
||||
In addition to the built-in queries, Attack Paths supports custom read-only [openCypher](https://opencypher.org/) queries. Custom queries provide direct access to the underlying graph so security teams can answer ad-hoc questions, prototype detections, or extend coverage beyond the built-in catalogue.
|
||||
|
||||
To write a custom query, select **Custom openCypher query** from the query dropdown. A code editor with syntax highlighting and line numbers appears, ready to receive the query.
|
||||
|
||||
### Constraints and Safety Limits
|
||||
|
||||
Custom queries are sandboxed to keep the graph database safe and responsive:
|
||||
|
||||
- **Read-only:** Only read operations are allowed. Statements that mutate the graph (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`, `DROP`, `LOAD CSV`, `CALL { ... }` writes, etc.) are rejected before execution.
|
||||
- **Length limit:** Each query is capped at **10,000 characters**.
|
||||
- **Scoped to the selected scan:** Results are automatically scoped to the provider and scan selected on the left panel. There is no need to filter by tenant or scan identifier in the query body.
|
||||
|
||||
### Example Queries
|
||||
|
||||
The following examples are read-only and can be pasted directly into the editor.
|
||||
|
||||
**List all S3 buckets in the scan:**
|
||||
|
||||
```cypher
|
||||
MATCH (b:S3Bucket)
|
||||
RETURN b.name AS bucket, b.region AS region
|
||||
LIMIT 50
|
||||
```
|
||||
|
||||
**Find IAM roles that can be assumed from the internet:**
|
||||
|
||||
```cypher
|
||||
MATCH (r:AWSRole)
|
||||
WHERE r.trust_policy CONTAINS '"Principal":"*"'
|
||||
RETURN r.arn AS role_arn, r.name AS role_name
|
||||
LIMIT 25
|
||||
```
|
||||
|
||||
**Find EC2 instances exposed to the internet with attached IAM roles:**
|
||||
|
||||
```cypher
|
||||
MATCH (i:EC2Instance)-[:STS_ASSUMEROLE_ALLOW]->(r:AWSRole)
|
||||
WHERE i.exposed_internet = true
|
||||
RETURN i.instanceid AS instance_id, r.arn AS role_arn
|
||||
LIMIT 25
|
||||
```
|
||||
|
||||
**Inspect Prowler findings linked to a specific resource type:**
|
||||
|
||||
```cypher
|
||||
MATCH (b:S3Bucket)-[:HAS_FINDING]->(f:ProwlerFinding)
|
||||
WHERE f.severity IN ['critical', 'high']
|
||||
RETURN b.name AS bucket, f.check_id AS check, f.severity AS severity
|
||||
LIMIT 50
|
||||
```
|
||||
|
||||
### Tips for Writing Queries
|
||||
|
||||
- Start small with `LIMIT` to inspect the shape of the data before broadening the pattern.
|
||||
- Use `RETURN` projections (`RETURN n.name, n.region`) instead of returning whole nodes to keep responses compact.
|
||||
- Combine resource nodes with `ProwlerFinding` nodes via `HAS_FINDING` to correlate misconfigurations with the affected resources.
|
||||
- When a query times out or returns no rows, simplify the pattern step by step until the first variant runs successfully, then add constraints back.
|
||||
|
||||
### Cartography Schema Reference
|
||||
|
||||
Attack Paths graphs are populated by [Cartography](https://github.com/cartography-cncf/cartography), an open-source graph ingestion framework. The node labels, relationship types, and properties available in custom queries follow the upstream Cartography schema for the corresponding provider.
|
||||
|
||||
For the complete catalogue of node labels and relationships available in custom queries, refer to the official Cartography schema documentation:
|
||||
|
||||
- **AWS:** [Cartography AWS Schema](https://github.com/cartography-cncf/cartography/blob/master/docs/root/modules/aws/schema.md)
|
||||
|
||||
In addition to the upstream schema, Prowler enriches the graph with:
|
||||
|
||||
- **`ProwlerFinding`** nodes representing Prowler check results, linked to affected resources via `HAS_FINDING` relationships.
|
||||
- **`Internet`** nodes used to model exposure paths from the public internet to internal resources.
|
||||
|
||||
<Note>
|
||||
AI assistants connected through Prowler MCP Server can fetch the exact
|
||||
Cartography schema for the active scan via the
|
||||
`prowler_app_get_attack_paths_cartography_schema` tool. This guarantees that
|
||||
generated queries match the schema version pinned by the running Prowler
|
||||
release.
|
||||
</Note>
|
||||
|
||||
## Executing a Query
|
||||
|
||||
To run the selected query against the scan data, click **Execute Query**. The button displays a loading state while the query processes.
|
||||
|
||||
@@ -17,6 +17,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
### 🔄 Changed
|
||||
|
||||
- Attack Paths custom openCypher queries now use a code editor with syntax highlighting and line numbers [(#10445)](https://github.com/prowler-cloud/prowler/pull/10445)
|
||||
- Attack Paths custom openCypher queries now link to the Prowler documentation with examples and how-to guidance instead of the upstream Cartography schema URL
|
||||
- Filter summary strip: removed redundant "Clear all" link next to pills (use top-bar Clear Filters instead) and switched chip variant from `outline` to `tag` for consistency [(#10481)](https://github.com/prowler-cloud/prowler/pull/10481)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { DOCS_URLS } from "@/lib/external-urls";
|
||||
import {
|
||||
ATTACK_PATH_QUERY_IDS,
|
||||
type AttackPathCartographySchemaAttributes,
|
||||
type AttackPathQuery,
|
||||
} from "@/types/attack-paths";
|
||||
|
||||
@@ -22,20 +22,9 @@ const presetQuery: AttackPathQuery = {
|
||||
};
|
||||
|
||||
describe("buildAttackPathQueries", () => {
|
||||
it("prepends a custom query with a schema documentation link", () => {
|
||||
// Given
|
||||
const schema: AttackPathCartographySchemaAttributes = {
|
||||
id: "aws-0.129.0",
|
||||
provider: "aws",
|
||||
cartography_version: "0.129.0",
|
||||
schema_url:
|
||||
"https://github.com/cartography-cncf/cartography/blob/0.129.0/docs/root/modules/aws/schema.md",
|
||||
raw_schema_url:
|
||||
"https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/0.129.0/docs/root/modules/aws/schema.md",
|
||||
};
|
||||
|
||||
it("prepends a custom query that links to the Prowler documentation", () => {
|
||||
// When
|
||||
const result = buildAttackPathQueries([presetQuery], schema);
|
||||
const result = buildAttackPathQueries([presetQuery]);
|
||||
|
||||
// Then
|
||||
expect(result[0]).toMatchObject({
|
||||
@@ -44,8 +33,8 @@ describe("buildAttackPathQueries", () => {
|
||||
name: "Custom openCypher query",
|
||||
short_description: "Write and run your own read-only query",
|
||||
documentation_link: {
|
||||
text: "Cartography schema used by Prowler for AWS graphs",
|
||||
link: schema.schema_url,
|
||||
text: "Learn how to write custom openCypher queries",
|
||||
link: DOCS_URLS.ATTACK_PATHS_CUSTOM_QUERIES,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DOCS_URLS } from "@/lib/external-urls";
|
||||
import { MetaDataProps } from "@/types";
|
||||
import {
|
||||
ATTACK_PATH_QUERY_IDS,
|
||||
type AttackPathCartographySchemaAttributes,
|
||||
AttackPathQueriesResponse,
|
||||
AttackPathQuery,
|
||||
QUERY_PARAMETER_INPUT_TYPES,
|
||||
@@ -61,15 +61,12 @@ const CUSTOM_QUERY_PLACEHOLDER = `MATCH (n)
|
||||
RETURN n
|
||||
LIMIT 25`;
|
||||
|
||||
const formatSchemaDocumentationLinkText = (
|
||||
schema: AttackPathCartographySchemaAttributes,
|
||||
): string => {
|
||||
return `Cartography schema used by Prowler for ${schema.provider.toUpperCase()} graphs`;
|
||||
};
|
||||
const CUSTOM_QUERY_DOCUMENTATION_LINK = {
|
||||
text: "Learn how to write custom openCypher queries",
|
||||
link: DOCS_URLS.ATTACK_PATHS_CUSTOM_QUERIES,
|
||||
} as const;
|
||||
|
||||
const createCustomQuery = (
|
||||
schema?: AttackPathCartographySchemaAttributes,
|
||||
): AttackPathQuery => ({
|
||||
const createCustomQuery = (): AttackPathQuery => ({
|
||||
type: "attack-paths-scans",
|
||||
id: ATTACK_PATH_QUERY_IDS.CUSTOM,
|
||||
attributes: {
|
||||
@@ -79,12 +76,7 @@ const createCustomQuery = (
|
||||
"Run a read-only openCypher query against the selected Attack Paths scan. Results are automatically scoped to the selected provider.",
|
||||
provider: "custom",
|
||||
attribution: null,
|
||||
documentation_link: schema
|
||||
? {
|
||||
text: formatSchemaDocumentationLinkText(schema),
|
||||
link: schema.schema_url,
|
||||
}
|
||||
: null,
|
||||
documentation_link: { ...CUSTOM_QUERY_DOCUMENTATION_LINK },
|
||||
parameters: [
|
||||
{
|
||||
name: "query",
|
||||
@@ -103,7 +95,6 @@ const createCustomQuery = (
|
||||
|
||||
export const buildAttackPathQueries = (
|
||||
queries: AttackPathQuery[],
|
||||
schema?: AttackPathCartographySchemaAttributes,
|
||||
): AttackPathQuery[] => {
|
||||
return [createCustomQuery(schema), ...queries];
|
||||
return [createCustomQuery(), ...queries];
|
||||
};
|
||||
|
||||
+8
-8
@@ -16,27 +16,27 @@ const customQuery: AttackPathQuery = {
|
||||
provider: "aws",
|
||||
attribution: null,
|
||||
documentation_link: {
|
||||
text: "Cartography schema used by Prowler for AWS graphs",
|
||||
link: "https://example.com/schema",
|
||||
text: "Learn how to write custom openCypher queries",
|
||||
link: "https://example.com/docs",
|
||||
},
|
||||
parameters: [],
|
||||
},
|
||||
};
|
||||
|
||||
describe("QueryDescription", () => {
|
||||
it("renders the schema documentation link inside an info alert", () => {
|
||||
it("renders the documentation link inside an info alert", () => {
|
||||
// Given
|
||||
render(<QueryDescription query={customQuery} />);
|
||||
|
||||
// When
|
||||
const alert = screen.getByRole("alert");
|
||||
const link = screen.getByRole("link", {
|
||||
name: /cartography schema used by prowler for aws graphs/i,
|
||||
name: /learn how to write custom opencypher queries/i,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(alert).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute("href", "https://example.com/schema");
|
||||
expect(link).toHaveAttribute("href", "https://example.com/docs");
|
||||
});
|
||||
|
||||
it("does not render unsafe documentation or attribution URLs as clickable links", () => {
|
||||
@@ -46,7 +46,7 @@ describe("QueryDescription", () => {
|
||||
attributes: {
|
||||
...customQuery.attributes,
|
||||
documentation_link: {
|
||||
text: "Cartography schema used by Prowler for AWS graphs",
|
||||
text: "Learn how to write custom openCypher queries",
|
||||
link: "javascript:alert('xss')",
|
||||
},
|
||||
attribution: {
|
||||
@@ -62,14 +62,14 @@ describe("QueryDescription", () => {
|
||||
// Then
|
||||
expect(
|
||||
screen.queryByRole("link", {
|
||||
name: /cartography schema used by prowler for aws graphs/i,
|
||||
name: /learn how to write custom opencypher queries/i,
|
||||
}),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("link", { name: /unsafe source/i }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/cartography schema used by prowler for aws graphs/i),
|
||||
screen.getByText(/learn how to write custom opencypher queries/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/unsafe source/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
executeQuery,
|
||||
getAttackPathScans,
|
||||
getAvailableQueries,
|
||||
getCartographySchema,
|
||||
} from "@/actions/attack-paths";
|
||||
import { adaptQueryResultToGraphData } from "@/actions/attack-paths/query-result.adapter";
|
||||
import { AutoRefresh } from "@/components/scans";
|
||||
@@ -144,14 +143,10 @@ export default function AttackPathsPage() {
|
||||
|
||||
setQueriesLoading(true);
|
||||
try {
|
||||
const [queriesData, schemaData] = await Promise.all([
|
||||
getAvailableQueries(scanId),
|
||||
getCartographySchema(scanId),
|
||||
]);
|
||||
const queriesData = await getAvailableQueries(scanId);
|
||||
|
||||
const availableQueries = buildAttackPathQueries(
|
||||
queriesData?.data ?? [],
|
||||
schemaData?.data.attributes,
|
||||
);
|
||||
|
||||
if (availableQueries.length > 0) {
|
||||
|
||||
@@ -6,6 +6,8 @@ export const DOCS_URLS = {
|
||||
"https://docs.prowler.com/user-guide/tutorials/prowler-app#step-8:-analyze-the-findings",
|
||||
AWS_ORGANIZATIONS:
|
||||
"https://docs.prowler.com/user-guide/tutorials/prowler-cloud-aws-organizations",
|
||||
ATTACK_PATHS_CUSTOM_QUERIES:
|
||||
"https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths#writing-custom-opencypher-queries",
|
||||
} as const;
|
||||
|
||||
// CloudFormation template URL for the ProwlerScan role.
|
||||
|
||||
Reference in New Issue
Block a user