Compare commits

..
Author SHA1 Message Date
Hugo P.Brito 8fcb8e53b7 fix(huaweicloud): preserve FunctionGraph region attribution
- Carry regional client keys through function discovery
- Cover regionless SDK clients with a regression test
- Document FunctionGraph initialization behavior
2026-08-20 13:13:55 +01:00
Hugo P.Brito 84e9a91186 fix(huaweicloud): clarify FunctionGraph VPC guidance
- Separate VPC attachment from public network access controls

- Add supported remediation steps and API documentation
2026-08-20 12:49:36 +01:00
Hugo P.Brito c1eceb9b5a fix(huaweicloud): enable FunctionGraph runtime discovery
- Add the pinned FunctionGraph SDK and regional session client

- Paginate discovery without publishing partial API results

- Preserve canonical function identities and VPC state in reports
2026-08-20 12:48:44 +01:00
tomitobio 082915cad9 Add changelog fragment for huaweicloud_functiongraph_function_vpc_configured 2026-08-12 04:41:20 +08:00
tomitobio ec6878c824 feat(providers/huaweicloud): add functiongraph_function_vpc_configured check 2026-07-29 04:11:52 +08:00
Daniel Barranquero 1218b0920f docs: restructure Attack Paths docs and add query developer guide (#12144) 2026-07-28 18:28:17 +02:00
Pepe Fagoaga 06ea61ffbf docs(saml): update missing images (#12166) 2026-07-28 18:24:08 +02:00
Rubén De la Torre Vico 8eeb37aea4 feat(mcp): add users and roles tools to the Prowler MCP Server (#12088) 2026-07-28 17:16:56 +02:00
Pepe Fagoaga 7ba96ab2e2 docs: update images (#12159) 2026-07-28 16:47:38 +02:00
Stefano BaldoandHugo P.Brito 4987d8a08e fix(gcp): make gen2 Cloud Functions IAM policy query thread-safe (#12107)
Co-authored-by: Hugo P.Brito <hugopbrit@gmail.com>
2026-07-28 15:40:19 +01:00
Pepe Fagoaga 2bd89ceb97 docs: update images (#12146) 2026-07-28 14:56:22 +02:00
Pedro Martínandalejandrobailo 0d4a21b5a4 feat(ui): add cross-account compliance view (#12086)
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
2026-07-28 14:03:58 +02:00
Alejandro Bailo 1460f7b188 feat(ui): add Lighthouse contextual transport core (#12068) 2026-07-28 12:12:19 +02:00
Daniel Barranquero e9bbde2f01 fix(api): remove cartesian product in Attack Paths IAM privesc queries (#12136) 2026-07-28 11:12:10 +02:00
Oleksandr_SaninandHugo P.Brito 76239b7076 feat(ocsf): populate analytic and attacks fields in OCSF detection finding output (#11492)
Signed-off-by: Oleksandr Sanin <alexaaander.sanin@gmail.com>
Co-authored-by: Hugo P.Brito <hugopbrit@gmail.com>
2026-07-28 08:19:23 +01:00
César Arroba 0c0f150cd4 chore(ui): drop the unused deployment-mode variable (#12131) 2026-07-27 18:44:00 +02:00
rayair250-droidandHugo P.Brito 43de7709cb fix(gcp): detect SSH/RDP exposure when the port is not first in a multi-port firewall rule (#12115)
Co-authored-by: Hugo P.Brito <hugopbrit@gmail.com>
2026-07-27 16:44:35 +01:00
Pepe Fagoaga bb18dcb882 docs(readme): agentic cloud defender and link to Prowler Cloud (#12135) 2026-07-27 17:21:17 +02:00
Bruno FerreiraCursorcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
f5fe9c7b40 ci(helm): publish immutable chart versions on release (#12056)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-07-27 16:58:59 +02:00
Hugo Pereira Brito 60c81f6549 docs: document Jira dispatch for Finding Groups (#12130) 2026-07-27 15:56:54 +01:00
Alan Buscaglia f18c2841a8 fix(ui): classify attack path findings exactly (#11244) 2026-07-27 14:48:01 +02:00
Bruno FerreiraandCursor 6b21e31a28 fix(app): cap Celery worker concurrency in the Helm chart (#12054)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 12:53:03 +02:00
Pepe Fagoaga 294e665d9e docs: changelog section (#12126) 2026-07-27 12:44:56 +02:00
mintlify[bot]andmintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> 94e14660da docs: apply brand tone and writing style fixes (#12125)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
2026-07-27 09:25:28 +02:00
Nithin ReddyandDaniel Barranquero 339930ef13 feat(ec2): add ec2_instance_stopped_older_than_specific_days check (#12076)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-07-27 09:13:18 +02:00
d933a8ecab feat(providers): add Huawei Cloud provider with CIS 1.0 benchmark (#11950)
Co-authored-by: tomitobio <tomitobio@users.noreply.github.com>
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
Co-authored-by: Lydia Vilchez <lydiavilchezlopez@gmail.com>
2026-07-27 08:52:58 +02:00
Prowler Botandprowler-bot da09ad9813 chore(release): Bump versions to v5.37.0 (#12113)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-07-24 14:56:54 +02:00
556 changed files with 21206 additions and 1288 deletions
+1 -1
View File
@@ -158,7 +158,7 @@ SENTRY_RELEASE=local
# REO_DEV_CLIENT_ID=
#### Prowler release version ####
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.36.0
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.37.0
# Social login credentials
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
+7 -4
View File
@@ -38,11 +38,14 @@ jobs:
- name: Set up Helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
- name: Set appVersion from release tag
- name: Set chart version and appVersion from release tag
run: |
RELEASE_TAG="${GITHUB_EVENT_RELEASE_TAG_NAME}"
echo "Setting appVersion to ${RELEASE_TAG}"
sed -i "s/^appVersion:.*/appVersion: \"${RELEASE_TAG}\"/" ${{ env.CHART_PATH }}/Chart.yaml
# Strip any leading "v" so the chart version is valid SemVer 2.
RELEASE_TAG="${GITHUB_EVENT_RELEASE_TAG_NAME#v}"
echo "Setting chart version and appVersion to ${RELEASE_TAG}"
# Publish an immutable chart version per release instead of the static
# 0.0.1 in source, so every release is a distinct, addressable artifact.
yq -i ".version = \"${RELEASE_TAG}\" | .appVersion = \"${RELEASE_TAG}\"" ${{ env.CHART_PATH }}/Chart.yaml
env:
GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
+6 -2
View File
@@ -6,7 +6,10 @@
<b><i>Prowler</b> is the Open Cloud Security Platform trusted by thousands to automate security and compliance in any cloud environment. With thousands of ready-to-use checks and compliance frameworks, Prowler delivers real-time, customizable monitoring and seamless integrations, making cloud security simple, scalable, and cost-effective for organizations of any size.
</p>
<p align="center">
<b>Secure ANY cloud at AI Speed at <a href="https://prowler.com">prowler.com</i></b>
<b>The Agentic Cloud Defender</i></b>
</p>
<p align="center">
<a href="https://cloud.prowler.com/sign-up">Try Prowler Cloud</a>
</p>
<p align="center">
@@ -56,7 +59,7 @@ Prowler includes hundreds of built-in controls to ensure compliance with standar
## Prowler Cloud & Prowler Local Server
[Prowler Cloud](https://cloud.prowler.com/) and Prowler Local Server, its self-hosted open-source version, are web applications that simplify running Prowler across your cloud provider accounts. They provide a user-friendly interface to visualize the results and streamline your security assessments.
[Prowler Cloud](https://cloud.prowler.com/sign-up) and Prowler Local Server, its self-hosted open-source version, are web applications that simplify running Prowler across your cloud provider accounts. They provide a user-friendly interface to visualize the results and streamline your security assessments.
![Prowler Cloud](docs/images/products/overview.png)
![Risk Pipeline](docs/images/products/risk-pipeline.png)
@@ -141,6 +144,7 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
| Vercel | 26 | 6 | 1 | 8 | Official | UI, API, CLI |
| Okta | 29 | 8 | 2 | 2 | Official | UI, API, CLI |
| Linode [Contact us](https://prowler.com/contact) | 10 | 3 | 1 | 4 | Unofficial | CLI |
| Huawei Cloud [Contact us](https://prowler.com/contact) | 25 | 10 | 1 | 6 | Unofficial | CLI |
| E2E Networks [Contact us](https://prowler.com/contact) | 27 | 6 | 0 | 2 | Unofficial | CLI |
| Scaleway [Contact us](https://prowler.com/contact) | 1 | 1 | 1 | 1 | Unofficial | CLI |
| StackIT [Contact us](https://prowler.com/contact) | 7 | 2 | 1 | 3 | Unofficial | CLI |
@@ -0,0 +1 @@
Attack Paths IAM privilege-escalation queries no longer build an all-nodes × all-resource-items cartesian product, fixing runtime errors and timeouts on accounts with many IAM roles, users, or groups
+2 -2
View File
@@ -45,7 +45,7 @@ dependencies = [
"gunicorn==26.0.0",
"uvloop==0.22.1",
"lxml==6.1.0",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.36",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
"psycopg2-binary==2.9.9",
"pytest-celery[redis] (==1.3.0)",
"sentry-sdk[django] (==2.56.0)",
@@ -71,7 +71,7 @@ name = "prowler-api"
package-mode = false
# Needed for the SDK compatibility
requires-python = ">=3.11,<3.13"
version = "1.37.0"
version = "1.38.0"
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
# target-version tracks this project's lowest supported Python.
+169 -74
View File
@@ -1927,12 +1927,20 @@ AWS_IAM_PRIVESC_CREATE_ACCESS_KEY = AttackPathsQueryDefinition(
OR act.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
// Find target users that the principal can create access keys for
MATCH path_target = (aws)--(target_user:AWSUser)
// Pre-aggregate this statement's resource values into a list so the user
// match below is evaluated once per user (in-memory `any`) instead of
// building an (all-users x all-resource-items) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_user.name
OR target_user.arn CONTAINS res.value
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Find target users that the principal can create access keys for.
// Bind name/arn once so the `any` predicate reads locals.
MATCH path_target = (aws)--(target_user:AWSUser)
WITH path_principal, path_target, res_values, res_wildcard,
target_user.name AS uname, target_user.arn AS uarn
WHERE res_wildcard
OR size([rv IN res_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -1975,16 +1983,24 @@ AWS_IAM_PRIVESC_DELETE_CREATE_ACCESS_KEY = AttackPathsQueryDefinition(
OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
// Find target users that the principal can rotate access keys for
MATCH path_target = (aws)--(target_user:AWSUser)
// Pre-aggregate both statements' resource values into lists so the user
// match below is evaluated once per user (in-memory `any`) instead of
// building an (all-users x res x res2) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_user.name
OR target_user.arn CONTAINS res.value
WITH aws, stmt2, path_principal, collect(DISTINCT res.value) AS res_values
MATCH (stmt2)-[:HAS_RESOURCE]->(res2:AWSPolicyStatementResourceItem)
WHERE res2.value = '*'
OR res2.value CONTAINS target_user.name
OR target_user.arn CONTAINS res2.value
WITH aws, path_principal, res_values, collect(DISTINCT res2.value) AS res2_values
WITH aws, path_principal, res_values, res2_values,
('*' IN res_values) AS res_wildcard,
('*' IN res2_values) AS res2_wildcard
// Find target users that the principal can rotate access keys for.
// Bind name/arn once so the `any` predicates read locals.
MATCH path_target = (aws)--(target_user:AWSUser)
WITH path_principal, path_target, res_values, res_wildcard, res2_values, res2_wildcard,
target_user.name AS uname, target_user.arn AS uarn
WHERE (res_wildcard OR size([rv IN res_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0)
AND (res2_wildcard OR size([rv IN res2_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0)
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -2019,12 +2035,20 @@ AWS_IAM_PRIVESC_CREATE_LOGIN_PROFILE = AttackPathsQueryDefinition(
OR act.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
// Find target users that the principal can create login profiles for
MATCH path_target = (aws)--(target_user:AWSUser)
// Pre-aggregate this statement's resource values into a list so the user
// match below is evaluated once per user (in-memory `any`) instead of
// building an (all-users x all-resource-items) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_user.name
OR target_user.arn CONTAINS res.value
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Find target users that the principal can create login profiles for.
// Bind name/arn once so the `any` predicate reads locals.
MATCH path_target = (aws)--(target_user:AWSUser)
WITH path_principal, path_target, res_values, res_wildcard,
target_user.name AS uname, target_user.arn AS uarn
WHERE res_wildcard
OR size([rv IN res_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -2097,12 +2121,20 @@ AWS_IAM_PRIVESC_UPDATE_LOGIN_PROFILE = AttackPathsQueryDefinition(
OR act.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
// Find target users that the principal can update login profiles for
MATCH path_target = (aws)--(target_user:AWSUser)
// Pre-aggregate this statement's resource values into a list so the user
// match below is evaluated once per user (in-memory `any`) instead of
// building an (all-users x all-resource-items) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_user.name
OR target_user.arn CONTAINS res.value
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Find target users that the principal can update login profiles for.
// Bind name/arn once so the `any` predicate reads locals.
MATCH path_target = (aws)--(target_user:AWSUser)
WITH path_principal, path_target, res_values, res_wildcard,
target_user.name AS uname, target_user.arn AS uarn
WHERE res_wildcard
OR size([rv IN res_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -2333,12 +2365,21 @@ AWS_IAM_PRIVESC_UPDATE_ASSUME_ROLE_POLICY = AttackPathsQueryDefinition(
// Collapse the action-item fan-out: one row per (statement chain), not per matching action
WITH DISTINCT aws, stmt, path_principal
// Find target roles whose trust policy this statement's resource can target
MATCH path_target = (aws)--(target_role:AWSRole)
// Pre-aggregate this statement's resource values into a list so the role
// match below is evaluated once per role (in-memory `any`) instead of
// building an (all-roles x all-resource-items) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_role.name
OR target_role.arn CONTAINS res.value
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Find target roles whose trust policy this statement's resource can target.
// Bind the role's name/arn once so the `any` predicate reads them from a
// local variable instead of re-reading the property store per resource.
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
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -2373,12 +2414,20 @@ AWS_IAM_PRIVESC_ADD_USER_TO_GROUP = AttackPathsQueryDefinition(
OR act.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
// Find target groups the principal can add users to
MATCH path_target = (aws)--(target_group:AWSGroup)
// Pre-aggregate this statement's resource values into a list so the group
// match below is evaluated once per group (in-memory `any`) instead of
// building an (all-groups x all-resource-items) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_group.name
OR target_group.arn CONTAINS res.value
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Find target groups the principal can add users to.
// Bind name/arn once so the `any` predicate reads locals.
MATCH path_target = (aws)--(target_group:AWSGroup)
WITH path_principal, path_target, res_values, res_wildcard,
target_group.name AS gname, target_group.arn AS garn
WHERE res_wildcard
OR size([rv IN res_values WHERE rv CONTAINS gname OR garn CONTAINS rv]) > 0
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -2462,16 +2511,24 @@ AWS_IAM_PRIVESC_ATTACH_USER_POLICY_CREATE_ACCESS_KEY = AttackPathsQueryDefinitio
OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
// Find target users the principal can attach policies to and create keys for
MATCH path_target = (aws)--(target_user:AWSUser)
// Pre-aggregate both statements' resource values into lists so the user
// match below is evaluated once per user (in-memory `any`) instead of
// building an (all-users x res x res2) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_user.name
OR target_user.arn CONTAINS res.value
WITH aws, stmt2, path_principal, collect(DISTINCT res.value) AS res_values
MATCH (stmt2)-[:HAS_RESOURCE]->(res2:AWSPolicyStatementResourceItem)
WHERE res2.value = '*'
OR res2.value CONTAINS target_user.name
OR target_user.arn CONTAINS res2.value
WITH aws, path_principal, res_values, collect(DISTINCT res2.value) AS res2_values
WITH aws, path_principal, res_values, res2_values,
('*' IN res_values) AS res_wildcard,
('*' IN res2_values) AS res2_wildcard
// Find target users the principal can attach policies to and create keys for.
// Bind name/arn once so the `any` predicates read locals.
MATCH path_target = (aws)--(target_user:AWSUser)
WITH path_principal, path_target, res_values, res_wildcard, res2_values, res2_wildcard,
target_user.name AS uname, target_user.arn AS uarn
WHERE (res_wildcard OR size([rv IN res_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0)
AND (res2_wildcard OR size([rv IN res2_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0)
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -2596,16 +2653,24 @@ AWS_IAM_PRIVESC_PUT_USER_POLICY_CREATE_ACCESS_KEY = AttackPathsQueryDefinition(
OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
// Find target users the principal can put policies on and create keys for
MATCH path_target = (aws)--(target_user:AWSUser)
// Pre-aggregate both statements' resource values into lists so the user
// match below is evaluated once per user (in-memory `any`) instead of
// building an (all-users x res x res2) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_user.name
OR target_user.arn CONTAINS res.value
WITH aws, stmt2, path_principal, collect(DISTINCT res.value) AS res_values
MATCH (stmt2)-[:HAS_RESOURCE]->(res2:AWSPolicyStatementResourceItem)
WHERE res2.value = '*'
OR res2.value CONTAINS target_user.name
OR target_user.arn CONTAINS res2.value
WITH aws, path_principal, res_values, collect(DISTINCT res2.value) AS res2_values
WITH aws, path_principal, res_values, res2_values,
('*' IN res_values) AS res_wildcard,
('*' IN res2_values) AS res2_wildcard
// Find target users the principal can put policies on and create keys for.
// Bind name/arn once so the `any` predicates read locals.
MATCH path_target = (aws)--(target_user:AWSUser)
WITH path_principal, path_target, res_values, res_wildcard, res2_values, res2_wildcard,
target_user.name AS uname, target_user.arn AS uarn
WHERE (res_wildcard OR size([rv IN res_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0)
AND (res2_wildcard OR size([rv IN res2_values WHERE rv CONTAINS uname OR uarn CONTAINS rv]) > 0)
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -2647,16 +2712,24 @@ AWS_IAM_PRIVESC_ATTACH_ROLE_POLICY_UPDATE_ASSUME_ROLE = AttackPathsQueryDefiniti
OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
// Find target roles the principal can attach policies to and update trust policy for
MATCH path_target = (aws)--(target_role:AWSRole)
// Pre-aggregate both statements' resource values into lists so the role
// match below is evaluated once per role (in-memory `any`) instead of
// building an (all-roles x res x res2) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_role.name
OR target_role.arn CONTAINS res.value
WITH aws, stmt2, path_principal, collect(DISTINCT res.value) AS res_values
MATCH (stmt2)-[:HAS_RESOURCE]->(res2:AWSPolicyStatementResourceItem)
WHERE res2.value = '*'
OR res2.value CONTAINS target_role.name
OR target_role.arn CONTAINS res2.value
WITH aws, path_principal, res_values, collect(DISTINCT res2.value) AS res2_values
WITH aws, path_principal, res_values, res2_values,
('*' IN res_values) AS res_wildcard,
('*' IN res2_values) AS res2_wildcard
// Find target roles the principal can attach policies to and update trust
// policy for. Bind name/arn once so the `any` predicates read locals.
MATCH path_target = (aws)--(target_role:AWSRole)
WITH path_principal, path_target, res_values, res_wildcard, res2_values, res2_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)
AND (res2_wildcard OR size([rv IN res2_values WHERE rv CONTAINS rname OR rarn CONTAINS rv]) > 0)
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
@@ -2698,17 +2771,31 @@ AWS_IAM_PRIVESC_CREATE_POLICY_VERSION_UPDATE_ASSUME_ROLE = AttackPathsQueryDefin
OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
// Find target roles with customer-managed policies the principal can modify and update trust policy for
MATCH path_target = (aws)--(target_role:AWSRole)
// Pre-aggregate both statements' resource values into lists so the role
// and policy matches below are evaluated with an in-memory `any` instead
// of building an (all-roles x res2) x (policies x res) cartesian product.
MATCH (stmt2)-[:HAS_RESOURCE]->(res2:AWSPolicyStatementResourceItem)
WHERE res2.value = '*'
OR res2.value CONTAINS target_role.name
OR target_role.arn CONTAINS res2.value
MATCH (target_role)-[:POLICY]->(target_policy:AWSPolicy)
WHERE target_policy.arn CONTAINS $provider_uid
WITH aws, stmt, path_principal, collect(DISTINCT res2.value) AS res2_values
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR target_policy.arn CONTAINS res.value
WITH aws, path_principal, res2_values, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res2_values, res_values,
('*' IN res2_values) AS res2_wildcard,
('*' IN res_values) AS res_wildcard
// Find target roles with customer-managed policies the principal can
// modify and update trust policy for. Bind name/arn once so the `any`
// predicates read locals instead of re-reading the property store.
MATCH path_target = (aws)--(target_role:AWSRole)
WITH path_principal, path_target, target_role, res_values, res_wildcard,
res2_values, res2_wildcard,
target_role.name AS rname, target_role.arn AS rarn
WHERE res2_wildcard
OR size([rv IN res2_values WHERE rv CONTAINS rname OR rarn CONTAINS rv]) > 0
MATCH (target_role)-[:POLICY]->(target_policy:AWSPolicy)
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
@@ -2750,16 +2837,24 @@ AWS_IAM_PRIVESC_PUT_ROLE_POLICY_UPDATE_ASSUME_ROLE = AttackPathsQueryDefinition(
OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
// Find target roles the principal can put inline policies on and update trust policy for
MATCH path_target = (aws)--(target_role:AWSRole)
// Pre-aggregate both statements' resource values into lists so the role
// match below is evaluated once per role (in-memory `any`) instead of
// building an (all-roles x res x res2) cartesian product.
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_role.name
OR target_role.arn CONTAINS res.value
WITH aws, stmt2, path_principal, collect(DISTINCT res.value) AS res_values
MATCH (stmt2)-[:HAS_RESOURCE]->(res2:AWSPolicyStatementResourceItem)
WHERE res2.value = '*'
OR res2.value CONTAINS target_role.name
OR target_role.arn CONTAINS res2.value
WITH aws, path_principal, res_values, collect(DISTINCT res2.value) AS res2_values
WITH aws, path_principal, res_values, res2_values,
('*' IN res_values) AS res_wildcard,
('*' IN res2_values) AS res2_wildcard
// Find target roles the principal can put inline policies on and update
// trust policy for. Bind name/arn once so the `any` predicates read locals.
MATCH path_target = (aws)--(target_role:AWSRole)
WITH path_principal, path_target, res_values, res_wildcard, res2_values, res2_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)
AND (res2_wildcard OR size([rv IN res2_values WHERE rv CONTAINS rname OR rarn CONTAINS rv]) > 0)
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
version: 1.37.0
version: 1.38.0
description: |-
Prowler API specification.
Generated
+4 -4
View File
@@ -4673,8 +4673,8 @@ wheels = [
[[package]]
name = "prowler"
version = "5.36.0"
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=v5.36#2298d4a3f881abe9e2266195f764b60d34af07eb" }
version = "5.35.0"
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#f5ea116763aeffede9f399c8934fc280eaccd315" }
dependencies = [
{ name = "alibabacloud-actiontrail20200706" },
{ name = "alibabacloud-credentials" },
@@ -4762,7 +4762,7 @@ dependencies = [
[[package]]
name = "prowler-api"
version = "1.37.0"
version = "1.38.0"
source = { virtual = "." }
dependencies = [
{ name = "cartography" },
@@ -4862,7 +4862,7 @@ requires-dist = [
{ name = "matplotlib", specifier = "==3.10.8" },
{ name = "neo4j", specifier = "==6.1.0" },
{ name = "openai", specifier = "==1.109.1" },
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=v5.36" },
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=master" },
{ name = "psycopg2-binary", specifier = "==2.9.9" },
{ name = "pytest-celery", extras = ["redis"], specifier = "==1.3.0" },
{ name = "reportlab", specifier = "==4.4.10" },
+5
View File
@@ -189,6 +189,11 @@ api:
DJANGO_STALE_WHILE_REVALIDATE: "60"
DJANGO_MANAGE_DB_PARTITIONS: "True"
DJANGO_BROKER_VISIBILITY_TIMEOUT: "86400"
# Caps the Celery prefork pool size on the worker pods. Without it, Celery
# sizes the pool from the number of visible CPUs, so on large nodes the
# worker spawns one child per CPU, each loading the full Prowler SDK, and
# OOMKills under memory pressure. Raise it on bigger workers.
DJANGO_CELERY_WORKER_CONCURRENCY: "2"
# Secret names to be used as env vars for api, worker, and worker_beat.
secrets: []
+652
View File
@@ -0,0 +1,652 @@
---
title: "Changelog"
description: "New features and improvements in each Prowler release"
rss: true
---
<Update label="v5.36.0" description="July 24, 2026">
### 🎫 Finding Groups - Jira
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Selected Findings, Finding Groups, and mixed selections can now be sent to Jira. When you select multiple findings, choose between one grouped issue or separate issues. Generated issues keep their Prowler context with deep links and filter details, while the UI provides clear dispatch and failure feedback.
![Send findings to Jira](/images/changelog/v5.36.0-finding-groups-jira.png)
Read more in the [Jira integration documentation](/user-guide/tutorials/prowler-app-jira-integration).
### 🕸️ Attack Paths - Queries
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Prowler Cloud now records which built-in Attack Paths queries returned data at the end of each scan. The query selector hides confirmed-empty queries for the selected scan, so you can focus on paths that exist without opening blank graph views. Errored, unknown, and parameterized queries remain available when they still require investigation or input.
All Attack Paths queries are now published on [Prowler Hub](https://hub.prowler.com), where you can browse the full catalog.
![Attack Paths query selector](/images/changelog/v5.36.0-attack-paths-queries.png)
Read more in the [Attack Paths documentation](/user-guide/tutorials/prowler-app-attack-paths).
### 🧑‍🏫 New Tutorials: Connect Your AI Agents to Prowler Cloud
<Note>
This feature needs a Prowler Cloud API key, so it is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
New tutorials walk you through connecting your own AI agents to Prowler Cloud, so they can query your security posture and act on it programmatically.
Read more in the [AI agents documentation](/user-guide/ai-agents/index).
### ☁️ Region-less Oracle Cloud Infrastructure Setup
Oracle Cloud Infrastructure (OCI) provider credentials no longer require a region. Existing clients can still send the legacy `region` field for compatibility, but the API ignores it before storing credentials or starting a scan. This removes an unnecessary step from OCI onboarding.
Read more in the [OCI documentation](/user-guide/providers/oci/getting-started-oci).
### 🔍 Checks
#### AWS
- `sagemaker_notebook_instance_no_secrets` scans the `OnCreate` and `OnStart` lifecycle scripts of SageMaker notebook instances for hardcoded API keys, passwords, tokens, connection strings, and other secrets. Thanks to @kiranrajsg!
Read more in the [AWS documentation](/user-guide/providers/aws/getting-started-aws). Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).
### 🔐 Security
- Integration responses and operations now respect provider visibility, preventing hidden-provider disclosure and blocking unauthorized attachment, connection checks, Jira dispatches, edits, and deletion.
- Next.js was updated from 16.2.9 to 16.2.11, patching four high-severity and five medium-severity vulnerabilities.
- The unused `npm` CLI was removed from the UI container image, eliminating the bundled `node-tar` CVE-2026-59873 and reducing exposure to future bundled npm vulnerabilities.
- Vitest and its browser packages were updated from 4.1.8 to 4.1.10, resolving the critical `@vitest/browser` file-access permission bypass. These are development dependencies and have no runtime impact.
- Kubernetes kubeconfig validation now blocks legacy `auth-provider.config.cmd-path` command authentication, closing a command-execution bypass.
- `next-auth` was updated from 5.0.0-beta.30 to 5.0.0-beta.32, patching two critical Auth.js advisories: existence-based authorization checks that could fail open when a provider is misconfigured, and a homoglyph `@` bypass in email address normalization. The bump also pulls in the patched `@auth/core` 0.41.3 transitively.
### 🙌 External Contributors
Thank you to our community contributors for this release!
- @kiranrajsg: AWS `sagemaker_notebook_instance_no_secrets` check ([#11843](https://github.com/prowler-cloud/prowler/pull/11843))
- @owenchenxy: Alibaba Cloud SSH and RDP security group checks now handle capitalized `Policy="Accept"` values correctly ([#12049](https://github.com/prowler-cloud/prowler/pull/12049))
- @rsaladra: S3 bucket name validation no longer raises an invalid escape sequence `SyntaxWarning` at startup ([#12041](https://github.com/prowler-cloud/prowler/pull/12041))
- @SujayKulkarni-2211: Updated the AWS check count in the README ([#12011](https://github.com/prowler-cloud/prowler/pull/12011))
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.36.0) for the complete list of changes.
</Update>
<Update label="v5.35.0" description="July 17, 2026">
### 💬 Lighthouse AI - Side Chat
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Lighthouse AI now lives in a side panel you can open from anywhere in the app. Ask about the findings you are looking at without leaving the page, and expand to the full-page chat at any time: your draft, messages, and streaming response come along. Finding and resource details share the same panel, with tabs to switch between Details and Lighthouse AI.
![Lighthouse AI side chat](/images/changelog/v5.35.0-lighthouse-ai-side-chat.png)
Read more in the [Lighthouse AI documentation](/getting-started/products/prowler-cloud-lighthouse#side-panel).
### 🤖 Lighthouse AI - Take Action
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Lighthouse AI is no longer read-only. Ask it to do things and it will: connect or remove providers, trigger a scan, schedule daily scans, update scan settings, and manage your mutelist and mute rules, straight from the chat. Every action is gated by RBAC: Lighthouse can only do what the user asking could do themselves.
Read more in the [Lighthouse AI capabilities](/getting-started/products/prowler-cloud-lighthouse#capabilities).
### ☁️ One-step AWS Organizations onboarding
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Onboarding an entire AWS Organization is now a single step. One CloudFormation quick-create link deploys the management account role and a service-managed StackSet that rolls the role out to every member account, replacing the manual StackSet console setup. Target the whole organization or a specific Organizational Unit or Root ID, and deploy from the management account or a delegated administrator. The S3 integration quick-create link also pre-fills the bucket owner account ID, preventing a stack validation error.
![AWS Organizations onboarding wizard](/images/changelog/v5.35.0-aws-orgs-wizard.png)
Built on the full-organization CloudFormation template contributed by @jchrisfarris — thanks!
Read more in the [AWS Organizations documentation](/user-guide/tutorials/prowler-cloud-aws-organizations).
### 🎯 Scan configurations: exclude checks and services
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Scan configurations now accept `excluded_checks` and `excluded_services` to narrow the execution scope. Skip individual checks or entire services per provider, and the scan does not run them at all: less noise, faster scans, and no findings you would mute anyway.
Read more in the [Scan Configuration documentation](/user-guide/tutorials/prowler-app-scan-configuration#limiting-the-scan-scope).
### 🧭 Redesigned sidebar navigation
The sidebar was redesigned around how you actually work: grouped sections for security, settings, and help, a Home/Chat switch at the top, collapsible configuration entries, clearer active states, and a responsive mobile overlay.
![Redesigned sidebar](/images/changelog/v5.35.0-new-menu.png)
### 🔌 Prowler MCP tools renamed to `prowler_*`
Core Prowler tools in Prowler MCP moved from the `prowler_app_*` prefix to the shorter `prowler_*` namespace, and the MCP documentation was restructured around it. Legacy `prowler_app_*` names keep working in Lighthouse AI, so existing setups are not broken.
Read more in the [Prowler MCP tools reference](/getting-started/basic-usage/prowler-mcp-tools).
### 🔐 Security
- Jira integration credentials now only accept bare Atlassian site names (letters, numbers, and hyphens), and Jira tenant information requests validate site names and no longer follow redirects.
- Social account linking now requires a verified matching email from both the identity provider and the existing user account, and account connection notification emails are disabled.
- 13 advisories reported by `pnpm audit` on the UI (3 high, 9 moderate, 1 low) are resolved with patched versions of `hono`, `ws`, `vite`, `dompurify`, `js-yaml`, `@opentelemetry/core`, and `@babel/core`, including `hono` CVE-2026-59896.
### 🙌 External Contributors
No external contributors in this release.
Special mention to @jchrisfarris, whose full-organization CloudFormation template from v5.34.0 powers the new one-step AWS Organizations onboarding ([#10403](https://github.com/prowler-cloud/prowler/pull/10403)).
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.35.0) for the complete list of changes.
</Update>
<Update label="v5.34.0" description="July 15, 2026">
### 🏷️ New product names
The Prowler family has grown, and the names now say what each product is. Same products, clearer names:
**Prowler products:**
- **Prowler Cloud** — the managed cloud security platform operated by the Prowler team.
- **Prowler Private Cloud** (formerly *Prowler Enterprise*) — the self-hosted deployment of Prowler Cloud in your own environment.
- **Prowler Hub** — the free public library of versioned checks, cloud service artifacts, and compliance frameworks.
- **Prowler Lighthouse AI** — The Agentic Cloud Defender in Prowler Cloud and Prowler Private Cloud.
- **Prowler MCP** — the MCP server that connects AI assistants and agents to Prowler, including the IDE plugins.
**Open source projects:**
- **Prowler CLI** — the command-line scanner for all supported providers.
- **Prowler Local Server** (formerly *Prowler App*) — the self-hosted web application and API to run scans, visualize findings, and manage providers.
- **Prowler Local Dashboard** — the web dashboard for visualizing Prowler CLI scan results, distributed with the CLI.
- **Prowler SDK** — the Python library behind Prowler CLI and Prowler Local Server.
See the full family in the [Prowler products documentation](/getting-started/products).
### 🧭 Cross-Provider Compliance
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
One framework, every cloud, a single answer. The new **Cross-provider** tab in Compliance takes the most recent completed scan of every compatible provider and rolls them up into a single compliance posture per framework, with a per-provider breakdown and a combined executive PDF report. Requirement status follows strict precedence (FAIL over PASS over MANUAL), so one failing provider is enough to flag a requirement across your whole estate.
![Cross-provider compliance overview](/images/changelog/v5.34.0-cross-provider-compliance-overview.png)
Three universal frameworks support it today:
- **CIS Controls 8.1** — AWS, Azure, Google Cloud, Microsoft 365, Kubernetes, GitHub, Google Workspace, Okta, Oracle Cloud, Alibaba Cloud, Cloudflare, MongoDB Atlas, OpenStack, and Vercel.
- **CSA CCM 4.0** — AWS, Azure, Google Cloud, Alibaba Cloud, and Oracle Cloud.
- **DORA 2022/2554** — AWS, Azure, Google Cloud, Alibaba Cloud, and Cloudflare.
Filter by provider type, account, or provider group, drill into each framework's requirements, and export the combined PDF.
![Cross-provider compliance detail](/images/changelog/v5.34.0-cross-provider-compliance-detail.png)
Read more in the [Cross-Provider Compliance documentation](/user-guide/compliance/tutorials/cross-provider-compliance).
### 🏢 New Provider — E2E Networks
Prowler now scans [**E2E Networks**](https://www.e2enetworks.com/), with **27 checks** spanning compute nodes, networking, security groups, load balancers, block and file storage, and managed databases. Thanks to @deepak7093 for their 1st provider in Prowler!
Available in the Prowler CLI:
```bash
export E2E_NETWORKS_API_KEY="your-api-key"
export E2E_NETWORKS_AUTH_TOKEN="your-auth-token"
export E2E_NETWORKS_PROJECT_ID="your-project-id"
prowler e2enetworks
```
Read more in the [E2E Networks documentation](/user-guide/providers/e2enetworks/getting-started-e2enetworks). Explore all E2E Networks checks at [Prowler Hub](https://hub.prowler.com/check?provider=e2enetworks).
### 🔐 Security
User role relationship updates in the API are now limited to the active tenant, preserving the role assignments the same user holds in other tenants.
### 🔍 Checks
#### AWS
- `ec2_ami_account_block_public_access` — verifies AMI block public access is enabled at the account level in each Region, so AMIs cannot be shared publicly. Thanks to @goutham-hari!
- `datapipeline_pipeline_no_secrets_in_definition` — scans Data Pipeline object fields, parameter objects, and parameter values for hardcoded secrets with Kingfisher. Thanks to @YinkaMetrics!
- `elbv2_listener_pqc_tls_enabled` — verifies ELBv2 HTTPS/TLS listeners use post-quantum TLS security policies with TLS 1.2 or higher, helping reduce harvest-now-decrypt-later exposure.
- `amplify_app_no_secrets_in_environment` — scans Amplify app and branch environment variables and build settings (buildSpec) for hardcoded secrets with Kingfisher. Thanks to @Deep070203!
#### Azure
- `app_function_ensure_http_is_redirected_to_https` — verifies that Function Apps enforce HTTPS-only traffic. Thanks to @amandalal007!
#### Kubernetes
- `core_minimize_hostpath_volume_mounts` — detects Pods that use `hostPath` volumes. Thanks to @0xTaoZ!
- `core_readonly_root_filesystem_enabled` — verifies that every container in each Pod explicitly sets `readOnlyRootFilesystem: true` in its security context. Thanks to @Weedle02!
#### STACKIT
- `iaas_server_public_ip_attached` — flags IaaS servers that have a public IP address directly attached to a network interface. Thanks to @johannes-engler-mw!
Explore all checks at [Prowler Hub](https://hub.prowler.com/check).
### 🙌 External Contributors
Thank you to our community contributors for this release!
- @jchrisfarris — Deploy AWS Organizations with the CloudFormation template in one step ([#10403](https://github.com/prowler-cloud/prowler/pull/10403))
- @deepak7093 — New E2E Networks provider: 27 checks across compute nodes, networking, security groups, load balancers, block/file storage, and managed databases ([#11654](https://github.com/prowler-cloud/prowler/pull/11654))
- @goutham-hari — AWS `ec2_ami_account_block_public_access` check ([#11828](https://github.com/prowler-cloud/prowler/pull/11828))
- @YinkaMetrics — AWS `datapipeline_pipeline_no_secrets_in_definition` check ([#11821](https://github.com/prowler-cloud/prowler/pull/11821))
- @amandalal007 — Azure `app_function_ensure_http_is_redirected_to_https` check ([#11929](https://github.com/prowler-cloud/prowler/pull/11929))
- @0xTaoZ — Kubernetes `core_minimize_hostpath_volume_mounts` check ([#11837](https://github.com/prowler-cloud/prowler/pull/11837))
- @Weedle02 — Kubernetes `core_readonly_root_filesystem_enabled` check ([#11835](https://github.com/prowler-cloud/prowler/pull/11835))
- @johannes-engler-mw — STACKIT `iaas_server_public_ip_attached` check ([#11549](https://github.com/prowler-cloud/prowler/pull/11549))
- @janderik — Trailing newlines added to compliance, region, and fixture data files for POSIX compliance ([#11765](https://github.com/prowler-cloud/prowler/pull/11765))
- @Deep070203 — AWS `amplify_app_no_secrets_in_environment` check ([#11825](https://github.com/prowler-cloud/prowler/pull/11825))
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.34.0) for the complete list of changes.
</Update>
<Update label="v5.33.0" description="July 7, 2026">
### 🤖 Lighthouse AI — The Agentic Cloud Defender
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Lighthouse AI is now a full agentic assistant wired to the Prowler Cloud backend. Ask it about your findings, your compliance posture, or your riskiest resources, and watch it work: the agent discovers and runs the Prowler tools it needs to answer, with every tool call visible in the new agentic view. It reads your security data through read-only tools, so it can never touch secrets or modify your tenant.
![Lighthouse AI agentic view](/images/changelog/v5.33.0-lighthouse-ai-1.webp)
The chat experience is rebuilt around **persistent sessions**: conversations stream in real time, stay in your session history, can be archived, and a **sidebar chat mode** lets you ask questions from any page in the app without losing your place.
![Lighthouse AI sessions](/images/changelog/v5.33.0-lighthouse-ai-2.webp)
You control the brain behind it. Configure one or more LLM providers — **OpenAI**, **Amazon Bedrock**, or any **OpenAI-compatible** endpoint (OpenRouter, Ollama) — with connection testing built into the setup and per-provider model selection. Add a shared **business context** (your security goals, compliance needs, organizational priorities) and every session uses it to give answers that fit your environment.
![Lighthouse AI LLM providers](/images/changelog/v5.33.0-lighthouse-ai-3.webp)
Read more in the [Lighthouse AI documentation](/getting-started/products/prowler-cloud-lighthouse) and the [multiple LLM providers guide](/user-guide/tutorials/prowler-cloud-lighthouse-multi-llm).
### 📄 Compliance PDF Reports Without Credentials
Compliance PDF reports no longer require the provider's credentials to be present. Findings are now enriched from the provider metadata stored in the database, so a report still generates even after the provider secret has been deleted or its credentials have become invalid.
Read more in the [compliance documentation](/user-guide/compliance/tutorials/compliance).
### ⏳ Scan Queueing
Overlapping scans for the same provider now queue behind the active one instead of dispatching concurrent scan workers. Launch a manual scan while a scheduled one is running and it waits its turn. No more duplicated work or racing scans.
### 🔐 Security
The Kubernetes provider credentials now reject kubeconfigs using `exec` authentication in Prowler Cloud, at the API and in the credential form, preventing user-supplied commands from running on Cloud workers.
Read more in the [Kubernetes provider authentication documentation](/user-guide/providers/kubernetes/getting-started-k8s#step-2-configure-kubernetes-authentication).
### 🙌 External Contributors
Thank you to our community contributors for this release!
- @kratos0718 — Azure `postgresql_flexible_server_log_retention_days_greater_3` Flexible Server log retention fix ([#11761](https://github.com/prowler-cloud/prowler/pull/11761))
- @Sanjays2402 — `KeyError: 'MANUAL'` crash fix in the compliance summary table, shipped early in v5.32.1 ([#11823](https://github.com/prowler-cloud/prowler/pull/11823))
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.33.0) for the complete list of changes.
</Update>
<Update label="v5.32.0" description="July 2, 2026">
### 🔎 Findings Triage
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Triage findings straight from the Findings view. Each finding gets a triage status you can move through its lifecycle:
**Open → Under Review → Remediating → Risk Accepted → False Positive → Resolved**
Add a triage note to record the decision, mute a finding, all from the row's actions menu. The current status shows inline on every finding row, so you keep track of what has been reviewed and stop re-checking the same issues scan after scan.
![Findings triage statuses](/images/changelog/v5.32.0-triage-1.png)
The status also follows the finding automatically across scans: when a finding flips from `FAIL` to `PASS` on the next scan it moves to **Resolved**, and when it flips from `PASS` back to `FAIL` it moves to **Reopened**. You always know whether an issue is genuinely fixed or has regressed, without touching it by hand.
![Findings triage lifecycle](/images/changelog/v5.32.0-triage-2.png)
Read more in the [Findings Triage documentation](/user-guide/tutorials/prowler-app-findings-triage).
### ⚙️ Scan Configuration
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Create named, reusable scan configurations from a dedicated **Scans / Configuration** page. Each configuration is YAML that follows the structure of [`prowler/config/config.yaml`](https://github.com/prowler-cloud/prowler/blob/master/prowler/config/config.yaml), so you only include the keys you want to override; the rest fall back to the built-in defaults. Values are validated on save against a per-provider, type-safe configuration schema that range-checks each field and rejects unknown keys, so a malformed config is caught before it ever reaches a scan. Attach a configuration to one or more providers so it applies on their next scan, or save it now and attach providers later.
![Scan configuration editor](/images/changelog/v5.32.0-config-1.png)
From the Providers view you can pick which configuration a provider uses (`Default` or any of your saved ones) without leaving the page. No more passing config files around by hand.
![Scan configuration per provider](/images/changelog/v5.32.0-config-2.png)
Read more in the [Scan Configuration documentation](/user-guide/tutorials/prowler-app-scan-configuration).
### ✅ Per-Requirement Configuration Validation
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Compliance frameworks can now declare `ConfigRequirements` on a requirement, so it's reported as **FAIL** when its mapped checks ran under a configuration too loose to satisfy it. Even if every individual finding PASSed. This applies across all compliance outputs: CSV, OCSF, and console tables, and is the engine behind Scan Configuration's "marked as FAIL" behavior described above.
![Per-requirement configuration validation](/images/changelog/v5.32.0-per-requirement-validation.png)
Read more in the [Configuration File documentation](/user-guide/cli/tutorials/configuration_file).
### ⏱️ Okta — Request Throttling & Retries
Prowler now proactively throttles Okta API requests to stay under rate limits, with reactive retries on HTTP 429 as a safety net. Both are set in the scan configuration (or their equivalent CLI flags):
- `okta_requests_per_second` (config file) / `--okta-requests-per-second` (CLI) — cap the request rate. Default: 4 req/s.
- `okta_max_retries` (config file) / `--okta-retries-max-attempts` (CLI) — bound retry attempts. Default: 5.
This makes large Okta scans more reliable and less likely to be rate-limited.
Read more in the [Okta rate limit documentation](/user-guide/providers/okta/retry-configuration#request-throttling-requests-per-second).
### 📉 AWS — Cap Resources Scanned per Service
Large AWS accounts can now cap how many resources Prowler analyzes for the highest-volume services, keeping scan time and cost under control. Set a global limit with `max_scanned_resources_per_service`, or override it per service:
- EBS snapshots (`max_ebs_snapshots`)
- Backup recovery points (`max_backup_recovery_points`)
- CloudWatch log groups (`max_cloudwatch_log_groups`)
- Lambda functions (`max_lambda_functions`)
- ECS task definitions (`max_ecs_task_definitions`)
- CodeArtifact packages (`max_codeartifact_packages`)
Limits are **disabled by default** (`0` = unlimited); only positive values cap the analyzed resources.
<Warning>
When a positive limit is set, compliance results reflect only the sampled resources, not every matching resource in the account.
</Warning>
Read more in the [configuration file documentation](/user-guide/cli/tutorials/configuration_file#supported-aws-resource-limits).
### 🏷️ Azure — Filter by Resource Group
Azure scans can now be scoped to one or more resource groups with the new `--azure-resource-group` / `--azure-resource-groups` option. This lets you run focused assessments against specific environments, teams, or workloads instead of scanning every accessible resource in the subscription. Thanks to @Legin-ML for contributing this feature!
```bash
# Single resource group
prowler azure --az-cli-auth --azure-resource-group rg-prod
# Multiple resource groups
prowler azure --az-cli-auth --azure-resource-group rg-prod1 rg-prod2
```
Read more in the [Azure Resource Groups documentation](/user-guide/providers/azure/resource-groups).
### 🧭 Provider Group Filter
Filter the **Overview, Findings, Resources, Scans, and Providers** views by provider group. Scope the whole app to a team, an environment, or a business unit in one click instead of filtering provider by provider.
![Provider group filter](/images/changelog/v5.32.0-provider-group-filter.png)
Read more about managing provider groups in the [RBAC documentation](/user-guide/tutorials/prowler-app-rbac).
### 🔬 API — Timestamp Precision in Findings Filters
The `/api/v1/findings` endpoint now accepts full timestamps on the `inserted_at` and `updated_at` filters (`filter[inserted_at__gte]`, `filter[inserted_at__lte]`, and the `updated_at` variants), so you can query narrow time windows instead of whole days. Date-only filtering keeps working, so existing integrations are unaffected.
```bash
# Findings inserted within a precise timestamp window
curl --globoff \
'http://localhost:8080/api/v1/findings?filter[inserted_at__gte]=2026-07-01T06:12:18Z&filter[inserted_at__lte]=2026-07-02T19:25:55Z' \
-H 'Authorization: Bearer <YOUR_TOKEN>' \
-H 'Accept: application/vnd.api+json'
```
### 🕸️ Attack Paths — Neptune as a persistent sink
Attack Paths can now persist its graph in **AWS Neptune** in addition to Neo4j, selectable via `ATTACK_PATHS_SINK_DATABASE=neptune` (default `neo4j`). Cartography's per-scan ingest database stays on Neo4j. The scan task preflights the ingest database and the configured sink before ingestion, and provider graph cleanup now deletes relationships in directed batches before deleting nodes.
This is the groundwork for scale: a managed graph database lets Attack Paths hold much larger graphs, extend coverage to more providers, and link resources across them so an attack path can cross provider boundaries instead of stopping at one cloud's edge.
Read more in the [Attack Paths documentation](/user-guide/tutorials/prowler-app-attack-paths).
### 🔐 New Secret-Scanning Engine — Kingfisher
Prowler's secret-scanning checks now run on [Kingfisher](https://github.com/mongodb/kingfisher) instead of `detect-secrets`. Scans run **fully offline by default**, and obvious placeholder values (e.g. `password123`, `changeme`) are no longer reported, cutting down false positives.
Opt in to **live validation** with the new `--scan-secrets-validate` flag (or the `aws.secrets_validate` config option): Prowler checks discovered secrets against the provider APIs, and any secret confirmed to be **live is reported as critical**, so you can prioritize the credentials that actually work.
<Note>
The `detect_secrets_plugins` configuration option has been removed, as it is no longer used by the new engine.
</Note>
Read more in the [secret detection documentation](/user-guide/cli/tutorials/pentesting#detect-secrets).
### 🔍 Checks
#### AWS
- `stepfunctions_statemachine_encrypted_with_cmk` — Step Functions state machines use a customer-managed KMS key for encryption at rest instead of the default AWS-owned key. Thanks to @Sid-0602!
- `waf_regional_webacl_logging_enabled` — AWS WAF Classic Regional Web ACLs have logging enabled to a Kinesis Data Firehose stream. Thanks to @Sid-0602!
- **IAM privilege escalation** — the privesc checks now cover **AWS Bedrock AgentCore** paths across Runtime, Harness, Code Interpreter, and Custom Browser. Thanks to @MrCloudSec!
- `apigateway_restapi_no_secrets_in_stage_variables` — scans API Gateway REST API stage variables for hardcoded passwords, API keys, and tokens. Thanks to @chirag1206!
- `awslambda_function_no_secrets_in_code` — this check now supports a `secrets_ignore_files` audit-config option to skip files inside the deployment package by glob pattern (e.g. `*.deps.json`), suppressing .NET dependency-manifest false positives without masking real secrets.
- `s3_bucket_object_public` — spot-checks a configurable sample of object ACLs in each bucket and flags objects granted to the `AllUsers` or `AuthenticatedUsers` groups. Disabled by default; opt in via the `s3_bucket_object_public_enabled` configuration option. Thanks to @Synchx00!
#### Microsoft 365
New **Conditional Access** hardening checks:
- `entra_conditional_access_policy_explicitly_targets_azure_devops` — at least one enabled policy explicitly includes the Azure DevOps cloud application, rather than relying on a broad "All cloud apps" policy. Thanks to @mzl2233!
- `entra_conditional_access_policy_no_exclusion_gaps` — every user, group, role, or application excluded from an enabled policy stays in scope of another enabled policy. Thanks to @UTKARSH698 with @arieleli01212 as co-author!
- `entra_conditional_access_policy_groups_management_restricted` — every security group referenced by an enabled or report-only policy is management-restricted or role-assignable. Thanks to @SAMurai-16!
- `exchange_application_access_policy_restricts_mailbox_apps` — every service principal with Microsoft Graph application-level Exchange mailbox permissions is restricted by an Exchange Online Application Access Policy. Thanks to @VasistAcharya!
### 📚 Compliance
#### CIS Benchmark Refresh — Six New Versions
Prowler ships a coordinated refresh of the CIS Benchmarks across six providers:
- **AWS** — CIS Amazon Web Services Foundations Benchmark v7.0.0, adding the new Organizations section (2.1.1-2.1.6), resource policy (2.21), web front-end access logging (4.10), and VPC Endpoints (6.8) recommendations.
- **Azure** — CIS Microsoft Azure Foundations Benchmark v6.0.0.
- **GCP** — CIS Google Cloud Platform Foundation Benchmark v5.0.0.
- **Kubernetes** — CIS Kubernetes Benchmark v2.0.1.
- **GitHub** — CIS GitHub Benchmark v1.2.0.
- **Microsoft 365** — CIS Microsoft 365 Foundations Benchmark v7.0.0.
#### CIS Controls v8.1 — Universal Framework
A new **universal** (cross-provider) compliance framework mapping existing checks across 18 providers — AWS, Azure, GCP, Kubernetes, M365, GitHub, AlibabaCloud, OracleCloud, GoogleWorkspace, Okta, Cloudflare, Vercel, MongoDB Atlas, OpenStack, Linode, StackIT, NHN, and Scaleway — to the 18 CIS Critical Security Controls and their Safeguards. Ships with a dedicated detail view and report mapping in the UI.
Read more in the [compliance documentation](/user-guide/compliance/tutorials/compliance). Explore the full compliance catalog at [Prowler Hub](https://hub.prowler.com/compliance).
### 🙌 External Contributors
Thank you to our community contributors for this release!
- @chirag1206 — `apigateway_restapi_no_secrets_in_stage_variables` check ([#11188](https://github.com/prowler-cloud/prowler/pull/11188))
- @MrCloudSec — AWS Bedrock AgentCore privilege escalation paths in the IAM privesc checks ([#11726](https://github.com/prowler-cloud/prowler/pull/11726))
- @Sid-0602 — `stepfunctions_statemachine_encrypted_with_cmk` ([#11538](https://github.com/prowler-cloud/prowler/pull/11538)) and `waf_regional_webacl_logging_enabled` ([#11539](https://github.com/prowler-cloud/prowler/pull/11539)) checks
- @mzl2233 — `entra_conditional_access_policy_explicitly_targets_azure_devops` check ([#11182](https://github.com/prowler-cloud/prowler/pull/11182))
- @UTKARSH698 with @arieleli01212 as co-author — `entra_conditional_access_policy_no_exclusion_gaps` check ([#11577](https://github.com/prowler-cloud/prowler/pull/11577))
- @SAMurai-16 — `entra_conditional_access_policy_groups_management_restricted` check ([#11342](https://github.com/prowler-cloud/prowler/pull/11342))
- @vahidg — Azure PostgreSQL flexible server collection resilience fix ([#11595](https://github.com/prowler-cloud/prowler/pull/11595))
- @davletd — Azure `keyvault_logging_enabled` `AuditEvent` category fix ([#11660](https://github.com/prowler-cloud/prowler/pull/11660))
- @VasistAcharya — `exchange_application_access_policy_restricts_mailbox_apps` ([#11247](https://github.com/prowler-cloud/prowler/pull/11247))
- @Legin-ML — Filter scans at Resource Group level ([#10657](https://github.com/prowler-cloud/prowler/pull/10657))
- @Synchx00 — `s3_bucket_object_public` check ([#9517](https://github.com/prowler-cloud/prowler/pull/9517))
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.32.0) for the complete list of changes.
</Update>
<Update label="v5.31.0" description="June 23, 2026">
### 🗓️ Flexible Scan Scheduling
<Note>
Available exclusively in **Prowler Cloud**. Prowler Local Server supports daily scans only.
</Note>
![Scan scheduling from the Providers page](/images/changelog/v5.31.0-schedule-1.png)
You can now set a per-provider scan schedule from the Providers page. Pick a **scan time** and a **repeat cadence**: Daily, Every 48 hours, Weekly (with a day-of-week selector), or Monthly. Schedules can be edited or removed at any time, and a new scan never interrupts access to existing data.
![Schedule editor](/images/changelog/v5.31.0-schedule-2.png)
All schedules are listed in one place under the **Scheduled** tab in **Scan Jobs**, showing each provider's cadence, next scan, and last scan at a glance.
![Scheduled tab in Scan Jobs](/images/changelog/v5.31.0-schedule-3.png)
Read more in the [scan scheduling documentation](/user-guide/tutorials/prowler-scan-scheduling).
### 📚 DORA — Expanded Provider Coverage
Prowler extends [**DORA**](https://www.eiopa.europa.eu/digital-operational-resilience-act-dora_en) (Digital Operational Resilience Act, Regulation (EU) 2022/2554) coverage to **Azure**, **GCP**, **Cloudflare**, and **Alibaba Cloud**, mapping each provider's existing checks across the five DORA pillars.
![DORA compliance for Alibaba Cloud](/images/changelog/v5.31.0-dora-alibaba.png)
<Note>
The framework follows the `<name>_<version>` naming convention as `DORA_2022_2554`.
</Note>
Read more in the [compliance documentation](/user-guide/compliance/tutorials/compliance).
### 🚀 Guided Onboarding
<Note>
Available exclusively in **Prowler Cloud**.
</Note>
New accounts now get a guided first-run experience. The Overview greets you with an **"Add your first provider"** prompt: connect a provider so Prowler has something to scan and assess, then get started in one click (or skip for now).
![Guided onboarding prompt](/images/changelog/v5.31.0-onboarding-1.png)
From there, contextual empty states across the product point you to the next action rather than leaving you stuck. Attack Paths, for example, explains that you need a completed scan before it can build a graph and links straight to **Scan Jobs**, with a **"See how it works"** affordance for first-timers.
![Contextual empty states](/images/changelog/v5.31.0-onboarding-2.png)
### 🔐 Optional SAML SSO `userType`
The SAML `userType` attribute is now optional. If your IdP does not send it, or sends it blank, Prowler keeps the user's existing roles unchanged instead of replacing them with a fallback role.
When `userType` is provided, Prowler still maps the user to the matching role. If that role does not exist yet, Prowler creates it with read-only access: visibility over all providers, with no management permissions.
Read more in the [SAML SSO documentation](/user-guide/tutorials/prowler-app-sso).
### 🏢 New Provider — Linode
Prowler now scans [**Linode**](https://www.linode.com/) (Akamai Cloud), covering its administration, compute, and networking services. Thanks to @varunmamillapalli for their 1st provider in Prowler!
<Note>
Linode is not officially supported. For more information, [contact us](https://prowler.com/contact).
</Note>
Read more in the [Linode documentation](/user-guide/providers/linode/getting-started-linode). Explore all Linode checks at [Prowler Hub](https://hub.prowler.com/check?provider=linode).
### 🔍 Checks
#### AWS
**Post-Quantum Cryptography readiness** — get ahead of the migration to quantum-resistant cryptography:
- `cloudfront_distributions_pqc_tls_enabled` — CloudFront distributions enforce a post-quantum TLS 1.3 security policy.
- `apigateway_domain_name_pqc_tls_enabled` — API Gateway custom domain names use a post-quantum TLS security policy.
- `transfer_server_pqc_ssh_kex_enabled` — Transfer Family servers use a post-quantum hybrid SSH key exchange.
- `acmpca_certificate_authority_pqc_key_algorithm` — Private CA authorities use a post-quantum (ML-DSA) key algorithm (new `acmpca` service).
- `rolesanywhere_trust_anchor_pqc_pki` — IAM Roles Anywhere trust anchors are backed by a post-quantum (ML-DSA) PKI (new `rolesanywhere` service).
**Organization-wide governance:**
- `securityhub_delegated_admin_enabled_all_regions` — Security Hub has a delegated administrator, active in all opted-in regions, with organization auto-enable on. Thanks to @ernestprovo23!
- `config_delegated_admin_and_org_aggregator_all_regions` — AWS Config has a delegated administrator and an organization aggregator covering all regions. Thanks to @ernestprovo23!
**Machine learning:**
- `sagemaker_clarify_exists` — verifies at least one SageMaker Clarify processing job exists per scanned region, so bias-detection and model-explainability controls are in place. Thanks to @AlexanderSanin!
#### Azure
A large batch of new Azure checks spanning data, compute, identity, and networking:
- **Cosmos DB** — automatic failover, continuous backup policy, minimum TLS 1.2, and public network access disabled.
- **MySQL & PostgreSQL Flexible Servers** — geo-redundant backup and high availability.
- **AKS** — auto-upgrade, Azure Monitor (Container Insights), local accounts disabled, and Microsoft Defender enabled.
- **Databricks** — public network access disabled and secure cluster connectivity (no public IP).
- **Defender** — CSPM on the Standard tier.
- **Networking** — NSG association on subnets and DDoS Network Protection on VNets.
- **Entra ID** — app registration credential expiry, users with recent sign-in and strong authentication enforcement.
- **Recovery Services** — vaults with at least one protected backup item and vaults with adequate backup policy.
Thanks to @s1ns3nz0 for all these contributions!
#### GCP
New coverage for high availability and public-exposure detection:
- `cloudsql_instance_high_availability_enabled` — Cloud SQL primary instances use `REGIONAL` availability for automatic zone failover.
- `cloudfunction_function_inside_vpc` — Cloud Functions use a Serverless VPC Access connector for private egress.
- `cloudfunction_function_not_publicly_accessible` — detects `allUsers` / `allAuthenticatedUsers` IAM invocation bindings.
- `secretmanager_secret_not_publicly_accessible` — detects Secret Manager secrets with public IAM bindings.
- `secretmanager_secret_rotation_enabled` — verifies Secret Manager secrets have automatic rotation configured with a period of 90 days or less and no missed rotation.
Thanks to @s1ns3nz0 for all these contributions!
#### Kubernetes
New core checks for container resource governance and reliability: CPU limits, CPU requests, memory limits, memory requests, fixed image tags, liveness probes, and readiness probes. Thanks to @Nikhilkumar2311 for all these contributions!
#### Microsoft 365
- `entra_directory_sync_object_takeover_blocked` — hybrid Entra tenants block cloud object takeover through soft-match and hard-match directory synchronization. Thanks to @PrettyFox0 and @omobolajiadeyan!
- `entra_conditional_access_policy_no_deleted_object_references` — flags Conditional Access policies that reference user, group, or role objects that no longer resolve in the directory. Thanks to @ernestprovo23!
#### Oracle Cloud Infrastructure
- `identity_storage_service_level_admins_scoped` — CIS 3.1 control 1.15, ensuring storage service-level administrators exclude delete permissions.
Explore all checks at [Prowler Hub](https://hub.prowler.com/check).
### 🐍 Python 3.13 Support
The Prowler SDK now supports **Python 3.13**. Thanks to @branchv!
### 🔐 Security Updates
- **SDK** — `pytest` 8.3.5 → 9.0.3, `black` 25.1.0 → 26.3.1, `microsoft-kiota-*` → 1.9.9, and `aiohttp` → 3.14.0, patching known CVEs.
- **API** — `aiohttp` → 3.14.0 and `idna` → 3.15, patching known CVEs.
- **UI** — bumped vulnerable `Next.js`, React, AI SDK, `postcss`, `hono`, `qs`, `esbuild`, and Alpine OpenSSL packages; `dompurify` 3.4.2 → 3.4.10, patching XSS sanitization bypass advisories.
- **Containers** — base image bumped to `python:3.12.13-slim-bookworm` (patches `libgnutls30` CVE-2026-33845 and CVE-2026-42010) and `trivy` to 0.71.0 (patches embedded `golang.org/x/crypto` and Go stdlib CVEs).
### 🙌 External Contributors
Thank you to our community contributors for this release!
- @varunmamillapalli — New Linode provider: administration, compute, and networking services ([#11633](https://github.com/prowler-cloud/prowler/pull/11633))
- @s1ns3nz0 — 20+ Azure & GCP checks across Cosmos DB, AKS, Databricks, Flexible Servers, Entra, networking, and GCP public-exposure
- @Nikhilkumar2311 — Kubernetes resource limits, requests, image tag, and probe checks ([#11373](https://github.com/prowler-cloud/prowler/pull/11373))
- @ernestprovo23 — AWS Security Hub/Config org-wide delegated admin checks ([#11259](https://github.com/prowler-cloud/prowler/pull/11259)) and M365 conditional access check ([#11236](https://github.com/prowler-cloud/prowler/pull/11236))
- @AlexanderSanin — `sagemaker_clarify_exists` check ([#11211](https://github.com/prowler-cloud/prowler/pull/11211))
- @PrettyFox0 with @omobolajiadeyan as co-author — M365 directory sync object takeover check ([#11098](https://github.com/prowler-cloud/prowler/pull/11098))
- @branchv — Python 3.13 support ([#9293](https://github.com/prowler-cloud/prowler/pull/9293))
- @alinealfa — GCP audit-filtered aggregated sinks fix ([#11575](https://github.com/prowler-cloud/prowler/pull/11575))
- @b-abderrahmane — Configurable Celery worker concurrency ([#11075](https://github.com/prowler-cloud/prowler/pull/11075))
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.31.0) for the complete list of changes.
</Update>
<Update label="Earlier releases">
Release notes for v5.30.0 and earlier, along with every patch release, are on [GitHub Releases](https://github.com/prowler-cloud/prowler/releases).
</Update>
@@ -0,0 +1,467 @@
---
title: "Attack Paths Queries"
---
This guide explains how to write and maintain Prowler Attack Paths queries: the read-only openCypher queries that traverse the Cartography-ingested cloud graph to detect privilege escalation chains, network exposure, and other graph-shaped security risks.
<Info>
**New to Attack Paths?** Start with the user documentation:
- [Attack Paths](/user-guide/tutorials/prowler-app-attack-paths) - What Attack Paths detects, how to run built-in queries, and how to explore the resulting graph.
- [Writing Custom openCypher Queries](/user-guide/tutorials/prowler-app-attack-paths#writing-custom-opencypher-queries) - Run ad-hoc read-only queries from the Prowler App.
</Info>
## Introduction
Attack Paths queries run against a property graph populated by [Cartography](https://github.com/cartography-cncf/cartography), an open-source graph ingestion framework, and enriched with Prowler findings. Every query is read-only openCypher (Version 9) so it runs on both the Neo4j and Amazon Neptune sinks.
Two categories of query exist, each with a different isolation model:
| | Predefined queries | Custom queries |
| ------------------ | ----------------------------------------------------------- | --------------------------------------------------------------------- |
| Where they live | `api/src/backend/api/attack_paths/queries/{provider}.py` | User-supplied through the custom query API endpoint |
| Provider isolation | `AWSAccount {id: $provider_uid}` anchor plus path connectivity | Automatic `_Provider_{uuid}` label injection by `cypher_sanitizer.py` |
| What to write | Chain every `MATCH` from the `aws` variable | Plain Cypher, no isolation boilerplate |
| Internal labels | Never use | Never use (system-injected) |
For **predefined queries**, every node must be reachable from the `AWSAccount` root through graph traversal. That reachability is the isolation boundary.
For **custom queries**, the runner injects a `_Provider_{uuid}` label into every node pattern, and a post-query filter handles edge cases, so query authors write natural Cypher without isolation boilerplate.
The rest of this guide focuses on predefined queries, though the graph model, list-property handling, and compatibility rules apply to both.
## The Graph Model
### Cartography Schema
Node labels, relationship types, and properties follow the upstream Cartography schema for each provider. Do not guess them, fetch the schema for the pinned Cartography version:
```bash
grep cartography api/pyproject.toml
```
Then read the schema for that exact tag:
```text
# 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
```
The public schema reference for AWS is available at [Cartography AWS Schema](https://cartography-cncf.github.io/cartography/modules/aws/schema.html).
### Prowler-Specific Additions
The Prowler sync task enriches the Cartography graph with the following labels and relationships. These are not part of the upstream schema:
| Label / Relationship | Description |
| ---------------------- | ----------------------------------------------------------- |
| `ProwlerFinding` | Finding node (`status`, `severity`, `check_id`) |
| `Internet` | Internet sentinel node used to model public exposure |
| `CAN_ACCESS` | `(Internet)-[:CAN_ACCESS]->(resource)` exposure edge |
| `HAS_FINDING` | `(resource)-[:HAS_FINDING]->(:ProwlerFinding)` finding link |
| `TRUSTS_AWS_PRINCIPAL` | Role trust relationship |
| `STS_ASSUMEROLE_ALLOW` | Principal can assume a role |
### Internal Isolation Labels
The sync layer also adds internal labels used only for tenant and provider isolation: `_ProviderResource`, `_AWSResource`, `_Tenant_*`, and `_Provider_*`. These must never appear in query text, predefined or custom. The runner applies isolation automatically.
## Query Structure
### Provider Scoping Parameter
| Parameter | Property | Used on | Purpose |
| --------------- | -------- | ------------ | -------------------------------------- |
| `$provider_uid` | `id` | `AWSAccount` | Scopes 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
```python
from api.attack_paths.queries.types import (
AttackPathsQueryAttribution,
AttackPathsQueryDefinition,
AttackPathsQueryParameterDefinition,
)
from tasks.jobs.attack_paths.config import PROWLER_FINDING_LABEL
```
Always reference `PROWLER_FINDING_LABEL` through f-string interpolation, never hardcode `"ProwlerFinding"`.
### Definition Fields
- **id**: kebab-case `{provider}-{category}-{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 `{` and `}` are escaped as `{{` and `}}`.
- **parameters**: `parameters=[]` when the query takes no input.
- **attribution**: optional `AttackPathsQueryAttribution(text, link)` for sourced queries. The `link` uses the lowercase ID.
Append the constant to the `{PROVIDER}_QUERIES` list at the bottom of the provider file.
## The Predefined Query Template
The canonical shape combines a principal walk, an optional target walk, deduplicated nodes, and a typed finding overlay:
```python
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 the source 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, 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 against the in-memory resource list
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
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 missing 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.
- The `RETURN` shape `paths, dpf, dpfr` is the contract the serializer and visualizer depend on. Do not change it.
## Avoiding Cartesian Products
The most common performance defect in Attack Paths queries is a Cartesian product between a target set and a policy statement's resource items. When the two are written as independent `MATCH` clauses, the planner pairs every target with every resource item before any filter runs. On accounts with many IAM principals, that multiplies into hundreds of thousands of rows and the query errors or times out.
### The Pattern That Causes It
```cypher
// One row per (target_role x resource_item): a Cartesian product
MATCH path_target = (aws)--(target_role:AWSRole)
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
OR res.value CONTAINS target_role.name
OR target_role.arn CONTAINS res.value
```
The two `MATCH` clauses share no relationship, so the engine enumerates all targets multiplied by all resource items, applies a non-indexable `CONTAINS` to each pair, then expands the finding overlay for every surviving row. Cost grows with `targets × resources`, and a second constrained statement (`stmt2`) multiplies it again.
### The Pattern That Avoids It
Collect the statement's resource values into a list once, then match each target a single time against that in-memory list:
```cypher
// 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
```
Cost now grows with `targets + resources` (linear) rather than `targets × resources`. The rewrite is a pure algebraic identity: the result set is unchanged.
Guidelines:
- **Aggregate resources before matching targets, not after.** `collect(DISTINCT res.value)` reduces the resource items to a single list per statement.
- **Short-circuit the wildcard grant** with `('*' IN res_values)`. When a statement grants `*`, every target matches, so the list scan is skipped entirely.
- **Bind `target.name` and `target.arn` to local variables** in a `WITH` before the predicate. The list comprehension then reads each once per target instead of re-reading the property store once per resource value.
- **Use `size([... ]) > 0`, not `any(...)`.** The `any()`, `all()`, and `none()` predicate functions are not part of the openCypher specification and fail on Amazon Neptune. See [openCypher Compatibility](#opencypher-compatibility).
- **For 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`.
Every IAM privilege escalation query in `aws.py` uses this pattern. The lateral-movement variants that constrain the target with a relationship (`STS_ASSUMEROLE_ALLOW`, `TRUSTS_AWS_PRINCIPAL`) already limit the target set before the resource filter, which keeps them efficient without further aggregation.
## Privilege Escalation Sub-Patterns
Four `path_target` shapes cover the common escalation types. Each shares the canonical template's `path_principal`, the resource pre-aggregation, the deduplication tail, and the `RETURN`; only the `path_target` `MATCH` and its resource predicate differ.
| Sub-pattern | Target | `path_target` shape | Example |
| ------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------- | ------- |
| Self-escalation | Principal's own policies | `(aws)--(target_policy:AWSPolicy)--(principal)` | IAM-001 |
| Lateral to user | Other IAM users | `(aws)--(target_user:AWSUser)` | IAM-002 |
| Assume-role lateral | Assumable roles | `(aws)--(target_role:AWSRole)-[:STS_ASSUMEROLE_ALLOW]-(principal)` | IAM-014 |
| PassRole plus service | Service-trusting roles | `(aws)--(target_role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]-(:AWSPrincipal {arn: '{service}.amazonaws.com'})` | EC2-001 |
**Multi-permission queries** (for example PassRole plus a service-create action) add permission gates before `path_target`. Reuse the per-query counter for new variables (`act2`, `policy2`, `stmt2`) and collapse rows after each gate:
```cypher
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
```
When a permission is an existence-only gate whose statement resource is not checked later, keep the policy and statement anonymous and carry only the variables still needed:
```cypher
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
```
## Network Exposure Pattern
The Internet node is reached through `CAN_ACCESS` from an already-scoped resource, never as a standalone lookup:
```python
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 through path connectivity from 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
"""
```
The `CAN_ACCESS` edge stays typed and directed (`-[:CAN_ACCESS]->`); that is its canonical sync-time orientation. Network-exposure queries extend the `RETURN` contract with `internet, can_access`.
## Working with List-Typed Properties
Some Cartography node properties carry a list of values: `AWSPolicyStatement.action`, `AWSPolicyStatement.resource`, `AWSPolicyStatement.notaction`, `AWSPolicyStatement.notresource`, `KMSKey.encryption_algorithms`, `CloudFrontDistribution.aliases`, the container-definition lists on `ECSContainerDefinition`, and many others. The graph models each such property as a set of child item nodes connected to the parent by a typed edge. Queries reach the values by traversing the edge; the parent does not carry the list as a single field.
### Naming Convention
For a list-typed parent property the sink stores:
- **Child label**: `<ParentLabel><PropertyPascal>Item`. Example: `AWSPolicyStatement.resource` becomes `AWSPolicyStatementResourceItem`.
- **Edge type**: `HAS_<PROPERTY_UPPER>`. Example: `resource` becomes `HAS_RESOURCE`.
- **Child property**: `value`, a single scalar string per list element. For list-of-dict properties (rare; for example `SecretsManagerSecretVersion.tags`) the child carries the original dict keys as named fields per the catalog's `field_map`.
### Variable Naming for Child-Item Matches
`aws.py` uses a per-query counter for each `HAS_*` traversal so chained matches stay unambiguous. The counter resets at the top of every query.
| Edge | First | Second | Third |
| ----------------- | ------ | ------- | ------- |
| `HAS_ACTION` | `act` | `act2` | `act3` |
| `HAS_RESOURCE` | `res` | `res2` | `res3` |
| `HAS_NOTACTION` | `nact` | `nact2` | `nact3` |
| `HAS_NOTRESOURCE` | `nres` | `nres2` | `nres3` |
### Matching an Action
To find statements that grant `iam:PassRole`, `iam:*`, or `*`, traverse the `HAS_ACTION` edge in its own `MATCH` clause and apply the predicate in the attached `WHERE`:
```cypher
MATCH (stmt:AWSPolicyStatement {effect: 'Allow'})
MATCH (stmt)-[:HAS_ACTION]->(act:AWSPolicyStatementActionItem)
WHERE toLower(act.value) IN ['iam:passrole', 'iam:*']
OR act.value = '*'
```
The literal-action list is case-folded with `toLower(act.value)` because IAM authors mix case (`iam:PassRole`, `iam:passrole`); the `*` wildcard never lower-cases.
### Matching a Resource Against a Target
To find statements whose resource can target a specific node, pre-aggregate the resource values and test the target against the list once (see [Avoiding Cartesian Products](#avoiding-cartesian-products)):
```cypher
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 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
```
Three predicates cover the resource cases: full wildcard (`*`), a pattern containing the target name (`arn:aws:iam::*:role/admin*`), and a pattern that is a prefix or component of the actual ARN.
### Every-Item and Any-Item Predicates on a Custom Query
Custom queries can express list predicates directly with pattern comprehensions. To check whether *every* item satisfies a predicate, count the counter-examples and require zero, together with a guard that ensures at least one item is attached:
```cypher
MATCH (stmt:AWSPolicyStatement)
WHERE size([
(stmt)-[:HAS_ACTION]->(a:AWSPolicyStatementActionItem)
WHERE NOT toLower(a.value) STARTS WITH 's3:'
| a
]) = 0
AND size([(stmt)-[:HAS_ACTION]->(a:AWSPolicyStatementActionItem) | a]) > 0
RETURN stmt
LIMIT 25
```
To return the list of values directly, collect them from the child items:
```cypher
MATCH (stmt:AWSPolicyStatement {effect: 'Allow'})
OPTIONAL MATCH (stmt)-[:HAS_ACTION]->(a:AWSPolicyStatementActionItem)
RETURN stmt, collect(a.value) AS actions
LIMIT 25
```
### Catalog of List Properties
The provider catalog lives in `api/src/backend/tasks/jobs/attack_paths/provider_config.py` (`AWS_NORMALIZED_LISTS`). Beyond policy statements it includes KMS algorithms, ECS container-definition lists (`entry_point`, `command`, `links`, `dns_servers`, and others), CloudFront aliases, Inspector finding URL and vulnerability lists, and RDS event-subscription categories. To query a list property that is not in the catalog, add an entry there first so the sync layer materializes it. Properties absent from the catalog are serialized to a comma-delimited string and emit a one-time warning during sync.
## Working with JSON-Encoded Properties
Some Cartography properties represent nested objects, most notably `condition` on `AWSPolicyStatement` and `S3PolicyStatement` nodes. To keep the schema portable across graph backends, object-typed properties are stored as JSON-encoded strings:
```
'{"StringEquals":{"aws:SourceAccount":"123456789012"}}'
```
No JSON parser is available at query time, so use `CONTAINS` for substring checks against keys or known values:
```cypher
MATCH (stmt:AWSPolicyStatement)
WHERE stmt.effect = 'Allow'
AND stmt.condition CONTAINS '"aws:SourceAccount"'
RETURN stmt
LIMIT 25
```
When a query needs to inspect the structured members of a condition (for example, to evaluate every operator and key), fetch the rows first and parse the JSON in application code. Cypher cannot navigate JSON object keys or values.
## openCypher Compatibility
Queries must run on both Neo4j and Amazon Neptune. Neptune implements a subset of Cypher, so several convenient constructs are unavailable. Avoid the following:
| Feature | Use instead |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| APOC procedures (`apoc.*`) | Real nodes and relationships in the graph |
| Neptune extensions | Standard openCypher |
| `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` |
| `reduce()` | `UNWIND` plus `collect()` |
| `FOREACH` | `WITH` plus `UNWIND` plus `SET` |
| Regex `=~` | `toLower()` plus exact match, or `STARTS WITH` / `CONTAINS` |
| `CALL () { UNION }` | Multi-label `OR` in `WHERE` |
| Carried value plus aggregate expression | Project the aggregate first (`WITH principal_paths, collect(...) AS target_paths`), then combine lists in the next `WITH` |
| `EXISTS { MATCH (pattern) WHERE pred }` | Standalone `MATCH (pattern)` plus `WHERE pred`; precede the downstream `collect(path...)` with `WITH DISTINCT <path-vars>` to dedupe the joins |
The carried-value-plus-aggregate rule is worth calling out because it is easy to hit. Neo4j 5.x rejects an expression that concatenates a carried list variable with an aggregate in the same projection:
```cypher
// Rejected: "Aggregation column contains implicit grouping expressions"
WITH principal_paths + collect(DISTINCT path_target) AS paths
```
Split it into two `WITH` clauses so the aggregation resolves before the concatenation:
```cypher
WITH principal_paths, collect(DISTINCT path_target) AS target_paths
WITH principal_paths + target_paths AS paths
```
For list-typed properties in the catalog (action, resource, and so on), traverse the `HAS_*` edges to the child item nodes rather than reading a single field; `split(...)` and comma-string predicates do not apply.
## Best Practices
1. **Chain every MATCH from the account anchor.** An unanchored `MATCH (role:AWSRole)` returns roles from every provider in the graph; `MATCH (aws)--(role:AWSRole)` is scoped. A second-permission `MATCH` such as `MATCH (principal)--(policy2:AWSPolicy)--(stmt2:AWSPolicyStatement)` is safe because `principal` is already bound to the account subgraph.
2. **Pre-aggregate resource lists before matching targets** to avoid Cartesian products (see [Avoiding Cartesian Products](#avoiding-cartesian-products)).
3. **Type the finding probe.** Always `OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})`. The type lets Neptune apply an inline edge filter; an untyped probe scans every incident edge of high-degree nodes.
4. **Comment each MATCH.** One inline `// ...` line per clause explaining its role.
5. **Never use internal labels.** `_ProviderResource`, `_AWSResource`, `_Tenant_*`, and `_Provider_*` are system isolation labels and must not appear in query text.
6. **Reach the Internet node through path connectivity** with `(internet:Internet)-[:CAN_ACCESS]->(resource)`, never as a standalone match.
7. **Preserve the RETURN contract.** `paths, dpf, dpfr` for the standard shape; add `internet, can_access` for network-exposure queries. The serializer and visualizer depend on these names.
## Naming Conventions
- **ID**: kebab-case `{provider}-{category}-{description}`, e.g. `aws-ec2-privesc-passrole-iam`.
- **Constant**: `UPPER_SNAKE_CASE` `{PROVIDER}_{CATEGORY}_{DESCRIPTION}`, e.g. `AWS_EC2_PRIVESC_PASSROLE_IAM`.
## Creating a New Query
New queries come from one of two input sources: a [pathfinding.cloud](https://github.com/DataDog/pathfinding.cloud) research ID (for example `ECS-001`, `GLUE-001`) or a natural-language description from the requester. The aggregated `paths.json` is too large to fetch whole; query a single path by ID:
```bash
# 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)"'
```
Then follow these steps:
1. **Read the queries module first** to match the existing style:
```text
api/src/backend/api/attack_paths/queries/
├── __init__.py
├── types.py # dataclass definitions
├── registry.py
└── {provider}.py
```
2. **Fetch the Cartography schema for the pinned version.** Do not guess labels, properties, or relationships. See [The Graph Model](#the-graph-model).
3. **Build the query** from the canonical template plus the appropriate sub-pattern (privilege escalation or network exposure). Pre-aggregate resource lists, traverse `HAS_*` edges for list-typed properties, and keep the `RETURN` contract.
4. **Register** the constant in the `{PROVIDER}_QUERIES` list at the bottom of the provider file.
5. **Verify compatibility** against the [openCypher Compatibility](#opencypher-compatibility) rules, and confirm the query parses and runs on Neo4j before it reaches Neptune.
<Note>
AI assistants connected through Prowler MCP Server can fetch the exact Cartography schema for the active scan with the `prowler_get_attack_paths_cartography_schema` tool, which guarantees that generated queries match the schema version pinned by the running Prowler release.
</Note>
## Reference
- **pathfinding.cloud**: [github.com/DataDog/pathfinding.cloud](https://github.com/DataDog/pathfinding.cloud) (use `curl | jq`; the aggregated `paths.json` is too large for a single fetch).
- **Cartography AWS schema**: [cartography-cncf.github.io/cartography/modules/aws/schema.html](https://cartography-cncf.github.io/cartography/modules/aws/schema.html).
- **Neptune openCypher compliance**: [docs.aws.amazon.com/neptune/latest/userguide/feature-opencypher-compliance.html](https://docs.aws.amazon.com/neptune/latest/userguide/feature-opencypher-compliance.html).
- **Neptune openCypher rewrites**: [docs.aws.amazon.com/neptune/latest/userguide/migration-opencypher-rewrites.html](https://docs.aws.amazon.com/neptune/latest/userguide/migration-opencypher-rewrites.html).
- **openCypher specification**: [github.com/opencypher/openCypher](https://github.com/opencypher/openCypher).
@@ -133,6 +133,7 @@ Only fields with a numeric range, a fixed value set, or a length cap are listed.
| `max_unused_sagemaker_access_days` | `7..180` days | |
| `max_security_group_rules` | `1..1000` | AWS hard limit is 1000 rules per security group |
| `max_ec2_instance_age_in_days` | `1..1095` days | 3 years |
| `max_ec2_instance_stopped_days` | `1..1095` days | 3 years |
| `ec2_high_risk_ports` | each port `1..65535` | port 0 is reserved |
| `max_idle_disconnect_timeout_in_seconds` | `60..1800` s | NIST AC-12: cap at 30 min |
| `max_disconnect_timeout_in_seconds` | `60..3600` s | |
+3
View File
@@ -45,6 +45,9 @@ Prowler is constantly evolving. Contributions to checks, services, or integratio
<Card title="Adding New Integrations" icon="link" href="/developer-guide/integrations">
Prowler can work with other tools and platforms through integrations.
</Card>
<Card title="Adding New Attack Paths Queries" icon="diagram-project" href="/developer-guide/attack-paths-queries">
Want to detect new privilege escalation or exposure patterns? Contribute read-only openCypher queries that traverse the cloud graph.
</Card>
<Card title="Proposing or Implementing Features" icon="lightbulb" href="https://github.com/prowler-cloud/prowler/issues/new?template=feature-request.yml">
Propose brand-new features or enhancements to existing ones, or help implement community-requested improvements.
</Card>
+23 -8
View File
@@ -153,6 +153,13 @@
"user-guide/tutorials/prowler-app-rbac"
]
},
{
"group": "Attack Paths",
"pages": [
"user-guide/tutorials/prowler-app-attack-paths",
"user-guide/tutorials/prowler-app-attack-paths-active-queries"
]
},
{
"group": "Compliance",
"pages": [
@@ -165,7 +172,6 @@
"group": "Findings",
"pages": [
"user-guide/tutorials/prowler-alerts",
"user-guide/tutorials/prowler-app-attack-paths",
"user-guide/tutorials/prowler-app-finding-groups",
"user-guide/tutorials/prowler-app-findings-triage"
]
@@ -366,6 +372,13 @@
"user-guide/providers/googleworkspace/authentication"
]
},
{
"group": "Huawei Cloud",
"pages": [
"user-guide/providers/huaweicloud/getting-started-huaweicloud",
"user-guide/providers/huaweicloud/authentication"
]
},
{
"group": "IaC",
"pages": [
@@ -488,7 +501,8 @@
"developer-guide/mcp-server",
"developer-guide/ai-skills",
"developer-guide/prowler-studio",
"developer-guide/server-sent-events"
"developer-guide/server-sent-events",
"developer-guide/attack-paths-queries"
]
},
{
@@ -558,15 +572,16 @@
"troubleshooting"
]
},
{
"tab": "Changelog",
"pages": [
"changelog"
]
},
{
"tab": "About Us",
"icon": "/favicon.ico",
"href": "https://prowler.com/about#team"
},
{
"tab": "Changelog",
"icon": "github",
"href": "https://github.com/prowler-cloud/prowler/releases"
}
],
"global": {
@@ -603,7 +618,7 @@
]
},
"banner": {
"content": "Prowler App is now Prowler Local Server, and Prowler Enterprise is now Prowler Private Cloud. See [Prowler product families](/getting-started/products).",
"content": "Prowler App is now Prowler Local Server, and Prowler Enterprise is now Prowler Private Cloud. See [Prowler product families](/getting-started/products). Check the [latest changes](/changelog).",
"dismissible": false
},
"markdown": {
@@ -10,7 +10,7 @@ Complete reference guide for all tools available in the Prowler MCP Server. Tool
|----------|------------|------------------------|
| Prowler Hub | 10 tools | No |
| Prowler Documentation | 2 tools | No |
| Prowler Cloud, Private Cloud & Local Server | 32 tools | Yes |
| Prowler Cloud, Private Cloud & Local Server | 39 tools | Yes |
## Tool Naming Convention
@@ -105,6 +105,23 @@ Tools for viewing compliance status and framework details across all cloud provi
- **`prowler_get_compliance_overview`** - Get high-level compliance status across all frameworks for a specific scan or provider, including pass/fail statistics per framework
- **`prowler_get_compliance_framework_state_details`** - Get detailed requirement-level breakdown for a specific compliance framework, including failed requirements and associated finding IDs
### User Management
Tools for viewing the users in your tenant and identifying the authenticated user.
- **`prowler_list_users`** - List the users in the tenant with their names and emails
- **`prowler_get_user`** - Get detailed information about a specific user by ID, including join date and role/membership IDs
- **`prowler_get_current_user`** - Identify which user the current credentials authenticate as
### Role Management
Tools for browsing RBAC roles and managing the role assigned to a user. A user holds exactly one role, so setting a role replaces the one they held before.
- **`prowler_list_roles`** - List the roles defined in the tenant with their permission scope
- **`prowler_get_role`** - Get detailed information about a specific role by ID, including granted capabilities, visibility scope, assigned users, and provider groups
- **`prowler_get_user_roles`** - List the roles assigned to a specific user, with the capabilities each role grants
- **`prowler_set_user_role`** - Set the role a user holds, replacing the role they had before (idempotent)
## Prowler Hub Tools
Access Prowler's security check catalog and compliance frameworks. **No authentication required.**
@@ -128,8 +128,8 @@ To update the environment file:
Edit the `.env` file and change version values:
```env
PROWLER_UI_VERSION="5.35.0"
PROWLER_API_VERSION="5.35.0"
PROWLER_UI_VERSION="5.36.0"
PROWLER_API_VERSION="5.36.0"
```
<Note>
@@ -50,6 +50,7 @@ Full access to your Prowler deployment — Prowler Cloud, Prowler Private Cloud,
- **Resource Inventory**: Search and view detailed information about your audited resources
- **Muting Management**: Create and manage muting lists/rules to suppress non-relevant findings
- **Attack Paths Analysis**: Analyze privilege escalation chains and security misconfigurations through graph-based analysis of cloud resource relationships
- **User & Role Management**: List the users in your tenant, identify the authenticated user, browse RBAC roles, and set the role a user holds
### 2. Prowler Hub
Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 659 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 KiB

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 649 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 812 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Some files were not shown because too many files have changed in this diff Show More