Compare commits

..
52 Commits
Author SHA1 Message Date
Pedro Martín bffc8844c1 fix(api): pin Redoc to 2.5.1 so the docs render (#13054) 2026-10-15 12:04:24 +02:00
Prowler Botandprowler-bot 54354d71cd chore(api): Update prowler dependency to v5.45 for release 5.45.0 (#13051)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-10-15 11:33:43 +02:00
Prowler Botandprowler-bot 9b56a3b657 chore(changelog): v5.45.0 (#13049)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-10-15 11:12:38 +02:00
Pedro Martín f70b648cde chore(changelog): v5.45.0 highlights (#13048) 2026-10-15 11:05:49 +02:00
César Arroba 1f8ca695e8 fix(api): recycle gunicorn workers to cap memory growth (#13047) 2026-10-15 09:36:33 +02:00
Pepe Fagoagaandcoderabbitai[bot] d8a4e98850 docs: new listings for the AWS Marketplace (#12948)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-10-14 23:08:32 +02:00
Pedro Martín d9b53b2637 fix(iac): block SSRF in repository connection test (#12960) 2026-10-14 16:25:13 +02:00
Pedro Martín a4d07678b4 fix(outputs): neutralise injection in CSV and HTML reports (#12963) 2026-10-14 13:55:43 +02:00
Alejandro Bailo 64c19b3db6 fix(ui): show Registry subscription and setup prompts (#13038) 2026-10-14 13:51:08 +02:00
Rajesh NadimintiandRubén De la Torre Vico 286ae6c7cf feat(azure): add aiservices network ACL and prompt shield checks (#12997)
Co-authored-by: Rubén De la Torre Vico <98956809+puchy22@users.noreply.github.com>
2026-10-14 10:55:27 +02:00
Pedro Martín 8e67f0251c fix(ui): point Slack partial list to inviting the app (#13036) 2026-10-14 10:32:12 +02:00
Rajesh NadimintiandRubén De la Torre Vico 3c50cec509 feat(azure): add aiservices private endpoint, outbound, identity and logging checks (#12926)
Co-authored-by: Rubén De la Torre Vico <98956809+puchy22@users.noreply.github.com>
2026-10-14 10:26:16 +02:00
Pedro Martín fbb22834f7 fix(container): bump PowerShell to 7.5.12 in SDK and API (#13037) 2026-10-14 10:09:18 +02:00
Pedro Martín 165b62a730 ci(sdk): run Huawei Cloud tests in sdk-tests (#13035) 2026-10-14 09:22:30 +02:00
Rajesh Nadimintiandpedrooot 8b814b7643 feat(azure): add aiservices service with network, local auth and CMK checks (#12925)
Co-authored-by: pedrooot <pedromarting3@gmail.com>
2026-10-14 09:03:25 +02:00
Pepe Fagoaga 22468c1443 ci(trufflehog): scan full history and pin scanner image (#13032) 2026-10-14 07:53:25 +02:00
a36991f60e feat(m365): add entra_cross_tenant_access_default_outbound_restricted check (#12991)
Co-authored-by: kinomakino <3662588+kinomakino@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
2026-10-13 16:25:42 +02:00
dd63fa960b feat(m365): add entra_policy_admin_self_service_password_reset_disabled check (#12990)
Co-authored-by: kinomakino <3662588+kinomakino@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
2026-10-13 15:59:20 +02:00
80c4113879 feat(m365): add entra_dynamic_group_member_of_rule_not_used check (#13003)
Co-authored-by: kinomakino <3662588+kinomakino@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
2026-10-13 15:59:01 +02:00
c438858dbd feat(m365): add entra_app_registration_redirect_uris_secure check (#12993)
Co-authored-by: kinomakino <3662588+kinomakino@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
2026-10-13 15:42:21 +02:00
f606a3c195 feat(m365): add entra_policy_msol_powershell_blocked check (#13004)
Co-authored-by: kinomakino <3662588+kinomakino@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
2026-10-13 15:22:01 +02:00
Pedro Martín c599a9cf1d fix(container): suppress Trivy HTTP/2 SETTINGS DoS CVE (#13028) 2026-10-13 11:55:48 +02:00
Pedro Martín 61d2cc55c4 fix(container): patch Trivy and MCP base image CVEs (#13009) 2026-10-09 18:17:48 +02:00
Pedro Martín 859a846147 fix(api): scope user memberships and roles to tenant (#12965) 2026-10-09 13:04:11 +02:00
Alejandro Bailo d8bdc8553f fix(ui): show registry provider logos and capitalize registry labels (#12985) 2026-10-08 18:16:08 +02:00
Pedro Martínandalejandrobailo f44ea4b6fa fix(api): require inline kubeconfig credentials (#12952)
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
2026-10-08 18:15:10 +02:00
César Arroba 27c4113d29 fix(aws): query Organizations in the audited partition (#12988) 2026-10-08 15:57:58 +02:00
Alejandro Bailo cf688e3fa1 fix(ui): bump Next.js to 16.3.8 to patch the image optimization SSRF advisory (#12987) 2026-10-08 15:17:59 +02:00
César Arroba 92216f1597 ci(release): auto-create minor release branch and lift freeze on publish
Create the minor release branch automatically in the release preparation workflow, check versions before creating it, match the version literally, and lift the release freeze when the release is published.
2026-10-08 12:47:38 +02:00
Alejandro Bailo b5a709664d fix(ui): show only the chosen registry credential variant (#12966) 2026-10-07 16:35:20 +02:00
Alejandro Bailo 9d8060adc5 test(ui): stabilize invite-teammate roles-unavailable test (#12973) 2026-10-07 16:14:23 +02:00
StylusFrost f1e822f7df chore(trivy): suppress @modelcontextprotocol/sdk CVE-2026-104850 (#12967) 2026-10-07 12:27:44 +02:00
StylusFrost 22bc834c09 chore(grype): suppress zlib CVE-2026-85091 until base images ship the fix (#12970) 2026-10-07 12:16:04 +02:00
StylusFrost 3f893176ba fix(ui): bump sharp to 0.35.5 to patch the librsvg advisory (#12968) 2026-10-07 11:48:19 +02:00
César Arroba d38e04dd3e chore(github): move code ownership from platform to engineering (#12958) 2026-10-06 10:42:52 +02:00
Pedro Martín 7ccdf7c0e6 fix(deps): patch pymongo CVEs and ignore proxy-addr (#12955) 2026-10-06 10:34:58 +02:00
Alejandro Bailo bee60c5471 fix(ui): stop reporting client-aborted RSC streams to Sentry (#12956) 2026-10-06 10:23:07 +02:00
Alejandro Bailo f850199645 fix(ui): pin proxy-addr to 2.0.8 to patch the IP spoofing advisory (#12957) 2026-10-06 10:10:17 +02:00
Pedro Martín 0321a4f167 fix(deps): patch Django, urllib3 and dulwich CVEs (#12953) 2026-10-06 08:48:54 +02:00
Alejandro Bailo 383a9bf903 fix(ui): bump Next.js to 16.3.6 to patch the next/og RCE advisory (#12922) 2026-10-01 11:54:44 +02:00
Alejandro Bailo 4605d9a770 feat(ui): invite a teammate from the AWS connect step (#12917) 2026-10-01 09:50:10 +02:00
César Arroba f0da33f451 revert(api): release providers blocked by scans whose worker died (#12915) 2026-09-30 13:00:50 +02:00
Alejandro Bailo a44a725507 fix(ui): retry the first-run redirect until the add-provider wizard opens (#12914) 2026-09-30 12:34:52 +02:00
César Arroba b8ca30400b fix(api): stop sending personal data to Sentry (#12912) 2026-09-30 12:28:42 +02:00
César Arroba a006525e78 fix(api): release providers blocked by scans whose worker died (#12899) 2026-09-30 11:09:21 +02:00
Pedro Martín ed510e217d chore(deps): bump pyjwt to 2.14.0 for osv-scanner (#12911) 2026-09-30 10:21:11 +02:00
Alejandro Bailo 04511f339e test(ui): stabilize attack-paths refit integration test (#12896) 2026-09-29 18:42:19 +02:00
Pedro Martín f418b32c81 fix(oci): use home region for identity bootstrap (#12865) 2026-09-29 17:50:54 +02:00
Pedro Martín 65fb146e76 chore(trivy): suppress fast-uri CVE-2026-84292 (#12907) 2026-09-29 17:04:46 +02:00
Prowler Botandprowler-bot 5ea363d582 chore(release): Bump versions to v5.45.0 (#12905)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-09-29 16:31:16 +02:00
Prowler Botandprowler-bot 3ec379a75a chore(changelog): v5.44.0 (#12900)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-09-29 13:32:41 +02:00
Pedro Martín ea020ed46e chore(changelog): v5.44.0 highlights (#12897) 2026-09-29 13:32:13 +02:00
291 changed files with 12003 additions and 622 deletions

No files matched your search

+1 -1
View File
@@ -174,7 +174,7 @@ SENTRY_RELEASE=local
# REO_DEV_CLIENT_ID=
#### Prowler release version ####
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.44.1
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.45.0
# Social login credentials
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
+7 -7
View File
@@ -19,10 +19,10 @@
# AI
/mcp_server/ @prowler-cloud/engineering
# Platform
/.github/ @prowler-cloud/platform
/Makefile @prowler-cloud/platform
/kubernetes/ @prowler-cloud/platform
**/Dockerfile* @prowler-cloud/platform
**/docker-compose*.yml @prowler-cloud/platform
**/docker-compose*.yaml @prowler-cloud/platform
# CI and infrastructure
/.github/ @prowler-cloud/engineering
/Makefile @prowler-cloud/engineering
/kubernetes/ @prowler-cloud/engineering
**/Dockerfile* @prowler-cloud/engineering
**/docker-compose*.yml @prowler-cloud/engineering
**/docker-compose*.yaml @prowler-cloud/engineering
+7 -4
View File
@@ -38,13 +38,16 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# PRs only need the diff range; push to master/release walks the new range from event.before.
# 50 is enough headroom for the longest realistic PR/push chain without paying for a full clone.
fetch-depth: 50
# Full history: a shallow clone cuts the PR's ancestry, git can no longer exclude commits
# already on the base branch, and TruffleHog scans the cut-off commit as a root (the whole
# tree as added). PRs that merge master in hit this and fail on pre-existing content.
fetch-depth: 0
persist-credentials: false
- name: Scan diff for secrets with TruffleHog
# Action auto-injects --since-commit/--branch from event payload; passing them in extra_args produces duplicate flags.
uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3.95.3
uses: trufflesecurity/trufflehog@e76dff8a7e57d6df0c60ad8cbf5d5cba505a8fc9 # v3.99.2
with:
# The action runs the `latest` image unless told otherwise: pin the scanner to the action's release.
version: 3.99.2@sha256:47a84bc18a0d04a165498bbbd3bacfd84176661cbc91f8e0f85467f6a771e99a
extra_args: --results=verified,unknown
+27 -2
View File
@@ -84,6 +84,8 @@ jobs:
fi
- name: Checkout release branch
env:
GH_TOKEN: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
run: |
echo "Checking out branch $BRANCH_NAME for release $PROWLER_VERSION..."
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
@@ -92,8 +94,32 @@ jobs:
elif git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then
echo "Branch $BRANCH_NAME exists remotely, checking out..."
git checkout -b "$BRANCH_NAME" "origin/$BRANCH_NAME"
elif [ "$PATCH_VERSION" -eq 0 ]; then
# Only cut the branch once the compiled changelogs are on master
MASTER_SHA=$(git rev-parse origin/master)
VERSION_REGEX="${PROWLER_VERSION//./\\.}"
FOUND_CHANGELOG=false
for changelog in prowler/CHANGELOG.md api/CHANGELOG.md ui/CHANGELOG.md mcp_server/CHANGELOG.md; do
if git show "origin/master:$changelog" 2>/dev/null | grep -qE "^## \[.*\(Prowler v?${VERSION_REGEX}\)"; then
FOUND_CHANGELOG=true
break
fi
done
if [ "$FOUND_CHANGELOG" != "true" ]; then
echo "ERROR: No changelog entry for Prowler $PROWLER_VERSION on master. Merge the compiled changelogs PR first."
exit 1
fi
# Check the SDK version on master before creating the remote branch
if ! git show "origin/master:pyproject.toml" | grep -qE "^version = \"${VERSION_REGEX}\"$" \
|| ! git show "origin/master:prowler/config/config.py" | grep -qE "^prowler_version = \"${VERSION_REGEX}\"$"; then
echo "ERROR: pyproject.toml or prowler/config/config.py on master is not at version $PROWLER_VERSION. Fix master before creating $BRANCH_NAME."
exit 1
fi
echo "Branch $BRANCH_NAME does not exist, creating it from master ($MASTER_SHA)..."
gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" -f ref="refs/heads/${BRANCH_NAME}" -f sha="${MASTER_SHA}"
git checkout -b "$BRANCH_NAME" "$MASTER_SHA"
else
echo "ERROR: Branch $BRANCH_NAME does not exist. For minor releases (X.Y.0), create it manually first. For patch releases (X.Y.Z), the branch should already exist."
echo "ERROR: Branch $BRANCH_NAME does not exist. For patch releases (X.Y.Z), the branch should already exist."
exit 1
fi
@@ -390,4 +416,3 @@ jobs:
if: always()
run: |
rm -f prowler_changelog.md api_changelog.md ui_changelog.md mcp_changelog.md combined_changelog.md
+27
View File
@@ -0,0 +1,27 @@
name: 'Tools: Release Freeze Lift'
run-name: 'Lift release freeze after Prowler ${{ github.event.release.tag_name }}'
on:
release:
types:
- 'published'
permissions: {}
jobs:
release-freeze-lift:
if: github.repository == 'prowler-cloud/prowler' && !github.event.release.prerelease
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Disable release freeze
env:
GH_TOKEN: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
run: |
gh variable set RELEASE_FREEZE --body false --repo "${GITHUB_REPOSITORY}"
+24
View File
@@ -640,6 +640,30 @@ jobs:
flags: prowler-py${{ matrix.python-version }}-e2enetworks
files: ./e2enetworks_coverage.xml
# Huawei Cloud Provider
- name: Check if Huawei Cloud files changed
if: steps.check-changes.outputs.any_changed == 'true'
id: changed-huaweicloud
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
with:
files: |
./prowler/**/huaweicloud/**
./tests/**/huaweicloud/**
./uv.lock
- name: Run Huawei Cloud tests
if: steps.changed-huaweicloud.outputs.any_changed == 'true'
run: uv run pytest -n auto --cov=./prowler/providers/huaweicloud --cov-report=xml:huaweicloud_coverage.xml tests/providers/huaweicloud
- name: Upload Huawei Cloud coverage to Codecov
if: steps.changed-huaweicloud.outputs.any_changed == 'true'
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: prowler-py${{ matrix.python-version }}-huaweicloud
files: ./huaweicloud_coverage.xml
# External Provider (dynamic loading)
- name: Check if External Provider files changed
if: steps.check-changes.outputs.any_changed == 'true'
+66
View File
@@ -51,6 +51,65 @@ ignore:
package:
name: golang.org/x/crypto
version: v0.55.0
# Go 1.27.1 and golang.org/x/net v0.59.0 in Trivy 0.75.0, the latest release. Fixed in
# Go 1.27.2 and x/net 0.60.0, which only Trivy main has. Almost every entry is an HTTP,
# HTTP/2 or TLS server path (or Windows-only), and Trivy runs here only as a client. The
# two client-side ones are a DoS from a hostile HTTP/2 peer (CVE-2026-78669) and an
# unframed CONNECT body sent through a proxy (CVE-2026-56866); Trivy's only peer is the
# TLS-authenticated database registry and it never sends CONNECT with a body. Pinned to
# the embedded versions. Remove with the Trivy exception by 2026-11-15.
- vulnerability: CVE-2026-56857
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-56866
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-78659
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-78660
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-78663
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-78669
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-94439
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-94440
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-97031
package:
name: stdlib
version: go1.27.1
- vulnerability: CVE-2026-78659
package:
name: golang.org/x/net
version: v0.59.0
- vulnerability: CVE-2026-78660
package:
name: golang.org/x/net
version: v0.59.0
- vulnerability: CVE-2026-78663
package:
name: golang.org/x/net
version: v0.59.0
- vulnerability: CVE-2026-78669
package:
name: golang.org/x/net
version: v0.59.0
- vulnerability: CVE-2026-56852
package:
name: golang.org/x/text
@@ -67,6 +126,13 @@ ignore:
package:
name: Microsoft.Bcl.Memory
# Documented in .trivyignore.yaml: ships inside ExchangeOnlineManagement
# 3.10.1, the latest gallery release. Re-check on the next EXO release.
- vulnerability: CVE-2026-84008
package:
name: System.Security.Cryptography.Pkcs
version: 10.0.0
# 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
+55 -50
View File
@@ -108,6 +108,23 @@ vulnerabilities:
purls:
- "pkg:nuget/System.Security.Cryptography.Xml"
expired_at: 2027-01-31
# Same manifest, pinned to 8.0.4: PowerShell's own copy is also flagged by these CVEs.
- id: CVE-2026-96222
purls:
- "pkg:nuget/System.Security.Cryptography.Xml@8.0.4"
expired_at: 2027-01-31
- id: CVE-2026-96958
purls:
- "pkg:nuget/System.Security.Cryptography.Xml@8.0.4"
expired_at: 2027-01-31
- id: CVE-2026-96959
purls:
- "pkg:nuget/System.Security.Cryptography.Xml@8.0.4"
expired_at: 2027-01-31
- id: CVE-2026-96960
purls:
- "pkg:nuget/System.Security.Cryptography.Xml@8.0.4"
expired_at: 2027-01-31
- id: CVE-2026-13676
purls:
- "pkg:npm/fast-uri"
@@ -181,61 +198,49 @@ vulnerabilities:
- "pkg:npm/%40modelcontextprotocol%2Fsdk@1.27.1"
expired_at: 2027-01-31
# CVE-2026-84304 is a DoS in grpc-go <= 1.83.0: a peer fragments a gRPC stream into
# millions of tiny HTTP/2 DATA frames until the receiver runs out of heap. Fixed in
# 1.83.1 (published 2026-09-01). Trivy 0.74.0, the latest published release and the
# version the images ship, pins 1.82.1 as an indirect dependency:
# https://github.com/aquasecurity/trivy/blob/v0.74.0/go.mod
# Upstream bump still open: https://github.com/aquasecurity/trivy/pull/11176
# Trivy only speaks gRPC in client/server mode (`trivy server`, `--server`). Prowler
# invokes it exclusively as `trivy image` and `trivy fs` on a local path, so no gRPC
# listener or connection ever exists in the image and the affected path is not
# reachable. Remove this temporary suppression as soon as a Trivy release pins
# grpc >= 1.83.1.
- id: CVE-2026-84304
# CVE-2026-78667 and CVE-2026-97031 are DoS bugs in the Go standard library: net/http
# burns CPU parsing a Range header with a large number of small ranges in FileServer,
# ServeContent and ServeFile, and crypto/tls lets a client exhaust server memory with
# multiple ECH outer extension references. Both are server-side code paths. Fixed in
# Go 1.26.9 and 1.27.2 (released 2026-10-08). Trivy 0.75.0, the latest published
# release and the version the images ship, is built with go1.27.1, and Trivy main
# still declares `go 1.27.0`:
# https://github.com/aquasecurity/trivy/blob/v0.75.0/go.mod
# Prowler invokes Trivy exclusively as `trivy fs` and `trivy image` on a local path,
# never `trivy server`, so no HTTP or TLS listener ever exists in the image; its only
# TLS use is as a client pulling the vulnerability database. The purl is pinned to the
# stdlib version so the entry retires itself on the next Trivy bump. Remove this
# temporary suppression as soon as a Trivy release is built with Go >= 1.27.2.
- id: CVE-2026-78667
purls:
- "pkg:golang/google.golang.org/grpc"
expired_at: 2026-10-15
- "pkg:golang/stdlib@v1.27.1"
expired_at: 2026-11-15
- id: CVE-2026-97031
purls:
- "pkg:golang/stdlib@v1.27.1"
expired_at: 2026-11-15
# CVE-2026-84445 is a DoS in grpc-go servers built with `xds.NewGRPCServer()`: a request
# carrying neither `:authority` nor `Host` reaches the xDS routing interceptor, which
# indexes an empty slice of authorities and panics. The per-RPC goroutine does not
# recover, so the whole server process dies. Fixed in 1.82.2 and 1.83.2 (published
# 2026-09-08). Trivy 0.74.0, the latest published release and the version the images
# ship, pins 1.82.1 as an indirect dependency:
# https://github.com/aquasecurity/trivy/blob/v0.74.0/go.mod
# Trivy main already carries 1.83.2, but no published release includes it yet.
# The reachability argument is the one made for CVE-2026-84304 above, only narrower:
# this panic needs an xDS-managed gRPC server. Prowler invokes Trivy exclusively as
# `trivy image` and `trivy fs` on a local path, never `trivy server`, so the image runs
# no gRPC server at all, xDS or otherwise. Remove this temporary suppression as soon as
# a Trivy release pins grpc >= 1.83.2.
# https://github.com/advisories/GHSA-2v4p-qf9q-27wj
- id: CVE-2026-84445
# CVE-2026-78669 is an HTTP/2 DoS in the same Trivy 0.75.0 binary (stdlib 1.27.1 and
# golang.org/x/net 0.59.0): a malicious peer burns CPU with many small SETTINGS frames.
# Unlike the two above it also hits clients, but Trivy's only HTTP/2 peer here is the
# TLS-authenticated registry serving the vulnerability database, and the worst case is
# a slow scan. Fixed in Go 1.27.2 and x/net 0.60.0, which Trivy main already pulls in
# but no release ships yet. Same retirement rule as above.
- id: CVE-2026-78669
purls:
- "pkg:golang/google.golang.org/grpc@v1.82.1"
expired_at: 2026-10-15
- "pkg:golang/stdlib@v1.27.1"
- "pkg:golang/golang.org/x/net@v0.59.0"
expired_at: 2026-11-15
# CVE-2026-56855 and CVE-2026-78662 are DoS deadlocks in x/crypto/ssh: a malicious peer
# can flood or misuse channel messages (RFC 4254) to block the whole connection.
# Fixed in golang.org/x/crypto v0.56.0 (published 2026-09-02). Trivy 0.74.0, the latest
# published release and the version the images ship, still pins v0.55.0, and Trivy main
# has not bumped it either:
# https://github.com/aquasecurity/trivy/blob/v0.74.0/go.mod
# x/crypto/ssh is pulled in transitively through go-git's ssh transport, the same
# dependency chain as the CVE-2026-71556 entry above. Prowler invokes Trivy only with
# `fs` on an existing local path or with `image`; it never asks Trivy to clone over SSH
# or to run `trivy server`, so no SSH connection -- as client or server -- ever exists in
# the image and the affected code path is not reachable. Remove this temporary
# suppression as soon as a fixed Trivy release is available.
- id: CVE-2026-56855
# Unlike the manifest entries above, this DLL ships in the image:
# ExchangeOnlineManagement/3.10.1/netCore/System.Security.Cryptography.Pkcs.dll. It is a
# DoS in CMS/PKCS parsing; Prowler only feeds it the tenant's own certificate. 3.10.1 is
# the latest gallery release, so only Microsoft can update it. Re-check on the next EXO
# release.
- id: CVE-2026-84008
purls:
- "pkg:golang/golang.org/x/crypto@v0.55.0"
expired_at: 2026-10-15
- id: CVE-2026-78662
purls:
- "pkg:golang/golang.org/x/crypto@v0.55.0"
expired_at: 2026-10-15
- "pkg:nuget/System.Security.Cryptography.Pkcs@10.0.0"
expired_at: 2026-12-15
- id: CVE-2026-56852
purls:
+7 -7
View File
@@ -1,24 +1,24 @@
FROM python:3.12.13-slim-trixie@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de AS build
FROM python:3.12.15-slim-trixie@sha256:05cda9777409a9c3ffddd94a4c476b79f0769a0b4857f0c7ed9226b6800b0d6f AS build
LABEL maintainer="https://github.com/prowler-cloud/prowler"
LABEL org.opencontainers.image.source="https://github.com/prowler-cloud/prowler"
ARG POWERSHELL_VERSION=7.5.11
ARG POWERSHELL_VERSION=7.5.12
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.74.0
ARG TRIVY_VERSION=0.75.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=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a
ARG TRIVY_SHA256_ARM64=b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5
ARG POWERSHELL_SHA256_AMD64=82a8b13d92b0f3ae48e56cf2f3f7961679371736ca90145ca71617c2913ba9d8
ARG POWERSHELL_SHA256_ARM64=830ebda118c731ece3fa7e6b7e8573a21346387cbbca5b2f5e3b9bfe24f96672
ARG TRIVY_SHA256_AMD64=c6e65abddb348e25f10549df887045629cf28cc72453cd1c63acb717316b3f3f
ARG TRIVY_SHA256_ARM64=a1ee9f6ffb7d112b64ff726a2a0717c21175c1114361391f4a132956751a13b3
ARG POWERSHELL_SHA256_AMD64=db85ae1f45005ffd20e456890585167257151fa99406ccd7ab344f7cf3a766fd
ARG POWERSHELL_SHA256_ARM64=29eb382cd510e4bd246902650ff6cf9f3b56f959d1bd0be75e0a1e425177c831
ARG ZIZMOR_SHA256_AMD64=a8000f3c683319a523d3b20df0e75457ba591f049cfcbfa98966631b56733c03
ARG ZIZMOR_SHA256_ARM64=d66e37ef8a375fb07939c630ebf9709a6e0f20242bdc3faf672a7ed97e0b768d
+49
View File
@@ -4,6 +4,55 @@ All notable changes to the **Prowler API** are documented in this file.
<!-- changelog: release notes start -->
## [1.46.0] (Prowler v5.45.0)
### 🐞 Fixed
- OCI provider secrets keep the region as home region for credential validation and scans, instead of always using us-ashburn-1 [(#12865)](https://github.com/prowler-cloud/prowler/pull/12865)
- Gunicorn workers are now recycled after 10000 requests (plus up to 1000 of jitter) to cap memory growth, configurable with `GUNICORN_MAX_REQUESTS` and `GUNICORN_MAX_REQUESTS_JITTER` [(#13047)](https://github.com/prowler-cloud/prowler/pull/13047)
### 🔐 Security
- `pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9 [(#12911)](https://github.com/prowler-cloud/prowler/pull/12911)
- Sentry error events no longer include user identity, IP addresses, cookies, headers or request bodies, which could contain personal data or provider credentials [(#12912)](https://github.com/prowler-cloud/prowler/pull/12912)
- Kubernetes kubeconfig validation accepts only inline credentials and known cluster fields, rejecting file-path fields (`tokenFile`, `client-certificate`, `client-key`, `certificate-authority`), command-based authentication (`exec`, `cmd-path`), `auth-provider` directives, proxy URLs and unknown user or cluster keys; stored kubeconfigs are re-validated before each scan and connection test [(#12952)](https://github.com/prowler-cloud/prowler/pull/12952)
- `django` from 5.1.15 to 5.2.17, patching CVE-2026-15307 [(#12953)](https://github.com/prowler-cloud/prowler/pull/12953)
- `dulwich` from 1.2.5 to 1.2.9, patching GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf, GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx [(#12953)](https://github.com/prowler-cloud/prowler/pull/12953)
- `urllib3` from 2.7.0 to 2.8.0, patching CVE-2026-97687 and CVE-2026-97689 [(#12953)](https://github.com/prowler-cloud/prowler/pull/12953)
- `pymongo` from 4.15.1 to 4.18.2, patching CVE-2026-96748 and CVE-2026-96749 [(#12955)](https://github.com/prowler-cloud/prowler/pull/12955)
- `GET /api/v1/users/{id}` no longer exposes another user's memberships, roles or tenant names from tenants other than the one in the request token [(#12965)](https://github.com/prowler-cloud/prowler/pull/12965)
- Python base image from 3.12.13 to 3.12.15 and Trivy from 0.74.0 to 0.75.0 in the API container image, patching CVE-2026-19445, CVE-2026-19553 and the grpc-go and x/crypto CVEs bundled in Trivy [(#13009)](https://github.com/prowler-cloud/prowler/pull/13009)
- PowerShell from 7.5.11 to 7.5.12 in the API container image, bundling .NET runtime 9.0.21 [(#13037)](https://github.com/prowler-cloud/prowler/pull/13037)
---
## [1.45.0] (Prowler v5.44.0)
### 🚀 Added
- Scan output uploads and downloads can now target S3-compatible object storage such as MinIO directly via `DJANGO_OUTPUT_S3_AWS_ENDPOINT_URL`, instead of relying on process-wide AWS environment variables that also hijacked unrelated AWS API calls [(#12871)](https://github.com/prowler-cloud/prowler/pull/12871)
### 🔄 Changed
- Unify how every endpoint resolves a provider latest completed scan, so overlapping scans no longer make findings, compliance and mute rules read from different scans [(#12858)](https://github.com/prowler-cloud/prowler/pull/12858)
### 🐞 Fixed
- Celery loggers are now declared explicitly in `custom_logging.py` so fatal worker errors are no longer silenced by `disable_existing_loggers=True`. All long-running services in `docker-compose.yml` now have `restart: unless-stopped` so containers recover automatically after unexpected crashes. [(#12465)](https://github.com/prowler-cloud/prowler/pull/12465)
- Scan report downloads from an S3 bucket with default SSE-KMS encryption no longer fail with an `InvalidArgument` error: when `DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION` is set, presigned download URLs are signed with AWS Signature Version 4 for that region [(#12746)](https://github.com/prowler-cloud/prowler/pull/12746)
- Adds a periodic sweep that drops orphaned Attack Paths temp Neo4j scan databases left behind when a worker or Neo4j crashes mid-scan, before they accumulate unbounded [(#12832)](https://github.com/prowler-cloud/prowler/pull/12832)
- Providers whose most recent completed scan has no `completed_at` timestamp are no longer missing from every endpoint that reports a provider's latest scan, which now falls back to scan creation order instead of skipping the provider [(#12858)](https://github.com/prowler-cloud/prowler/pull/12858)
- Resources no longer keep a stale failed findings count forever when a scoped or imported scan for the same provider completes after a full scan, which used to make the full scan skip its own cleanup [(#12858)](https://github.com/prowler-cloud/prowler/pull/12858)
- `POST /api/v1/scans` again returns the new scan id in the response `task_args`, which had been empty since the scan broker publish moved to transaction commit [(#12878)](https://github.com/prowler-cloud/prowler/pull/12878)
- API key authentication no longer locks the key row on every request and now throttles `last_used_at` updates to once per 60 seconds, preventing a hot key from serializing all its requests onto a single locked row [(#12882)](https://github.com/prowler-cloud/prowler/pull/12882)
- Provider deletion no longer fails when the provider has Attack Paths scans recorded on a sink that is no longer configured, such as Neptune after moving back to Neo4j [(#12894)](https://github.com/prowler-cloud/prowler/pull/12894)
### 🔐 Security
- `DELETE /api/v1/tasks/{id}` requires the permission of the operation that queued the task and rejects provider deletions, and `GET /api/v1/tasks` hides tasks of providers outside the visibility of the role [(#12893)](https://github.com/prowler-cloud/prowler/pull/12893)
---
## [1.44.0] (Prowler v5.43.0)
### 🐞 Fixed
+7 -7
View File
@@ -1,23 +1,23 @@
FROM python:3.12.13-slim-trixie@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de AS build
FROM python:3.12.15-slim-trixie@sha256:05cda9777409a9c3ffddd94a4c476b79f0769a0b4857f0c7ed9226b6800b0d6f AS build
LABEL maintainer="https://github.com/prowler-cloud/api"
ARG POWERSHELL_VERSION=7.5.11
ARG POWERSHELL_VERSION=7.5.12
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.74.0
ARG TRIVY_VERSION=0.75.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=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a
ARG TRIVY_SHA256_ARM64=b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5
ARG POWERSHELL_SHA256_AMD64=82a8b13d92b0f3ae48e56cf2f3f7961679371736ca90145ca71617c2913ba9d8
ARG POWERSHELL_SHA256_ARM64=830ebda118c731ece3fa7e6b7e8573a21346387cbbca5b2f5e3b9bfe24f96672
ARG TRIVY_SHA256_AMD64=c6e65abddb348e25f10549df887045629cf28cc72453cd1c63acb717316b3f3f
ARG TRIVY_SHA256_ARM64=a1ee9f6ffb7d112b64ff726a2a0717c21175c1114361391f4a132956751a13b3
ARG POWERSHELL_SHA256_AMD64=db85ae1f45005ffd20e456890585167257151fa99406ccd7ab344f7cf3a766fd
ARG POWERSHELL_SHA256_ARM64=29eb382cd510e4bd246902650ff6cf9f3b56f959d1bd0be75e0a1e425177c831
ARG ZIZMOR_SHA256_AMD64=a8000f3c683319a523d3b20df0e75457ba591f049cfcbfa98966631b56733c03
ARG ZIZMOR_SHA256_ARM64=d66e37ef8a375fb07939c630ebf9709a6e0f20242bdc3faf672a7ed97e0b768d
@@ -1 +0,0 @@
API key authentication no longer locks the key row on every request and now throttles `last_used_at` updates to once per 60 seconds, preventing a hot key from serializing all its requests onto a single locked row
@@ -1 +0,0 @@
Adds a periodic sweep that drops orphaned Attack Paths temp Neo4j scan databases left behind when a worker or Neo4j crashes mid-scan, before they accumulate unbounded
@@ -1 +0,0 @@
`django` from 5.1.15 to 5.2.17, patching CVE-2026-15307
@@ -1 +0,0 @@
`dulwich` from 1.2.5 to 1.2.9, patching GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf, GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx
@@ -1 +0,0 @@
Resources no longer keep a stale failed findings count forever when a scoped or imported scan for the same provider completes after a full scan, which used to make the full scan skip its own cleanup
@@ -1 +0,0 @@
Providers whose most recent completed scan has no `completed_at` timestamp are no longer missing from every endpoint that reports a provider's latest scan, which now falls back to scan creation order instead of skipping the provider
@@ -1 +0,0 @@
Unify how every endpoint resolves a provider latest completed scan, so overlapping scans no longer make findings, compliance and mute rules read from different scans
@@ -1 +0,0 @@
OCI provider secrets keep the region as home region for credential validation and scans, instead of always using us-ashburn-1
@@ -1 +0,0 @@
Provider deletion no longer fails when the provider has Attack Paths scans recorded on a sink that is no longer configured, such as Neptune after moving back to Neo4j
-1
View File
@@ -1 +0,0 @@
`pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9
@@ -1 +0,0 @@
`pymongo` from 4.15.1 to 4.18.2, patching CVE-2026-96748 and CVE-2026-96749
@@ -1 +0,0 @@
Scan output uploads and downloads can now target S3-compatible object storage such as MinIO directly via `DJANGO_OUTPUT_S3_AWS_ENDPOINT_URL`, instead of relying on process-wide AWS environment variables that also hijacked unrelated AWS API calls
@@ -1 +0,0 @@
Scan report downloads from an S3 bucket with default SSE-KMS encryption no longer fail with an `InvalidArgument` error: when `DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION` is set, presigned download URLs are signed with AWS Signature Version 4 for that region
@@ -1 +0,0 @@
`POST /api/v1/scans` again returns the new scan id in the response `task_args`, which had been empty since the scan broker publish moved to transaction commit
@@ -1 +0,0 @@
`DELETE /api/v1/tasks/{id}` requires the permission of the operation that queued the task and rejects provider deletions, and `GET /api/v1/tasks` hides tasks of providers outside the visibility of the role
@@ -1 +0,0 @@
`urllib3` from 2.7.0 to 2.8.0, patching CVE-2026-97687 and CVE-2026-97689
@@ -1 +0,0 @@
Celery loggers are now declared explicitly in `custom_logging.py` so fatal worker errors are no longer silenced by `disable_existing_loggers=True`. All long-running services in `docker-compose.yml` now have `restart: unless-stopped` so containers recover automatically after unexpected crashes.
+2 -2
View File
@@ -45,7 +45,7 @@ dependencies = [
"gunicorn==26.0.0",
"uvloop==0.22.1",
"lxml==6.1.0",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.44",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.45",
"psycopg2-binary==2.9.9",
"pytest-celery[redis] (==1.3.0)",
"sentry-sdk[django] (==2.56.0)",
@@ -71,7 +71,7 @@ name = "prowler-api"
package-mode = false
# Needed for the SDK compatibility
requires-python = ">=3.11,<3.13"
version = "1.45.1"
version = "1.46.0"
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
# target-version tracks this project's lowest supported Python.
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
version: 1.45.1
version: 1.46.0
description: |-
Prowler API specification.
+15
View File
@@ -30,6 +30,21 @@ def test_initialize_sentry_uses_configured_dsn():
assert mock_init.call_args.kwargs["before_send"] is sentry_settings.before_send
def test_initialize_sentry_sends_no_personal_data():
with (
patch.object(
sentry_settings.env,
"str",
return_value="https://fake-public-key@sentry.example.invalid/1",
),
patch.object(sentry_settings.sentry_sdk, "init") as mock_init,
):
sentry_settings.initialize_sentry()
assert mock_init.call_args.kwargs["send_default_pii"] is False
assert mock_init.call_args.kwargs["max_request_body_size"] == "never"
def _make_log_record(msg, level=logging.ERROR, name="test", args=None):
"""Build a real LogRecord so getMessage() works like in production."""
record = logging.LogRecord(
@@ -5,6 +5,8 @@ from api.v1.serializer_utils.integrations import (
)
from api.v1.serializer_utils.providers import ProviderSecretField
from api.v1.serializers import (
KUBERNETES_KUBECONFIG_INVALID_ERROR,
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
ImageProviderSecret,
IntegrationSerializer,
IntegrationUpdateSerializer,
@@ -340,6 +342,278 @@ current-context: test-context
assert not serializer.is_valid()
assert "kubeconfig_content" in serializer.errors
@staticmethod
def _kubeconfig(user_extra="", cluster_extra=""):
return f"""
apiVersion: v1
kind: Config
clusters:
- name: test-cluster
cluster:
server: https://kubernetes.example.test
{cluster_extra}
users:
- name: test-user
user:
token: test-token
{user_extra}
contexts:
- name: test-context
context:
cluster: test-cluster
user: test-user
current-context: test-context
"""
@staticmethod
def _assert_rejected_without_echo(
kubeconfig_content, leaked_value, expected_message
):
serializer = KubernetesProviderSecret(
data={"kubeconfig_content": kubeconfig_content}
)
assert serializer.is_valid() is False
assert serializer.errors["kubeconfig_content"] == [expected_message]
assert leaked_value not in str(serializer.errors)
def test_inline_token_with_certificate_authority_data_is_accepted(self):
kubeconfig_content = self._kubeconfig(
cluster_extra=" certificate-authority-data: dGVzdA=="
)
serializer = KubernetesProviderSecret(
data={"kubeconfig_content": kubeconfig_content}
)
assert serializer.is_valid()
def test_kubeconfig_with_token_file_is_rejected(self):
self._assert_rejected_without_echo(
self._kubeconfig(user_extra=" tokenFile: /etc/passwd"),
"/etc/passwd",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_kubeconfig_with_client_certificate_file_is_rejected(self):
self._assert_rejected_without_echo(
self._kubeconfig(user_extra=" client-certificate: /etc/passwd"),
"/etc/passwd",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_kubeconfig_with_client_key_file_is_rejected(self):
self._assert_rejected_without_echo(
self._kubeconfig(user_extra=" client-key: /etc/passwd"),
"/etc/passwd",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_kubeconfig_with_certificate_authority_file_is_rejected(self):
self._assert_rejected_without_echo(
self._kubeconfig(cluster_extra=" certificate-authority: /etc/passwd"),
"/etc/passwd",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_kubeconfig_with_cluster_proxy_url_is_rejected(self):
self._assert_rejected_without_echo(
self._kubeconfig(cluster_extra=" proxy-url: http://proxy.evil.test"),
"proxy.evil.test",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
@pytest.mark.parametrize("provider_name", ["gcp", "oidc", "azure"])
def test_kubeconfig_with_any_auth_provider_is_rejected(self, provider_name):
self._assert_rejected_without_echo(
self._kubeconfig(
user_extra=(
" auth-provider:\n"
f" name: {provider_name}\n"
" config:\n"
" idp-issuer-url: https://idp.evil.test"
)
),
"idp.evil.test",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_kubeconfig_with_unknown_user_key_is_rejected(self):
self._assert_rejected_without_echo(
self._kubeconfig(user_extra=" bogus: /etc/passwd"),
"/etc/passwd",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_kubeconfig_with_unknown_cluster_key_is_rejected(self):
self._assert_rejected_without_echo(
self._kubeconfig(cluster_extra=" bogus: /etc/passwd"),
"/etc/passwd",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_kubeconfig_with_allowlisted_cluster_keys_is_accepted(self):
serializer = KubernetesProviderSecret(
data={
"kubeconfig_content": self._kubeconfig(
cluster_extra=(
" certificate-authority-data: dGVzdA==\n"
" insecure-skip-tls-verify: false\n"
" tls-server-name: kubernetes.example.test\n"
" disable-compression: true\n"
" extensions: []"
)
)
}
)
assert serializer.is_valid(), serializer.errors
def test_kubeconfig_with_non_list_clusters_is_rejected(self):
kubeconfig_content = """
apiVersion: v1
kind: Config
clusters:
name: /etc/passwd
"""
self._assert_rejected_without_echo(
kubeconfig_content, "/etc/passwd", KUBERNETES_KUBECONFIG_INVALID_ERROR
)
def test_kubeconfig_with_non_dict_cluster_entry_is_rejected(self):
kubeconfig_content = """
apiVersion: v1
kind: Config
clusters:
- /etc/passwd
"""
self._assert_rejected_without_echo(
kubeconfig_content, "/etc/passwd", KUBERNETES_KUBECONFIG_INVALID_ERROR
)
def test_kubeconfig_with_non_dict_cluster_value_is_rejected(self):
kubeconfig_content = """
apiVersion: v1
kind: Config
clusters:
- name: test-cluster
cluster: /etc/passwd
"""
self._assert_rejected_without_echo(
kubeconfig_content, "/etc/passwd", KUBERNETES_KUBECONFIG_INVALID_ERROR
)
@pytest.mark.parametrize(
"user_extra",
[
" token: test-token",
" username: test-user\n password: test-password",
" client-certificate-data: dGVzdA==\n client-key-data: dGVzdA==",
" token: test-token\n as: other-user",
" token: test-token\n as-uid: 1234",
" token: test-token\n as-groups:\n - group-a",
" token: test-token\n as-user-extra:\n scopes:\n - read",
],
ids=[
"token",
"username-password",
"client-cert-data",
"as",
"as-uid",
"as-groups",
"as-user-extra",
],
)
def test_kubeconfig_with_allowlisted_user_keys_is_accepted(self, user_extra):
kubeconfig_content = f"""
apiVersion: v1
kind: Config
clusters:
- name: test-cluster
cluster:
server: https://kubernetes.example.test
certificate-authority-data: dGVzdA==
users:
- name: test-user
user:
{user_extra}
contexts:
- name: test-context
context:
cluster: test-cluster
user: test-user
current-context: test-context
"""
serializer = KubernetesProviderSecret(
data={"kubeconfig_content": kubeconfig_content}
)
assert serializer.is_valid()
@pytest.mark.parametrize(
"kubeconfig_content",
[
"""
apiVersion: v1
kind: Config
clusters:
- name: test-cluster
cluster:
server: https://kubernetes.example.test
users:
- name: good-user
user:
token: test-token
- name: bad-user
user:
tokenFile: /etc/passwd
""",
"""
apiVersion: v1
kind: Config
clusters:
- name: good-cluster
cluster:
server: https://kubernetes.example.test
- name: bad-cluster
cluster:
server: https://kubernetes.example.test
certificate-authority: /etc/passwd
users:
- name: test-user
user:
token: test-token
""",
],
ids=["second-user", "second-cluster"],
)
def test_kubeconfig_with_bad_second_entry_is_rejected(self, kubeconfig_content):
self._assert_rejected_without_echo(
kubeconfig_content,
"/etc/passwd",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_kubeconfig_with_indented_document_is_rejected(self):
kubeconfig_content = (
" wrapper:\n"
" users:\n"
" - name: u\n"
" user:\n"
" tokenFile: /etc/passwd\n"
" clusters:\n"
" - name: c\n"
" cluster:\n"
" server: https://example.test\n"
)
self._assert_rejected_without_echo(
kubeconfig_content,
"/etc/passwd",
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
)
def test_malformed_kubeconfig_is_rejected(self):
serializer = KubernetesProviderSecret(
data={"kubeconfig_content": "apiVersion: ["}
+96 -4
View File
@@ -14,6 +14,7 @@ from api.utils import (
return_prowler_provider,
validate_invitation,
)
from api.v1.serializers import KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR
from prowler.providers.alibabacloud.alibabacloud_provider import AlibabacloudProvider
from prowler.providers.aws.aws_provider import AwsProvider
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHubConnection
@@ -35,6 +36,34 @@ from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvid
from prowler.providers.vercel.vercel_provider import VercelProvider
from rest_framework.exceptions import NotFound, ValidationError
VALID_KUBECONFIG = """\
apiVersion: v1
kind: Config
clusters:
- name: test-cluster
cluster:
server: https://example.invalid:6443
certificate-authority-data: Y2E=
users:
- name: test-user
user:
token: test-token
contexts:
- name: test-context
context:
cluster: test-cluster
user: test-user
current-context: test-context
"""
TOKEN_FILE_KUBECONFIG = VALID_KUBECONFIG.replace(
"token: test-token", "tokenFile: /etc/passwd"
)
EXEC_KUBECONFIG = VALID_KUBECONFIG.replace(
"token: test-token", "exec:\n command: test-command"
)
class TestMergeDicts:
def test_simple_merge(self):
@@ -288,6 +317,40 @@ class TestProwlerProviderConnectionTest:
raise_on_exception=False,
)
@patch("api.utils.return_prowler_provider")
def test_kubernetes_connection_test_rejects_invalid_kubeconfig(
self, mock_return_prowler_provider
):
provider = MagicMock()
provider.provider = Provider.ProviderChoices.KUBERNETES.value
provider.uid = "provider_uid"
provider.secret.secret = {"kubeconfig_content": TOKEN_FILE_KUBECONFIG}
connection = prowler_provider_connection_test(provider)
assert connection.is_connected is False
assert (
str(connection.error) == KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR
)
mock_return_prowler_provider.return_value.test_connection.assert_not_called()
@patch("api.utils.return_prowler_provider")
def test_kubernetes_connection_test_with_valid_kubeconfig(
self, mock_return_prowler_provider
):
provider = MagicMock()
provider.provider = Provider.ProviderChoices.KUBERNETES.value
provider.uid = "provider_uid"
provider.secret.secret = {"kubeconfig_content": VALID_KUBECONFIG}
prowler_provider_connection_test(provider)
mock_return_prowler_provider.return_value.test_connection.assert_called_once_with(
kubeconfig_content=VALID_KUBECONFIG,
provider_id="provider_uid",
raise_on_exception=False,
)
@pytest.mark.django_db
@patch("api.utils.return_prowler_provider")
def test_prowler_provider_connection_test_without_secret(
@@ -405,10 +468,6 @@ class TestGetProwlerProviderKwargs:
Provider.ProviderChoices.GOOGLEWORKSPACE.value,
{},
),
(
Provider.ProviderChoices.KUBERNETES.value,
{"context": "provider_uid"},
),
(
Provider.ProviderChoices.M365.value,
{},
@@ -459,6 +518,39 @@ class TestGetProwlerProviderKwargs:
expected_result = {**secret_dict, **expected_extra_kwargs}
assert result == expected_result
def test_get_prowler_provider_kwargs_kubernetes_valid_kubeconfig(self):
provider = MagicMock()
provider.provider = Provider.ProviderChoices.KUBERNETES.value
provider.uid = "provider_uid"
provider.secret.secret = {"kubeconfig_content": VALID_KUBECONFIG}
assert get_prowler_provider_kwargs(provider) == {
"kubeconfig_content": VALID_KUBECONFIG,
"context": "provider_uid",
}
@pytest.mark.parametrize(
"secret",
[
{"kubeconfig_content": TOKEN_FILE_KUBECONFIG},
{"kubeconfig_content": EXEC_KUBECONFIG},
{"kubeconfig_content": "[]"},
{"key": "value"},
],
)
def test_get_prowler_provider_kwargs_kubernetes_rejects_invalid_kubeconfig(
self, secret
):
provider = MagicMock()
provider.provider = Provider.ProviderChoices.KUBERNETES.value
provider.uid = "provider_uid"
provider.secret.secret = secret
with pytest.raises(ValidationError) as exc_info:
get_prowler_provider_kwargs(provider)
assert "/etc/passwd" not in str(exc_info.value)
def test_get_prowler_provider_kwargs_oraclecloud_maps_region_to_home_region(
self,
):
+140
View File
@@ -63,6 +63,7 @@ from api.models import (
from api.rbac.permissions import TASK_REVOKE_PERMISSIONS
from api.rls import Tenant
from api.uuid_utils import datetime_to_uuid7
from api.v1.serializers import MembershipIncludeSerializer
from api.v1.views import (
ComplianceOverviewViewSet,
CustomSAMLLoginView,
@@ -133,6 +134,145 @@ class TestUserViewSet:
assert response.status_code == status.HTTP_200_OK
assert response.json()["data"]["attributes"]["email"] == create_test_user.email
@staticmethod
def _shared_user_in_two_tenants(tenant_a, tenant_b):
shared_user = User.objects.create_user(
name="shared_user",
email=f"shared-user-{uuid4()}@prowler.com",
password="TmpPass123@",
)
Membership.objects.create(user=shared_user, tenant=tenant_a)
Membership.objects.create(user=shared_user, tenant=tenant_b)
role_a = Role.objects.create(name="role-a", tenant=tenant_a)
role_b = Role.objects.create(name="role-b", tenant=tenant_b)
UserRoleRelationship.objects.create(
user=shared_user, role=role_a, tenant=tenant_a
)
UserRoleRelationship.objects.create(
user=shared_user, role=role_b, tenant=tenant_b
)
return shared_user, role_a, role_b
@staticmethod
def _user_in_two_tenants_with_unique_roles(tenant_a, tenant_b):
suffix = uuid4()
user = User.objects.create_user(
name="scaled_user",
email=f"scaled-user-{suffix}@prowler.com",
password="TmpPass123@",
)
for tenant in (tenant_a, tenant_b):
Membership.objects.create(user=user, tenant=tenant)
role = Role.objects.create(name=f"role-{tenant.id}-{suffix}", tenant=tenant)
UserRoleRelationship.objects.create(user=user, role=role, tenant=tenant)
return user
def test_users_list_query_count_does_not_grow_with_user_count(
self, authenticated_client, tenants_fixture
):
tenant_a, tenant_b, _ = tenants_fixture
self._user_in_two_tenants_with_unique_roles(tenant_a, tenant_b)
params = {"include": "memberships,roles"}
with CaptureQueriesContext(connection) as baseline:
response = authenticated_client.get(reverse("user-list"), params)
assert response.status_code == status.HTTP_200_OK
baseline_users = len(response.json()["data"])
for _ in range(3):
self._user_in_two_tenants_with_unique_roles(tenant_a, tenant_b)
with CaptureQueriesContext(connection) as scaled:
response = authenticated_client.get(reverse("user-list"), params)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()["data"]) > baseline_users
assert len(scaled.captured_queries) == len(baseline.captured_queries), (
"Tenant-scoped relations are re-querying per user: "
f"{len(baseline.captured_queries)} queries for {baseline_users} users "
f"vs {len(scaled.captured_queries)} for "
f"{len(response.json()['data'])}. The prefetch is being discarded."
)
def test_users_retrieve_scopes_relations_to_active_tenant(
self, authenticated_client, tenants_fixture
):
tenant_a, tenant_b, _ = tenants_fixture
shared_user, role_a, role_b = self._shared_user_in_two_tenants(
tenant_a, tenant_b
)
response = authenticated_client.get(
reverse("user-detail", kwargs={"pk": shared_user.id}),
{"include": "memberships,roles,memberships.tenant"},
)
assert response.status_code == status.HTTP_200_OK
payload = response.json()
relationships = payload["data"]["relationships"]
assert len(relationships["memberships"]["data"]) == 1
assert [item["id"] for item in relationships["roles"]["data"]] == [
str(role_a.id)
]
included = payload["included"]
included_memberships = [i for i in included if i["type"] == "memberships"]
assert len(included_memberships) == 1
assert included_memberships[0]["relationships"]["tenant"]["data"]["id"] == str(
tenant_a.id
)
assert {i["id"] for i in included if i["type"] == "roles"} == {str(role_a.id)}
assert {
i["attributes"]["name"] for i in included if i["type"] == "tenants"
} == {tenant_a.name}
body = response.content.decode()
assert tenant_b.name not in body
assert str(tenant_b.id) not in body
assert str(role_b.id) not in body
def test_users_me_keeps_own_memberships_and_scopes_roles(
self, authenticated_client, create_test_user, tenants_fixture
):
tenant_a, tenant_b, _ = tenants_fixture
response = authenticated_client.get(
reverse("user-me"), {"include": "memberships,roles,memberships.tenant"}
)
assert response.status_code == status.HTTP_200_OK
payload = response.json()
relationships = payload["data"]["relationships"]
assert len(relationships["memberships"]["data"]) == 2
assert {
i["attributes"]["name"]
for i in payload["included"]
if i["type"] == "tenants"
} == {tenant_a.name, tenant_b.name}
active_roles = create_test_user.roles.filter(tenant_id=tenant_a.id)
assert {i["id"] for i in relationships["roles"]["data"]} == {
str(role.id) for role in active_roles
}
def test_membership_include_serializer_hides_tenant_of_non_member(
self, create_test_user, tenants_fixture
):
tenant_a, _, isolated_tenant = tenants_fixture
outsider = User.objects.create_user(
name="outsider",
email=f"outsider-{uuid4()}@prowler.com",
password="TmpPass123@",
)
foreign_membership = Membership.objects.create(
user=outsider, tenant=isolated_tenant
)
own_membership = Membership.objects.get(user=create_test_user, tenant=tenant_a)
request = SimpleNamespace(user=create_test_user, tenant_id=str(tenant_a.id))
serializer = MembershipIncludeSerializer(context={"request": request})
assert serializer.get_tenant(own_membership) == tenant_a
assert serializer.get_tenant(foreign_membership) is None
def test_users_create(self, client):
valid_user_payload = {
"name": "test",
+19 -1
View File
@@ -17,7 +17,7 @@ from api.models import (
Role,
UserRoleRelationship,
)
from api.v1.serializers import FindingMetadataSerializer
from api.v1.serializers import FindingMetadataSerializer, KubernetesProviderSecret
from django.contrib.postgres.aggregates import ArrayAgg
from django.db import transaction
from django.db.models import Subquery
@@ -201,6 +201,13 @@ def return_prowler_provider(
return prowler_provider
def _validate_kubernetes_secret(secret: dict) -> None:
"""Re-validate a stored kubeconfig at use time; errors never echo its content."""
KubernetesProviderSecret(
data={"kubeconfig_content": secret.get("kubeconfig_content")}
).is_valid(raise_exception=True)
def get_prowler_provider_kwargs(
provider: Provider, mutelist_processor: Processor | None = None
) -> dict:
@@ -225,6 +232,7 @@ def get_prowler_provider_kwargs(
"project_ids": [provider.uid],
}
elif provider.provider == Provider.ProviderChoices.KUBERNETES.value:
_validate_kubernetes_secret(prowler_provider_kwargs)
prowler_provider_kwargs = {**prowler_provider_kwargs, "context": provider.uid}
elif provider.provider == Provider.ProviderChoices.GITHUB.value:
if provider.uid:
@@ -392,6 +400,16 @@ def prowler_provider_connection_test(provider: Provider) -> Connection:
except Provider.secret.RelatedObjectDoesNotExist as secret_error:
return Connection(is_connected=False, error=secret_error)
if provider.provider == Provider.ProviderChoices.KUBERNETES.value:
# A rejected stored kubeconfig is a failed connection, not a task error.
try:
_validate_kubernetes_secret(prowler_provider_kwargs)
except ValidationError as validation_error:
return Connection(
is_connected=False,
error=Exception(validation_error.detail["kubeconfig_content"][0]),
)
# For IaC provider, construct the kwargs properly for test_connection
if provider.provider == Provider.ProviderChoices.IAC.value:
# Don't pass repository_url from secret, use scan_repository_url with the UID
@@ -214,7 +214,8 @@ from rest_framework_json_api import serializers
"kubeconfig_content": {
"type": "string",
"description": "The content of the Kubernetes kubeconfig file, encoded as a string. "
"Kubeconfig command-based authentication is not supported in Prowler Cloud for security reasons.",
"Only inline credentials are supported: token, username/password, or client-certificate-data with client-key-data for users, and certificate-authority-data for clusters. "
"File-path and auth-provider kubeconfig fields are not supported.",
}
},
"required": ["kubeconfig_content"],
+109 -34
View File
@@ -377,27 +377,48 @@ class UserSerializer(BaseModelSerializerV1):
def _can_view_relationships(self, instance) -> bool:
"""Allow self to view own relationships. Require manage_account to view others."""
role = self.context.get("role")
return self._is_self(instance) or bool(role and role.manage_account)
def _is_self(self, instance) -> bool:
request = self.context.get("request")
is_self = bool(
return bool(
request
and getattr(request, "user", None)
and getattr(instance, "id", None) == request.user.id
)
return is_self or (role and role.manage_account)
def _active_tenant_id(self):
return getattr(self.context.get("request"), "tenant_id", None)
@staticmethod
def _in_tenant(related_manager, tenant_id):
"""Filter a prefetched related manager in memory.
``.filter()`` clones the queryset without its result cache, so it would
discard the viewset's prefetch and re-query once per serialized user.
"""
return [
item
for item in related_manager.all()
if str(item.tenant_id) == str(tenant_id)
]
def get_roles(self, instance):
return (
instance.roles.all()
if self._can_view_relationships(instance)
else Role.objects.none()
)
tenant_id = self._active_tenant_id()
if not tenant_id or not self._can_view_relationships(instance):
return Role.objects.none()
return self._in_tenant(instance.roles, tenant_id)
def get_memberships(self, instance):
return (
instance.memberships.all()
if self._can_view_relationships(instance)
else Membership.objects.none()
)
if not self._can_view_relationships(instance):
return Membership.objects.none()
# Own memberships span tenants on purpose: the tenant switcher reads them.
if self._is_self(instance):
return instance.memberships.all()
tenant_id = self._active_tenant_id()
if not tenant_id:
return Membership.objects.none()
return self._in_tenant(instance.memberships, tenant_id)
class UserMeSerializer(UserSerializer):
@@ -731,11 +752,32 @@ class MembershipIncludeSerializer(serializers.ModelSerializer):
included_serializers = {"tenant": "api.v1.serializers.TenantIncludeSerializer"}
_REQUESTER_TENANTS_ATTR = "_prowler_requester_tenants"
def _requester_tenants(self):
"""Tenants the requester belongs to, resolved once per request.
Memoised on the request because DRF builds a serializer instance per
included object, so a per-instance cache would still query per membership.
"""
request = self.context.get("request")
user = getattr(request, "user", None)
if user is None:
return {}
tenants = getattr(request, self._REQUESTER_TENANTS_ATTR, None)
if tenants is None:
tenants = {
tenant.id: tenant
for tenant in Tenant.objects.using(MainRouter.admin_db).filter(
membership__user_id=user.id
)
}
setattr(request, self._REQUESTER_TENANTS_ATTR, tenants)
return tenants
def get_tenant(self, instance):
try:
return Tenant.objects.using(MainRouter.admin_db).get(id=instance.tenant_id)
except Tenant.DoesNotExist:
return None
"""Resolve the tenant only when the requester is a member of it."""
return self._requester_tenants().get(instance.tenant_id)
# Provider Groups
@@ -1659,14 +1701,43 @@ class FindingMetadataSerializer(BaseSerializerV1):
# Provider secrets
KUBERNETES_KUBECONFIG_UNSUPPORTED_COMMAND_AUTH_ERROR = (
"Kubernetes kubeconfig command-based authentication is not supported in "
"Prowler Cloud for security reasons."
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR = (
"Only inline Kubernetes credentials are supported. "
"For user authentication use token, username/password, or client-certificate-data with client-key-data; "
"clusters may use certificate-authority-data. "
"File-path fields (tokenFile, client-certificate, client-key, certificate-authority), "
"command-based authentication (exec, cmd-path), auth-provider directives, and proxy URLs are not supported."
)
KUBERNETES_KUBECONFIG_INVALID_ERROR = "Invalid Kubernetes kubeconfig content."
def kubeconfig_contains_unsupported_command_auth(kubeconfig: dict) -> bool:
KUBECONFIG_ALLOWED_USER_KEYS = frozenset(
{
"token",
"username",
"password",
"client-certificate-data",
"client-key-data",
"as",
"as-uid",
"as-groups",
"as-user-extra",
}
)
KUBECONFIG_ALLOWED_CLUSTER_KEYS = frozenset(
{
"server",
"certificate-authority-data",
"insecure-skip-tls-verify",
"tls-server-name",
"disable-compression",
"extensions",
}
)
def kubeconfig_is_inline_only_credentials(kubeconfig: dict) -> bool:
"""Accept only inline credentials; reject file-reference and auth-provider fields."""
users = kubeconfig.get("users", [])
if not isinstance(users, list):
raise ValidationError(KUBERNETES_KUBECONFIG_INVALID_ERROR)
@@ -1679,21 +1750,25 @@ def kubeconfig_contains_unsupported_command_auth(kubeconfig: dict) -> bool:
if not isinstance(user, dict):
raise ValidationError(KUBERNETES_KUBECONFIG_INVALID_ERROR)
if "exec" in user:
return True
if any(key not in KUBECONFIG_ALLOWED_USER_KEYS for key in user):
return False
auth_provider = user.get("auth-provider", {})
if not isinstance(auth_provider, dict):
continue
clusters = kubeconfig.get("clusters", [])
if not isinstance(clusters, list):
raise ValidationError(KUBERNETES_KUBECONFIG_INVALID_ERROR)
auth_provider_config = auth_provider.get("config", {})
if not isinstance(auth_provider_config, dict):
continue
for cluster_entry in clusters:
if not isinstance(cluster_entry, dict):
raise ValidationError(KUBERNETES_KUBECONFIG_INVALID_ERROR)
if "cmd-path" in auth_provider_config:
return True
cluster = cluster_entry.get("cluster", {})
if not isinstance(cluster, dict):
raise ValidationError(KUBERNETES_KUBECONFIG_INVALID_ERROR)
return False
if any(key not in KUBECONFIG_ALLOWED_CLUSTER_KEYS for key in cluster):
return False
return True
class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
@@ -1876,7 +1951,7 @@ class MongoDBAtlasProviderSecret(serializers.Serializer):
class KubernetesProviderSecret(serializers.Serializer):
kubeconfig_content = serializers.CharField()
kubeconfig_content = serializers.CharField(trim_whitespace=False)
def validate_kubeconfig_content(self, kubeconfig_content):
try:
@@ -1889,9 +1964,9 @@ class KubernetesProviderSecret(serializers.Serializer):
if not isinstance(kubeconfig, dict):
raise serializers.ValidationError(KUBERNETES_KUBECONFIG_INVALID_ERROR)
if kubeconfig_contains_unsupported_command_auth(kubeconfig):
if not kubeconfig_is_inline_only_credentials(kubeconfig):
raise serializers.ValidationError(
KUBERNETES_KUBECONFIG_UNSUPPORTED_COMMAND_AUTH_ERROR
KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR
)
return kubeconfig_content
+2
View File
@@ -130,6 +130,8 @@ REST_FRAMEWORK = {
SPECTACULAR_SETTINGS = {
"SERVE_INCLUDE_SCHEMA": False,
"COMPONENT_SPLIT_REQUEST": True,
# redoc@latest is 3.x, whose standalone bundle fails as a classic script
"REDOC_DIST": "https://cdn.jsdelivr.net/npm/redoc@2.5.1",
"PREPROCESSING_HOOKS": [
"drf_spectacular_jsonapi.hooks.fix_nested_path_parameters",
],
+4
View File
@@ -53,6 +53,10 @@ preload_app = not DEBUG
# that may take longer, such as complex API operations.
timeout = env.int("GUNICORN_TIMEOUT", default=120)
# Recycle workers after N requests (+ jitter) to cap memory growth. GUNICORN_MAX_REQUESTS=0 disables.
max_requests = env.int("GUNICORN_MAX_REQUESTS", default=10000)
max_requests_jitter = env.int("GUNICORN_MAX_REQUESTS_JITTER", default=1000)
# Logging
logconfig_dict = DJANGO_LOGGERS
gunicorn_logger = logging.getLogger(BackendLogger.GUNICORN)
+3 -3
View File
@@ -193,10 +193,10 @@ def initialize_sentry():
sentry_sdk.init(
dsn=sentry_dsn,
# Add data like request headers and IP for users,
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
before_send=before_send,
send_default_pii=True,
# No user identity, IPs, cookies, headers or request bodies: bodies carry emails and provider details.
send_default_pii=False,
max_request_body_size="never",
traces_sample_rate=env.float("DJANGO_SENTRY_TRACES_SAMPLE_RATE", default=0.02),
_experiments={
# Set continuous_profiling_auto_start to True
+39 -3
View File
@@ -3,7 +3,8 @@ from datetime import UTC, datetime
from unittest.mock import MagicMock, patch
import pytest
from api.models import Integration, LighthouseConfiguration, Provider
from api.models import Integration, LighthouseConfiguration, Provider, ProviderSecret
from api.v1.serializers import KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR
from tasks.jobs.connection import (
check_integration_connection,
check_lighthouse_connection,
@@ -65,17 +66,52 @@ def test_check_provider_connection_exception(
mock_provider_connection_test.return_value = MagicMock()
mock_provider_connection_test.return_value.is_connected = False
mock_provider_connection_test.return_value.error = Exception()
mock_provider_connection_test.return_value.error = Exception(
"Unable to assume role"
)
result = check_provider_connection(provider_id="provider_id")
assert result["connected"] is False
assert result["error"] is not None
assert result["error"] == "Unable to assume role"
mock_provider_instance.save.assert_called_once()
assert mock_provider_instance.connected is False
@pytest.mark.django_db
def test_check_provider_connection_with_stored_non_inline_kubeconfig(
kubernetes_provider,
):
"""A kubeconfig stored before the inline-only rule marks the provider as disconnected."""
kubernetes_provider.connected = True
kubernetes_provider.save()
ProviderSecret.objects.create(
tenant_id=kubernetes_provider.tenant_id,
provider=kubernetes_provider,
secret_type=ProviderSecret.TypeChoices.STATIC,
secret={
"kubeconfig_content": (
"apiVersion: v1\n"
"kind: Config\n"
"users:\n"
"- name: test-user\n"
" user:\n"
" tokenFile: /etc/passwd\n"
)
},
)
result = check_provider_connection(provider_id=str(kubernetes_provider.id))
kubernetes_provider.refresh_from_db()
assert result == {
"connected": False,
"error": KUBERNETES_KUBECONFIG_NON_INLINE_CREDENTIALS_ERROR,
}
assert kubernetes_provider.connected is False
@pytest.mark.parametrize(
"lighthouse_data",
[
Generated
+20 -5
View File
@@ -1228,6 +1228,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/32/b3/8ec1268082f4d20cc8bf723a1a8e6b9e330bcc338a4dbcee9c7737e9dc1c/azure_mgmt_authorization-4.0.0-py3-none-any.whl", hash = "sha256:d8feeb3842e6ddf1a370963ca4f61fb6edc124e8997b807dd025bc9b2379cd1a", size = 1072620, upload-time = "2023-07-25T04:47:49.26Z" },
]
[[package]]
name = "azure-mgmt-cognitiveservices"
version = "14.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-mgmt-core" },
{ name = "msrest" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fa/8e/9fcfdd507413a2536c5458b40942705ea8d74fe4e17f05fd1c32bb0225a9/azure_mgmt_cognitiveservices-14.1.0.tar.gz", hash = "sha256:915191374d0adb443863c20aaea2c9f3b9d558a849ac2b78f152249262fdcaf8", size = 184444, upload-time = "2025-10-24T07:28:14.229Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d3/25/ed6bbc01a03991255dd22aa41091360d225f1be20c88710af0f4a6c27459/azure_mgmt_cognitiveservices-14.1.0-py3-none-any.whl", hash = "sha256:3c0eecc00d183842a11d754f4f5c3328ff86f2d6c0a2b81b28dd7e073680258e", size = 290096, upload-time = "2025-10-24T07:28:15.766Z" },
]
[[package]]
name = "azure-mgmt-compute"
version = "34.0.0"
@@ -4214,7 +4228,7 @@ wheels = [
[package.optional-dependencies]
broker = [
{ name = "pymsalruntime" },
{ name = "pymsalruntime", marker = "sys_platform == 'win32'" },
]
[[package]]
@@ -4847,8 +4861,8 @@ wheels = [
[[package]]
name = "prowler"
version = "5.44.0"
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=v5.44#60b936005cc109c611ad8b7f7c41cadb586fb4b6" }
version = "5.45.0"
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=v5.45#9b56a3b657ef72c456a50ae9c9a0b044ce81dd5c" }
dependencies = [
{ name = "alibabacloud-actiontrail20200706" },
{ name = "alibabacloud-credentials" },
@@ -4869,6 +4883,7 @@ dependencies = [
{ name = "azure-mgmt-apimanagement" },
{ name = "azure-mgmt-applicationinsights" },
{ name = "azure-mgmt-authorization" },
{ name = "azure-mgmt-cognitiveservices" },
{ name = "azure-mgmt-compute" },
{ name = "azure-mgmt-containerregistry" },
{ name = "azure-mgmt-containerservice" },
@@ -4951,7 +4966,7 @@ dependencies = [
[[package]]
name = "prowler-api"
version = "1.45.1"
version = "1.46.0"
source = { virtual = "." }
dependencies = [
{ name = "cartography" },
@@ -5051,7 +5066,7 @@ requires-dist = [
{ name = "matplotlib", specifier = "==3.10.8" },
{ name = "neo4j", specifier = "==6.1.0" },
{ name = "openai", specifier = "==1.109.1" },
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=v5.44" },
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=v5.45" },
{ name = "psycopg2-binary", specifier = "==2.9.9" },
{ name = "pytest-celery", extras = ["redis"], specifier = "==1.3.0" },
{ name = "reportlab", specifier = "==4.4.10" },
+127
View File
@@ -4,6 +4,133 @@ description: "New features and improvements in each Prowler release"
rss: true
---
<Update label="v5.45.0" description="October 15, 2026">
### 🤖 Azure — AI Services Coverage
A new Azure AI Services service audits the Azure AI and Azure OpenAI accounts of each subscription, with nine checks across network exposure, authentication, encryption, identity, logging and content safety, all contributed by @rajeshnadiminti. Thank you!
- `aiservices_account_public_network_access_disabled`, `aiservices_account_network_acls_default_action_deny`, `aiservices_account_uses_private_endpoint` and `aiservices_account_outbound_network_restricted` cover how the account can be reached and what it can reach.
- `aiservices_account_local_auth_disabled` and `aiservices_account_managed_identity_enabled` cover key-based access and the account's own identity.
- `aiservices_account_encrypted_with_cmk` and `aiservices_account_diagnostic_logging_enabled` cover customer-managed key encryption and diagnostic logs.
- `aiservices_deployment_content_filter_prompt_shield_enabled` verifies that model deployments have Prompt Shields enabled in their content filter.
### 🪪 Microsoft 365 — New Entra ID Checks
Five new Entra ID checks, all contributed by @kinomakino. Thank you!
- `entra_cross_tenant_access_default_outbound_restricted` verifies that the default cross-tenant access policy does not let every user reach every external application through B2B collaboration or B2B direct connect.
- `entra_policy_admin_self_service_password_reset_disabled` verifies that Self-Service Password Reset is disabled for administrators.
- `entra_policy_msol_powershell_blocked` verifies that the tenant authorization policy blocks the legacy MSOnline PowerShell module.
- `entra_app_registration_redirect_uris_secure` flags app registrations with HTTP, wildcard, malformed or `azurewebsites.net` redirect URIs.
- `entra_dynamic_group_member_of_rule_not_used` flags dynamic groups whose membership rule uses the `memberOf` operator, which Microsoft retires on November 3, 2026.
Explore all checks at [Prowler Hub](https://hub.prowler.com/check).
### ☁️ OCI — Connect Tenancies Outside us-ashburn-1
OCI API key credentials now take a home region, and the credentials form in Prowler App requires it. Identity calls that validate the credentials and discover the subscribed regions used to go to `us-ashburn-1` regardless of the tenancy, so a tenancy not subscribed to that region could not connect. The home region is only used for that bootstrap; scans still audit every subscribed region.
Read more in the [Getting Started with OCI documentation](https://docs.prowler.com/user-guide/providers/oci/getting-started-oci).
### 🛠️ Improvements and Fixes
- **AWS:** Organizations metadata is fetched from the partition of the audited account, using the region that answered the STS bootstrap, instead of the commercial `us-east-1` endpoint, which timed out in GovCloud.
- API Gunicorn workers are recycled after 10,000 requests, plus up to 1,000 of jitter, to cap memory growth. Both values are configurable with `GUNICORN_MAX_REQUESTS` and `GUNICORN_MAX_REQUESTS_JITTER`.
- The first-login redirect to the Add Provider wizard is retried on the next load when the navigation was cut short, instead of being written off after a single attempt.
- The AWS connect step offers to invite a teammate when the user cannot access the account credentials.
- **Prowler Cloud:** the Slack channel picker reads the channels the Prowler app is in before the public ones, so invited private channels show up in large workspaces, and the partial list message tells the user to invite the app to a missing channel.
### 🔐 Security Updates
- Kubernetes kubeconfigs accept only inline credentials and known cluster fields. File-path fields, command-based authentication, `auth-provider` directives, proxy URLs and unknown keys are rejected by the API and the credentials form, and stored kubeconfigs are re-validated before each scan and connection test.
- IaC repository URLs that resolve to loopback, private or otherwise non-public hosts are rejected before the connection test and the scan clone, including every destination a redirect points to.
- CSV outputs prefix cells starting with `=`, `+`, `-`, `@`, tab or carriage return with a single quote so spreadsheet applications treat them as text, and the HTML report escapes the compliance column.
- `GET /api/v1/users/{id}` no longer exposes another user's memberships, roles or tenant names from tenants other than the one in the request token.
- API error reports sent to Sentry no longer include user identity, IP addresses, cookies, headers or request bodies.
- PowerShell upgraded to 7.5.12 in the SDK and API container images, bundling .NET runtime 9.0.21.
- Python base images upgraded to 3.12.15 in the SDK and API and to 3.13.16 in the MCP Server, and Trivy to 0.75.0, patching CVE-2026-19445, CVE-2026-19553 and the grpc-go and x/crypto CVEs bundled in Trivy.
- `django` upgraded to 5.2.17 in the API, patching CVE-2026-15307.
- `pyjwt` upgraded to 2.14.0 in the SDK, the API and the MCP Server, patching GHSA-ffc3-869f-jxw9.
- `pymongo` upgraded to 4.18.2 and `urllib3` to 2.8.0 in the SDK and the API, patching CVE-2026-96748, CVE-2026-96749, CVE-2026-97687 and CVE-2026-97689.
- `dulwich` upgraded to 1.2.9 in the SDK and the API, patching GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf, GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx.
- `next` upgraded to 16.3.8 in the UI, patching a remote code execution in `next/og` (GHSA-vcvr-r3jv-pc5j) and a server-side request forgery in Image Optimization (GHSA-cjq9-62q9-8jv4).
- `sharp` upgraded to 0.35.5 and `proxy-addr` to 2.0.8 in the UI, patching GHSA-wq5f-xc86-pv6w and GHSA-jqcg-44mw-7w3h.
### 🙌 External Contributors
Thank you to our community contributors for this release!
- @rajeshnadiminti: Azure AI Services service and its checks ([#12925](https://github.com/prowler-cloud/prowler/pull/12925), [#12926](https://github.com/prowler-cloud/prowler/pull/12926), [#12997](https://github.com/prowler-cloud/prowler/pull/12997))
- @kinomakino: Microsoft 365 `entra_cross_tenant_access_default_outbound_restricted` ([#12991](https://github.com/prowler-cloud/prowler/pull/12991)), `entra_policy_admin_self_service_password_reset_disabled` ([#12990](https://github.com/prowler-cloud/prowler/pull/12990)), `entra_policy_msol_powershell_blocked` ([#13004](https://github.com/prowler-cloud/prowler/pull/13004)), `entra_app_registration_redirect_uris_secure` ([#12993](https://github.com/prowler-cloud/prowler/pull/12993)) and `entra_dynamic_group_member_of_rule_not_used` ([#13003](https://github.com/prowler-cloud/prowler/pull/13003)) checks
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.45.0) for the complete list of changes.
</Update>
<Update label="v5.44.0" description="September 29, 2026">
### 🔁 Findings — Re-check a Resource with a Partial Scan
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
A resource that has just been fixed can be confirmed from the Findings page without waiting for the next full scan. **Re-check resource** is available in the actions menu of every resource row and in the resource detail drawer, and a hint icon next to **Last seen** opens it directly. It launches a partial scan that runs again only the checks that last reported on that resource; its findings update when the scan completes, and every other resource keeps the results of the latest full scan. Roles need the Manage Scans permission, and a re-check is refused while the provider has a scan running or queued.
Re-checked resources that now pass drop out of the finding groups list and its drill-down instead of opening a detail panel that still reports `FAIL`. Partial scans do not change overviews or compliance until the next full scan and produce no report files, so the Scans table marks them as **Partial**, offers no report download for them, and the per-scan Compliance selector leaves them out.
Partial scans can also be launched outside the Findings page:
- **API:** `POST /api/v1/scans` accepts up to 10 resources in `resource_uids`, and scans expose `is_partial` with a `filter[is_partial]` filter.
- **MCP Server:** `prowler_trigger_scan` takes a `resource_uids` argument, and `prowler_list_scans` and `prowler_get_scan` return `is_partial`, with an `is_partial` filter on `prowler_list_scans`.
- **Lighthouse AI:** can launch a partial scan to re-check specific resources, such as confirming a remediation.
### ☁️ AWS — Connect an Account in One Step
The Add Provider wizard connects an AWS account in a single step. The account ID is read from the role ARN (or typed when using static access keys), the role is assumed with Prowler's own credentials, and the account, its credentials and the connection test are handled by one submit. A confirmed connection goes straight to the launch step. A refused connection stays on the form with the reason the API returned, so the fields can be fixed and retried without registering the account twice.
New tenants without providers now land on this wizard on their first sign-in instead of a welcome modal, and the sidebar action reads **Add Provider** until the first provider is connected.
Read more in the [Getting Started with AWS documentation](https://docs.prowler.com/user-guide/providers/aws/getting-started-aws).
### 🔌 Connection Tests No Longer Give Up Early
The provider connection test no longer reports `Max retries exceeded` for checks that take longer than 30 seconds, such as networks where some AWS endpoints are unreachable. The UI now waits for the full time limit of the backend task, and if that is still exhausted it shows the provider's current connection state instead of a failure.
On the SDK side, STS calls after a role assumption reuse the region that answered, so an unreachable partition region is waited on once instead of twice. The new `PROWLER_AWS_BOTO3_RETRIES_MAX_ATTEMPTS` environment variable sets the Boto3 retries for deployments that build the AWS provider without CLI flags, next to the existing timeout variables; `0` disables retries.
Read more in the [Boto3 configuration documentation](https://docs.prowler.com/user-guide/providers/aws/boto3-configuration#retries-configuration).
### 🗄️ Self-Hosted — S3-Compatible Storage and Air-Gapped Deployments
- `DJANGO_OUTPUT_S3_AWS_ENDPOINT_URL` points scan output uploads and downloads at S3-compatible object storage such as MinIO. Previously the only way to reach it was exporting process-wide AWS environment variables, which also hijacked unrelated AWS API calls such as role assumption for AWS providers.
- Report downloads from a bucket with default SSE-KMS encryption no longer fail with `InvalidArgument`: when `DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION` is set, download URLs are signed with Signature Version 4 for that region.
- Icons ship in the UI bundle instead of being fetched from `api.iconify.design`, so pages render correctly without internet access.
- Celery worker fatal errors are logged instead of silenced, and every long-running service in `docker-compose.yml` restarts automatically after an unexpected crash.
### 📊 Consistent Latest Scan Across Endpoints
Every endpoint now resolves a provider's latest completed scan the same way, so overlapping scans no longer make findings, compliance and mute rules read from different scans. Providers whose latest completed scan has no `completed_at` timestamp are no longer missing from those endpoints, and resources no longer keep a stale failed findings count when a scoped or imported scan completes after a full scan.
### 🛠️ Prowler App Fixes
- API key authentication no longer locks the key row on every request, so a heavily used key no longer serializes all its requests; `last_used_at` is updated at most once per minute.
- `POST /api/v1/scans` returns the new scan ID in `task_args` again.
- Provider deletion no longer fails when the provider has Attack Paths scans recorded on a sink that is no longer configured, such as Neptune after moving back to Neo4j.
- A periodic sweep drops orphaned Attack Paths temporary Neo4j databases left behind when a worker or Neo4j crashes mid-scan.
- **Prowler Cloud:** imported findings no longer stay stuck in `pending` when the ingestion worker picks up the job before it is committed, and a failed enqueue marks the ingestion as failed.
- **Prowler Cloud:** the Lighthouse AI connection check reports a network failure as one, naming the endpoint it could not reach, instead of hitting a time limit that looked the same as a bad key.
- **Prowler Cloud:** the finding groups endpoints no longer query Manual Pass triages once per finding, and skip that overlay for tenants with no active Manual Pass.
- The Findings page renders a skeleton at once and streams the table before the filters, and the **Finding Group** options load when the dropdown opens.
- Mute rule creation errors show the API error message instead of the raw response body.
- The sidebar no longer throws a hydration error on full page loads for users who last used the chat mode.
### 🔐 Security Updates
- `DELETE /api/v1/tasks/{id}` requires the permission of the operation that queued the task and rejects provider deletions, and `GET /api/v1/tasks` hides tasks of providers outside the role's visibility.
- The UI E2E workflow receives its AWS credentials through environment variables instead of template expansion.
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.44.0) for the complete list of changes.
</Update>
<Update label="v5.43.0" description="September 21, 2026">
### 🏛️ Compliance — FedRAMP 20x Consolidated Rules 2026
@@ -128,8 +128,8 @@ To update the environment file:
Edit the `.env` file and change version values:
```env
PROWLER_UI_VERSION="5.43.0"
PROWLER_API_VERSION="5.43.0"
PROWLER_UI_VERSION="5.44.0"
PROWLER_API_VERSION="5.44.0"
```
<Note>
@@ -2,54 +2,337 @@
title: "AWS Marketplace"
---
This section contains the instructions to subscribe to **Prowler Cloud** through the **AWS Marketplace**.
This page explains how to subscribe to **Prowler Cloud** through the **AWS Marketplace**, how to activate the subscription in Prowler Cloud, how billing works, and how to fix the errors you may hit on the way.
## How to Subscribe
## Plans
To get to the **Prowler Cloud** product listing in the AWS Marketplace, and click the `View purchase options` button:
Prowler Cloud has two listings in the AWS Marketplace. Both give you the same product. They differ in how you commit and how you pay.
1. Use this link to be taken directly to the [Prowler Cloud Marketplace Listing](https://aws.amazon.com/marketplace/pp/prodview-6ochhig5kxpok):
A **provider** is a cloud account you connect to Prowler Cloud: an AWS account, an Azure subscription, a GCP project, a Kubernetes cluster, and so on.
![](/images/aws-marketplace/marketplace-listing.png)
| | Monthly (pay as you go) | Annual (billed annually) |
| --- | --- | --- |
| Listing | [Prowler Cloud $99/provider per month (Pay as You Go)](https://aws.amazon.com/marketplace/pp/prodview-nmbvelz746oyk) | [Prowler Cloud $79/provider per month (Billed Annually)](https://aws.amazon.com/marketplace/pp/prodview-wwehsmzui25ri) |
| Billing | Monthly, per connected provider | Once, at checkout, for the full 12 months |
| Commitment | None. Cancel anytime. | 12 months for the number of providers you choose during checkout on AWS. |
| Resources beyond the included amount | Billed per resource | Billed per resource, at the annual rate |
| Providers beyond your commitment | Not applicable | Billed monthly at the pay-as-you-go rate |
2. Then, scroll down to the "Purchase Details" section and click the `Subscribe` button:
For prices, included resources and per-resource rates, see the [Prowler Cloud pricing page](https://prowler.com/pricing/).
![](/images/aws-marketplace/marketplace-subscribe.png)
## Before You Subscribe
## Set Up Your Account
- **One AWS account pays for the whole organization.** Subscribe from a single AWS account. You then connect as many providers as you need, from any cloud, inside Prowler Cloud. You do not subscribe again for each AWS account you scan.
- **Decide which Prowler Cloud organization will pay.** Activation links the AWS purchase to the organization you are signed in to. If you belong to several organizations, switch to the right one before you activate. See [Activate the Subscription in Prowler Cloud](#activate-the-subscription-in-prowler-cloud).
- **Annual plan: decide the provider count first.** AWS asks for it during checkout, and AWS charges the full year at that moment. Providers you add beyond that count are billed monthly at the pay-as-you-go rate. AWS may not let you cancel an annual contract yourself from the console. See [Cancel the Subscription](#cancel-the-subscription) before you commit.
- **Cancel any card-billed subscription first.** An organization already billed by credit card cannot switch to AWS Marketplace billing until that subscription is cancelled. This prevents double charges.
After you have subscribed to the **Prowler Cloud** product, you will need to set up your **Prowler Cloud** account:
## Subscribe on AWS Marketplace
1. Click the `Set up your account` button:
<Steps>
<Step title="Open the listing">
Sign in to the AWS account that will pay, open the listing for the plan you want, and click **View purchase options**.
![](/images/aws-marketplace/marketplace-message.png)
<Tabs>
<Tab title="Monthly">
Open [Prowler Cloud $99/provider per month (Pay as You Go)](https://aws.amazon.com/marketplace/pp/prodview-nmbvelz746oyk).
2. You will be redirected to **Prowler Cloud Sign In** page. You can sign in with an existing account or sign up with a new account:
<Frame>
<img src="/images/aws-marketplace/listing-monthly.png" alt="Monthly Prowler Cloud listing on AWS Marketplace with the View purchase options button highlighted" />
</Frame>
</Tab>
<Tab title="Annual">
Open [Prowler Cloud $79/provider per month (Billed Annually)](https://aws.amazon.com/marketplace/pp/prodview-wwehsmzui25ri).
![](/images/aws-marketplace/marketplace-sign-up.png)
<Frame>
<img src="/images/aws-marketplace/listing-annual.png" alt="Annual Prowler Cloud listing on AWS Marketplace with the View purchase options button highlighted" />
</Frame>
</Tab>
</Tabs>
</Step>
3. Once you have successfully authenticated, you should be automatically redirected to the **Prowler Cloud** [Billing](https://cloud.prowler.com/billing) page where you should now see that your account has the `AWS Marketplace Subscription` badge.
<Step title="Review the purchase and subscribe">
<Tabs>
<Tab title="Monthly">
Scroll to **Purchase details** and click **Subscribe**. There is no upfront charge. AWS bills your usage at the end of each month.
![](/images/aws-marketplace/marketplace-my-account.png)
<Frame>
<img src="/images/aws-marketplace/purchase-monthly.png" alt="Purchase details for the monthly listing with the Subscribe button highlighted" />
</Frame>
</Tab>
<Tab title="Annual">
Under **Pricing details**, select the **providers** dimension and enter the **Number of units**. One unit is one provider for the 12-month term. Review the **Contract total**, then click **Subscribe** at the bottom of the page. AWS charges the full year now.
If you have any issues signing up, please contact us at support@prowler.com.
<Frame>
<img src="/images/aws-marketplace/purchase-annual.png" alt="Purchase options for the annual listing showing the contract details, the providers dimension and the Number of units table" />
</Frame>
**Auto-renewal** is on by default. Leave it on to have AWS renew the contract for another 12 months at the end date, or choose **Do not automatically renew this contract**. You can change this from **Manage subscriptions** any time before the renewal date.
</Tab>
</Tabs>
</Step>
## Billing
<Step title="Click Set up your account">
AWS confirms the purchase and shows a **Set up your account** button. Click it. AWS sends your browser to Prowler Cloud with a setup link.
You will be charged monthly based on resources scanned and monitored depending on usage in **Prowler Cloud**. For more information on billing, please see the [Prowler Cloud Pricing FAQ](https://prowler.com/pricing/).
<Frame>
<img src="/images/aws-marketplace/set-up-your-account.png" alt="AWS Marketplace purchase confirmation banner with the Set up your account button" />
</Frame>
**Note:** Your **Prowler Cloud** bills can be seen at [AWS Billing](https://us-east-1.console.aws.amazon.com/billing/home#/bills).
<Note>
The setup link expires quickly: AWS documents roughly one hour, and never more than four. If the link expires, you close the tab, or need to start over, return to [Manage subscriptions](https://console.aws.amazon.com/marketplace/subscriptions) in the AWS Marketplace console, open **Prowler Cloud**, and click **Set up your account** again to get a fresh link.
</Note>
</Step>
</Steps>
## Subscription
## Activate the Subscription in Prowler Cloud
If you subscribe to Prowler Cloud through the AWS Marketplace it is not necessary to subscribe from different AWS accounts to use Prowler Cloud for those accounts.
Nothing is linked until you confirm it in Prowler Cloud. The purchase is safe on the AWS side while you complete these steps.
In Prowler Cloud you only need to subscribe from one of your AWS accounts through the AWS Marketplace and add multiple provider accounts once you are in the Prowler Cloud console. We will send usage metrics to the AWS Marketplace regardless of the number of accounts you add in our platform, so the AWS Marketplace will bill you based on those usage metrics.
<Steps>
<Step title="Sign in or create your account">
Prowler Cloud shows the sign-in page with a banner that reads "To finish setting up your AWS Marketplace subscription, sign in or register if you don't have an account yet."
<Frame>
<img src="/images/aws-marketplace/sign-in-marketplace.png" alt="Prowler Cloud sign-in page with the AWS Marketplace banner" />
</Frame>
If you do not have an account yet, click **Sign up**. The purchase carries over once you sign in with the new account.
<Frame>
<img src="/images/aws-marketplace/sign-up-marketplace.png" alt="Prowler Cloud sign-up page with the AWS Marketplace banner" />
</Frame>
</Step>
<Step title="Check the organization and activate">
The billing page shows a card titled **Activate your AWS Marketplace subscription**. It names the organization the purchase will be linked to.
<Frame>
<img src="/images/aws-marketplace/activate-confirm.png" alt="Activation card naming the organization, with the Activate subscription button and the Continue to billing without activating link" />
</Frame>
<Warning>
Read the organization name before you click. Activation is a billing decision: charges for that organization go to the AWS account you subscribed with. If it is not the organization you want to pay for, switch organizations first, then come back to the billing page.
</Warning>
Click **Activate subscription**. Prowler Cloud links the purchase and activates the subscription. Two short status messages appear while it works: "Linking your AWS Marketplace purchase…" and "Activating your subscription…".
<Frame>
<img src="/images/aws-marketplace/activate-working.png" alt="Activation card showing the Activating your subscription progress message" />
</Frame>
**Continue to billing without activating** discards the setup link and leaves the organization unchanged. Use it only if you landed in the wrong organization. To activate later, get a fresh link from the AWS Marketplace console as described above.
</Step>
<Step title="Confirm the subscription is active">
The billing page now states that you subscribed through AWS Marketplace, shows the AWS account that pays, and links to your subscription in the AWS Marketplace console.
<Frame>
<img src="/images/aws-marketplace/billing-subscribed.png" alt="Prowler Cloud billing page for an AWS Marketplace subscription, showing the paying AWS account and the link to the AWS Marketplace console" />
</Frame>
There is no payment method, invoice history, or cancel button on this page. AWS handles all of that.
</Step>
</Steps>
If something goes wrong during activation, see [Activation errors](#activation-errors).
## After Activation
- Add providers from the **Providers** page in Prowler Cloud. Every provider you add is covered by the same AWS subscription, whatever cloud it belongs to.
- All charges come from AWS. Find them under **Amazon Web Services, Inc. - Marketplace** in [AWS Billing](https://console.aws.amazon.com/billing/home#/bills).
<Frame>
<img src="/images/aws-marketplace/marketplace-billing.png" alt="AWS Billing page showing the Amazon Web Services, Inc. - Marketplace line" />
</Frame>
### Monthly
AWS bills you at the end of each month for the providers connected and the resources scanned in Prowler Cloud during that month.
### Annual
AWS charges the full 12-month subscription once, at checkout. The providers covered by your commitment cost nothing more during the year. Your monthly AWS bill only includes usage beyond the commitment: extra providers at the pay-as-you-go rate, and resources beyond the included amount at the annual plan's per-resource rate.
If auto-renewal is on, AWS renews the contract for another 12 months at the end date and charges the new term the same way. Turn it off from **Manage subscriptions** before the renewal date if you do not want that.
## Cancel the Subscription
There is no cancel button in Prowler Cloud for AWS Marketplace subscriptions. How you cancel depends on the plan, following the [AWS Marketplace cancellation rules](https://docs.aws.amazon.com/marketplace/latest/buyerguide/cancel-subscription.html).
### Monthly
Cancel from the AWS Marketplace console at any time:
1. Open [Manage subscriptions](https://console.aws.amazon.com/marketplace/subscriptions) and open **Prowler Cloud**.
2. Under **Active agreements**, select the agreement and click **View details**.
3. Open the **Actions** menu and choose **Cancel subscription**.
4. Type `confirm` and choose **Yes, cancel subscription**.
<Frame>
<img src="/images/aws-marketplace/aws-manage-subscriptions.png" alt="Prowler Cloud agreement page in the AWS Marketplace console with the Actions menu open on Cancel subscription" />
</Frame>
Cancelling stops future charges. Usage accrued before the cancellation still appears on your next AWS bill.
### Annual
Open the agreement from [Manage subscriptions](https://console.aws.amazon.com/marketplace/subscriptions) first. If the **Actions** menu shows **Cancel subscription**, follow the monthly steps above. If it does not, AWS does not allow self-service cancellation for that contract:
- **Within 48 hours of purchase**, contact AWS Customer Service. AWS can cancel the contract and refund the full amount, but only if you ask for the refund.
- **After 48 hours**, contact [support@prowler.com](mailto:support@prowler.com). Prowler starts the cancellation request through AWS Marketplace and you approve it in the AWS Marketplace console. A refund is not automatic. Ask for it when you request the cancellation.
See [Refunds and cancellations in AWS Marketplace](https://docs.aws.amazon.com/marketplace/latest/buyerguide/buyer-refunds.html) for the AWS side of the process.
### What happens after cancellation
Once AWS ends the subscription, Prowler Cloud pauses scans for the organization. Providers, findings and scan history stay readable. A **Subscription required** card in the sidebar, labeled **Subscription cancelled**, explains it: "Scans are paused and your assessment data remains available. Subscribe again to continue scanning." The billing page shows the plan selection page.
<Frame>
<img src="/images/aws-marketplace/billing-cancelled.png" alt="Prowler Cloud after an AWS Marketplace cancellation, showing the Subscription cancelled card in the sidebar and the plan selection page" />
</Frame>
To subscribe again from the same AWS account into the same Prowler Cloud organization, subscribe from the listing and activate as before.
<Warning>
Deleting your Prowler Cloud organization does not cancel the AWS subscription. AWS keeps charging the agreement until you cancel it as described above.
</Warning>
## Troubleshooting
### SEPA Payment Method Issues
### Activation errors
Each error appears on the Prowler Cloud billing page with the title below. Your AWS purchase is never lost by any of them. **Continue to billing** closes the message without activating.
<AccordionGroup>
<Accordion title="Your AWS Marketplace link has expired">
AWS no longer accepts the setup link, usually because it timed out. AWS documents roughly one hour of validity, and never more than four.
Open **Manage subscriptions** in the AWS Marketplace console, open **Prowler Cloud**, and click **Set up your account** to get a fresh link.
<Frame>
<img src="/images/aws-marketplace/error-token-expired.png" alt="Error notice: Your AWS Marketplace link has expired" />
</Frame>
</Accordion>
<Accordion title="This AWS Marketplace product is not supported yet">
The purchase went through on AWS, but this listing is not enabled for activation in Prowler Cloud. You see this when you subscribed to an earlier Prowler Cloud listing instead of one of the [two current plans](#plans).
Contact [support@prowler.com](mailto:support@prowler.com) with the AWS account you subscribed with. Click **Try again** once support confirms.
<Frame>
<img src="/images/aws-marketplace/error-product-not-supported.png" alt="Error notice: This AWS Marketplace product is not supported yet" />
</Frame>
</Accordion>
<Accordion title="This AWS subscription is already linked">
The purchase cannot be linked to the organization you are signed in to. The message shows the reason reported for this purchase:
- **"This AWS Marketplace subscription is no longer active."** The subscription was cancelled on AWS. Subscribe again from the listing. If you see this seconds after a new purchase, AWS may still be registering it: wait a minute, go back to **Manage subscriptions**, and click **Set up your account** again.
- **"This AWS Marketplace purchase is already linked to another Prowler account."** AWS identifies a purchase by AWS account and product, so one AWS account backs one Prowler Cloud organization per plan. Sign in to the organization that already holds it, or subscribe from a different AWS account.
- **"This account is already linked to another active AWS Marketplace purchase."** This organization already has an active AWS Marketplace subscription. Cancel that subscription in the AWS Marketplace console before activating a new one.
If none of these applies, contact [support@prowler.com](mailto:support@prowler.com).
<Frame>
<img src="/images/aws-marketplace/error-agreement-in-use.png" alt="Error notice: This AWS subscription is already linked, with the reason shown below the message" />
</Frame>
</Accordion>
<Accordion title="Your purchase is still being linked">
Another request is linking the same purchase, usually a second click or a second tab. Wait a moment and click **Try again**.
<Frame>
<img src="/images/aws-marketplace/error-link-in-progress.png" alt="Error notice: Your purchase is still being linked" />
</Frame>
</Accordion>
<Accordion title="You already have an active subscription">
This organization is billed by credit card. Cancel that subscription first, so you are never charged twice. Then open **Manage subscriptions** in the AWS Marketplace console and click **Set up your account** again.
<Frame>
<img src="/images/aws-marketplace/error-billing-provider-conflict.png" alt="Error notice: You already have an active subscription" />
</Frame>
</Accordion>
<Accordion title="This AWS purchase is billed under another organization">
AWS Marketplace billing for this AWS account and plan is already set up in a different Prowler Cloud organization. This also happens after you delete the organization that held the subscription and subscribe again from a new one.
Sign in to the organization that holds the billing and restart the setup from **Manage subscriptions** in the AWS Marketplace console. If that organization no longer exists, contact [support@prowler.com](mailto:support@prowler.com) so they can release the billing link.
</Accordion>
<Accordion title="We could not find your AWS purchase">
Prowler Cloud received no purchase to activate. Open **Manage subscriptions** in the AWS Marketplace console and click **Set up your account** to start again.
<Frame>
<img src="/images/aws-marketplace/error-no-agreement.png" alt="Error notice: We could not find your AWS purchase" />
</Frame>
</Accordion>
<Accordion title="We could not reach AWS Marketplace">
A temporary problem talking to AWS. Click **Try again**. If it keeps failing, contact [support@prowler.com](mailto:support@prowler.com) and include the detail text shown under the message.
<Frame>
<img src="/images/aws-marketplace/error-resolve-failed.png" alt="Error notice: We could not reach AWS Marketplace" />
</Frame>
</Accordion>
<Accordion title="We could not activate your subscription">
The purchase was linked but the activation did not complete. Click **Try again**. If it keeps failing, contact [support@prowler.com](mailto:support@prowler.com) and include the detail text shown under the message.
<Frame>
<img src="/images/aws-marketplace/error-subscribe-failed.png" alt="Error notice: We could not activate your subscription" />
</Frame>
</Accordion>
<Accordion title="Please sign in again">
Your Prowler Cloud session expired before the activation finished. Click **Sign in again** and pick up where you left off. The setup link stays valid during its expiry window.
<Frame>
<img src="/images/aws-marketplace/error-unauthorized.png" alt="Error notice: Please sign in again" />
</Frame>
</Accordion>
<Accordion title="We could not read your AWS purchase">
The setup link arrived damaged. Open **Manage subscriptions** in the AWS Marketplace console and click **Set up your account** to get a fresh one.
<Frame>
<img src="/images/aws-marketplace/error-invalid-input.png" alt="Error notice: We could not read your AWS purchase" />
</Frame>
</Accordion>
</AccordionGroup>
### Errors before the sign-in page
If the redirect from AWS itself fails, Prowler Cloud shows a plain page instead of the app. Each page links back to Prowler Cloud billing and to your AWS Marketplace subscriptions.
<AccordionGroup>
<Accordion title="We couldn't verify this request">
The setup page only accepts the redirect from AWS Marketplace. You reach it by opening the setup URL directly or from a bookmark. Start again from **Set up your account** in the AWS Marketplace console.
<Frame>
<img src="/images/aws-marketplace/fulfillment-error-verify.png" alt="Error page: We couldn't verify this request" />
</Frame>
</Accordion>
<Accordion title="We couldn't read your AWS purchase">
The redirect arrived without a valid setup token. Start again from **Set up your account** in the AWS Marketplace console.
<Frame>
<img src="/images/aws-marketplace/fulfillment-error-read.png" alt="Error page: We couldn't read your AWS purchase" />
</Frame>
</Accordion>
<Accordion title="Something went wrong">
Prowler Cloud could not process the redirect. Your purchase is safe. Wait a moment, then start again from **Set up your account** in the AWS Marketplace console. Contact [support@prowler.com](mailto:support@prowler.com) if it happens again.
<Frame>
<img src="/images/aws-marketplace/fulfillment-error-generic.png" alt="Error page: Something went wrong" />
</Frame>
</Accordion>
</AccordionGroup>
### SEPA payment method issues
If AWS Marketplace notifies that payment failed due to an issue with the payment method, this typically occurs when a SEPA bank account is set as the default payment method. AWS Marketplace does not support SEPA bank accounts for product subscriptions, even when the account includes valid alternative payment methods. This is because AWS Marketplace invoices are issued by AWS Inc., a US entity. SEPA accounts do not recognize these invoices as valid, causing subscription failures.
@@ -73,10 +356,14 @@ The AWS Marketplace team acknowledges this limitation: "We apologize for these a
</Note>
### Credit and Debit Card Storage Restrictions (AISPL Customers in India)
### Credit and debit card storage restrictions (AISPL customers in India)
AWS Marketplace no longer supports payments using credit or debit cards stored on file for Amazon Internet Services Private Limited (AISPL) customers. This restriction stems from Reserve Bank of India (RBI) regulations regarding payment aggregators, which prohibit the storage of card data. As explained in [this AWS blog post](https://aws.amazon.com/blogs/awsmarketplace/restriction-on-credit-and-debit-card-purchases-for-aispl-customers-using-aws-marketplace/):
> AWS Marketplace can no longer support payments using credit or debit cards stored on file. [The Reserve Bank of India (RBI) has issued a notice regarding regulation of payment aggregators](https://www.rbi.org.in/Scripts/NotificationUser.aspx?Id=11822&Mode=0), which restricts the storage of card data. If you are currently using credit or debit card as your default payment instrument, your ability to use AWS Marketplace products will be restricted. However, you can switch your default payment instrument to Pay By Invoice to avoid disruption or restore your original experience.
To maintain uninterrupted access to AWS Marketplace products, change the default payment instrument from stored card data to Pay By Invoice billing.
## Support
If you get stuck at any step, contact [support@prowler.com](mailto:support@prowler.com) and include the AWS account you subscribed with and the exact error title you see.
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

@@ -11,3 +11,28 @@ Prowler's Infrastructure as Code (IaC) provider enables you to scan local or rem
- [**GitHub Username and Personal Access Token (PAT)**](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic)
- [**GitHub OAuth App Token**](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token)
- [**Git URL**](https://git-scm.com/docs/git-clone#_git_urls)
### Private Repository Hosts
**Prowler CLI scans a repository on an internal network with no extra configuration.** On the CLI the operator supplies the repository URL themselves, so there is nothing to defend against and the check below does not apply.
**Prowler App rejects a repository URL that resolves to a non-public address**, as an SSRF defense. There the URL arrives from a tenant and the worker must not be used to reach the internal network it runs in. The check covers both the connection test and the scan clone. To scan a self-hosted GitLab, Gitea or Bitbucket from Prowler App, declare the trusted ranges:
```console
PROWLER_ALLOWED_PRIVATE_NETWORKS="10.20.0.0/16,192.168.65.254/32"
```
The value is a comma-separated list of IPs and CIDRs. A resolved address inside an allowlisted range is permitted; every other non-public address stays blocked, so link-local (`169.254.169.254`), loopback and the rest of the internal network remain protected. Malformed entries are rejected, and a non-empty allowlist is logged as a relaxed security control.
The scan runs in the worker, not the API, so the variable has to reach the worker:
- **Docker Compose**: set it in the root `.env`; it reaches the worker through the shared environment file.
- **Helm**: add it under `api.djangoConfig`, which is rendered into the API ConfigMap that the worker inherits through `envFrom`.
```yaml
api:
djangoConfig:
PROWLER_ALLOWED_PRIVATE_NETWORKS: "10.20.0.0/16"
```
Setting it only on the API container has no effect. The same variable applies to the Kubernetes and OpenStack providers. The Image provider has its own, `PROWLER_IMAGE_PROVIDER_ALLOWED_PRIVATE_NETWORKS`.
@@ -24,7 +24,7 @@ title: 'Getting Started with Kubernetes'
For Kubernetes, Prowler Cloud uses a `kubeconfig` file to authenticate. Paste the contents of your `kubeconfig` file into the `Kubeconfig content` field.
<Note>
Kubeconfigs that use `users[].user.exec` authentication are not supported in Prowler Cloud or Prowler Local Server. For security reasons, Prowler Cloud does not run commands declared by uploaded kubeconfigs. Use kubeconfig credentials that do not rely on `exec` authentication, such as the ServiceAccount token flow documented below.
Only inline kubeconfig credentials are supported: `token`, `username`/`password`, or `client-certificate-data` with `client-key-data` for users, and `certificate-authority-data` for clusters. Kubeconfigs that reference external files (`tokenFile`, `client-certificate`, `client-key`, `certificate-authority`), run commands (`exec`, `cmd-path`), use `auth-provider` directives, set a proxy URL, or include unsupported user or cluster fields are rejected. Impersonation fields (`as`, `as-uid`, `as-groups`, `as-user-extra`) are accepted but not applied, so Prowler connects with the identity of the user credentials. Use inline credentials such as the ServiceAccount token flow documented below.
</Note>
By default, the `kubeconfig` file is located at `~/.kube/config`.
@@ -96,6 +96,8 @@ If you are adding an **EKS**, **GKE**, **AKS** or external cluster, follow these
kubectl config set-context <CONTEXT_NAME> --user=prowler-sa
```
Prowler validates the entire kubeconfig file and requires it to contain only the ServiceAccount user and its cluster entry with certificate-authority-data; remove any exec entries from EKS, GKE, or AKS or Prowler will reject the file.
Replace `<SA_TOKEN>` with the generated token and `<CONTEXT_NAME>` with your KubeConfig Context Name of your EKS, GKE or AKS cluster.
4. Add the modified `kubeconfig` in Prowler Cloud and test the connection.
@@ -27,7 +27,7 @@ This guide covers the Slack integration in Prowler Cloud and Prowler Private Clo
When connected and configured:
1. A Slack workspace is approved once through Slack's app install flow, and Prowler stores the resulting credential encrypted.
2. Prowler reads the channels it can post to: the workspace's public channels, plus the private channels the Prowler app has been invited to.
2. Prowler reads the channels it can post to: first the channels the Prowler app is already in, public or private, then the rest of the workspace's public channels.
3. Several of those channels are selected and saved as the integration's authorized channels.
4. The connection check verifies the credential and every authorized channel, and posts a one-time confirmation message to each channel it has not confirmed yet.
5. Features that deliver to Slack, such as [Alerts](/user-guide/tutorials/prowler-alerts), choose their destinations from the confirmed channels.
@@ -98,7 +98,7 @@ Declining the consent screen creates nothing. Prowler reports that the workspace
Prowler posts to the channels authorized on the integration. Several channels can be authorized at once, and once the connection check has confirmed them they are the pool every consumer of the integration draws from: an [Alert](/user-guide/tutorials/prowler-alerts) picks its Slack destinations from the confirmed channels, never from the whole workspace.
1. Open the **Destination channels** selection. It lists the workspace's public channels, plus the private channels the Prowler app has been invited to, each marked **Private**.
1. Open the **Destination channels** selection. It lists the channels the Prowler app is already in, including the private channels it has been invited to, each marked **Private**, followed by the rest of the workspace's public channels.
![Destination channels selection listing public channels and an invited private channel marked Private](/images/prowler-app/slack/channel-picker.png)
@@ -109,7 +109,7 @@ Prowler validates the selection against Slack and derives each channel name itse
If the selection reports that no channels are available, the workspace exposes nothing Prowler can see. Create a public channel, or invite `@Prowler Cloud` to a private one, then click **Refresh channels**.
A workspace can hold more channels than Prowler reads in one go. When that happens, the selection says so and lists what was read: every listed channel is usable, and a channel missing from a partial list is not necessarily one `@Prowler Cloud` has to be invited to. Only listed channels can be selected: **Refresh channels** repeats the same bounded read rather than reading further, and the selection's search filters what was already read, so neither surfaces a channel the read left out.
A workspace can hold more public channels than Prowler reads in one go. When that happens, the selection says so and lists what was read: every listed channel is usable, and the channels `@Prowler Cloud` is already in are always among them, because they are read first. Only listed channels can be selected: **Refresh channels** repeats the same bounded read rather than reading further, and the selection's search filters what was already read, so neither surfaces a public channel the read left out. To make such a channel selectable, invite `@Prowler Cloud` to it and click **Refresh channels**.
Saving a new selection replaces the authorized set: channels left out of it stop being authorized, and channels added to it are authorized but not yet confirmed. Changing which channels are in the set also resets the integration's connection state, so the check runs again over the new set — reordering the same channels does not. Saving an empty selection leaves the integration with no authorized channels, and **Test connection** cannot be run again until at least one channel is authorized.
@@ -93,6 +93,10 @@ After adding your cloud account credentials, click the `Check connection` button
For a single AWS account, Prowler tests the connection as part of the `Connect account` step, so the wizard moves straight to launching the scan.
</Note>
<Note>
To delegate the AWS connection, select `I don't have access, invite a teammate` on the same step when you cannot create the IAM role or do not have the account credentials. Prowler App sends the invitation to the tenant and shows the link to share. Prowler Cloud also emails it. This option is available to users who can manage the account.
</Note>
## Step 6: Scan Started
After the connection check succeeds, save the provider and start your first scan with the `Launch Scan` button. The `Scans` section shows the scan in progress:
+9
View File
@@ -4,6 +4,15 @@ All notable changes to the **Prowler MCP Server** are documented in this file.
<!-- changelog: release notes start -->
## [0.12.3] (Prowler v5.45.0)
### 🔐 Security
- `pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9 [(#12911)](https://github.com/prowler-cloud/prowler/pull/12911)
- Python from 3.13.14 to 3.13.16 in the MCP container image, patching CVE-2026-19445 and CVE-2026-19553 [(#13009)](https://github.com/prowler-cloud/prowler/pull/13009)
---
## [0.12.2] (Prowler v5.43.0)
### 🔐 Security
+6 -20
View File
@@ -25,34 +25,20 @@ RUN --mount=type=cache,target=/root/.cache/uv \
# =============================================================================
# Final stage - Minimal runtime environment
# =============================================================================
FROM python:3.13.14-alpine3.23@sha256:9fdbf2e3e82628351513560b121e2ee6ce31cac212be9e070c5a5e2769fb5e76
FROM python:3.13.16-alpine3.23@sha256:07b20c774a247152e74f622034c1482495d299e070eec620ddff13e01264290b
LABEL maintainer="https://github.com/prowler-cloud"
# High CVEs fixed in Alpine 3.23 but not yet in the pinned base image:
# sqlite-libs 3.53.4-r0 CVE-2026-11822, CVE-2026-11824 (image ships 3.51.2-r0)
# libcrypto3/libssl3 3.5.8-r0 CVE-2026-14456 (image ships 3.5.7-r0)
# libuuid 2.41.6-r1 CVE-2026-53612, -53613, -53614, -76642, -78408, -78410
# (image ships 2.41.4-r0; -78408 is the one that needs -r1 rather than -r0)
# The base image pins python 3.13.14, which has not been rebuilt since those
# packages were published, so the upgrade is taken here rather than by moving
# the pin -- the newest published python:3.13-alpine3.23 carries the same
# vulnerable versions. libcrypto3 and libssl3 are both built from openssl and
# are flagged separately, so both are named.
# `>=` rather than `=`: Alpine keeps only the newest build of a package in a
# branch's index, so an exact pin breaks this build the day one of these is
# superseded. Drop an entry once the base image ships that version or later.
RUN apk add --no-cache --upgrade \
"sqlite-libs>=3.53.4-r0" \
"libcrypto3>=3.5.8-r0" \
"libssl3>=3.5.8-r0" \
"libuuid>=2.41.6-r1"
# Create non-root user for security
# Using specific UID/GID for consistency across environments
RUN addgroup -g 1001 prowler && \
adduser -D -u 1001 -G prowler prowler
# pip is build-only; the entrypoint runs the venv directly.
RUN rm -rf /usr/local/lib/python3.13/site-packages/pip \
/usr/local/lib/python3.13/site-packages/pip-*.dist-info \
/usr/local/bin/pip /usr/local/bin/pip3 /usr/local/bin/pip3.13
WORKDIR /app
USER prowler
@@ -1 +0,0 @@
`pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9
+48
View File
@@ -4,6 +4,54 @@ All notable changes to the **Prowler SDK** are documented in this file.
<!-- changelog: release notes start -->
## [5.45.0] (Prowler v5.45.0)
### 🚀 Added
- Azure AI Services service for Azure provider, including the `aiservices_account_public_network_access_disabled`, `aiservices_account_local_auth_disabled`, and `aiservices_account_encrypted_with_cmk` checks [(#12925)](https://github.com/prowler-cloud/prowler/pull/12925)
- `aiservices_account_uses_private_endpoint`, `aiservices_account_outbound_network_restricted`, `aiservices_account_managed_identity_enabled`, and `aiservices_account_diagnostic_logging_enabled` checks for Azure provider [(#12926)](https://github.com/prowler-cloud/prowler/pull/12926)
- `entra_policy_admin_self_service_password_reset_disabled` check for M365 provider, verifying that Self-Service Password Reset is disabled for administrators [(#12990)](https://github.com/prowler-cloud/prowler/pull/12990)
- `entra_cross_tenant_access_default_outbound_restricted` check for M365 provider, verifying that default cross-tenant access does not let all users reach all external applications through B2B collaboration or B2B direct connect [(#12991)](https://github.com/prowler-cloud/prowler/pull/12991)
- `entra_app_registration_redirect_uris_secure` check for M365 provider, flagging app registrations with HTTP, wildcard, malformed or `azurewebsites.net` redirect URIs [(#12993)](https://github.com/prowler-cloud/prowler/pull/12993)
- `aiservices_account_network_acls_default_action_deny` and `aiservices_deployment_content_filter_prompt_shield_enabled` checks for Azure provider [(#12997)](https://github.com/prowler-cloud/prowler/pull/12997)
- `entra_dynamic_group_member_of_rule_not_used` check for M365 provider, flagging dynamic groups whose membership rule uses the `memberOf` operator that Microsoft retires on November 3, 2026 [(#13003)](https://github.com/prowler-cloud/prowler/pull/13003)
- `entra_policy_msol_powershell_blocked` check for M365 provider, verifying that the tenant authorization policy blocks the legacy MSOnline PowerShell module [(#13004)](https://github.com/prowler-cloud/prowler/pull/13004)
### 🐞 Fixed
- OCI API key credentials accept a home region to bootstrap identity calls, so tenancies not subscribed to us-ashburn-1 can connect [(#12865)](https://github.com/prowler-cloud/prowler/pull/12865)
- AWS Organizations metadata is fetched from the partition of the audited account, using the region that answered the STS bootstrap, instead of the commercial `us-east-1` endpoint a session without a region resolves to, which timed out in GovCloud [(#12988)](https://github.com/prowler-cloud/prowler/pull/12988)
### 🔐 Security
- `pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9 [(#12911)](https://github.com/prowler-cloud/prowler/pull/12911)
- `dulwich` from 1.2.5 to 1.2.9, patching GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf, GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx [(#12953)](https://github.com/prowler-cloud/prowler/pull/12953)
- `urllib3` from 2.7.0 to 2.8.0, patching CVE-2026-97687 and CVE-2026-97689 [(#12953)](https://github.com/prowler-cloud/prowler/pull/12953)
- `pymongo` from 4.15.1 to 4.18.2, patching CVE-2026-96748 and CVE-2026-96749 [(#12955)](https://github.com/prowler-cloud/prowler/pull/12955)
- IaC repository URLs resolving to loopback, private or otherwise non-public hosts rejected before both the connection test and the scan clone, including every destination a redirect points to, with raw errors no longer echoed to the caller or the logs [(#12960)](https://github.com/prowler-cloud/prowler/pull/12960)
- HTML report compliance column is now escaped, so framework names and requirement IDs containing markup are shown as text instead of being rendered by the browser [(#12963)](https://github.com/prowler-cloud/prowler/pull/12963)
- CSV outputs (findings, compliance and AWS quick inventory) prefix cells starting with `=`, `+`, `-`, `@`, tab or carriage return with a single quote so spreadsheet applications treat them as text instead of formulas; signed numbers such as `-1` are left untouched [(#12963)](https://github.com/prowler-cloud/prowler/pull/12963)
- Python base image from 3.12.13 to 3.12.15 and Trivy from 0.74.0 to 0.75.0 in the SDK container image, patching CVE-2026-19445, CVE-2026-19553 and the grpc-go and x/crypto CVEs bundled in Trivy [(#13009)](https://github.com/prowler-cloud/prowler/pull/13009)
- PowerShell from 7.5.11 to 7.5.12 in the SDK container image, bundling .NET runtime 9.0.21 [(#13037)](https://github.com/prowler-cloud/prowler/pull/13037)
---
## [5.44.0] (Prowler v5.44.0)
### 🚀 Added
- `PROWLER_AWS_BOTO3_RETRIES_MAX_ATTEMPTS` environment variable to set the Boto3 retries for deployments without CLI flags [(#12870)](https://github.com/prowler-cloud/prowler/pull/12870)
### 🐞 Fixed
- STS calls after role assumption use the answering region, avoiding a second wait for an unreachable partition region [(#12870)](https://github.com/prowler-cloud/prowler/pull/12870)
### 🔐 Security
- Pass the E2E AWS credentials to the UI E2E workflow through environment variables instead of template expansion [(#12864)](https://github.com/prowler-cloud/prowler/pull/12864)
---
## [5.43.0] (Prowler v5.43.0)
### 🚀 Added
+7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import tempfile
from os import environ
@@ -55,6 +56,7 @@ from prowler.lib.check.custom_checks_metadata import (
from prowler.lib.check.models import CheckMetadata
from prowler.lib.cli.parser import ProwlerArgumentParser
from prowler.lib.logger import logger, set_logging_config
from prowler.lib.network.ssrf import SKIP_OUTBOUND_CHECK_ENV
from prowler.lib.outputs.asff.asff import ASFF
from prowler.lib.outputs.compliance.asd_essential_eight.asd_essential_eight_aws import (
ASDEssentialEightAWS,
@@ -236,6 +238,11 @@ def _send_ocsf_to_cloud(file_path: str) -> dict | None:
def prowler():
# On the CLI the operator supplies the target themselves, so the outbound host
# check defends nothing and would refuse their own private network. setdefault,
# so an operator who set the variable explicitly keeps their choice.
os.environ.setdefault(SKIP_OUTBOUND_CHECK_ENV, "true")
# Parse Arguments
# Refactor(CLI)
parser = ProwlerArgumentParser()
@@ -1 +0,0 @@
`PROWLER_AWS_BOTO3_RETRIES_MAX_ATTEMPTS` environment variable to set the Boto3 retries for deployments without CLI flags
@@ -1 +0,0 @@
STS calls after role assumption use the answering region, avoiding a second wait for an unreachable partition region
@@ -1 +0,0 @@
`dulwich` from 1.2.5 to 1.2.9, patching GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf, GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx
@@ -1 +0,0 @@
OCI API key credentials accept a home region to bootstrap identity calls, so tenancies not subscribed to us-ashburn-1 can connect
@@ -1 +0,0 @@
`pyjwt` from 2.13.0 to 2.14.0, patching GHSA-ffc3-869f-jxw9
@@ -1 +0,0 @@
`pymongo` from 4.15.1 to 4.18.2, patching CVE-2026-96748 and CVE-2026-96749
@@ -1 +0,0 @@
Pass the E2E AWS credentials to the UI E2E workflow through environment variables instead of template expansion
@@ -1 +0,0 @@
`urllib3` from 2.7.0 to 2.8.0, patching CVE-2026-97687 and CVE-2026-97689
@@ -174,7 +174,9 @@
}
],
"Checks": [
"entra_app_registration_redirect_uris_secure",
"entra_conditional_access_policy_app_enforced_restrictions",
"entra_cross_tenant_access_default_outbound_restricted",
"exchange_transport_config_smtp_auth_disabled",
"exchange_transport_rules_mail_forwarding_disabled",
"exchange_transport_rules_whitelist_disabled",
@@ -207,6 +209,8 @@
"entra_conditional_access_policy_block_unknown_device_platforms",
"entra_conditional_access_policy_directory_sync_account_excluded",
"entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced",
"entra_cross_tenant_access_default_outbound_restricted",
"entra_dynamic_group_member_of_rule_not_used",
"entra_policy_guest_users_access_restrictions",
"entra_seamless_sso_disabled"
]
@@ -256,6 +260,7 @@
"entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced",
"entra_legacy_authentication_blocked",
"entra_managed_device_required_for_authentication",
"entra_policy_admin_self_service_password_reset_disabled",
"entra_seamless_sso_disabled",
"entra_service_principal_no_secrets_for_permanent_tier0_roles",
"entra_users_mfa_enabled",
@@ -279,6 +284,7 @@
"Checks": [
"entra_admin_portals_access_restriction",
"entra_app_registration_no_unused_privileged_permissions",
"entra_dynamic_group_member_of_rule_not_used",
"entra_policy_guest_users_access_restrictions",
"entra_service_principal_no_secrets_for_permanent_tier0_roles",
"sharepoint_external_sharing_managed",
@@ -672,8 +678,10 @@
"entra_break_glass_account_fido2_security_key_registered",
"entra_emergency_access_exclusion",
"entra_app_registration_no_unused_privileged_permissions",
"entra_policy_admin_self_service_password_reset_disabled",
"entra_policy_ensure_default_user_cannot_create_tenants",
"entra_policy_guest_invite_only_for_admin_roles",
"entra_policy_msol_powershell_blocked",
"entra_seamless_sso_disabled",
"entra_service_principal_no_secrets_for_permanent_tier0_roles"
]
@@ -731,6 +739,7 @@
"entra_emergency_access_exclusion",
"entra_identity_protection_sign_in_risk_enabled",
"entra_managed_device_required_for_authentication",
"entra_policy_admin_self_service_password_reset_disabled",
"entra_seamless_sso_disabled",
"entra_service_principal_no_secrets_for_permanent_tier0_roles",
"entra_users_mfa_enabled"
@@ -830,6 +839,7 @@
],
"Checks": [
"entra_app_registration_no_unused_privileged_permissions",
"entra_policy_msol_powershell_blocked",
"entra_policy_restricts_user_consent_for_apps",
"entra_thirdparty_integrated_apps_not_allowed",
"teams_external_domains_restricted",
@@ -925,6 +935,7 @@
"admincenter_users_admins_reduced_license_footprint",
"defender_malware_policy_comprehensive_attachments_filter_applied",
"entra_app_registration_no_unused_privileged_permissions",
"entra_app_registration_redirect_uris_secure",
"entra_policy_restricts_user_consent_for_apps",
"entra_thirdparty_integrated_apps_not_allowed",
"sharepoint_modern_authentication_required"
+1 -1
View File
@@ -52,7 +52,7 @@ class _MutableTimestamp:
timestamp = _MutableTimestamp(datetime.today())
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
prowler_version = "5.44.1"
prowler_version = "5.45.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"
View File
Whitespace-only changes.
Loaded 100 of 291 files, more files were not shown because too many files have changed in this diff. Show more