diff --git a/.env b/.env
index 65e82f8ee3..e50d90a292 100644
--- a/.env
+++ b/.env
@@ -158,7 +158,7 @@ SENTRY_RELEASE=local
# REO_DEV_CLIENT_ID=
#### Prowler release version ####
-NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.39.0
+NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.40.0
# Social login credentials
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
diff --git a/.github/actions/trivy-scan/action.yml b/.github/actions/trivy-scan/action.yml
index 13810cbb68..4032da469b 100644
--- a/.github/actions/trivy-scan/action.yml
+++ b/.github/actions/trivy-scan/action.yml
@@ -64,7 +64,7 @@ runs:
scanners: 'vuln'
ignore-unfixed: 'true' # A finding with no available fix is not actionable, so it must not gate
timeout: '5m'
- version: 'v0.72.0'
+ version: 'v0.74.0'
# Not trivyignores: that input drops the .yaml extension Trivy parses by.
env:
TRIVY_IGNOREFILE: '.trivyignore.yaml'
@@ -81,7 +81,7 @@ runs:
scanners: 'vuln'
ignore-unfixed: 'true' # A finding with no available fix is not actionable, so it must not gate
timeout: '5m'
- version: 'v0.72.0'
+ version: 'v0.74.0'
# Not trivyignores: that input drops the .yaml extension Trivy parses by.
env:
TRIVY_IGNOREFILE: '.trivyignore.yaml'
diff --git a/.github/agents/issue-triage.md b/.github/agents/issue-triage.md
index 9de627e316..c383a88105 100644
--- a/.github/agents/issue-triage.md
+++ b/.github/agents/issue-triage.md
@@ -199,7 +199,7 @@ You MUST structure your response using this EXACT format. Do NOT include anythin
### For Check Logic Bug
-```
+```markdown
### AI Assessment [Experimental]: Check Logic Bug
**Component**: {component from issue template}
@@ -297,7 +297,7 @@ Write tests FIRST (TDD). The skills contain all testing conventions and patterns
### For Bug (non-check)
-```
+```markdown
### AI Assessment [Experimental]: Bug
**Component**: {CLI/SDK | API | UI | Dashboard | MCP Server | Other}
@@ -378,7 +378,7 @@ Write tests FIRST (TDD). The skills contain all testing conventions and patterns
### For Already Fixed
-```
+```markdown
### AI Assessment [Experimental]: Already Fixed
**Component**: {component}
@@ -401,7 +401,7 @@ Upgrade to the latest version. Close the issue as resolved.
### For Feature Request
-```
+```markdown
### AI Assessment [Experimental]: Feature Request
**Component**: {component}
@@ -419,7 +419,7 @@ Upgrade to the latest version. Close the issue as resolved.
### For Not a Bug
-```
+```markdown
### AI Assessment [Experimental]: Not a Bug
**Component**: {component}
@@ -440,7 +440,7 @@ Upgrade to the latest version. Close the issue as resolved.
### For Needs More Information
-```
+```markdown
### AI Assessment [Experimental]: Needs More Information
**Component**: {component or "Unknown"}
diff --git a/.github/labeler.yml b/.github/labeler.yml
index b9abb1dfd0..e6888039da 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -52,6 +52,16 @@ provider/alibabacloud:
- any-glob-to-any-file: "prowler/providers/alibabacloud/**"
- any-glob-to-any-file: "tests/providers/alibabacloud/**"
+provider/huaweicloud:
+ - changed-files:
+ - any-glob-to-any-file: "prowler/providers/huaweicloud/**"
+ - any-glob-to-any-file: "tests/providers/huaweicloud/**"
+
+provider/image:
+ - changed-files:
+ - any-glob-to-any-file: "prowler/providers/image/**"
+ - any-glob-to-any-file: "tests/providers/image/**"
+
provider/cloudflare:
- changed-files:
- any-glob-to-any-file: "prowler/providers/cloudflare/**"
@@ -82,6 +92,11 @@ provider/linode:
- any-glob-to-any-file: "prowler/providers/linode/**"
- any-glob-to-any-file: "tests/providers/linode/**"
+provider/stackit:
+ - changed-files:
+ - any-glob-to-any-file: "prowler/providers/stackit/**"
+ - any-glob-to-any-file: "tests/providers/stackit/**"
+
github_actions:
- changed-files:
- any-glob-to-any-file: ".github/workflows/*"
diff --git a/.github/scripts/slack-messages/README.md b/.github/scripts/slack-messages/README.md
index e7fcf00fdd..5466efc7df 100644
--- a/.github/scripts/slack-messages/README.md
+++ b/.github/scripts/slack-messages/README.md
@@ -8,11 +8,11 @@ These JSON templates are used with the `slackapi/slack-github-action` using the
### Available Templates
-**Container Releases**
+#### Container Releases
- `container-release-started.json`: Simple one-line notification when container push starts
- `container-release-completed.json`: Simple one-line notification when container release completes
-**Deployments**
+#### Deployments
- `deployment-started.json`: Deployment start notification with Block Kit formatting
- `deployment-completed.json`: Deployment completion notification (updates the start message)
@@ -416,17 +416,17 @@ For deployments that start with one message and update it with the final status:
### Container Release (Simple One-Line)
**Start message:**
-```
+```text
API container release 4.5.0 push started... View run
```
**Completion message (success):**
-```
+```text
[β] API container release 4.5.0 push completed successfully! View run
```
**Completion message (failure):**
-```
+```text
[β] API container release 4.5.0 push failed View run
```
diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml
index 7918c701e5..abd42ded07 100644
--- a/.github/workflows/markdown-lint.yml
+++ b/.github/workflows/markdown-lint.yml
@@ -55,6 +55,10 @@ jobs:
# Pin must match .pre-commit-config.yaml so prek and CI behave identically.
# pnpm dlx doesn't accept --ignore-scripts as a flag; the env var
# disables postinstall scripts on transitives the same way.
+ #
+ # Files come from `git ls-files` because markdownlint doesn't traverse
+ # dot-directories, so `.github/**/*.md` went unlinted.
+ # `.markdownlintignore` still applies to the listed paths.
env:
pnpm_config_ignore_scripts: 'true'
- run: pnpm dlx markdownlint-cli@0.45.0 '**/*.md'
+ run: git ls-files -z '*.md' | xargs -0 -r pnpm dlx markdownlint-cli@0.45.0 --
diff --git a/.github/workflows/mcp-pypi-release.yml b/.github/workflows/mcp-pypi-release.yml
index a6dae75017..f6ad43e39f 100644
--- a/.github/workflows/mcp-pypi-release.yml
+++ b/.github/workflows/mcp-pypi-release.yml
@@ -113,7 +113,7 @@ jobs:
- name: Publish prowler-mcp package to PyPI
if: steps.pypi-check.outputs.skip != 'true'
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
packages-dir: ${{ env.WORKING_DIRECTORY }}/dist/
print-hash: true
diff --git a/.github/workflows/sdk-package-checks.yml b/.github/workflows/sdk-package-checks.yml
new file mode 100644
index 0000000000..8c3ab50e47
--- /dev/null
+++ b/.github/workflows/sdk-package-checks.yml
@@ -0,0 +1,196 @@
+name: 'SDK: Package Checks'
+
+# Rehearses the PyPI release on every packaging change and once a week, from the
+# consumer's side. Two incidents this guards against:
+#
+# - 5.38.0 shipped an unsatisfiable Requires-Dist (cryptography==50.0.0 while
+# alibabacloud-tea-openapi and pyopenssl cap it below 49). A [tool.uv] override hid
+# the conflict inside the repo; pip could not install the wheel and silently
+# resolved `pip install prowler` to 5.37.1 for a week.
+# - 5.39.0 never published: an unpinned build backend started emitting core metadata
+# 2.5 and the twine bundled in the publish action rejected it.
+#
+# Both were only detectable at release time because nothing built and installed the
+# artifact earlier. The weekly run also catches releases yanked from PyPI after we
+# pinned them (zstd 1.5.7.3, "buggy - not thread safe", sat in uv.lock for months).
+
+on:
+ push:
+ branches:
+ - 'master'
+ - 'v5.*'
+ pull_request:
+ branches:
+ - 'master'
+ - 'v5.*'
+ schedule:
+ # Monday 06:00 UTC. Yanks and upstream releases happen without a commit here.
+ - cron: '0 6 * * 1'
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions: {}
+
+env:
+ # Must equal the twine bundled in the pypa/gh-action-pypi-publish pin used by
+ # sdk-pypi-release.yml (requirements/runtime.txt in that repo at the pinned tag).
+ # A metadata check that passes here must pass there.
+ TWINE_VERSION: '7.0.0'
+
+jobs:
+ changes:
+ if: github.repository == 'prowler-cloud/prowler'
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ contents: read
+ outputs:
+ # Scheduled and manual runs always execute; pushes and PRs only when a packaging
+ # input changed. Jobs skipped this way still report success to branch protection.
+ run: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || steps.filter.outputs.any_changed == 'true' }}
+
+ steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
+ with:
+ egress-policy: block
+ allowed-endpoints: >
+ github.com:443
+ api.github.com:443
+
+ - name: Checkout repository
+ if: github.event_name == 'push' || github.event_name == 'pull_request'
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ # zizmor: ignore[artipacked]
+ persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
+
+ - name: Detect packaging changes
+ if: github.event_name == 'push' || github.event_name == 'pull_request'
+ id: filter
+ uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
+ with:
+ files: |
+ pyproject.toml
+ uv.lock
+ README.md
+ util/replicate_pypi_package.py
+ util/check_yanked_pins.py
+ api/pyproject.toml
+ api/uv.lock
+ mcp_server/pyproject.toml
+ mcp_server/uv.lock
+ .github/workflows/sdk-package-checks.yml
+ .github/workflows/sdk-pypi-release.yml
+ .github/actions/setup-python-uv/**
+
+ install-from-wheel:
+ needs: changes
+ if: needs.changes.outputs.run == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version:
+ - '3.10'
+ - '3.11'
+ - '3.12'
+ - '3.13'
+ package:
+ - 'prowler'
+ include:
+ # prowler-cloud is the same tree renamed by util/replicate_pypi_package.py;
+ # one Python is enough to prove the rename and its build still work.
+ - python-version: '3.12'
+ package: 'prowler-cloud'
+
+ steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
+ with:
+ egress-policy: block
+ allowed-endpoints: >
+ github.com:443
+ api.github.com:443
+ release-assets.githubusercontent.com:443
+ pypi.org:443
+ files.pythonhosted.org:443
+
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+
+ - name: Setup Python with uv
+ uses: ./.github/actions/setup-python-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+ install-dependencies: 'false'
+
+ - name: Rename package to prowler-cloud
+ if: matrix.package == 'prowler-cloud'
+ run: |
+ pip install --no-cache-dir toml
+ python util/replicate_pypi_package.py
+
+ - name: Build sdist and wheel
+ run: uv build
+
+ - name: Check metadata with the release workflow's twine
+ run: uvx --from "twine==${TWINE_VERSION}" twine check --strict dist/*
+
+ - name: Install the wheel with pip into a clean virtualenv
+ # Plain pip, --isolated, from outside the repo: consumers never see [tool.uv]
+ # override-dependencies or constraint-dependencies, so neither does this step.
+ run: |
+ python -m venv "${RUNNER_TEMP}/consumer"
+ "${RUNNER_TEMP}/consumer/bin/python" -m pip install --quiet --upgrade pip
+ cd "${RUNNER_TEMP}"
+ "${RUNNER_TEMP}/consumer/bin/python" -m pip install --isolated --no-cache-dir "${GITHUB_WORKSPACE}"/dist/*.whl
+
+ - name: Smoke test the installed CLI
+ run: |
+ cd "${RUNNER_TEMP}"
+ "${RUNNER_TEMP}/consumer/bin/prowler" --version
+ # Loads every AWS check module from the installed wheel: catches files missing
+ # from the package. grep fails the step if the summary line never appears.
+ "${RUNNER_TEMP}/consumer/bin/prowler" aws --list-checks | grep 'available checks'
+
+ pinned-releases-not-yanked:
+ needs: changes
+ if: needs.changes.outputs.run == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ contents: read
+
+ steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
+ with:
+ egress-policy: block
+ allowed-endpoints: >
+ github.com:443
+ api.github.com:443
+ release-assets.githubusercontent.com:443
+ pypi.org:443
+ files.pythonhosted.org:443
+
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+
+ - name: Set up Python
+ uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
+ with:
+ python-version: '3.12'
+
+ - name: Check every pinned and locked release against PyPI
+ run: python util/check_yanked_pins.py . api mcp_server
diff --git a/.github/workflows/sdk-pypi-release.yml b/.github/workflows/sdk-pypi-release.yml
index a61c9157e8..1504ce5ca1 100644
--- a/.github/workflows/sdk-pypi-release.yml
+++ b/.github/workflows/sdk-pypi-release.yml
@@ -84,8 +84,18 @@ jobs:
- name: Build Prowler package
run: uv build
+ - name: Verify the wheel installs with pip
+ # Same check as "SDK: Package Checks", repeated on the exact artifact about to be
+ # published. Plain pip, --isolated, from outside the repo: an unsatisfiable
+ # Requires-Dist fails here instead of on users' machines (5.38.0 shipped one).
+ run: |
+ python -m venv "${RUNNER_TEMP}/consumer"
+ "${RUNNER_TEMP}/consumer/bin/python" -m pip install --quiet --upgrade pip
+ cd "${RUNNER_TEMP}"
+ "${RUNNER_TEMP}/consumer/bin/python" -m pip install --isolated --no-cache-dir --dry-run "${GITHUB_WORKSPACE}"/dist/*.whl
+
- name: Publish Prowler package to PyPI
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
print-hash: true
@@ -128,7 +138,17 @@ jobs:
- name: Build prowler-cloud package
run: uv build
+ - name: Verify the wheel installs with pip
+ # Same check as "SDK: Package Checks", repeated on the exact artifact about to be
+ # published. Plain pip, --isolated, from outside the repo: an unsatisfiable
+ # Requires-Dist fails here instead of on users' machines (5.38.0 shipped one).
+ run: |
+ python -m venv "${RUNNER_TEMP}/consumer"
+ "${RUNNER_TEMP}/consumer/bin/python" -m pip install --quiet --upgrade pip
+ cd "${RUNNER_TEMP}"
+ "${RUNNER_TEMP}/consumer/bin/python" -m pip install --isolated --no-cache-dir --dry-run "${GITHUB_WORKSPACE}"/dist/*.whl
+
- name: Publish prowler-cloud package to PyPI
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
print-hash: true
diff --git a/.grype.yaml b/.grype.yaml
index 7c36171f65..8fe74513c9 100644
--- a/.grype.yaml
+++ b/.grype.yaml
@@ -33,6 +33,19 @@ ignore:
package:
name: Microsoft.Bcl.Memory
+ # The .NET runtime bundled inside the PowerShell tarball the Dockerfile pins.
+ # CVE-2026-62901 is the same temporary exception documented in .trivyignore.yaml:
+ # fixed in .NET 9.0.19 / 10.0.11 (2026-08-11), but no published PowerShell release
+ # ships a patched runtime yet (7.5.9 bundles 9.0.18; 7.6.4 bundles 10.0.x < 10.0.11).
+ # pwsh runs only local M365 module cmdlets; nothing listens for inbound WebSocket
+ # connections. Remove with the Trivy exception by 2026-09-15.
+ - vulnerability: CVE-2026-62901
+ package:
+ name: Microsoft.NETCore.App.Runtime.linux-x64
+ - vulnerability: CVE-2026-62901
+ package:
+ name: Microsoft.NETCore.App.Runtime.linux-arm64
+
# The CPython interpreter, compiled into the official base image.
# TEMPORARY, unlike the entries above: moving to Python 3.13 clears seven of these, and
diff --git a/.trivyignore.yaml b/.trivyignore.yaml
index dffc42c784..b78162ecc4 100644
--- a/.trivyignore.yaml
+++ b/.trivyignore.yaml
@@ -118,25 +118,25 @@ vulnerabilities:
- "pkg:npm/ip-address"
expired_at: 2027-01-31
- # .NET 9 runtime that ships with PowerShell 7.x (which the images bundle so
- # the M365 provider can drive Exchange/Teams/Skype cmdlets). Trivy flags it
- # via the SBOM PowerShell embeds under Modules/*/_manifest. The vulnerable
- # code paths are not reachable from Prowler: the runtime is executed only
- # inside `pwsh` subprocesses that Prowler spawns for M365 cmdlets, none of
- # which handles the untrusted input the CVE requires. There is no published
- # fix in a Prowler-compatible PowerShell release yet. Short expiry to force
- # a re-look once Microsoft ships a patched .NET 9.0.x or PowerShell drops
- # the vulnerable runtime.
+ # CVE-2026-62901 is a DoS in System.Net.WebSockets (unchecked input for loop condition,
+ # CWE-606), fixed in .NET 9.0.19 / 10.0.11 (published 2026-08-11). The vulnerable runtime
+ # ships inside the PowerShell tarball the Dockerfile pins: 7.5.9 is the latest 7.5.x and
+ # bundles .NET 9.0.18; 7.6.4 bundles .NET 10.0.x < 10.0.11, so no published PowerShell
+ # release contains the fix yet. Prowler only invokes pwsh locally to run M365 module
+ # cmdlets; the image does not accept inbound WebSocket connections, so the DoS path is
+ # not reachable from the network. Remove this temporary suppression as soon as a
+ # PowerShell release shipping .NET 9.0.19+ is available.
- id: CVE-2026-62901
purls:
- "pkg:nuget/Microsoft.NETCore.App.Runtime.linux-x64"
- expired_at: 2026-11-30
+ - "pkg:nuget/Microsoft.NETCore.App.Runtime.linux-arm64"
+ expired_at: 2026-09-15
# Modules compiled into the Trivy binary the images ship. The binary is pinned by version
# and verified by checksum in the Dockerfile; only a rebuild by its vendor moves these.
# CVE-2026-71556 affects go-git worktree operations that can follow symlinks outside a
- # cloned repository. Trivy 0.72.0 contains go-git 5.19.1, and even the latest published
- # Trivy release, 0.73.0, still pins that vulnerable version:
+ # cloned repository. Trivy 0.73.0, the latest published release and the version the
+ # images ship, still pins that vulnerable version:
# https://github.com/aquasecurity/trivy/blob/v0.73.0/go.mod#L46
# Trivy main already contains the 5.19.2 fix, but no published release includes it yet:
# https://github.com/aquasecurity/trivy/commit/a2edba9a03987ba0d2ebc8212c1a9a1e6979497b
@@ -164,7 +164,3 @@ vulnerabilities:
purls:
- "pkg:golang/oras.land/oras-go/v2"
expired_at: 2026-12-31
- - id: CVE-2026-39822
- purls:
- - "pkg:golang/stdlib"
- expired_at: 2026-12-31
diff --git a/Dockerfile b/Dockerfile
index 62aa3c4f1e..c07c6f29da 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,15 +8,15 @@ ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
# Opt out of PowerShell telemetry (Application Insights -> dc.services.visualstudio.com)
ENV POWERSHELL_TELEMETRY_OPTOUT=1
-ARG TRIVY_VERSION=0.72.0
+ARG TRIVY_VERSION=0.74.0
ENV TRIVY_VERSION=${TRIVY_VERSION}
ARG ZIZMOR_VERSION=1.24.1
ENV ZIZMOR_VERSION=${ZIZMOR_VERSION}
# Pinned here, not fetched with the artefact: a compromised release ships its own checksum.
-ARG TRIVY_SHA256_AMD64=bbb64b9695866ce4a7a8f5c9592002c5961cab378577fa3f8a040df362b9b2ea
-ARG TRIVY_SHA256_ARM64=2ca2c023109c2db6b2b77366b6717291452d4531167377d95c79547f0c8e3467
+ARG TRIVY_SHA256_AMD64=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a
+ARG TRIVY_SHA256_ARM64=b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5
ARG POWERSHELL_SHA256_AMD64=492ff26bb958336bf61e597ce19e07648b4003bd2a08659e02f0e3e0446ebfe0
ARG POWERSHELL_SHA256_ARM64=2503b71da3e83635592b092df59a0aca4c3606b4d9b068217bb00be989cb0d56
ARG ZIZMOR_SHA256_AMD64=a8000f3c683319a523d3b20df0e75457ba591f049cfcbfa98966631b56733c03
@@ -26,6 +26,7 @@ ARG ZIZMOR_SHA256_ARM64=d66e37ef8a375fb07939c630ebf9709a6e0f20242bdc3faf672a7ed9
RUN apt-get update && apt-get install -y --no-install-recommends \
wget libicu76 libunwind8 libssl3 libcurl4 ca-certificates apt-transport-https gnupg \
build-essential pkg-config libzstd-dev zlib1g-dev \
+ && apt-get install -y --no-install-recommends --only-upgrade util-linux \
&& rm -rf /var/lib/apt/lists/*
# Install PowerShell
diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md
index b119fbaafc..183f1270ee 100644
--- a/api/CHANGELOG.md
+++ b/api/CHANGELOG.md
@@ -4,6 +4,37 @@ All notable changes to the **Prowler API** are documented in this file.
+## [1.40.1] (Prowler v5.39.1)
+
+### π Changed
+
+- Bump alibabacloud-tea-openapi to 0.4.6, oci to 2.184.1 and pyopenssl to 26.4.0 to match the SDK; the cryptography override now names its actual blockers (azure-cli-core pins msal below 1.37, workos 8.3.0 requires cryptography 48) [(#12477)](https://github.com/prowler-cloud/prowler/pull/12477)
+
+### π Fixed
+
+- Pin zstd to 1.5.7.2; 1.5.7.3 was yanked from PyPI as not thread safe [(#12477)](https://github.com/prowler-cloud/prowler/pull/12477)
+
+### π Security
+
+- Trivy from v0.72.0 to v0.73.0 in the container image, fixing HIGH CVE-2026-46600 in the bundled `golang.org/x/net` [(#12445)](https://github.com/prowler-cloud/prowler/pull/12445)
+- Trivy v0.74.0 and Debian util-linux 2.41.5-0+deb13u1 in the API container image, patching Go standard library vulnerabilities and CVE-2026-53615 [(#12470)](https://github.com/prowler-cloud/prowler/pull/12470)
+
+---
+
+## [1.40.0] (Prowler v5.39.0)
+
+### π Changed
+
+- `GET /api/v1/users/me` membership relationships identify the active tenant with `meta.active` for JWT and API key authentication [(#12388)](https://github.com/prowler-cloud/prowler/pull/12388)
+
+### π Fixed
+
+- Tenant deletion no longer leaves memberships partially removed when exclusive-user cleanup fails [(#12379)](https://github.com/prowler-cloud/prowler/pull/12379)
+- `/api/v1/accounts/saml/{organization_slug}/acs/` rejects non-POST requests before SAML response processing [(#12393)](https://github.com/prowler-cloud/prowler/pull/12393)
+- Social login derives a valid user name when identity providers omit the profile name [(#12413)](https://github.com/prowler-cloud/prowler/pull/12413)
+
+---
+
## [1.39.0] (Prowler v5.38.0)
### π Added
diff --git a/api/Dockerfile b/api/Dockerfile
index af7b07e16c..0f7e5883bb 100644
--- a/api/Dockerfile
+++ b/api/Dockerfile
@@ -7,15 +7,15 @@ ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
# Opt out of PowerShell telemetry (Application Insights -> dc.services.visualstudio.com)
ENV POWERSHELL_TELEMETRY_OPTOUT=1
-ARG TRIVY_VERSION=0.72.0
+ARG TRIVY_VERSION=0.74.0
ENV TRIVY_VERSION=${TRIVY_VERSION}
ARG ZIZMOR_VERSION=1.24.1
ENV ZIZMOR_VERSION=${ZIZMOR_VERSION}
# Pinned here, not fetched with the artefact: a compromised release ships its own checksum.
-ARG TRIVY_SHA256_AMD64=bbb64b9695866ce4a7a8f5c9592002c5961cab378577fa3f8a040df362b9b2ea
-ARG TRIVY_SHA256_ARM64=2ca2c023109c2db6b2b77366b6717291452d4531167377d95c79547f0c8e3467
+ARG TRIVY_SHA256_AMD64=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a
+ARG TRIVY_SHA256_ARM64=b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5
ARG POWERSHELL_SHA256_AMD64=492ff26bb958336bf61e597ce19e07648b4003bd2a08659e02f0e3e0446ebfe0
ARG POWERSHELL_SHA256_ARM64=2503b71da3e83635592b092df59a0aca4c3606b4d9b068217bb00be989cb0d56
ARG ZIZMOR_SHA256_AMD64=a8000f3c683319a523d3b20df0e75457ba591f049cfcbfa98966631b56733c03
@@ -36,6 +36,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libtool \
libxslt1-dev \
python3-dev \
+ && apt-get install -y --no-install-recommends --only-upgrade util-linux \
&& rm -rf /var/lib/apt/lists/*
# Install PowerShell
diff --git a/api/changelog.d/saml-acs-post-only.fixed.md b/api/changelog.d/saml-acs-post-only.fixed.md
deleted file mode 100644
index 91f00e7d62..0000000000
--- a/api/changelog.d/saml-acs-post-only.fixed.md
+++ /dev/null
@@ -1 +0,0 @@
-`/api/v1/accounts/saml/{organization_slug}/acs/` rejects non-POST requests before SAML response processing
diff --git a/api/changelog.d/tenant-deletion-transaction.fixed.md b/api/changelog.d/tenant-deletion-transaction.fixed.md
deleted file mode 100644
index 5679bc4564..0000000000
--- a/api/changelog.d/tenant-deletion-transaction.fixed.md
+++ /dev/null
@@ -1 +0,0 @@
-Tenant deletion no longer leaves memberships partially removed when exclusive-user cleanup fails
diff --git a/api/changelog.d/users-me-active-membership.changed.md b/api/changelog.d/users-me-active-membership.changed.md
deleted file mode 100644
index 4e02368ab4..0000000000
--- a/api/changelog.d/users-me-active-membership.changed.md
+++ /dev/null
@@ -1 +0,0 @@
-`GET /api/v1/users/me` membership relationships identify the active tenant with `meta.active` for JWT and API key authentication
diff --git a/api/pyproject.toml b/api/pyproject.toml
index 3be69d7c43..511b1f80a2 100644
--- a/api/pyproject.toml
+++ b/api/pyproject.toml
@@ -71,7 +71,7 @@ name = "prowler-api"
package-mode = false
# Needed for the SDK compatibility
requires-python = ">=3.11,<3.13"
-version = "1.40.0"
+version = "1.41.0"
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
# target-version tracks this project's lowest supported Python.
@@ -92,8 +92,7 @@ extend-select = [
[tool.uv]
# Transitive pins matching master to avoid silent drift; bump deliberately.
-# workos and pyopenssl run ahead of master: the versions master pins cap cryptography
-# below 48, so both were bumped to versions that allow it (PROWLER-2310).
+# workos is api-only; pyopenssl matches master (PROWLER-2310).
constraint-dependencies = [
"about-time==4.2.1",
"adal==1.2.7",
@@ -130,7 +129,7 @@ constraint-dependencies = [
"alibabacloud-sls20201230==5.9.0",
"alibabacloud-sts20150401==1.1.6",
"alibabacloud-tea==0.4.3",
- "alibabacloud-tea-openapi==0.4.5",
+ "alibabacloud-tea-openapi==0.4.6",
"alibabacloud-tea-util==0.3.14",
"alibabacloud-tea-xml==0.0.3",
"alibabacloud-vpc20160428==6.13.0",
@@ -339,7 +338,7 @@ constraint-dependencies = [
"nltk==3.9.4",
"numpy==2.2.6",
"oauthlib==3.3.1",
- "oci==2.183.0",
+ "oci==2.184.1",
"openai==1.109.1",
"openstacksdk==4.2.0",
"opentelemetry-api==1.39.1",
@@ -380,7 +379,7 @@ constraint-dependencies = [
"pylint==3.2.5",
"pymsalruntime==0.18.1",
"pynacl==1.6.2",
- "pyopenssl==26.2.0",
+ "pyopenssl==26.4.0",
"pyparsing==3.3.2",
"pyreadline3==3.5.4",
"pysocks==1.7.1",
@@ -458,7 +457,7 @@ constraint-dependencies = [
"zipp==3.23.0",
"zope-event==6.1",
"zope-interface==8.2",
- "zstd==1.5.7.3"
+ "zstd==1.5.7.2"
]
# prowler@master needs okta==3.4.2, but cartography 0.138.1 requires okta<1.0.0.
# Attack Paths does not ingest Okta today, so override the Cartography
@@ -485,7 +484,12 @@ constraint-dependencies = [
# that request pyjwt[crypto] and leave cryptography (needed for RS256) only transitive.
override-dependencies = [
"okta==3.4.2",
- # alibabacloud-tea-openapi 0.4.5 caps cryptography below 49 and is the latest release.
+ # prowler requires cryptography==50.0.0. Two api-only dependencies still cap it below
+ # 49 and cannot move yet: msal, pinned exactly by azure-cli-core (2.83.0 -> 1.35.0b1,
+ # 2.89.1 -> 1.36.0, both <49; cartography needs azure-cli-core), and workos 8.3.0
+ # (~=48.0; workos 10.1.1+ needs ~=50.0 and is a separate SDK upgrade). This api is
+ # deployed from this lock with `uv sync --locked`, so the override applies to what runs.
+ # Remove when azure-cli-core pins msal>=1.37.0 and workos is on 10.x.
"cryptography==50.0.0",
"azure-mgmt-containerservice==34.1.0",
"microsoft-kiota-abstractions==1.9.10",
diff --git a/api/src/backend/api/adapters.py b/api/src/backend/api/adapters.py
index 55ac440f59..6806289fdb 100644
--- a/api/src/backend/api/adapters.py
+++ b/api/src/backend/api/adapters.py
@@ -12,11 +12,37 @@ from api.models import (
UserRoleRelationship,
)
from api.utils import accept_invitation_for_user
+from django.core.exceptions import ValidationError
from django.db import transaction
from django.http import HttpResponseForbidden
class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
+ @staticmethod
+ def _get_social_account_name(extra_data: dict, email: str) -> str:
+ name_field = User._meta.get_field("name")
+ for value in (
+ extra_data.get("name"),
+ extra_data.get("login"),
+ extra_data.get("username"),
+ email,
+ ):
+ if not isinstance(value, str):
+ continue
+
+ candidate = value.strip()[: name_field.max_length].rstrip()
+ if not candidate:
+ continue
+
+ try:
+ name_field.run_validators(candidate)
+ except ValidationError:
+ continue
+
+ return candidate
+
+ raise ValueError("Social account does not provide a valid user identity.")
+
@staticmethod
def get_user_by_email(email: str):
try:
@@ -116,11 +142,8 @@ class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
if provider != "saml":
# Handle other providers (e.g., GitHub, Google)
+ user.name = self._get_social_account_name(extra, user.email)
user.save(using=MainRouter.admin_db)
- social_account_name = extra.get("name")
- if social_account_name:
- user.name = social_account_name
- user.save(using=MainRouter.admin_db)
invitation_token = self._get_invitation_token(request)
if invitation_token:
diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml
index dce066dbed..6cb1d28392 100644
--- a/api/src/backend/api/specs/v1.yaml
+++ b/api/src/backend/api/specs/v1.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
- version: 1.40.0
+ version: 1.41.0
description: |-
Prowler API specification.
diff --git a/api/src/backend/api/tests/test_adapters.py b/api/src/backend/api/tests/test_adapters.py
index 7d8e06bb0e..3fd14afbf7 100644
--- a/api/src/backend/api/tests/test_adapters.py
+++ b/api/src/backend/api/tests/test_adapters.py
@@ -111,6 +111,110 @@ def _verify_local_email(user):
)
+def test_social_account_name_falls_back_to_login_for_blank_name():
+ adapter = ProwlerSocialAccountAdapter()
+
+ name = adapter._get_social_account_name(
+ {"name": " ", "login": "octocat"},
+ "verified@example.com",
+ )
+
+ assert name == "octocat"
+
+
+@pytest.mark.parametrize("provider_name", [None, "", " ", 123, ["name"]])
+def test_social_account_name_ignores_unusable_provider_names(provider_name):
+ adapter = ProwlerSocialAccountAdapter()
+
+ name = adapter._get_social_account_name(
+ {"name": provider_name, "login": "octocat"},
+ "verified@example.com",
+ )
+
+ assert name == "octocat"
+
+
+def test_social_account_name_uses_login_when_name_is_missing():
+ adapter = ProwlerSocialAccountAdapter()
+
+ name = adapter._get_social_account_name(
+ {"login": "octocat"},
+ "verified@example.com",
+ )
+
+ assert name == "octocat"
+
+
+def test_social_account_name_falls_back_to_username_then_email():
+ adapter = ProwlerSocialAccountAdapter()
+
+ username_name = adapter._get_social_account_name(
+ {"name": "ab", "login": None, "username": " monalisa "},
+ "verified@example.com",
+ )
+ email_name = adapter._get_social_account_name({}, " verified@example.com ")
+
+ assert username_name == "monalisa"
+ assert email_name == "verified@example.com"
+
+
+def test_social_account_name_trims_and_limits_provider_name():
+ adapter = ProwlerSocialAccountAdapter()
+ max_length = User._meta.get_field("name").max_length
+
+ trimmed_name = adapter._get_social_account_name(
+ {"name": " Ada Lovelace "},
+ "verified@example.com",
+ )
+ limited_name = adapter._get_social_account_name(
+ {"name": "a" * (max_length + 1)},
+ "verified@example.com",
+ )
+
+ assert trimmed_name == "Ada Lovelace"
+ assert limited_name == "a" * max_length
+
+
+def test_social_account_name_rejects_missing_identity():
+ adapter = ProwlerSocialAccountAdapter()
+
+ with pytest.raises(
+ ValueError,
+ match="Social account does not provide a valid user identity",
+ ):
+ adapter._get_social_account_name({}, "")
+
+
+def test_save_user_applies_normalized_social_account_name(rf):
+ adapter = ProwlerSocialAccountAdapter()
+ request = rf.post("/")
+ request.session = {}
+ sociallogin = MagicMock(spec=SocialLogin)
+ sociallogin.provider = MagicMock()
+ sociallogin.provider.id = "github"
+ sociallogin.account = MagicMock()
+ sociallogin.account.extra_data = {"name": None, "login": " octocat "}
+ user = User(email="verified@example.com")
+ user.save = MagicMock()
+ invitation = SimpleNamespace(tenant_id="tenant-id")
+
+ with (
+ patch("api.adapters.super") as mock_super,
+ patch("api.adapters.transaction.atomic"),
+ patch("api.adapters.write_db_alias"),
+ patch.object(adapter, "_get_invitation_token", return_value="token"),
+ patch(
+ "api.adapters.accept_invitation_for_user",
+ return_value=(invitation, True),
+ ),
+ ):
+ mock_super.return_value.save_user.return_value = user
+ saved_user = adapter.save_user(request, sociallogin)
+
+ assert saved_user.name == "octocat"
+ assert request.prowler_invitation_token == "token"
+
+
@pytest.mark.django_db
class TestProwlerSocialAccountAdapter:
def test_get_user_by_email_returns_user(self, create_test_user):
diff --git a/api/uv.lock b/api/uv.lock
index 604e3bc35c..bbbd1a2e31 100644
--- a/api/uv.lock
+++ b/api/uv.lock
@@ -45,7 +45,7 @@ constraints = [
{ name = "alibabacloud-sls20201230", specifier = "==5.9.0" },
{ name = "alibabacloud-sts20150401", specifier = "==1.1.6" },
{ name = "alibabacloud-tea", specifier = "==0.4.3" },
- { name = "alibabacloud-tea-openapi", specifier = "==0.4.5" },
+ { name = "alibabacloud-tea-openapi", specifier = "==0.4.6" },
{ name = "alibabacloud-tea-util", specifier = "==0.3.14" },
{ name = "alibabacloud-tea-xml", specifier = "==0.0.3" },
{ name = "alibabacloud-vpc20160428", specifier = "==6.13.0" },
@@ -254,7 +254,7 @@ constraints = [
{ name = "nltk", specifier = "==3.9.4" },
{ name = "numpy", specifier = "==2.2.6" },
{ name = "oauthlib", specifier = "==3.3.1" },
- { name = "oci", specifier = "==2.183.0" },
+ { name = "oci", specifier = "==2.184.1" },
{ name = "openai", specifier = "==1.109.1" },
{ name = "openstacksdk", specifier = "==4.2.0" },
{ name = "opentelemetry-api", specifier = "==1.39.1" },
@@ -295,7 +295,7 @@ constraints = [
{ name = "pylint", specifier = "==3.2.5" },
{ name = "pymsalruntime", specifier = "==0.18.1" },
{ name = "pynacl", specifier = "==1.6.2" },
- { name = "pyopenssl", specifier = "==26.2.0" },
+ { name = "pyopenssl", specifier = "==26.4.0" },
{ name = "pyparsing", specifier = "==3.3.2" },
{ name = "pyreadline3", specifier = "==3.5.4" },
{ name = "pysocks", specifier = "==1.7.1" },
@@ -373,7 +373,7 @@ constraints = [
{ name = "zipp", specifier = "==3.23.0" },
{ name = "zope-event", specifier = "==6.1" },
{ name = "zope-interface", specifier = "==8.2" },
- { name = "zstd", specifier = "==1.5.7.3" },
+ { name = "zstd", specifier = "==1.5.7.2" },
]
overrides = [
{ name = "azure-mgmt-containerservice", specifier = "==34.1.0" },
@@ -860,7 +860,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0
[[package]]
name = "alibabacloud-tea-openapi"
-version = "0.4.5"
+version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "alibabacloud-credentials" },
@@ -869,9 +869,9 @@ dependencies = [
{ name = "cryptography" },
{ name = "darabonba-core" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/3b/73/fb0c4d44759791ecdf269fc715c1e810fa1aba3981bfaaf8a01f61899296/alibabacloud_tea_openapi-0.4.5.tar.gz", hash = "sha256:75fa1f4360a46e41f5bf5f8d4917e52efb6f64885839bc1328c35590670c97b9", size = 26616, upload-time = "2026-07-14T13:15:39.364Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/ab/34/1918a2d780676494365c7f945bfab397ecddb988054d78025bd26f438977/alibabacloud_tea_openapi-0.4.6.tar.gz", hash = "sha256:dafc32401712f5b21c12dc3d05ba887a91ad156d9b49a7662279f9fd90526fb2", size = 26742, upload-time = "2026-08-17T08:34:11.55Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8d/ec/6b368a10e9c2e8b1b394c69b96ac213ae66e8c4895e0baa1ffaf7178fd32/alibabacloud_tea_openapi-0.4.5-py3-none-any.whl", hash = "sha256:338979095c7beda80a5b413c31262892cafdc12069dde4ce4fc2e4f7ce0fc609", size = 33333, upload-time = "2026-07-14T13:15:38.365Z" },
+ { url = "https://files.pythonhosted.org/packages/35/00/2f534f5884e5f299d9cb3a1e8be2def8071bc6a6e2a192ba4ff2a8cd5e02/alibabacloud_tea_openapi-0.4.6-py3-none-any.whl", hash = "sha256:c9e1727b9fb2936f487d050fc3590c99f9f2065256dc3a927e5b61f414674ed6", size = 33448, upload-time = "2026-08-17T08:34:10.472Z" },
]
[[package]]
@@ -4426,7 +4426,7 @@ wheels = [
[[package]]
name = "oci"
-version = "2.183.0"
+version = "2.184.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -4439,9 +4439,9 @@ dependencies = [
{ name = "pytz" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1e/2a/77bd6cbf1c69b2f368fe3d6462d84369b0cba15e37ce713cdc08d459b95a/oci-2.183.0.tar.gz", hash = "sha256:ff572ef5f2030a788796bb509d257e6a41c6510ef9b4b6a75a079efd06e533ce", size = 17759723, upload-time = "2026-07-28T06:02:29.76Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/74/2d/fa5368cfabb868f4111c6978e8b5f66aa3a55076c40c1a59ac3081b0227b/oci-2.184.1.tar.gz", hash = "sha256:617dad69caf8dd6e521d224dbc3e8a8bc289906943a0214fd2c3419094e26435", size = 17990631, upload-time = "2026-08-11T11:01:26.194Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a9/de/8574b3e527996a099d196e87794a4652d91a0c3185fcc7fdbb5649b75a8a/oci-2.183.0-py3-none-any.whl", hash = "sha256:bd789c98a94d7c5ea08c20d11dcf68c9cd1ad479b134727d80a930b84387070b", size = 36133501, upload-time = "2026-07-28T06:02:18.239Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/63/5ae22e42aaf96a5da74dc2b9de449c78b4d7418cce621d5da723b3e49f32/oci-2.184.1-py3-none-any.whl", hash = "sha256:bd814e38a70da2190e721937455a08689ab13c0750bd2ef8dd0c98b2dc5a38ea", size = 36628063, upload-time = "2026-08-11T11:01:18.178Z" },
]
[[package]]
@@ -4835,8 +4835,8 @@ wheels = [
[[package]]
name = "prowler"
-version = "5.38.0"
-source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#b3d174d0c1eb202ed7cb9a9daf0500683f4443be" }
+version = "5.40.0"
+source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#b6e9967da6bebd6c7b8b237317a2a95e2e0c65bc" }
dependencies = [
{ name = "alibabacloud-actiontrail20200706" },
{ name = "alibabacloud-credentials" },
@@ -4935,7 +4935,7 @@ dependencies = [
[[package]]
name = "prowler-api"
-version = "1.40.0"
+version = "1.41.0"
source = { virtual = "." }
dependencies = [
{ name = "cartography" },
@@ -5426,15 +5426,15 @@ wheels = [
[[package]]
name = "pyopenssl"
-version = "26.2.0"
+version = "26.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1a/51/27a5ad5f939d08f690a326ef9582cda7140555180db71695f6fb747d6a36/pyopenssl-26.2.0.tar.gz", hash = "sha256:8c6fcecd1183a7fc897548dfe388b0cdb7f37e018200d8409cf33959dbe35387", size = 182195, upload-time = "2026-05-04T23:06:09.72Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/e8/7325d258199b159eb2c03fe32107533e2832e70e63f4fb88a6aa00023201/pyopenssl-26.4.0.tar.gz", hash = "sha256:28dfcce0162b9211413e26dfbfdf1d24317fbeba18fc93c12400a1856b2a0bc7", size = 182046, upload-time = "2026-08-01T19:50:50.512Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/73/b8/a0e2790ae249d6f38c9f66de7a211621a7ab2650217bcd04e1262f578a56/pyopenssl-26.2.0-py3-none-any.whl", hash = "sha256:4f9d971bc5298b8bc1fab282803da04bf000c755d4ad9d99b52de2569ca19a70", size = 55823, upload-time = "2026-05-04T23:06:08.395Z" },
+ { url = "https://files.pythonhosted.org/packages/51/ad/2cf6d3fa2fae5c79e1ed9960c0d42badd0f94d81dd12b50604cdc839e648/pyopenssl-26.4.0-py3-none-any.whl", hash = "sha256:f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c", size = 56026, upload-time = "2026-08-01T19:50:48.94Z" },
]
[[package]]
@@ -6623,39 +6623,27 @@ wheels = [
[[package]]
name = "zstd"
-version = "1.5.7.3"
+version = "1.5.7.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/49/62/b9c075ad664e7c4cbb3d8d2be7c246506abe1bc7f778eb58d260ef9538c8/zstd-1.5.7.3.tar.gz", hash = "sha256:403e5205f4ac04b92e6b0cda654be2f51de268228a0db0067bc087faacf2f495", size = 672559, upload-time = "2026-01-08T16:24:43.361Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/78/9a476e09c825304df47b98be80d1ffe223733b03550af71325415028f615/zstd-1.5.7.2.tar.gz", hash = "sha256:6d8684c69009be49e1b18ec251a5eb0d7e24f93624990a8a124a1da66a92fc8a", size = 670481, upload-time = "2025-06-23T12:36:08.131Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/75/0d/8c89c0d010b58c21a7865a239790bb1c6822029c053b1ded858d6b573e3a/zstd-1.5.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a3c1781a24e2ced2c0ddee11d45b1f04018b03615eeb622a62eca4d56d3358a", size = 267641, upload-time = "2026-01-08T16:30:50.812Z" },
- { url = "https://files.pythonhosted.org/packages/a3/6d/155d8c344d96eca2a5a003a5ddd63373a5f13591fd5cf2b9490250d6805a/zstd-1.5.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c7c81056362b60a04baa34632e713d596662a860ec34efd8e9b109c10e6ec7", size = 230962, upload-time = "2026-01-08T16:30:49.155Z" },
- { url = "https://files.pythonhosted.org/packages/c8/c7/ab93916a26eb58cd501ad701974c31b4bc67a7f6abd6c24bef8fe4d7649b/zstd-1.5.7.3-cp311-cp311-manylinux_2_14_x86_64.whl", hash = "sha256:e564f34a55effc7d654eb293468edc80b64d476b0f899f82760ecd8323223ff5", size = 304166, upload-time = "2026-01-10T11:17:45.697Z" },
- { url = "https://files.pythonhosted.org/packages/c2/54/27a7040a360019a4602343e3c98c0c0a140f382186002c01e1992fd21837/zstd-1.5.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:fbc49a57188184931d5e3c9f1133cad7eea5a370a9e9418fb8122d58c14340a5", size = 1540288, upload-time = "2026-01-08T17:50:26.913Z" },
- { url = "https://files.pythonhosted.org/packages/96/93/4a4d4edd1b2e809e0ebbb16000404bdcc9a09743c04ee1661442c9581b75/zstd-1.5.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d121d3e63722819e1fe5effbcd9628d8a7cfea0cddabcc5bb37ea861a6a83424", size = 1619134, upload-time = "2026-01-08T17:50:32.324Z" },
- { url = "https://files.pythonhosted.org/packages/31/6b/cd6f0a7f4f0d98e4110aa77763cf3e85f594d983ea9ca3d64cc0cee10684/zstd-1.5.7.3-cp311-cp311-manylinux_2_4_i686.whl", hash = "sha256:621f2e7ca8e9eb52a83eb9c91ec3cd283d87591bf75cc658de486b65f44742c7", size = 300166, upload-time = "2026-01-10T11:12:27.938Z" },
- { url = "https://files.pythonhosted.org/packages/05/3f/c717e0d15127d04b7fa58ba9b4c56e8b88b803048b9766cd9d158dbb22ea/zstd-1.5.7.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:c1950fcae690ba32d0f31702b335c548fb42547821565925e48576afdad774a5", size = 1525776, upload-time = "2026-01-08T17:50:35.518Z" },
- { url = "https://files.pythonhosted.org/packages/3e/a2/1813cd787d1a2f9ab8e8a90d28dcbc8e8098997dd04de38897ea8e75dd08/zstd-1.5.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bac4f0d03da69115878bedbfa03c4a3f64364e8396b432028c4ce0f05141a0fb", size = 2096057, upload-time = "2026-01-08T17:50:33.984Z" },
- { url = "https://files.pythonhosted.org/packages/36/ce/f5a3c7c12de458dd9ce15c484d627fe5412b60c155da23dacb5fcf08d9d5/zstd-1.5.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:da0ab134b7fd28023dedf013751ca850de300a090eb11f689d2a1c178c87d9dc", size = 2132659, upload-time = "2026-01-08T17:50:29.534Z" },
- { url = "https://files.pythonhosted.org/packages/f1/66/151f9546498bfd8971a0b6ad67d87c26d7a0df17d57f724da674f3778666/zstd-1.5.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b9923175842ee8f7602ec9cc578f5fc396896f0e8460d3ac9a5adc3cea77244e", size = 2124811, upload-time = "2026-01-08T17:50:37.612Z" },
- { url = "https://files.pythonhosted.org/packages/6a/34/4d2dbb36cb2373d3f115c047cb901b64f89de0703d10779da39de9453812/zstd-1.5.7.3-cp311-cp311-win32.whl", hash = "sha256:0612b604948d7b58aecc6788c7ceb53c5f21d94a155bb6ea9bd0f54ffa43725d", size = 150363, upload-time = "2026-01-08T17:11:02.392Z" },
- { url = "https://files.pythonhosted.org/packages/d9/de/f53687e0dd8c0d0ebfaed9ae88f6a96a1a0388ae7424b469e74bb17ac57d/zstd-1.5.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:5b7f8c81b2bd3b62c0345242247d484cafa4b518d59d18619813d9225af5c5c3", size = 167577, upload-time = "2026-01-08T17:11:03.356Z" },
- { url = "https://files.pythonhosted.org/packages/f2/58/d4a6a902e229e953ed273fe9b78587ed31f57567aa68d3e34af6056e42af/zstd-1.5.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:ea112e3acd9e1765adca35df7b54ac75b36194290f64ea03a3a59664209c8527", size = 157238, upload-time = "2026-01-08T16:36:06.25Z" },
- { url = "https://files.pythonhosted.org/packages/aa/ed/5a3bf2e29dc56d4cc7619929bb51f0c758de6d02967cc73c5d8755a862c0/zstd-1.5.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01a39efb0eeab7cc45cb308618233b624b0840d5e16dcf85456b6cca0592f203", size = 268124, upload-time = "2026-01-08T16:29:57.091Z" },
- { url = "https://files.pythonhosted.org/packages/e2/1d/efc2074ac90af938e78f2ed4004639fe24f294d9086c5280f8d9a02b9897/zstd-1.5.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7a8e8838cf35fa3987bfe1958584cc22e1797efce8e155a63544b4144fc671f8", size = 230988, upload-time = "2026-01-08T16:29:55.604Z" },
- { url = "https://files.pythonhosted.org/packages/2a/52/178393b8d70e23fba67f42dfce4663e4e8a30867110168beb490a36d4639/zstd-1.5.7.3-cp312-cp312-manylinux_2_14_i686.whl", hash = "sha256:f3920ac1d1cc7e9f252f3e29f217fe3cd36f2191bb3dbcae826c29e189b7ad54", size = 300207, upload-time = "2026-01-10T11:26:58.351Z" },
- { url = "https://files.pythonhosted.org/packages/6a/7a/8dcd86a2efb2ed3f9dae39545a05d3c7ed26c7678330786ce4a44cd8b099/zstd-1.5.7.3-cp312-cp312-manylinux_2_14_x86_64.whl", hash = "sha256:143f9062953fb5590cbd47c1040d357336742c79696bf90b6d5b835279a68304", size = 304154, upload-time = "2026-01-10T11:17:40.91Z" },
- { url = "https://files.pythonhosted.org/packages/6f/ce/0c96905ab01ffe0e53a3cec8132123b82db26bd583a71608029bcc789ebc/zstd-1.5.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36d1fd8647e47e1f21b345e192f1a279e925678c23dad8236b547d04456cd699", size = 2162222, upload-time = "2026-01-08T18:02:22.762Z" },
- { url = "https://files.pythonhosted.org/packages/11/c4/db4807d6a68b4628c74fd379de7e3c67ec34f19a2a80ac246b3837cde6cb/zstd-1.5.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1538db419afa62773cf534fc7f3009ff59ecf55ecee4e889587ac2ef0010ed8", size = 2201732, upload-time = "2026-01-08T18:02:20.835Z" },
- { url = "https://files.pythonhosted.org/packages/c5/99/c19a3c0f5580ff9c33a74f06d98d6060ed1fa6bd09b55aed9be852ec191f/zstd-1.5.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5efd16adb092e2a547a7d51cfdaf6fd5680528227684c5bafc7669ab4a55f41", size = 2096459, upload-time = "2026-01-08T18:02:25.336Z" },
- { url = "https://files.pythonhosted.org/packages/23/fd/02eac30419475dbe50212c119043a2d0698a0cbc756da85fd3fd9abddf42/zstd-1.5.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:39b3438e64637d80a5b1860526903b92020acb9bae9ceb5adffd9838c1441328", size = 2125442, upload-time = "2026-01-08T18:02:17.715Z" },
- { url = "https://files.pythonhosted.org/packages/bb/43/3a16ff0a8c913bb9825379db1bd533c75c57c2d2f31dd9111aa9b53711f4/zstd-1.5.7.3-cp312-cp312-win32.whl", hash = "sha256:cbf48c53461e224ffc2490cfe5120a1ff40d14c84d2b512c6d6d99fc91685cf3", size = 150367, upload-time = "2026-01-08T17:03:40.178Z" },
- { url = "https://files.pythonhosted.org/packages/46/83/b85875d7428e63dfa9247e41d17fac611443c774f7892f8643bd4164a6b2/zstd-1.5.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:943a189910f2fea997462e3e4d7fbf727a06d231ef801ebee557b1c87568981c", size = 167604, upload-time = "2026-01-08T17:03:41.355Z" },
- { url = "https://files.pythonhosted.org/packages/37/42/cf291e26804de2f55500cdac93f5e9fa6267cf315def8aa402529bae3a87/zstd-1.5.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:85c4d508f8109afa7c51c4960626c3325af2cf1e442c6c36ebfea15d04757e3f", size = 157241, upload-time = "2026-01-08T16:47:34.615Z" },
- { url = "https://files.pythonhosted.org/packages/04/b8/d13d584867d5eb1bc607877a870858e02a256d4706a4274e475413a000aa/zstd-1.5.7.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:76c49ea969bc08389ea59155cea7c5dea224522ffc62f443f3c0a915f5fd184d", size = 260025, upload-time = "2026-01-08T16:57:45.739Z" },
- { url = "https://files.pythonhosted.org/packages/16/a1/1e5faf75bedfd2bfccfb83e18736b115bed6e348504bd21800cd8f30dcea/zstd-1.5.7.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b1a638ff3dfce8f4cb1203c662fb5606dd99b4a62c5ddc4c406d2d1326bcfdd", size = 221038, upload-time = "2026-01-08T17:16:32.005Z" },
- { url = "https://files.pythonhosted.org/packages/b7/2c/0fe74d8b2029eef8000bc71aac5b3e5b55d00581238711cf627814183ea3/zstd-1.5.7.3-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5e96a5cb100a0edc162935227f2d9784b1031ce4a8a83e96e66eae2673c10143", size = 326792, upload-time = "2026-01-08T16:57:35.631Z" },
- { url = "https://files.pythonhosted.org/packages/96/e0/2c7f081f3524f872128ff31bea2acb6b21cb1dacccef920eb6a1a77a87c6/zstd-1.5.7.3-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bda0bbf3a9553720cd33f1f85940a259656c7ffba4be717ff82b7f062052188", size = 322283, upload-time = "2026-01-08T16:57:36.759Z" },
- { url = "https://files.pythonhosted.org/packages/c9/a7/3bebfcc18d66b90bc7b506a61b2ff4af5ee1b0b16e784ea644afa06241c5/zstd-1.5.7.3-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac36e4022422f6e49b3f07bdbb8a964fd348223d3dc9c82ad5398a4f0432a719", size = 311553, upload-time = "2026-01-08T16:57:38.465Z" },
- { url = "https://files.pythonhosted.org/packages/41/75/8a791cae2c98e5e44a158e15db50d21b7ec0b37aeaffa68d151bc8ffb6d6/zstd-1.5.7.3-pp311-pypy311_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:fa4d760a220541b18ce732a3a2cf7547ea05afc76d05b3b39edebfeb721f6079", size = 317071, upload-time = "2026-01-08T16:36:07.47Z" },
- { url = "https://files.pythonhosted.org/packages/2f/25/b6624e6b08d515242154436c9d06fb20b790d300ac82e84f3c4c133e25e1/zstd-1.5.7.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a69e60146bf8aaa6a0e6c9a94a7c5f3133d68091e2e5c5a3c5ababf71fd5ec7a", size = 167654, upload-time = "2026-01-08T17:00:56.667Z" },
+ { url = "https://files.pythonhosted.org/packages/43/2a/0885f6f1921ec1ef4a8f8ab29ab0a335cc867abe4c7aaa4e5031435a32a5/zstd-1.5.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f799c1e9900ad77e7a3d994b9b5146d7cfd1cbd1b61c3db53a697bf21ffcc57b", size = 269702, upload-time = "2025-06-23T12:50:11.695Z" },
+ { url = "https://files.pythonhosted.org/packages/05/e6/629cf6b77e47fc7149f5724fb4853c48edcdeb10d8c64e391d7026cb10e1/zstd-1.5.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ff4c667f29101566a7b71f06bbd677a63192818396003354131f586383db042", size = 228145, upload-time = "2025-06-23T12:50:10.411Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/b8/9ddefd4670bfe9328ca6657ad335eb8d9c657466247e234a579818b6b0b9/zstd-1.5.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8526a32fa9f67b07fd09e62474e345f8ca1daf3e37a41137643d45bd1bc90773", size = 1536530, upload-time = "2025-06-23T13:51:38.853Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/6a/1bb836c18760dc1e28ca7a9706016e482ebdea633b980d8505dbb65e18f8/zstd-1.5.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:2cec2472760d48a7a3445beaba509d3f7850e200fed65db15a1a66e315baec6a", size = 1616141, upload-time = "2025-06-23T13:51:34.152Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/7a/bb6c6e2cb2a066e347dc27d45d5205058b69d6c8b8d4ae2ee7d6b91c64a5/zstd-1.5.7.2-cp311-cp311-manylinux_2_4_i686.whl", hash = "sha256:a200c479ee1bb661bc45518e016a1fdc215a1d8f7e4bf6c7de0af254976cfdf6", size = 322188, upload-time = "2025-06-23T13:01:48.704Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/4f/cf0669c8a89fdcc91814bf92bd05cc363d5d12a79b656418c0add6f2d266/zstd-1.5.7.2-cp311-cp311-manylinux_2_4_x86_64.whl", hash = "sha256:f5d159e57a13147aa8293c0f14803a75e9039fd8afdf6cf1c8c2289fb4d2333a", size = 302736, upload-time = "2025-06-23T13:05:33.649Z" },
+ { url = "https://files.pythonhosted.org/packages/be/bc/e5f8b7f61826323e39e099db1eb5c0e09b18315df1b1ff778f7ae9aadcac/zstd-1.5.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:7206934a2bd390080e972a1fed5a897e184dfd71dbb54e978dc11c6b295e1806", size = 1522687, upload-time = "2025-06-23T13:51:35.494Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/8c/7660a949a020ac9d02b3166a25dd1c12144572d77b11ae92a31d341016da/zstd-1.5.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e0027b20f296d1c9a8e85b8436834cf46560240a29d623aa8eaa8911832eb58", size = 2098794, upload-time = "2025-06-23T13:51:37.219Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/b2/730c811a78d670104d40c7f08cc8092577cdff870cba42b3158f20fceb57/zstd-1.5.7.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d6b17e5581dd1a13437079bd62838d2635db8eb8aca9c0e9251faa5d4d40a6d7", size = 2112266, upload-time = "2025-06-23T13:51:31.258Z" },
+ { url = "https://files.pythonhosted.org/packages/44/74/2c16e1632094db36c8920d4c13b8e2e843024d548ae26888c2d22af6a676/zstd-1.5.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b13285c99cc710f60dd270785ec75233018870a1831f5655d862745470a0ca29", size = 2109465, upload-time = "2025-06-23T13:51:32.884Z" },
+ { url = "https://files.pythonhosted.org/packages/58/6e/b9c9a834769d96cab2122da1be8c8c700d3f76be796d2b7516e85d2eca0e/zstd-1.5.7.2-cp311-cp311-win32.whl", hash = "sha256:cdb5ec80da299f63f8aeccec0bff3247e96252d4c8442876363ff1b438d8049b", size = 149448, upload-time = "2025-06-23T13:06:21.144Z" },
+ { url = "https://files.pythonhosted.org/packages/47/b7/fc22ad6292a32d7676ab815de3a23573beac3679e8abd9914288d1496ceb/zstd-1.5.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:4f6861c8edceb25fda37cdaf422fc5f15dcc88ced37c6a5b3c9011eda51aa218", size = 166592, upload-time = "2025-06-23T13:06:22.126Z" },
+ { url = "https://files.pythonhosted.org/packages/45/14/096bb77f3e5ef525b452cd6294da33de7f8a8c9647ba78293378fbb0a7ce/zstd-1.5.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ebe3e60dbace52525fa7aa604479e231dc3e4fcc76d0b4c54d8abce5e58734", size = 269408, upload-time = "2025-06-23T13:11:46.492Z" },
+ { url = "https://files.pythonhosted.org/packages/08/b8/2bc2590a34c733ea0570f366e6ad7d889d05c7825bd3ccab01f36ece71c6/zstd-1.5.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ef201b6f7d3a6751d85cc52f9e6198d4d870e83d490172016b64a6dd654a9583", size = 228188, upload-time = "2025-06-23T13:11:47.539Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/80/6252de3a70cfd7767718ad476893f1c7dc129f942cc7ed0322e3137c03d9/zstd-1.5.7.2-cp312-cp312-manylinux_2_14_x86_64.whl", hash = "sha256:ac7bdfedda51b1fcdcf0ab69267d01256fc97ddf666ce894fde0fae9f3630eac", size = 302720, upload-time = "2025-06-23T12:40:11.522Z" },
+ { url = "https://files.pythonhosted.org/packages/af/b6/af908387814b99172d3aea6aeb24b19583aadfa45f6021e5e2a0d6d8e99a/zstd-1.5.7.2-cp312-cp312-manylinux_2_4_i686.whl", hash = "sha256:b835405cc4080b378e45029f2fe500e408d1eaedfba7dd7402aba27af16955f9", size = 322237, upload-time = "2025-06-23T13:17:35.482Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/d7/ab9142e002a7eaa451cb4bb37a74c390c489ba8ae75ade543840496eda04/zstd-1.5.7.2-cp312-cp312-win32.whl", hash = "sha256:e4cf97bb97ed6dbb62d139d68fd42fa1af51fd26fd178c501f7b62040e897c50", size = 149453, upload-time = "2025-06-23T13:13:02.786Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/c7/c182ea7bc283f591e3f3c5f0f239e7a92c9bc1f626642ae2c4dfbe51d6f2/zstd-1.5.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:55e2edc4560a5cf8ee9908595e90a15b1f47536ea9aad4b2889f0e6165890a38", size = 166628, upload-time = "2025-06-23T13:13:03.745Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/c9/a6495a7bf168a78f0a0c01d61d830ebfb401315a64fd1ae8d725c458114c/zstd-1.5.7.2-pp311-pypy311_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:5fb2ff5718fe89181223c23ce7308bd0b4a427239379e2566294da805d8df68a", size = 315542, upload-time = "2025-06-23T12:39:27.598Z" },
]
diff --git a/docs/changelog.mdx b/docs/changelog.mdx
index 2233ee1077..bbdcba6462 100644
--- a/docs/changelog.mdx
+++ b/docs/changelog.mdx
@@ -4,6 +4,117 @@ description: "New features and improvements in each Prowler release"
rss: true
---
+
+ ### π€ Lighthouse AI β Finding Skills
+
+
+ This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
+
+
+ Lighthouse AI now embeds a Skills menu on every finding, answering the questions an analyst actually asks. **Contextual Fix** produces the fix for the finding, **Triage Decision** judges whether it is real and closes it out when it is not, and **Systemic Scope** determines whether the problem is a one-off or everywhere. A free-form "Ask Lighthouse anything" prompt sits in the same menu, and each run shows its progress and offers follow-up actions such as creating a Jira issue or muting the finding.
+
+ 
+
+ Read more in the [Lighthouse AI documentation](https://docs.prowler.com/getting-started/products/prowler-cloud-lighthouse).
+
+ ### βοΈ Azure Management Group Onboarding
+
+
+ This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
+
+
+ Azure subscriptions no longer onboard one at a time. Choose "Add Multiple Subscriptions With Azure Management Group" in the add-provider wizard, enter the Microsoft Entra tenant ID, and authenticate once with a single tenant-wide service principal: Prowler discovers the entire management-group hierarchy under the tenant root, lets you select the subscriptions to onboard, and creates their providers with the management-group structure preserved. Azure now matches the one-step onboarding that AWS Organizations and GCP organizations already have.
+
+ 
+
+ Read more in the [Azure Management Groups documentation](https://docs.prowler.com/user-guide/tutorials/prowler-cloud-azure-management-groups).
+
+ ### β Findings Triage β Verify MANUAL Findings as PASS
+
+
+ This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
+
+
+ Checks that require human judgment report `MANUAL` findings. For these findings, and only for them, the triage status selector now offers **Resolved**: choosing it asks for the required written evidence and verifies the finding as passing. The finding then reports an effective `PASS` while preserving the raw `MANUAL` scan result, across findings, finding groups, compliance reports, and scans, with the attestation's author, evidence, and validity always visible. Attestations expire automatically after 90 days, or as soon as a new scan reports a real failure, returning the finding to the review queue.
+
+ 
+
+ 
+
+ Read more in the [Findings Triage documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-findings-triage#verify-a-manual-finding-as-pass).
+
+ ### βοΈ Prowler Cloud MCP β Organizations Management and Grouped Jira Dispatch
+
+
+ This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
+
+
+ The hosted Prowler Cloud MCP server adds eight organization tools, so an agent can onboard and manage entire cloud organizations end to end: create the organization, discover its accounts, subscriptions, and projects, apply the selection, and manage the resulting providers. The tools cover AWS Organizations, GCP organizations, and Azure tenant root management groups, and they are available to Lighthouse AI.
+
+ `prowler_send_findings_to_jira` also gains Cloud-only dispatch capabilities: select failed findings by check IDs against the latest completed scan, and send them in grouped mode, one Jira work item per check listing up to 50 affected resources, with per-group error reporting.
+
+ Read more in the [Prowler MCP tools documentation](https://docs.prowler.com/getting-started/basic-usage/prowler-mcp-tools) and its [Jira operations reference](https://docs.prowler.com/getting-started/basic-usage/prowler-mcp-tools#jira-operations).
+
+ ### πΈοΈ Attack Paths β Grouped Graph with Outcome Destinations
+
+
+ This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
+
+
+ The Attack Paths graph now reads from source to destination. Resources of the same class collapse into a single expandable node with a count, clicking reveals its members, and every path terminates in an explicit outcome node naming the destination impact: code execution, privilege escalation, public exposure, or resource inventory. The per-account hub node is gone, and the clicked resource stays highlighted while its findings are expanded.
+
+ 
+
+ Explore the full Attack Paths query catalog at [Prowler Hub](https://hub.prowler.com/attack-paths).
+
+ Read more in the [Attack Paths documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths).
+
+ ### π New Compliance Framework β CMMC 2.0
+
+ The Cybersecurity Maturity Model Certification (CMMC) is the certification the US Department of Defense requires from contractors and suppliers that handle federal contract data. Prowler now includes CMMC 2.0 as a universal framework with all 149 requirements defined by the CMMC Program rule (32 CFR Part 170), organized in its three levels:
+
+ - **Level 1 (Foundational):** 15 requirements for the basic safeguarding of Federal Contract Information, from FAR 52.204-21.
+ - **Level 2 (Advanced):** 110 requirements from NIST SP 800-171 Rev 2, protecting Controlled Unclassified Information.
+ - **Level 3 (Expert):** 24 enhanced requirements from NIST SP 800-172 for the most sensitive programs.
+
+ Requirements map to Prowler checks across AWS, Azure, GCP, Alibaba Cloud, Oracle Cloud, and Microsoft 365, so one framework reports the compliance posture of the whole estate.
+
+ Read more in the [Compliance documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance).
+
+ ### π Checks
+
+ #### Microsoft 365
+
+ Twenty new Entra ID checks expand the coverage of CIS Microsoft 365 Foundations Benchmark v7.0.0:
+
+ - **Password protection:** custom banned password list, on-premises enforcement, and lockout threshold and duration.
+ - **Default user permissions:** security group and Microsoft 365 group creation restricted, and guest invitations limited to allowed domains.
+ - **Conditional Access:** high and medium sign-in risk blocked, authentication transfer blocked, untrusted locations blocked, trusted named locations defined, sign-in frequency enforced, and token protection enforced.
+ - **Sessions and authentication methods:** idle session timeout configured, email one-time passcodes disabled, and Microsoft Authenticator context shown.
+ - **PIM and access reviews:** approval required to activate the Global Administrator and Privileged Role Administrator roles, and access reviews configured for guest users and privileged roles.
+
+ Explore all Microsoft 365 checks at [Prowler Hub](https://hub.prowler.com/check?provider=m365).
+
+ #### AWS
+
+ Two new checks detect hardcoded secrets:
+
+ - `batch_job_definition_no_secrets` scans Batch job definition environment variables and command parameters. Thanks to @praneetrajv!
+ - `awslambda_layer_no_secrets_in_content` scans Lambda layer package content. Thanks to @ganiganesh25!
+
+ Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).
+
+ ### π External Contributors
+
+ Thank you to our community contributors for this release!
+
+ - @praneetrajv: AWS `batch_job_definition_no_secrets` check ([#12117](https://github.com/prowler-cloud/prowler/pull/12117))
+ - @ganiganesh25: AWS `awslambda_layer_no_secrets_in_content` check ([#12233](https://github.com/prowler-cloud/prowler/pull/12233))
+ - @andoniaf: GitHub `organization_repository_creation_limited` now reports low severity when repository creation is limited to private or internal visibility ([#12164](https://github.com/prowler-cloud/prowler/pull/12164))
+
+ See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.39.0) for the complete list of changes.
+
+
### π Compliance Watchlist
diff --git a/docs/developer-guide/checks.mdx b/docs/developer-guide/checks.mdx
index dca22cb0cd..93facfaadf 100644
--- a/docs/developer-guide/checks.mdx
+++ b/docs/developer-guide/checks.mdx
@@ -224,9 +224,9 @@ Each check **must** populate the report with a unique identifier for the audited
- `resource_name`: Description of the configuration (e.g., "SharePoint Settings")
- GitHub
- Resource ID β `report.resource_id`.
- - The ID of the Github resource. This is a system-generated integer that uniquely identifies the resource within the Github platform.
+ - The ID of the GitHub resource. This is a system-generated integer that uniquely identifies the resource within the GitHub platform.
- Resource Name β `report.resource_name`.
- - The name of the Github resource. In the case of a repository, this is just the repository name. For full repository names use the resource `full_name`.
+ - The name of the GitHub resource. In the case of a repository, this is just the repository name. For full repository names use the resource `full_name`.
### Configurable Checks in Prowler
diff --git a/docs/developer-guide/provider.mdx b/docs/developer-guide/provider.mdx
index be709a12a2..1a570ccb68 100644
--- a/docs/developer-guide/provider.mdx
+++ b/docs/developer-guide/provider.mdx
@@ -107,7 +107,7 @@ Once you have decided the provider you want or need to add to Prowler, the next
- **SDK Providers**: Low complexity. You have mature examples like AWS, Azure, GCP, Kubernetes, etc. that you can leverage to implement your provider.
- **API Providers**: Medium complexity. You need to implement the authentication and session management, and the API calls to the provider. You now have NHN and MongoDB Atlas as example to follow.
- **Tool/Wrapper Providers**: High complexity. You need to implement the argument/output mapping to the provider and handle problems that the tool/wrapper may have. You now have IAC and the PowerShell wrapper as example to follow.
-- **Hybrid Providers**: High complexity. You need to "customize" your provider, mixing the other types of providers in order to achieve the desired result. You have M365 (msgraph SDK + PowerShell wrapper) and Github (PyGithub SDK + graphql API requests) as examples.
+- **Hybrid Providers**: High complexity. You need to "customize" your provider, mixing the other types of providers in order to achieve the desired result. You have M365 (msgraph SDK + PowerShell wrapper) and GitHub (PyGithub SDK + graphql API requests) as examples.
### Determining Regional vs Non-Regional Architecture
@@ -814,7 +814,7 @@ class YourProviderMutelist(Mutelist):
Region management is essential for cloud providers that operate across multiple geographic locations. This component handles region validation and provides region-specific functionality.
-Regions are optional, only if the provider has regions, for example Github does not have regions, but AWS does.
+Regions are optional, only if the provider has regions, for example GitHub does not have regions, but AWS does.
**File:** `prowler/providers//lib/regions/_regions.py`
@@ -1773,7 +1773,7 @@ The implementation of the mutelist is the same as the [SDK providers](#step-5-im
Region management is essential for cloud providers that operate across multiple geographic locations. This component handles region validation and provides region-specific functionality.
-Regions are optional, only if the provider has regions, for example Github does not have regions, but AWS does.
+Regions are optional, only if the provider has regions, for example GitHub does not have regions, but AWS does.
**File:** `prowler/providers//lib/regions/_regions.py`
diff --git a/docs/getting-started/basic-usage/prowler-mcp-tools.mdx b/docs/getting-started/basic-usage/prowler-mcp-tools.mdx
index 0a7ae1f271..24cb2f4b2a 100644
--- a/docs/getting-started/basic-usage/prowler-mcp-tools.mdx
+++ b/docs/getting-started/basic-usage/prowler-mcp-tools.mdx
@@ -11,10 +11,10 @@ Complete reference guide for all tools available in the Prowler MCP Server. Tool
| Prowler Hub | 10 tools | No | Cloud and Local MCP Server |
| Prowler Documentation | 2 tools | No | Cloud and Local MCP Server |
| Prowler Cloud, Private Cloud & Local Server | 49 tools | Yes | Cloud and Local MCP Server |
-| Prowler Cloud management | 32 tools | Yes | Cloud MCP Server only |
+| Prowler Cloud management | 40 tools | Yes | Cloud MCP Server only |
-48 of the 49 Prowler tools are available on both servers. `prowler_schedule_daily_scan` is the exception: it is Local-only, because the Cloud MCP Server supersedes it with the `prowler_cloud_*` [Scan Scheduling](#scan-scheduling) tools.
+48 of the 49 Prowler tools are available on both servers. `prowler_schedule_daily_scan` is the exception: it is Local-only, because the Cloud MCP Server supersedes it with the `prowler_cloud_*` [Scan Scheduling](#scan-scheduling) tools. `prowler_send_findings_to_jira` is exposed by both servers but accepts two [extra parameters](#jira-operations) on the Cloud MCP Server.
## Tool Naming Convention
@@ -124,7 +124,20 @@ Tools for managing where Prowler sends its results: Amazon S3 buckets, AWS Secur
#### Jira Operations
- **`prowler_get_jira_issue_types`** - List the issue types available in a Jira project, fetched live from Jira
-- **`prowler_send_findings_to_jira`** - Create one Jira work item per finding, with its severity, resource, risk, and remediation steps
+- **`prowler_send_findings_to_jira`** - Create Jira work items from findings, each carrying the check title, severity, status, provider, region, resource, risk, and remediation steps. Select the findings either by ID with `finding_ids`, or β on Prowler Cloud only β by check with `check_ids`, and choose between one work item per finding or one per check with `dispatch_mode`
+
+
+`check_ids` and `dispatch_mode` are **Prowler Cloud only**:
+
+- **`check_ids`** - Send the failing findings of a check (for example `s3_bucket_public_access`) without listing their IDs. Prowler resolves them server-side, taking only the failed findings of the latest completed scan of every provider. Get the check IDs from `prowler_list_finding_groups`. Exactly one of `finding_ids` or `check_ids` is required β Prowler combines both filters, so sending both would only dispatch their intersection. A Local MCP Server rejects `check_ids` with a client error.
+- **`dispatch_mode`** - `individual` (the default) creates one work item per finding. `grouped` creates one work item per check instead, listing up to 50 affected resources and linking back to the finding group in Prowler Cloud, which keeps a noisy check to a single ticket. Grouped dispatch only covers failed, unmuted findings of the latest completed scan of every provider. A Local MCP Server ignores `dispatch_mode` instead of rejecting it, and creates one work item per finding.
+
+In `grouped` mode the response counters change meaning: `created_count` counts work items (one per check) rather than findings, `failed_count` counts the entries of the new `failed_groups` field, and `failed_groups` details each failure with its reason and the `check_id` whose work item could not be created.
+
+
+
+`prowler_send_findings_to_jira` creates real work items that Prowler cannot delete or update afterwards. Only retry the same dispatch when the previous response returned `safe_to_retry: true`, otherwise the work items already created are duplicated. Combining `check_ids` with the default `individual` mode opens one work item per failing resource, which can be hundreds of them β use `dispatch_mode="grouped"` to keep it to one per check.
+
### Attack Paths Analysis
@@ -167,6 +180,23 @@ Manage Prowler Cloud-only features and configuration. **Requires authentication.
These tools are available **only on the Cloud MCP Server** (`https://mcp.prowler.com/mcp`). A Local MCP Server does not expose them, because the features they manage exist only in Prowler Cloud.
+### Organizations
+
+Tools for onboarding a cloud provider organization as a whole β an AWS Organization, an Azure tenant with its management groups, or a GCP organization with its folders. An organization holds org-level credentials, discovers the real account, subscription, or project structure in the cloud, and turns a selection from that discovery into Prowler providers linked into a hierarchy of nodes. Every tool that changes something β creating, updating, deleting, discovering, applying a discovery, or adjusting provider membership β requires the **Manage Providers** permission; listing and reading do not.
+
+
+Use these tools for the whole organization. To register providers one by one, use the [Provider Management](#provider-management) tools instead; to build arbitrary RBAC buckets of providers, use provider groups.
+
+
+- **`prowler_cloud_list_organizations`** - Browse the registered organizations with lightweight data (name, type, external id, provider and node counts), filtered by type or cloud-side external id
+- **`prowler_cloud_get_organization`** - Get one organization in full: attributes, linked providers, credentials status, latest discovery, and the OU / management group / folder hierarchy. Set `include_hierarchy` to `false` to skip the tree on large organizations
+- **`prowler_cloud_create_organization`** - Register an organization, optionally storing its org-level credentials in the same call. Idempotent: an organization with the same type and external id is reused and its credentials rotated, reported as `created: false`
+- **`prowler_cloud_update_organization`** - Rename an organization, replace its metadata, and/or create or rotate its org-level credentials. `org_type` and `external_id` are immutable after creation
+- **`prowler_cloud_delete_organization`** - Delete an organization, its entire hierarchy, and every linked provider
+- **`prowler_cloud_discover_organization`** - Enumerate the real cloud structure: AWS accounts and OUs, Azure subscriptions and management groups, or GCP projects and folders. Each item comes back with its registration state so you can choose what to onboard
+- **`prowler_cloud_apply_organization_discovery`** - Turn a discovery selection into Prowler providers and hierarchy nodes
+- **`prowler_cloud_manage_organization_providers`** - Manually `add`, `replace`, or `remove` the providers linked to an organization or to one of its hierarchy nodes. Providers are detached, never deleted
+
### Scan Configurations
Tools for managing reusable scan configurations β per-provider check and compliance selections β and attaching them to providers. Providers without a configuration attached use the default.
diff --git a/docs/getting-started/installation/prowler-app.mdx b/docs/getting-started/installation/prowler-app.mdx
index 529d544fa6..b28575a47a 100644
--- a/docs/getting-started/installation/prowler-app.mdx
+++ b/docs/getting-started/installation/prowler-app.mdx
@@ -128,12 +128,12 @@ To update the environment file:
Edit the `.env` file and change version values:
```env
-PROWLER_UI_VERSION="5.38.0"
-PROWLER_API_VERSION="5.38.0"
+PROWLER_UI_VERSION="5.39.0"
+PROWLER_API_VERSION="5.39.0"
```
- You can find the latest versions of Prowler Local Server in the [Releases Github section](https://github.com/prowler-cloud/prowler/releases) or in the [Container Versions](#container-versions) section of this documentation.
+ You can find the latest versions of Prowler Local Server in the [Releases GitHub section](https://github.com/prowler-cloud/prowler/releases) or in the [Container Versions](#container-versions) section of this documentation.
diff --git a/docs/images/changelog/v5.39.0-attack-paths-graph.png b/docs/images/changelog/v5.39.0-attack-paths-graph.png
new file mode 100644
index 0000000000..68cd44278f
Binary files /dev/null and b/docs/images/changelog/v5.39.0-attack-paths-graph.png differ
diff --git a/docs/images/changelog/v5.39.0-azure-mg-selector.png b/docs/images/changelog/v5.39.0-azure-mg-selector.png
new file mode 100644
index 0000000000..2badc72932
Binary files /dev/null and b/docs/images/changelog/v5.39.0-azure-mg-selector.png differ
diff --git a/docs/images/changelog/v5.39.0-lighthouse-finding-skills.png b/docs/images/changelog/v5.39.0-lighthouse-finding-skills.png
new file mode 100644
index 0000000000..06775297eb
Binary files /dev/null and b/docs/images/changelog/v5.39.0-lighthouse-finding-skills.png differ
diff --git a/docs/images/changelog/v5.39.0-manual-pass-details.png b/docs/images/changelog/v5.39.0-manual-pass-details.png
new file mode 100644
index 0000000000..1914cc71e3
Binary files /dev/null and b/docs/images/changelog/v5.39.0-manual-pass-details.png differ
diff --git a/docs/images/changelog/v5.39.0-manual-pass-selector.png b/docs/images/changelog/v5.39.0-manual-pass-selector.png
new file mode 100644
index 0000000000..f14b05f877
Binary files /dev/null and b/docs/images/changelog/v5.39.0-manual-pass-selector.png differ
diff --git a/docs/images/prowler-app/attack-paths/fullscreen-mode.png b/docs/images/prowler-app/attack-paths/fullscreen-mode.png
index 190d973638..ff88c2b6de 100644
Binary files a/docs/images/prowler-app/attack-paths/fullscreen-mode.png and b/docs/images/prowler-app/attack-paths/fullscreen-mode.png differ
diff --git a/docs/images/prowler-app/attack-paths/graph-filtered.png b/docs/images/prowler-app/attack-paths/graph-filtered.png
index 3eddf04473..59fff5b50f 100644
Binary files a/docs/images/prowler-app/attack-paths/graph-filtered.png and b/docs/images/prowler-app/attack-paths/graph-filtered.png differ
diff --git a/docs/images/prowler-app/attack-paths/graph-visualization-expanded.png b/docs/images/prowler-app/attack-paths/graph-visualization-expanded.png
new file mode 100644
index 0000000000..a870df7519
Binary files /dev/null and b/docs/images/prowler-app/attack-paths/graph-visualization-expanded.png differ
diff --git a/docs/images/prowler-app/attack-paths/graph-visualization.png b/docs/images/prowler-app/attack-paths/graph-visualization.png
index d7f2a747eb..af1371c0d8 100644
Binary files a/docs/images/prowler-app/attack-paths/graph-visualization.png and b/docs/images/prowler-app/attack-paths/graph-visualization.png differ
diff --git a/docs/images/prowler-app/attack-paths/navigation.png b/docs/images/prowler-app/attack-paths/navigation.png
index 0526e05e5c..560a2d10a1 100644
Binary files a/docs/images/prowler-app/attack-paths/navigation.png and b/docs/images/prowler-app/attack-paths/navigation.png differ
diff --git a/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-details.png b/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-details.png
new file mode 100644
index 0000000000..1914cc71e3
Binary files /dev/null and b/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-details.png differ
diff --git a/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-note.png b/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-note.png
new file mode 100644
index 0000000000..d447478dbe
Binary files /dev/null and b/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-note.png differ
diff --git a/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-selector.png b/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-selector.png
new file mode 100644
index 0000000000..f14b05f877
Binary files /dev/null and b/docs/images/prowler-app/findings-triage/findings-triage-manual-pass-selector.png differ
diff --git a/docs/user-guide/cli/tutorials/configuration_file.mdx b/docs/user-guide/cli/tutorials/configuration_file.mdx
index 2b5ef617cf..90de1dbdbc 100644
--- a/docs/user-guide/cli/tutorials/configuration_file.mdx
+++ b/docs/user-guide/cli/tutorials/configuration_file.mdx
@@ -495,7 +495,7 @@ aws:
# AWS CloudTrail Configuration
# aws.cloudtrail_threat_detection_privilege_escalation
- threat_detection_privilege_escalation_threshold: 0.2 #Β Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.2 (20%)
+ threat_detection_privilege_escalation_threshold: 0.2 #Β Percentage of actions found to decide if it is a privilege_escalation attack event, by default is 0.2 (20%)
threat_detection_privilege_escalation_minutes: 1440 # Past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours)
threat_detection_privilege_escalation_actions:
[
diff --git a/docs/user-guide/tutorials/prowler-app-attack-paths.mdx b/docs/user-guide/tutorials/prowler-app-attack-paths.mdx
index 0242c817f5..d4e60d6cf2 100644
--- a/docs/user-guide/tutorials/prowler-app-attack-paths.mdx
+++ b/docs/user-guide/tutorials/prowler-app-attack-paths.mdx
@@ -16,26 +16,24 @@ Attack Paths analyzes relationships between cloud resources, permissions, and se
By mapping these relationships as a graph, Attack Paths reveals risks that individual security checks cannot detect on their own, such as an IAM role that can escalate its own permissions, or a chain of policies that grants unintended access to sensitive resources.
- Attack Paths is currently available for **AWS** providers. Support for
- additional providers is planned.
+ Attack Paths is currently available for **AWS** providers. Support for additional providers is planned.
## Prerequisites
The following prerequisites are required for Attack Paths:
-- **An AWS provider is configured** with valid credentials in Prowler Cloud. For setup instructions, see [Getting Started with AWS](/user-guide/providers/aws/getting-started-aws).
+- **An AWS provider is configured** with valid credentials. For setup instructions, see [Getting Started with AWS](/user-guide/providers/aws/getting-started-aws).
- **At least one scan has completed** on the configured AWS provider and produced graph data. Attack Paths scans run automatically alongside regular security scans, no separate configuration is required.
## How Attack Paths Scans Work
-Attack Paths scans are generated automatically when a security scan runs on an AWS provider. Each completed scan produces graph data that maps relationships between IAM principals, policies, trust configurations, and other resources.
+Attack Paths scans are generated automatically when a security scan runs on an AWS provider. When a scan produces graph data, it maps relationships between IAM principals, policies, trust configurations, and other resources. A scan can complete without producing graph data.
-Once the scan finishes and graph data is ready, the scan appears in the Attack Paths scan table with a **Completed** status and a check in the **Graph** column. Scans that are still queued or running remain visible, but they cannot be selected until graph data is ready.
+When graph data is ready, the scan appears in the Attack Paths scan table with a check in the **Graph** column and can be selected regardless of its current status. Scans without graph data remain visible but cannot be selected. If a new scan cycle starts after graph data is available, the previous cycle remains available while the new scan runs.
- Since Prowler scans all configured providers every **24 hours** by default,
- Attack Paths data stays up to date automatically.
+ Prowler Cloud and Prowler Private Cloud scan configured providers every **24 hours** by default, so Attack Paths data stays up to date automatically.
## Accessing Attack Paths
@@ -66,7 +64,7 @@ The scans table displays all Attack Paths scans with the following columns:
- **Graph:** Whether Attack Paths graph data is available for the scan.
- **Duration:** Total scan time.
-To select a scan for analysis, click the radio button on any row with a **Completed** status and available graph data.
+To select a scan for analysis, click any row with a check in the **Graph** column. A row can remain selectable while a new scan cycle runs because Attack Paths keeps the graph from the previous completed cycle available.
- Only scans with graph data can be selected. Disabled rows include a tooltip
- that explains why the graph is not available yet.
+ Only scans with graph data can be selected. Disabled rows include a tooltip that explains why the graph is not available yet.
## Choosing a Query
@@ -97,9 +94,7 @@ To choose a query, click the dropdown and select from the available options. Eac
Once selected, a description panel appears below the dropdown with more context about the query.
- In Prowler Cloud and Prowler Private Cloud, the query selector hides queries
- confirmed empty for the selected scan, so only queries that return data remain
- visible. See [Active Queries](/user-guide/tutorials/prowler-app-attack-paths-active-queries).
+ In Prowler Cloud and Prowler Private Cloud, the query selector hides built-in queries confirmed empty for the selected scan. Built-in queries without a confirmed empty result and the **Custom openCypher query** remain visible. See [Active Queries](/user-guide/tutorials/prowler-app-attack-paths-active-queries).
## Configuring Query Parameters
@@ -120,7 +115,7 @@ For example, **Internet-Exposed EC2 with Sensitive S3 Access** uses **Tag key**
## Writing Custom openCypher Queries
-In addition to the built-in queries, Attack Paths supports custom read-only [openCypher](https://opencypher.org/) queries. Custom queries provide direct access to the underlying graph so security teams can answer ad-hoc questions, prototype detections, or extend coverage beyond the built-in catalogue.
+In addition to the built-in queries, Attack Paths supports custom read-only [openCypher](https://opencypher.org/) queries. Custom queries provide direct access to the underlying graph so security teams can answer ad-hoc questions, prototype detections, or extend coverage beyond the built-in catalog.
To write a custom query, select **Custom openCypher query** from the query dropdown. A code editor with syntax highlighting and line numbers appears, ready to receive the query.
@@ -192,11 +187,7 @@ Custom queries traverse the same Cartography graph the built-in queries use. Nod
For the complete reference, including the graph model, list-typed and JSON-encoded properties, performance guidance, and openCypher compatibility rules, see [Attack Paths Queries](/developer-guide/attack-paths-queries) in the Developer Guide.
- AI assistants connected through Prowler MCP Server can fetch the exact
- Cartography schema for the active scan via the
- `prowler_get_attack_paths_cartography_schema` tool. This guarantees that
- generated queries match the schema version pinned by the running Prowler
- release.
+ AI assistants connected through Prowler MCP Server can fetch the exact Cartography schema for the active scan via the `prowler_get_attack_paths_cartography_schema` tool. This guarantees that generated queries match the schema version pinned by the running Prowler release.
## Executing a Query
@@ -221,12 +212,22 @@ If the query returns no results, an informational message appears. Common reason
After a successful execution, the graph visualization renders below the query builder. The graph maps relationships between cloud resources, IAM entities, public exposure, and security findings.
+### Grouped Graphs and Query Outcomes
+
+
+
+Prowler Cloud and Prowler Private Cloud group resources of the same class and graph level into expandable nodes. Built-in query graphs also end with a query outcome, which states the result that the path can lead to.
+
+Prowler Local Server keeps the flat graph view, including the provider root. Custom openCypher queries do not have a catalog outcome, so their graphs do not include an outcome node.
+
### Node Types
-- **Provider root nodes:** Represent the AWS account or provider root for the selected scan.
-- **Resource nodes:** Represent cloud resources such as IAM roles, policies, EC2 instances, security groups, and S3 buckets.
+- **Grouped resource nodes:** Represent multiple resources of the same class in Prowler Cloud and Prowler Private Cloud. A number in the upper-right corner shows how many resources the node contains. A red outline indicates that one or more resources in the group have findings.
+- **Resource nodes:** Represent individual cloud resources such as IAM roles, policies, EC2 instances, security groups, and S3 buckets. A class with one resource remains an individual node.
- **Internet nodes:** Represent exposure from the public internet.
- **Finding nodes:** Represent Prowler findings linked to resources. Finding colors indicate risk level, such as critical, high, medium, or low.
+- **Outcome nodes:** Mark the terminal result of a built-in query in Prowler Cloud and Prowler Private Cloud. The orange node displays outcomes such as **Code execution**, **Privilege escalation**, **Public exposure**, or **Resource inventory**. A dashed ring and the label **Latent outcome** indicate an inventory or another partial outcome.
+- **Provider root nodes:** Represent the AWS account or provider root in the Prowler Local Server flat graph.
### Edge Types
@@ -234,7 +235,7 @@ After a successful execution, the graph visualization renders below the query bu
- **Finding edges:** Dashed relationships between resources and their associated findings.
- **Highlighted paths:** Green edges that show the active path when you hover a node or focus a finding.
-The standard graph view includes a minimap and a legend below the canvas. The legend shows the provider roots, visible node types, finding risk levels, node states, and edge types present in the current view.
+The standard graph view includes a minimap and a legend below the canvas. The legend shows the visible node types, finding risk levels, node states, and edge types present in the current view. Prowler Local Server also displays the provider root in the legend.
+
### Showing Related Findings
Resource nodes with related findings are clickable. Click one of these resources to show its finding nodes. Click the resource again to hide them.
-The graph automatically fits the selected resource and its related findings when the findings are shown.
+The selected resource is highlighted in green while its findings are visible. The graph automatically fits the selected resource and its related findings when the findings are shown.
### Focusing a Finding Path
@@ -279,12 +295,12 @@ The toolbar in the top-right corner of the graph provides:
- **Zoom in / Zoom out:** Adjust the zoom level
- **Fit graph to view:** Reset the view to fit the visible graph
-- **Export graph:** Download the current graph as a PNG file
+- **Collapse all groups:** Close every expanded resource group. This control appears only when a group is expanded
+- **Export graph:** Download the current graph, including its grouped or expanded state and outcome, as a PNG file
- **Fullscreen:** Open the graph in a full-size modal
- Use **Ctrl + Scroll** (or **Cmd + Scroll** on macOS) to zoom directly within
- the graph area.
+ Use **Ctrl + Scroll** (or **Cmd + Scroll** on macOS) to zoom directly within the graph area.
## Viewing Finding Details
diff --git a/docs/user-guide/tutorials/prowler-app-findings-triage.mdx b/docs/user-guide/tutorials/prowler-app-findings-triage.mdx
index bbceaef992..408f201645 100644
--- a/docs/user-guide/tutorials/prowler-app-findings-triage.mdx
+++ b/docs/user-guide/tutorials/prowler-app-findings-triage.mdx
@@ -42,12 +42,12 @@ The status selector includes manual statuses. Prowler also sets automatic status
| **Remediating** | Manual | Work is in progress to fix the finding. |
| **Risk Accepted** | Manual | The team accepts the risk and wants to mute the finding. |
| **False Positive** | Manual | The finding does not apply and should be muted. |
-| **Resolved** | Automatic | A finding changed from `FAIL` to `PASS` in a later scan. A passed finding with no saved triage state also appears as **Resolved**. |
+| **Resolved** | Automatic / Manual | A finding changed from `FAIL` to `PASS` in a later scan. A passed finding with no saved triage state also appears as **Resolved**. On `MANUAL` findings, select it to verify the finding as passing (see [Verify a MANUAL Finding as Pass](#verify-a-manual-finding-as-pass)). |
| **Reopened** | Automatic | A finding changed from `PASS` to `FAIL` in a later scan. |

-Resolved and Reopened are not manual selector options.
+**Reopened** is never a manual selector option. **Resolved** appears in the selector only on `MANUAL` findings, where it starts the [Manual Pass verification](#verify-a-manual-finding-as-pass).
These automatic states keep triage tied to the finding UID across scans, even when each scan creates a new finding snapshot.
@@ -93,6 +93,39 @@ Triage notes are visible only to the team in the current organization. Each note
To remove an existing note, clear the note text and save the change.
+## Verify a MANUAL Finding as Pass
+
+
+
+Checks that Prowler cannot judge automatically report `MANUAL` findings. When a team verifies such a control outside Prowler, the triage selector on that finding offers **Resolved**: choosing it records a Manual Pass attestation, and the finding reports an effective `PASS` while keeping the raw `MANUAL` scan result.
+
+
+
+
+
+ Go to **Findings** and filter by status **Manual**.
+
+
+ Expand a Finding Group and click the current status in the **Triage** column of an individual finding.
+
+
+ Select **Resolved**. Prowler opens the triage note modal with a required **Manual pass evidence** field.
+
+
+ Describe how the control was verified, then click **Save**. The evidence supports up to 500 characters.
+
+
+
+
+
+After saving, the finding reports `PASS` in finding tables, finding groups, compliance reports, and scans. While the attestation is active, the triage status is managed automatically and cannot be changed. **View Manual Pass details** shows who verified the finding, the evidence, the attestation time, and its expiration.
+
+
+
+### Attestation Expiration
+
+A Manual Pass attestation is valid for 90 days. It also ends early when a later scan reports a real failure for the finding. In both cases the finding returns to its raw `MANUAL` status for a new review.
+
## Mutelist Behavior
Findings Triage uses Mutelist when a status means the finding should be muted:
@@ -118,7 +151,7 @@ Confirm that the user role has **Manage Scans** permission. Prowler Local Server
### Resolved or Reopened is missing from the selector
-This is expected. Prowler sets **Resolved** and **Reopened** automatically from scan result changes.
+**Reopened** is always automatic. **Resolved** is set automatically from scan result changes and appears as a selector option only on `MANUAL` findings, where it records a [Manual Pass](#verify-a-manual-finding-as-pass). On findings with any other status, this is expected.
### Risk Accepted or False Positive muted a finding
diff --git a/permissions/prowler-additions-policy.json b/permissions/prowler-additions-policy.json
index 13aff68168..25ea46b09d 100644
--- a/permissions/prowler-additions-policy.json
+++ b/permissions/prowler-additions-policy.json
@@ -31,6 +31,8 @@
"ec2:GetInstanceMetadataDefaults",
"ecr:Describe*",
"ecr:GetRegistryScanningConfiguration",
+ "ecr:BatchGetImage",
+ "ecr:GetDownloadUrlForLayer",
"elasticfilesystem:DescribeBackupPolicy",
"glue:GetConnections",
"glue:GetSecurityConfiguration*",
@@ -42,6 +44,7 @@
"lightsail:GetRelationalDatabases",
"macie2:GetMacieSession",
"macie2:GetAutomatedDiscoveryConfiguration",
+ "rolesanywhere:ListProfiles",
"rolesanywhere:ListTagsForResource",
"rolesanywhere:ListTrustAnchors",
"s3:GetAccountPublicAccessBlock",
diff --git a/permissions/templates/azure/bicep/bicepconfig.json b/permissions/templates/azure/bicep/bicepconfig.json
deleted file mode 100644
index 05d5a46af3..0000000000
--- a/permissions/templates/azure/bicep/bicepconfig.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "experimentalFeaturesEnabled": {
- "extensibility": true
- },
- "extensions": {
- "microsoftGraphV1": "br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:1.0.0"
- }
-}
diff --git a/permissions/templates/cloudformation/prowler-scan-role.yml b/permissions/templates/cloudformation/prowler-scan-role.yml
index c9eee71950..d04c8f25d6 100644
--- a/permissions/templates/cloudformation/prowler-scan-role.yml
+++ b/permissions/templates/cloudformation/prowler-scan-role.yml
@@ -203,6 +203,8 @@ Resources:
- "ec2:GetInstanceMetadataDefaults"
- "ecr:Describe*"
- "ecr:GetRegistryScanningConfiguration"
+ - "ecr:BatchGetImage"
+ - "ecr:GetDownloadUrlForLayer"
- "elasticfilesystem:DescribeBackupPolicy"
- "glue:GetConnections"
- "glue:GetSecurityConfiguration*"
@@ -213,6 +215,7 @@ Resources:
- "lightsail:GetRelationalDatabases"
- "macie2:GetMacieSession"
- "macie2:GetAutomatedDiscoveryConfiguration"
+ - "rolesanywhere:ListProfiles"
- "rolesanywhere:ListTagsForResource"
- "rolesanywhere:ListTrustAnchors"
- "s3:GetAccountPublicAccessBlock"
@@ -469,6 +472,8 @@ Resources:
- "ec2:GetInstanceMetadataDefaults"
- "ecr:Describe*"
- "ecr:GetRegistryScanningConfiguration"
+ - "ecr:BatchGetImage"
+ - "ecr:GetDownloadUrlForLayer"
- "elasticfilesystem:DescribeBackupPolicy"
- "glue:GetConnections"
- "glue:GetSecurityConfiguration*"
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index c019e8d087..076bffb52a 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -4,6 +4,43 @@ All notable changes to the **Prowler SDK** are documented in this file.
+## [5.39.1] (Prowler v5.39.1)
+
+### π Fixed
+
+- Bump alibabacloud-tea-openapi to 0.4.6, oci to 2.184.1 and pyopenssl to 26.4.0 so the published wheel installs with cryptography 50.0.0; 5.38.0 declared cryptography 50.0.0 while those packages capped it below 50, so pip could not install it and `pip install prowler` silently fell back to 5.37.1 [(#12477)](https://github.com/prowler-cloud/prowler/pull/12477)
+- Pin zstd to 1.5.7.2; 1.5.7.3 was yanked from PyPI as not thread safe [(#12477)](https://github.com/prowler-cloud/prowler/pull/12477)
+- ECS task-definition checks no longer report PASS when `DescribeTaskDefinition` fails before container evidence is gathered [(#12478)](https://github.com/prowler-cloud/prowler/pull/12478)
+- `ses_identity_not_publicly_accessible` now evaluates every SES identity authorization policy and marks mixed public Allow and Deny statements for manual review [(#12480)](https://github.com/prowler-cloud/prowler/pull/12480)
+
+### π Security
+
+- Trivy from v0.72.0 to v0.73.0 in the container image, fixing HIGH CVE-2026-46600 in the bundled `golang.org/x/net` [(#12445)](https://github.com/prowler-cloud/prowler/pull/12445)
+- Trivy v0.74.0 and Debian util-linux 2.41.5-0+deb13u1 in the SDK container image, patching Go standard library vulnerabilities and CVE-2026-53615 [(#12470)](https://github.com/prowler-cloud/prowler/pull/12470)
+
+---
+
+## [5.39.0] (Prowler v5.39.0)
+
+### π Added
+
+- `batch_job_definition_no_secrets` check for AWS provider, scanning Batch job definition environment variables and command parameters for hardcoded secrets [(#12117)](https://github.com/prowler-cloud/prowler/pull/12117)
+- 7 M365 Entra checks covering CIS Microsoft 365 Foundations Benchmark v7.0.0 password protection, default user permissions, and guest invitation domain restrictions [(#12153)](https://github.com/prowler-cloud/prowler/pull/12153)
+- 7 M365 entra checks covering CIS Microsoft 365 Foundations Benchmark v7.0.0 Conditional Access (5.2.2.x) and idle session timeout controls [(#12154)](https://github.com/prowler-cloud/prowler/pull/12154)
+- `entra_authentication_method_email_otp_disabled`, `entra_authentication_method_authenticator_show_context`, `entra_pim_global_administrator_approval_required`, `entra_pim_privileged_role_administrator_approval_required`, `entra_access_review_guest_users_configured` and `entra_access_review_privileged_roles_configured` checks for M365 provider covering CIS Microsoft 365 Foundations Benchmark v7.0.0 authentication method, PIM approval and access review controls [(#12155)](https://github.com/prowler-cloud/prowler/pull/12155)
+- `awslambda_layer_no_secrets_in_content` check for AWS provider, scanning Lambda layer package content for hardcoded secrets [(#12233)](https://github.com/prowler-cloud/prowler/pull/12233)
+- CMMC 2.0 universal compliance framework (`cmmc_2.0`) with the 149 official requirements from 32 CFR Part 170 β Level 1 (15, 48 CFR 52.204-21), Level 2 (110, NIST SP 800-171 Rev 2) and Level 3 (24, NIST SP 800-172) β with AWS, Azure, GCP, Alibaba Cloud, Oracle Cloud and M365 check mappings and config guardrails [(#12401)](https://github.com/prowler-cloud/prowler/pull/12401)
+
+### π Changed
+
+- GitHub `organization_repository_creation_limited` check now reports low severity for FAIL findings when repository creation is provably limited to private/internal visibility, instead of always reporting high [(#12164)](https://github.com/prowler-cloud/prowler/pull/12164)
+
+### π Security
+
+- HTML report header now HTML-escapes every provider identity field across all 23 providers, closing a stored XSS in the header block (Secur0, CWE-79) that was left unaddressed by the earlier finding-row fix in #12221 [(#12424)](https://github.com/prowler-cloud/prowler/pull/12424)
+
+---
+
## [5.38.0] (Prowler v5.38.0)
### π Added
diff --git a/prowler/changelog.d/awslambda-layer-no-secrets-in-content.added.md b/prowler/changelog.d/awslambda-layer-no-secrets-in-content.added.md
deleted file mode 100644
index 56a192b088..0000000000
--- a/prowler/changelog.d/awslambda-layer-no-secrets-in-content.added.md
+++ /dev/null
@@ -1 +0,0 @@
-`awslambda_layer_no_secrets_in_content` check for AWS provider, scanning Lambda layer package content for hardcoded secrets
diff --git a/prowler/changelog.d/batch-job-definition-no-secrets.added.md b/prowler/changelog.d/batch-job-definition-no-secrets.added.md
deleted file mode 100644
index 797dc06a12..0000000000
--- a/prowler/changelog.d/batch-job-definition-no-secrets.added.md
+++ /dev/null
@@ -1 +0,0 @@
-`batch_job_definition_no_secrets` check for AWS provider, scanning Batch job definition environment variables and command parameters for hardcoded secrets
diff --git a/prowler/changelog.d/cmmc-2.0-universal.added.md b/prowler/changelog.d/cmmc-2.0-universal.added.md
deleted file mode 100644
index 2c57c05c93..0000000000
--- a/prowler/changelog.d/cmmc-2.0-universal.added.md
+++ /dev/null
@@ -1 +0,0 @@
-CMMC 2.0 universal compliance framework (`cmmc_2.0`) with the 149 official requirements from 32 CFR Part 170 β Level 1 (15, 48 CFR 52.204-21), Level 2 (110, NIST SP 800-171 Rev 2) and Level 3 (24, NIST SP 800-172) β with AWS, Azure, GCP, Alibaba Cloud, Oracle Cloud and M365 check mappings and config guardrails
diff --git a/prowler/changelog.d/ecr-repository-image-no-secrets.added.md b/prowler/changelog.d/ecr-repository-image-no-secrets.added.md
new file mode 100644
index 0000000000..eb92d46823
--- /dev/null
+++ b/prowler/changelog.d/ecr-repository-image-no-secrets.added.md
@@ -0,0 +1 @@
+`ecr_repository_image_no_secrets` check for AWS provider, scanning the latest ECR repository image's configuration and filesystem layers for hardcoded secrets
diff --git a/prowler/changelog.d/gcp-wif-provider-attribute-condition.added.md b/prowler/changelog.d/gcp-wif-provider-attribute-condition.added.md
new file mode 100644
index 0000000000..4ab15242e2
--- /dev/null
+++ b/prowler/changelog.d/gcp-wif-provider-attribute-condition.added.md
@@ -0,0 +1 @@
+Add the `iam_workload_identity_pool_provider_attribute_condition` check to flag GCP Workload Identity Federation providers that trust a multi-tenant issuer without an attribute condition restricting which external identities can impersonate federated principals
diff --git a/prowler/changelog.d/github-organization-repository-creation-severity.changed.md b/prowler/changelog.d/github-organization-repository-creation-severity.changed.md
deleted file mode 100644
index 6e8f831447..0000000000
--- a/prowler/changelog.d/github-organization-repository-creation-severity.changed.md
+++ /dev/null
@@ -1 +0,0 @@
-GitHub `organization_repository_creation_limited` check now reports low severity for FAIL findings when repository creation is provably limited to private/internal visibility, instead of always reporting high
diff --git a/prowler/changelog.d/m365-cis7-entra-authn-pim.added.md b/prowler/changelog.d/m365-cis7-entra-authn-pim.added.md
deleted file mode 100644
index ac633fb58d..0000000000
--- a/prowler/changelog.d/m365-cis7-entra-authn-pim.added.md
+++ /dev/null
@@ -1 +0,0 @@
-`entra_authentication_method_email_otp_disabled`, `entra_authentication_method_authenticator_show_context`, `entra_pim_global_administrator_approval_required`, `entra_pim_privileged_role_administrator_approval_required`, `entra_access_review_guest_users_configured` and `entra_access_review_privileged_roles_configured` checks for M365 provider covering CIS Microsoft 365 Foundations Benchmark v7.0.0 authentication method, PIM approval and access review controls
diff --git a/prowler/changelog.d/m365-cis7-entra-conditional-access.added.md b/prowler/changelog.d/m365-cis7-entra-conditional-access.added.md
deleted file mode 100644
index 66856e5e20..0000000000
--- a/prowler/changelog.d/m365-cis7-entra-conditional-access.added.md
+++ /dev/null
@@ -1 +0,0 @@
-7 M365 entra checks covering CIS Microsoft 365 Foundations Benchmark v7.0.0 Conditional Access (5.2.2.x) and idle session timeout controls
diff --git a/prowler/changelog.d/m365-cis7-entra-directory-settings.added.md b/prowler/changelog.d/m365-cis7-entra-directory-settings.added.md
deleted file mode 100644
index 4fc76e535f..0000000000
--- a/prowler/changelog.d/m365-cis7-entra-directory-settings.added.md
+++ /dev/null
@@ -1 +0,0 @@
-7 M365 Entra checks covering CIS Microsoft 365 Foundations Benchmark v7.0.0 password protection, default user permissions, and guest invitation domain restrictions
diff --git a/prowler/changelog.d/rolesanywhere-profile-session-scoping.added.md b/prowler/changelog.d/rolesanywhere-profile-session-scoping.added.md
new file mode 100644
index 0000000000..6f50731aae
--- /dev/null
+++ b/prowler/changelog.d/rolesanywhere-profile-session-scoping.added.md
@@ -0,0 +1 @@
+Add the `rolesanywhere_profile_restricts_session_permissions` check to flag AWS IAM Roles Anywhere profiles that reference an administrative role without scoping down the vended session with a session policy or managed policies
diff --git a/prowler/compliance/cmmc_2.0.json b/prowler/compliance/cmmc_2.0.json
index a3d7b67ea6..7c5783af13 100644
--- a/prowler/compliance/cmmc_2.0.json
+++ b/prowler/compliance/cmmc_2.0.json
@@ -265,20 +265,6 @@
"Operator": "lte",
"Value": 90,
"Provider": "aws"
- },
- {
- "Check": "iam_user_accesskey_unused",
- "ConfigKey": "max_unused_access_keys_days",
- "Operator": "lte",
- "Value": 45,
- "Provider": "aws"
- },
- {
- "Check": "iam_user_console_access_unused",
- "ConfigKey": "max_console_access_days",
- "Operator": "lte",
- "Value": 45,
- "Provider": "aws"
}
]
},
@@ -1064,20 +1050,6 @@
"Operator": "lte",
"Value": 90,
"Provider": "aws"
- },
- {
- "Check": "iam_user_accesskey_unused",
- "ConfigKey": "max_unused_access_keys_days",
- "Operator": "lte",
- "Value": 45,
- "Provider": "aws"
- },
- {
- "Check": "iam_user_console_access_unused",
- "ConfigKey": "max_console_access_days",
- "Operator": "lte",
- "Value": 45,
- "Provider": "aws"
}
]
},
@@ -2008,23 +1980,7 @@
"alibabacloud": [],
"oraclecloud": [],
"m365": []
- },
- "config_requirements": [
- {
- "Check": "guardduty_is_enabled",
- "ConfigKey": "mute_non_default_regions",
- "Operator": "eq",
- "Value": false,
- "Provider": "aws"
- },
- {
- "Check": "securityhub_enabled",
- "ConfigKey": "mute_non_default_regions",
- "Operator": "eq",
- "Value": false,
- "Provider": "aws"
- }
- ]
+ }
},
{
"id": "AU.L2-3.3.5",
diff --git a/prowler/config/config.py b/prowler/config/config.py
index e2732a90f1..6d2e71132f 100644
--- a/prowler/config/config.py
+++ b/prowler/config/config.py
@@ -49,7 +49,7 @@ class _MutableTimestamp:
timestamp = _MutableTimestamp(datetime.today())
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
-prowler_version = "5.39.0"
+prowler_version = "5.40.0"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://raw.githubusercontent.com/prowler-cloud/prowler/dc7d2d5aeb92fdf12e8604f42ef6472cd3e8e889/docs/img/prowler-logo-black.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
diff --git a/prowler/lib/outputs/html/html.py b/prowler/lib/outputs/html/html.py
index 1dcbfb5416..60428bede7 100644
--- a/prowler/lib/outputs/html/html.py
+++ b/prowler/lib/outputs/html/html.py
@@ -463,6 +463,11 @@ class HTML(Output):
audited_regions = "All Regions"
else:
audited_regions = ", ".join(provider.identity.audited_regions)
+ account = escape(str(provider.identity.account))
+ profile = escape(str(profile))
+ audited_regions = escape(str(audited_regions))
+ user_id = escape(str(provider.identity.user_id))
+ identity_arn = escape(str(provider.identity.identity_arn))
return f"""