Compare commits
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "prowler-plugins",
|
||||
"description": "Prowler Cloud Security for Claude Code",
|
||||
"description": "Prowler Cloud security and compliance for Claude",
|
||||
"owner": {
|
||||
"name": "Prowler",
|
||||
"email": "support@prowler.com"
|
||||
@@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "prowler",
|
||||
"source": "./claude_plugins/prowler",
|
||||
"description": "Prowler Cloud security and compliance skills for Claude, powered by the Prowler MCP server. Triage compliance gaps and remediate findings on AWS, Azure, GCP, Kubernetes, Microsoft 365, GitHub and many more providers listed at https://registry.prowler.com, plus custom providers, until your account meets the framework you choose.",
|
||||
"description": "Connect Claude to Prowler Cloud through the Prowler MCP server. Reads providers, scans, findings, compliance results and resources for AWS, Azure, GCP, Kubernetes, Microsoft 365, GitHub and the other providers listed at https://registry.prowler.com, and returns remediation guidance for failed checks. Writes are limited to Prowler Cloud itself: triggering scans, finding triage, mute rules and the mutelist, providers and integrations. Your cloud resources are not changed by the connector.",
|
||||
"category": "security",
|
||||
"homepage": "https://prowler.com"
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ SENTRY_RELEASE=local
|
||||
# REO_DEV_CLIENT_ID=
|
||||
|
||||
#### Prowler release version ####
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.46.0
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.48.0
|
||||
|
||||
# Social login credentials
|
||||
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
||||
|
||||
@@ -81,6 +81,7 @@ jobs:
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
tuf-repo-cdn.sigstore.dev:443
|
||||
raw.githubusercontent.com:443
|
||||
objects.githubusercontent.com:443
|
||||
grype.anchore.io:443
|
||||
|
||||
@@ -77,6 +77,7 @@ jobs:
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
tuf-repo-cdn.sigstore.dev:443
|
||||
ghcr.io:443
|
||||
pkg-containers.githubusercontent.com:443
|
||||
files.pythonhosted.org:443
|
||||
|
||||
@@ -81,6 +81,7 @@ jobs:
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
tuf-repo-cdn.sigstore.dev:443
|
||||
api.github.com:443
|
||||
mirror.gcr.io:443
|
||||
check.trivy.dev:443
|
||||
|
||||
@@ -78,6 +78,7 @@ jobs:
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
tuf-repo-cdn.sigstore.dev:443
|
||||
registry.npmjs.org:443
|
||||
dl-cdn.alpinelinux.org:443
|
||||
fonts.googleapis.com:443
|
||||
|
||||
@@ -39,7 +39,10 @@ jobs:
|
||||
github.repository == 'prowler-cloud/prowler' &&
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) &&
|
||||
(needs.impact-analysis.outputs.has-ui-e2e == 'true' || needs.impact-analysis.outputs.run-all == 'true')
|
||||
runs-on: ubuntu-latest
|
||||
# ubuntu-latest is moving to 26.04 (rollout began October 19, 2026; completion
|
||||
# targeted for November 19, 2026), where `playwright install` refuses to install
|
||||
# Chromium for the pinned Playwright. Stay on 24.04 until the suite upgrades.
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
AUTH_SECRET: "fallback-ci-secret-for-testing"
|
||||
AUTH_TRUST_HOST: true
|
||||
@@ -391,6 +394,12 @@ jobs:
|
||||
working-directory: ./ui
|
||||
run: pnpm run test:e2e:registry
|
||||
|
||||
# A worker that crashes on import or a connection test that never resolves
|
||||
# only shows up here; Playwright just reports a missing footer button.
|
||||
- name: Dump API service logs
|
||||
if: failure()
|
||||
run: docker compose logs --no-color --tail=300 api worker worker-beat || true
|
||||
|
||||
- name: Upload test reports
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: failure()
|
||||
|
||||
@@ -21,7 +21,10 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
ui-tests:
|
||||
runs-on: ubuntu-latest
|
||||
# ubuntu-latest is moving to 26.04 (rollout began October 19, 2026; completion
|
||||
# targeted for November 19, 2026), where `playwright install` refuses to install
|
||||
# Chromium for the pinned Playwright. Stay on 24.04 until the suite upgrades.
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -136,10 +139,6 @@ jobs:
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: pnpm run healthcheck
|
||||
|
||||
- name: Check product-tour alignment
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: pnpm run tour:check
|
||||
|
||||
- name: Run pnpm audit
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: pnpm run audit
|
||||
|
||||
@@ -95,3 +95,10 @@ ignore:
|
||||
package:
|
||||
name: zlib
|
||||
version: 1.3.2-r0
|
||||
|
||||
# Documented in .trivyignore.yaml: go-jose 4.1.5 compiled into the Trivy binary (0.76.1,
|
||||
# the latest release). Pinned to 4.1.5 so it stops matching with the next Trivy bump.
|
||||
- vulnerability: CVE-2026-116587
|
||||
package:
|
||||
name: github.com/go-jose/go-jose/v4
|
||||
version: v4.1.5
|
||||
@@ -226,3 +226,11 @@ vulnerabilities:
|
||||
purls:
|
||||
- "pkg:nuget/System.Security.Cryptography.Pkcs@10.0.0"
|
||||
expired_at: 2026-12-15
|
||||
|
||||
# go-jose 4.1.5 is compiled into the Trivy binary (usr/local/bin/trivy). Trivy merged the
|
||||
# 4.1.6 bump upstream (aquasecurity/trivy#11488) but 0.76.1 is still the latest release.
|
||||
# Bump TRIVY_VERSION and drop this entry once a release ships it.
|
||||
- id: CVE-2026-116587
|
||||
purls:
|
||||
- "pkg:golang/github.com/go-jose/go-jose/v4@v4.1.5"
|
||||
expired_at: 2026-11-20
|
||||
@@ -51,7 +51,6 @@ Use these skills for detailed patterns on-demand:
|
||||
| `django-migration-psql` | Django migration best practices for PostgreSQL | [SKILL.md](skills/django-migration-psql/SKILL.md) |
|
||||
| `postgresql-indexing` | PostgreSQL indexing, EXPLAIN, monitoring, maintenance | [SKILL.md](skills/postgresql-indexing/SKILL.md) |
|
||||
| `prowler-attack-paths-query` | Create Attack Paths openCypher queries | [SKILL.md](skills/prowler-attack-paths-query/SKILL.md) |
|
||||
| `prowler-tour` | Keep product-tour definitions aligned with the UI | [SKILL.md](skills/prowler-tour/SKILL.md) |
|
||||
| `gh-aw` | GitHub Agentic Workflows (gh-aw) | [SKILL.md](skills/gh-aw/SKILL.md) |
|
||||
| `skill-creator` | Create new AI agent skills | [SKILL.md](skills/skill-creator/SKILL.md) |
|
||||
|
||||
@@ -69,12 +68,10 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
||||
| Adding new providers | `prowler-provider` |
|
||||
| Adding privilege escalation detection queries | `prowler-attack-paths-query` |
|
||||
| Adding services to existing providers | `prowler-provider` |
|
||||
| Adding, updating, or removing a tour definition (*.tour.ts) | `prowler-tour` |
|
||||
| After creating/modifying a skill | `skill-sync` |
|
||||
| App Router / Server Actions | `nextjs-16` |
|
||||
| Auditing check-to-requirement mappings as a cloud auditor | `prowler-compliance` |
|
||||
| Building AI chat features | `ai-sdk-5` |
|
||||
| Changing button labels or section headings on a tour-covered page | `prowler-tour` |
|
||||
| Committing changes | `prowler-commit` |
|
||||
| Configuring MCP servers in agentic workflows | `gh-aw` |
|
||||
| Create PR that requires changelog entry | `prowler-changelog` |
|
||||
@@ -94,7 +91,6 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
||||
| Creating/updating compliance frameworks | `prowler-compliance` |
|
||||
| Debug why a GitHub Actions job is failing | `prowler-ci` |
|
||||
| Debugging gh-aw compilation errors | `gh-aw` |
|
||||
| Editing a UI file containing data-tour-id attributes | `prowler-tour` |
|
||||
| Fill .github/pull_request_template.md (Context/Description/Steps to review/Checklist) | `prowler-pr` |
|
||||
| Fixing bug | `tdd` |
|
||||
| Fixing compliance JSON bugs (duplicate IDs, empty Section, stale refs) | `prowler-compliance` |
|
||||
@@ -111,8 +107,6 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|
||||
| Modifying gh-aw workflow frontmatter or safe-outputs | `gh-aw` |
|
||||
| Refactoring code | `tdd` |
|
||||
| Regenerate AGENTS.md Auto-invoke tables (sync.sh) | `skill-sync` |
|
||||
| Renaming or removing a data-tour-id attribute value | `prowler-tour` |
|
||||
| Restructuring routes or layouts covered by a tour | `prowler-tour` |
|
||||
| Review PR requirements: template, title conventions, changelog gate | `prowler-pr` |
|
||||
| Review changelog format and conventions | `prowler-changelog` |
|
||||
| Reviewing JSON:API compliance | `jsonapi` |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.12.15-slim-trixie@sha256:05cda9777409a9c3ffddd94a4c476b79f0769a0b4857f0c7ed9226b6800b0d6f AS build
|
||||
FROM python:3.12.16-slim-trixie@sha256:8523d6abeb48ceae0b7240d30c5808fc26cb79c1a635bacdf19cf3baecfbec07 AS build
|
||||
|
||||
LABEL maintainer="https://github.com/prowler-cloud/prowler"
|
||||
LABEL org.opencontainers.image.source="https://github.com/prowler-cloud/prowler"
|
||||
@@ -30,6 +30,7 @@ ARG ZIZMOR_SHA256_ARM64=d66e37ef8a375fb07939c630ebf9709a6e0f20242bdc3faf672a7ed9
|
||||
# perl-base 5.40.1-6+deb13u1 CVE-2026-42497, -48962, -57432
|
||||
# libssh2-1t64 1.11.1-1+deb13u2 CVE-2026-58050
|
||||
# libpcre2-8-0 10.46-1~deb13u2 CVE-2026-86145, -89161
|
||||
# libbrotli1 1.1.0-2+deb13u1 CVE-2026-111497
|
||||
# Taken as a targeted --only-upgrade rather than by moving the digest: the newest
|
||||
# published python:3.12-slim-trixie carries the same vulnerable versions. The three
|
||||
# openssl packages are flagged separately, so all are named.
|
||||
@@ -40,7 +41,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential pkg-config libzstd-dev zlib1g-dev \
|
||||
&& apt-get install -y --no-install-recommends --only-upgrade \
|
||||
util-linux libssl3t64 openssl openssl-provider-legacy \
|
||||
libsqlite3-0 gzip perl-base libssh2-1t64 libpcre2-8-0 \
|
||||
libsqlite3-0 gzip perl-base libssh2-1t64 libpcre2-8-0 libbrotli1 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PowerShell
|
||||
@@ -126,8 +127,10 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
||||
RUN uv sync --locked --compile-bytecode && \
|
||||
rm -rf ~/.cache/uv
|
||||
|
||||
# Install PowerShell modules
|
||||
RUN .venv/bin/python prowler/providers/m365/lib/powershell/m365_powershell.py
|
||||
# Install PowerShell modules. Drop the MicrosoftTeams build SBOM: Trivy reports the npm
|
||||
# tooling it declares as installed, though none ships (see .trivyignore.yaml).
|
||||
RUN .venv/bin/python prowler/providers/m365/lib/powershell/m365_powershell.py && \
|
||||
rm -rf "${HOME}"/.local/share/powershell/Modules/MicrosoftTeams/*/_manifest
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
@@ -4,6 +4,33 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
|
||||
## [1.48.0] (Prowler v5.47.0)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- pymongo to 4.18.3, dnspython to 2.9.0 and oauthlib to 4.0.0 in the API container image, with django-allauth from 65.15.0 to 65.19.7 because earlier releases cap oauthlib below 4, patching the BSON decoding and DNS compression pointer denial of service advisories and the OAuth device flow token disclosure [(#13185)](https://github.com/prowler-cloud/prowler/pull/13185)
|
||||
|
||||
---
|
||||
|
||||
## [1.47.0] (Prowler v5.46.0)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Compliance report PDF cover table wraps every value, so long account IDs such as OCI tenancy OCIDs no longer overflow their cell [(#13063)](https://github.com/prowler-cloud/prowler/pull/13063)
|
||||
- Scan ingestion compares the serialized resource metadata with the stored value and serializes sets in a stable order, so unchanged resources no longer get their metadata, details and updated_at rewritten on every scan [(#13148)](https://github.com/prowler-cloud/prowler/pull/13148)
|
||||
- Report generation shares the parsed resource metadata between the findings of a batch and releases each batch before building the next one [(#13149)](https://github.com/prowler-cloud/prowler/pull/13149)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- `GET /resources/metadata`, `GET /resources/metadata/latest`, `GET /findings/metadata` and `GET /findings/metadata/latest` now honour the provider groups of the caller role instead of aggregating every provider in the tenant [(#12964)](https://github.com/prowler-cloud/prowler/pull/12964)
|
||||
- Trivy from 0.75.0 to 0.76.0 in the API container image, built with Go 1.27.2 and x/net 0.60.0, patching the HTTP/2 and TLS CVEs bundled in Trivy 0.75.0 [(#13064)](https://github.com/prowler-cloud/prowler/pull/13064)
|
||||
- Pillow from 12.3.0 to 13.0.0, certifi to 2026.10.19, aiohttp to 3.14.5 with yarl 1.25.1, uv to 0.13.4 and Trivy to 0.76.1 in the API container image, patching the Pillow JPEG decoder heap overflow, the compromised emSign root CAs bundled in certifi and uv, the aiohttp resource exhaustion advisories and the Go 1.27.2 stdlib CVE in the Trivy binary [(#13107)](https://github.com/prowler-cloud/prowler/pull/13107)
|
||||
- Attack Paths custom queries strip block comments before the blocked-operation check and drop APOC virtual nodes and relationships from results, so `LOAD CSV` can no longer be split with a comment nor its response returned under a forged provider label [(#13120)](https://github.com/prowler-cloud/prowler/pull/13120)
|
||||
- SAML SSO configuration endpoints require the `manage_account` permission instead of `manage_integrations`, since the configured IdP controls who signs in and which role they receive [(#13121)](https://github.com/prowler-cloud/prowler/pull/13121)
|
||||
- djangorestframework from 3.15.2 to 3.18.3 in the API, patching the JSON parser charset and Accept header denial of service and the TokenProxy revocation bypass, and the API container image no longer ships the MicrosoftTeams build SBOM that Trivy read as installed zod and body-parser [(#13157)](https://github.com/prowler-cloud/prowler/pull/13157)
|
||||
|
||||
---
|
||||
|
||||
## [1.46.0] (Prowler v5.45.0)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.12.15-slim-trixie@sha256:05cda9777409a9c3ffddd94a4c476b79f0769a0b4857f0c7ed9226b6800b0d6f AS build
|
||||
FROM python:3.12.16-slim-trixie@sha256:8523d6abeb48ceae0b7240d30c5808fc26cb79c1a635bacdf19cf3baecfbec07 AS build
|
||||
|
||||
LABEL maintainer="https://github.com/prowler-cloud/api"
|
||||
|
||||
@@ -134,8 +134,10 @@ RUN uv sync --locked --no-install-project && \
|
||||
rm -rf ~/.cache/uv
|
||||
|
||||
# Invoked as a module so the base image's Python minor version is not baked
|
||||
# into a site-packages path.
|
||||
RUN .venv/bin/python -m prowler.providers.m365.lib.powershell.m365_powershell
|
||||
# into a site-packages path. Drop the MicrosoftTeams build SBOM: Trivy reports the npm
|
||||
# tooling it declares as installed, though none ships (see .trivyignore.yaml).
|
||||
RUN .venv/bin/python -m prowler.providers.m365.lib.powershell.m365_powershell && \
|
||||
rm -rf /home/prowler/.local/share/powershell/Modules/MicrosoftTeams/*/_manifest
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Pillow from 12.3.0 to 13.0.0, certifi to 2026.10.19, aiohttp to 3.14.5 with yarl 1.25.1, uv to 0.13.4 and Trivy to 0.76.1 in the API container image, patching the Pillow JPEG decoder heap overflow, the compromised emSign root CAs bundled in certifi and uv, the aiohttp resource exhaustion advisories and the Go 1.27.2 stdlib CVE in the Trivy binary
|
||||
@@ -1 +0,0 @@
|
||||
Trivy from 0.75.0 to 0.76.0 in the API container image, built with Go 1.27.2 and x/net 0.60.0, patching the HTTP/2 and TLS CVEs bundled in Trivy 0.75.0
|
||||
@@ -0,0 +1 @@
|
||||
Python base image from 3.12.15 to 3.12.16 in the API container image, patching CVE-2026-115557
|
||||
@@ -1 +0,0 @@
|
||||
Attack Paths custom queries strip block comments before the blocked-operation check and drop APOC virtual nodes and relationships from results, so `LOAD CSV` can no longer be split with a comment nor its response returned under a forged provider label
|
||||
@@ -1 +0,0 @@
|
||||
`GET /resources/metadata`, `GET /resources/metadata/latest`, `GET /findings/metadata` and `GET /findings/metadata/latest` now honour the provider groups of the caller role instead of aggregating every provider in the tenant
|
||||
@@ -1 +0,0 @@
|
||||
Compliance report PDF cover table wraps every value, so long account IDs such as OCI tenancy OCIDs no longer overflow their cell
|
||||
@@ -1 +0,0 @@
|
||||
SAML SSO configuration endpoints require the `manage_account` permission instead of `manage_integrations`, since the configured IdP controls who signs in and which role they receive
|
||||
@@ -26,7 +26,7 @@ dependencies = [
|
||||
"celery (==5.6.2)",
|
||||
"dj-rest-auth[with_social,jwt] (==7.0.1)",
|
||||
"django (==5.2.17)",
|
||||
"django-allauth[saml] (==65.15.0)",
|
||||
"django-allauth[saml] (==65.19.7)",
|
||||
"django-celery-beat (==2.9.0)",
|
||||
"django-celery-results (==2.6.0)",
|
||||
"django-cors-headers==4.4.0",
|
||||
@@ -34,7 +34,7 @@ dependencies = [
|
||||
"django-filter==24.3",
|
||||
"django-guid==3.5.0",
|
||||
"django-postgres-extra (==2.0.9)",
|
||||
"djangorestframework==3.15.2",
|
||||
"djangorestframework==3.18.3",
|
||||
"djangorestframework-jsonapi==7.0.2",
|
||||
"djangorestframework-simplejwt (==5.5.1)",
|
||||
"drf-nested-routers (==0.95.0)",
|
||||
@@ -71,7 +71,7 @@ name = "prowler-api"
|
||||
package-mode = false
|
||||
# Needed for the SDK compatibility
|
||||
requires-python = ">=3.11,<3.13"
|
||||
version = "1.47.0"
|
||||
version = "1.49.0"
|
||||
|
||||
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
|
||||
# target-version tracks this project's lowest supported Python.
|
||||
@@ -223,7 +223,7 @@ constraint-dependencies = [
|
||||
"distro==1.9.0",
|
||||
"dj-rest-auth==7.0.1",
|
||||
"django==5.2.17",
|
||||
"django-allauth==65.15.0",
|
||||
"django-allauth==65.19.7",
|
||||
"django-celery-beat==2.9.0",
|
||||
"django-celery-results==2.6.0",
|
||||
"django-cors-headers==4.4.0",
|
||||
@@ -234,10 +234,10 @@ constraint-dependencies = [
|
||||
"django-postgres-extra==2.0.9",
|
||||
"django-silk==5.3.2",
|
||||
"django-timezone-field==7.2.1",
|
||||
"djangorestframework==3.15.2",
|
||||
"djangorestframework==3.18.3",
|
||||
"djangorestframework-jsonapi==7.0.2",
|
||||
"djangorestframework-simplejwt==5.5.1",
|
||||
"dnspython==2.8.0",
|
||||
"dnspython==2.9.0",
|
||||
"docker==7.1.0",
|
||||
"dogpile-cache==1.5.0",
|
||||
"dparse==0.6.4",
|
||||
@@ -337,7 +337,7 @@ constraint-dependencies = [
|
||||
"nest-asyncio==1.6.0",
|
||||
"nltk==3.9.4",
|
||||
"numpy==2.2.6",
|
||||
"oauthlib==3.3.1",
|
||||
"oauthlib==4.0.0",
|
||||
"oci==2.184.1",
|
||||
"openai==1.109.1",
|
||||
"openstacksdk==4.2.0",
|
||||
@@ -485,7 +485,7 @@ override-dependencies = [
|
||||
"okta==3.4.2",
|
||||
# huaweicloudsdkcore (via prowler) pins pymongo==4.15.1 exactly; CVE-2026-96748 and
|
||||
# CVE-2026-96749 are fixed in 4.18.2. Only bson types are used, so the override is safe.
|
||||
"pymongo==4.18.2",
|
||||
"pymongo==4.18.3",
|
||||
# prowler requires cryptography==50.0.0. Two api-only dependencies still cap it below
|
||||
# 49 and cannot move yet: msal, pinned exactly by azure-cli-core (2.83.0 -> 1.35.0b1,
|
||||
# 2.89.1 -> 1.36.0, both <49; cartography needs azure-cli-core), and workos 8.3.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Prowler API
|
||||
version: 1.47.0
|
||||
version: 1.49.0
|
||||
description: |-
|
||||
Prowler API specification.
|
||||
|
||||
|
||||
@@ -997,11 +997,12 @@ def _process_finding_micro_batch(
|
||||
snapshot_resource_fields(resource_uid, resource_instance)
|
||||
resource_instance.type = finding.resource_type
|
||||
updated = True
|
||||
if resource_instance.metadata != finding.resource_metadata:
|
||||
serialized_metadata = json.dumps(
|
||||
finding.resource_metadata, cls=CustomEncoder
|
||||
)
|
||||
if resource_instance.metadata != serialized_metadata:
|
||||
snapshot_resource_fields(resource_uid, resource_instance)
|
||||
resource_instance.metadata = json.dumps(
|
||||
finding.resource_metadata, cls=CustomEncoder
|
||||
)
|
||||
resource_instance.metadata = serialized_metadata
|
||||
updated = True
|
||||
if resource_instance.details != finding.resource_details:
|
||||
snapshot_resource_fields(resource_uid, resource_instance)
|
||||
|
||||
@@ -897,8 +897,12 @@ def generate_outputs_task(scan_id: str, provider_id: str, tenant_id: str):
|
||||
)
|
||||
with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS):
|
||||
for batch, is_last in batched(qs, DJANGO_FINDINGS_BATCH_SIZE):
|
||||
metadata_cache = {}
|
||||
fos = [
|
||||
FindingOutput.transform_api_finding(f, prowler_provider) for f in batch
|
||||
FindingOutput.transform_api_finding(
|
||||
f, prowler_provider, metadata_cache=metadata_cache
|
||||
)
|
||||
for f in batch
|
||||
]
|
||||
|
||||
# Outputs
|
||||
@@ -979,6 +983,8 @@ def generate_outputs_task(scan_id: str, provider_id: str, tenant_id: str):
|
||||
writer.batch_write_data_to_file()
|
||||
writer._data.clear()
|
||||
|
||||
del fos, metadata_cache
|
||||
|
||||
compressed = _compress_output_files(out_dir)
|
||||
|
||||
upload_uri = _upload_to_s3(
|
||||
|
||||
@@ -2349,6 +2349,78 @@ class TestProcessFindingMicroBatch:
|
||||
assert resource_cache[finding.resource_uid].service == finding.service_name
|
||||
assert tag_cache.keys() == {("team", "devsec")}
|
||||
|
||||
def test_process_finding_micro_batch_skips_unchanged_resource(
|
||||
self, tenants_fixture, scans_fixture
|
||||
):
|
||||
tenant = tenants_fixture[0]
|
||||
scan = scans_fixture[0]
|
||||
provider = scan.provider
|
||||
|
||||
resource_metadata = {"owner": "platform", "ports": [22, 443]}
|
||||
existing_resource = Resource.objects.create(
|
||||
tenant_id=tenant.id,
|
||||
provider=provider,
|
||||
uid="arn:aws:ec2:us-east-1:123456789012:instance/i-001",
|
||||
name="i-001",
|
||||
region="us-east-1",
|
||||
service="ec2",
|
||||
type="instance",
|
||||
metadata=json.dumps(resource_metadata, cls=CustomEncoder),
|
||||
details="details",
|
||||
partition="aws",
|
||||
)
|
||||
|
||||
finding = FakeFinding(
|
||||
uid="finding-unchanged",
|
||||
status=StatusChoices.PASS,
|
||||
status_extended="passing",
|
||||
severity=Severity.low,
|
||||
check_id="ec2_public_instance",
|
||||
resource_uid=existing_resource.uid,
|
||||
resource_name=existing_resource.name,
|
||||
region=existing_resource.region,
|
||||
service_name=existing_resource.service,
|
||||
resource_type=existing_resource.type,
|
||||
resource_tags={},
|
||||
resource_metadata=resource_metadata,
|
||||
resource_details=existing_resource.details,
|
||||
partition=existing_resource.partition,
|
||||
raw={"status": "PASS"},
|
||||
compliance={},
|
||||
metadata={"source": "prowler"},
|
||||
muted=False,
|
||||
)
|
||||
|
||||
with (
|
||||
patch("tasks.jobs.scan.rls_transaction", new=noop_rls_transaction),
|
||||
patch("api.db_utils.rls_transaction", new=noop_rls_transaction),
|
||||
patch(
|
||||
"tasks.jobs.scan.Resource.objects.bulk_update",
|
||||
wraps=Resource.objects.bulk_update,
|
||||
) as bulk_update,
|
||||
):
|
||||
_process_finding_micro_batch(
|
||||
str(tenant.id),
|
||||
[finding],
|
||||
scan,
|
||||
provider,
|
||||
{existing_resource.uid: existing_resource},
|
||||
{},
|
||||
{},
|
||||
{existing_resource.uid: 0},
|
||||
set(),
|
||||
set(),
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
)
|
||||
|
||||
bulk_update.assert_not_called()
|
||||
assert Finding.objects.filter(
|
||||
tenant_id=tenant.id, scan_id=scan.id, uid=finding.uid
|
||||
).exists()
|
||||
|
||||
def test_process_finding_micro_batch_refreshes_empty_resource_name(
|
||||
self, tenants_fixture, scans_fixture
|
||||
):
|
||||
|
||||
@@ -742,7 +742,7 @@ class TestGenerateOutputs:
|
||||
patch("tasks.tasks.FindingOutput._transform_findings_stats"),
|
||||
patch(
|
||||
"tasks.tasks.FindingOutput.transform_api_finding",
|
||||
side_effect=lambda f, _prov: f,
|
||||
side_effect=lambda f, _prov, **_kw: f,
|
||||
),
|
||||
patch("tasks.tasks._compress_output_files", return_value="outdir.zip"),
|
||||
patch("tasks.tasks._upload_to_s3", return_value="s3://bucket/outdir.zip"),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import json
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
||||
from django_celery_results.models import TaskResult
|
||||
from tasks.utils import batched, get_next_execution_datetime
|
||||
from tasks.utils import CustomEncoder, batched, get_next_execution_datetime
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -100,3 +101,18 @@ class TestBatchedFunction:
|
||||
result = list(batched([1, 2, 3], 5))
|
||||
expected = [([1, 2, 3], True)]
|
||||
assert result == expected
|
||||
|
||||
|
||||
class TestCustomEncoder:
|
||||
def test_set_serialized_in_stable_order(self):
|
||||
zones = {"us-east-1d", "us-east-1a", "us-east-1c", "us-east-1b"}
|
||||
|
||||
assert json.dumps({"zones": zones}, cls=CustomEncoder) == json.dumps(
|
||||
{"zones": "{'us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d'}"}
|
||||
)
|
||||
|
||||
def test_empty_set_keeps_str_representation(self):
|
||||
assert json.dumps(set(), cls=CustomEncoder) == json.dumps("set()")
|
||||
|
||||
def test_frozenset_serialized_like_set(self):
|
||||
assert json.dumps(frozenset({2, 1}), cls=CustomEncoder) == json.dumps("{1, 2}")
|
||||
@@ -19,6 +19,11 @@ class CustomEncoder(json.JSONEncoder):
|
||||
return o.isoformat(timespec="seconds")
|
||||
if isinstance(o, timedelta):
|
||||
return o.total_seconds()
|
||||
# Same representation as str(set), with a stable order across processes
|
||||
if isinstance(o, (set, frozenset)):
|
||||
if not o:
|
||||
return "set()"
|
||||
return "{" + ", ".join(repr(item) for item in sorted(o, key=repr)) + "}"
|
||||
|
||||
# Custom object serialization
|
||||
try:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
revision = 3
|
||||
requires-python = ">=3.11, <3.13"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12' and sys_platform == 'win32'",
|
||||
@@ -139,7 +139,7 @@ constraints = [
|
||||
{ name = "distro", specifier = "==1.9.0" },
|
||||
{ name = "dj-rest-auth", specifier = "==7.0.1" },
|
||||
{ name = "django", specifier = "==5.2.17" },
|
||||
{ name = "django-allauth", specifier = "==65.15.0" },
|
||||
{ name = "django-allauth", specifier = "==65.19.7" },
|
||||
{ name = "django-celery-beat", specifier = "==2.9.0" },
|
||||
{ name = "django-celery-results", specifier = "==2.6.0" },
|
||||
{ name = "django-cors-headers", specifier = "==4.4.0" },
|
||||
@@ -150,10 +150,10 @@ constraints = [
|
||||
{ name = "django-postgres-extra", specifier = "==2.0.9" },
|
||||
{ name = "django-silk", specifier = "==5.3.2" },
|
||||
{ name = "django-timezone-field", specifier = "==7.2.1" },
|
||||
{ name = "djangorestframework", specifier = "==3.15.2" },
|
||||
{ name = "djangorestframework", specifier = "==3.18.3" },
|
||||
{ name = "djangorestframework-jsonapi", specifier = "==7.0.2" },
|
||||
{ name = "djangorestframework-simplejwt", specifier = "==5.5.1" },
|
||||
{ name = "dnspython", specifier = "==2.8.0" },
|
||||
{ name = "dnspython", specifier = "==2.9.0" },
|
||||
{ name = "docker", specifier = "==7.1.0" },
|
||||
{ name = "dogpile-cache", specifier = "==1.5.0" },
|
||||
{ name = "dparse", specifier = "==0.6.4" },
|
||||
@@ -253,7 +253,7 @@ constraints = [
|
||||
{ name = "nest-asyncio", specifier = "==1.6.0" },
|
||||
{ name = "nltk", specifier = "==3.9.4" },
|
||||
{ name = "numpy", specifier = "==2.2.6" },
|
||||
{ name = "oauthlib", specifier = "==3.3.1" },
|
||||
{ name = "oauthlib", specifier = "==4.0.0" },
|
||||
{ name = "oci", specifier = "==2.184.1" },
|
||||
{ name = "openai", specifier = "==1.109.1" },
|
||||
{ name = "openstacksdk", specifier = "==4.2.0" },
|
||||
@@ -388,7 +388,7 @@ overrides = [
|
||||
{ name = "microsoft-kiota-serialization-text", specifier = "==1.9.10" },
|
||||
{ name = "okta", specifier = "==3.4.2" },
|
||||
{ name = "pyjwt", extras = ["crypto"], specifier = "==2.14.0" },
|
||||
{ name = "pymongo", specifier = "==4.18.2" },
|
||||
{ name = "pymongo", specifier = "==4.18.3" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2345,15 +2345,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django-allauth"
|
||||
version = "65.15.0"
|
||||
version = "65.19.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/84/c1/d3385f4c3169c1d6eea3c63aed0f36af51478c1d72e46db12bb1a08f8034/django_allauth-65.15.0.tar.gz", hash = "sha256:b404d48cf0c3ee14dacc834c541f30adedba2ff1c433980ecc494d6cb0b395a8", size = 2215709, upload-time = "2026-03-09T13:51:28.675Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/33/7c8f8fc85f4ee644ee3acb20c1fe4d04516298ab5903f812e1556b65f947/django_allauth-65.19.7.tar.gz", hash = "sha256:c7749551b659ca954e483f6f634cd0c262d65dd8144f5219b3a31cba0426e981", size = 2296178, upload-time = "2026-10-01T12:35:24.152Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/75/b8/c8411339171bd8bc075c09ef190fb42195e9a2149e5c5026e094fe62fce0/django_allauth-65.15.0-py3-none-any.whl", hash = "sha256:ad9fc49c49a9368eaa5bb95456b76e2a4f377b3c6862ee8443507816578c098d", size = 2022994, upload-time = "2026-03-09T13:51:19.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/c4/a42cc4439b18b8d2767f8c54999e011b9f620a478806ed265baed7c3dc01/django_allauth-65.19.7-py3-none-any.whl", hash = "sha256:8899377f38afabf10445c9ee2f808a730304ca8aecc540657d51a8b3928283d6", size = 2076429, upload-time = "2026-10-01T12:35:20.844Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -2510,14 +2510,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "djangorestframework"
|
||||
version = "3.15.2"
|
||||
version = "3.18.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/ce/31482eb688bdb4e271027076199e1aa8d02507e530b6d272ab8b4481557c/djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad", size = 1067420, upload-time = "2024-06-19T07:59:32.891Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/b2/1cee76b188301f7d03169ca77fab2b3d3ec5d01b3469189f944af2d7f11a/djangorestframework-3.18.3.tar.gz", hash = "sha256:446a9b352e7eff630421ab3f2328bd2401b109a9470afa4a31189994911ed030", size = 923563, upload-time = "2026-10-06T21:33:51.521Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/b6/fa99d8f05eff3a9310286ae84c4059b08c301ae4ab33ae32e46e8ef76491/djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20", size = 1071235, upload-time = "2024-06-19T07:59:26.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/dd/de5f4543765bec94e036a5e9593a42f8fa7efec8de78eb56b9ca834385f3/djangorestframework-3.18.3-py3-none-any.whl", hash = "sha256:8544bb674846731b1e3c9b309236ee1dc412905a0aa725be2ec193ca950a7d12", size = 903986, upload-time = "2026-10-06T21:33:49.917Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2550,11 +2550,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/4a/50822184bd67cc6493f0fb6a880749158fcd31ab3fa07409acfd91f9fc85/dnspython-2.9.0.tar.gz", hash = "sha256:b44dc6b18f07a8b1c56676a19fbfdb5209415b046a9cece286baafa87ff3f7f1", size = 423560, upload-time = "2026-10-09T00:07:24.352Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/02/cdcc9b7c051786a103c3b09e1003a82fa0c66bcb91ffbdabcfbf7b4163b9/dnspython-2.9.0-py3-none-any.whl", hash = "sha256:9a4aedb833c3c1b49214d04d44d3032ab7a9135f7c1d29a549b4ff78fd82fda9", size = 354822, upload-time = "2026-10-09T00:07:22.622Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4419,11 +4419,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.3.1"
|
||||
version = "4.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/d8/a1bcc8ba112a627f8ffbdc212a78ce18d3ac07e91a5ca65d27918eee25a1/oauthlib-4.0.0.tar.gz", hash = "sha256:efb274799819440f95b4ab3b818869f1ce9ae26c5beacba0201d1a1b76b54f86", size = 187232, upload-time = "2026-09-28T06:01:18.77Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/f4/78229a1066068ca14fc60fb26cf7381cabe4382261392b90e5f9552722d4/oauthlib-4.0.0-py3-none-any.whl", hash = "sha256:624c28c13a0a59cabf9747dfa52af63be3e512a7f2714df16e91b5b3a145e6cd", size = 159715, upload-time = "2026-09-28T06:01:17.008Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4945,7 +4945,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prowler-api"
|
||||
version = "1.47.0"
|
||||
version = "1.49.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "cartography" },
|
||||
@@ -5020,7 +5020,7 @@ requires-dist = [
|
||||
{ name = "defusedxml", specifier = "==0.7.1" },
|
||||
{ name = "dj-rest-auth", extras = ["with-social", "jwt"], specifier = "==7.0.1" },
|
||||
{ name = "django", specifier = "==5.2.17" },
|
||||
{ name = "django-allauth", extras = ["saml"], specifier = "==65.15.0" },
|
||||
{ name = "django-allauth", extras = ["saml"], specifier = "==65.19.7" },
|
||||
{ name = "django-celery-beat", specifier = "==2.9.0" },
|
||||
{ name = "django-celery-results", specifier = "==2.6.0" },
|
||||
{ name = "django-cors-headers", specifier = "==4.4.0" },
|
||||
@@ -5029,7 +5029,7 @@ requires-dist = [
|
||||
{ name = "django-filter", specifier = "==24.3" },
|
||||
{ name = "django-guid", specifier = "==3.5.0" },
|
||||
{ name = "django-postgres-extra", specifier = "==2.0.9" },
|
||||
{ name = "djangorestframework", specifier = "==3.15.2" },
|
||||
{ name = "djangorestframework", specifier = "==3.18.3" },
|
||||
{ name = "djangorestframework-jsonapi", specifier = "==7.0.2" },
|
||||
{ name = "djangorestframework-simplejwt", specifier = "==5.5.1" },
|
||||
{ name = "drf-nested-routers", specifier = "==0.95.0" },
|
||||
@@ -5371,33 +5371,33 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pymongo"
|
||||
version = "4.18.2"
|
||||
version = "4.18.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "dnspython" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/04/455039b475bb8afa5af9b8c363fb773a45d898016f04ee40c1c75003a3f9/pymongo-4.18.2.tar.gz", hash = "sha256:1bd4321cc63c06954953eae9eb8fb5d9075296b96c0e3ed34ccb91a0dcce31a5", size = 2746691, upload-time = "2026-09-24T16:59:59.472Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/d8/2421a5ae0d6dcdaad2a0fb75d4071eaede9f764e73b829c62b6185c3ee6b/pymongo-4.18.3.tar.gz", hash = "sha256:5dd6e659b6014288a1c53458929402a58f44a032e6f29bcef44e7477c5268e48", size = 2747872, upload-time = "2026-10-08T19:44:08.343Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/65/01/e8358c562ce4e3255035f16eed43a6f77669b0779f3aa918362c670e869f/pymongo-4.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257519b59d8509a950b21c4a626e5295b3ad4397c12ec47e6e93353c49236145", size = 818562, upload-time = "2026-09-24T16:58:20.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/18/48a9e7f44a35560284b36bba24622a959ec1dd940e7c4f7cd308cfdba756/pymongo-4.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:845b890622e7a2f031140f9cb1c0f266c1baa876ea359f7e51d0c38112cb3c6e", size = 819081, upload-time = "2026-09-24T16:58:22.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/24/2d9c0bb5649f262257f507b03f84419ac1254e94894cd493bda87fb935e1/pymongo-4.18.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a746f7e183c6d48aba6152d9f333e3bae2e8b6b00fac7abe69d4bdb3d10915c8", size = 1023284, upload-time = "2026-09-24T16:58:23.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/40/af888435ce8e3ae1e1a201b87fb397aa91e3ce115bb1bb3999197f2b9b72/pymongo-4.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9d13d812e55d406080b8ec3d5b9ef8eb2279bac32529e4f7b41cfefd5340e2", size = 1031549, upload-time = "2026-09-24T16:58:24.869Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/66/f5053862cbeddf37b3184870fa179a3e9d9e27e668c720bb7ab28b65b753/pymongo-4.18.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:930e8bcdf781a73d3df05976f5eaca1193102571cb91726e30009d64878ef8ce", size = 1054025, upload-time = "2026-09-24T16:58:26.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/29/4d7c9d7cb718ea7bbac56465de3aea5f79d1266683c16bdcd40e77746530/pymongo-4.18.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a695bd34d1e942eca93a186e679be196e288370f1a203e383b2187c06349e348", size = 1046337, upload-time = "2026-09-24T16:58:27.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/aa/c18c564883c5b2616744d1ee7ab073a3b4045aaa4bbbd524105448d4f630/pymongo-4.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d95e339e94b82950bfd62ec9ff8fea9bc8dfd9db0940e9134d1647d366560df5", size = 1032845, upload-time = "2026-09-24T16:58:29.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/16/f8a1cb7ac3f1c2c45649f18b7cb398779b4b45e1954bbfcb9317c74c744d/pymongo-4.18.2-cp311-cp311-win32.whl", hash = "sha256:2e2a3b816270c493f6645a168150ecbfb19a0bd0d9d3b56be728a847954da717", size = 814203, upload-time = "2026-09-24T16:58:30.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/53/e9d6350ea753b7a72d0346fb2c8e0c589e0eb443b5041989f3b4ea52ce55/pymongo-4.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:812b4ad5fe577f829cdb4e39a5601025b9cd7f2e3f8cd373cd5ab64ce78b0fb0", size = 821265, upload-time = "2026-09-24T16:58:32.479Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/2a/be9693eca04bb6f53f3bdb027220a210bd429c7bf902b79538471849297e/pymongo-4.18.2-cp311-cp311-win_arm64.whl", hash = "sha256:c36cadbc3a7712575900c6f50cdf24ebe2b376b6ad643cb5df1b3dce93447eae", size = 815444, upload-time = "2026-09-24T16:58:34.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/eb/995ec978741cfc56a29223ec0806976218b95d79a516d9b719c984ead130/pymongo-4.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78a3b324407ba7228a57f207e175efc40341c1312d1e7df2d4ad8141ff6d8e00", size = 819057, upload-time = "2026-09-24T16:58:35.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/e4/5a166bbac7f6e620cfb75b0640e793dd3ce69423a1f89cf6a0bfc588c107/pymongo-4.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:498187b5171d5efa87291b30e4cbf1e9849929c02bc436e95a3d01ff19e49f42", size = 819448, upload-time = "2026-09-24T16:58:37.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/c8/82194d9a1387a56cac996901f92ea6a25ae817622ee32d15c986638b263f/pymongo-4.18.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2c003646d3fa5db4e835483fc29018eb06d8c637cf69f676ffbb3f0734005220", size = 1039602, upload-time = "2026-09-24T16:58:38.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/06/628f93f15f269d8a1ea20a024083e67871f722876a33992aec5525a6c6c4/pymongo-4.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c743347143298252948e038f768ea6f71b71ef8a85b7cf0dc13b734e072f74da", size = 1050746, upload-time = "2026-09-24T16:58:40.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/93/58ea76803128651ec9c732b59b8c1eb73d511c085c2e5786da3289d4015c/pymongo-4.18.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:09e6afa68001433c42925a258b57f2ceccb95c73fe8920452e147a82d90c1ae9", size = 1075681, upload-time = "2026-09-24T16:58:41.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/6d/702ee4593420e2a8ceb169ece6394f0bb9fc2e8e8d8101e99f2468ed6d0d/pymongo-4.18.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8d3445caeeb47dc64bf416dc03e0b3bfcf3b60a109dec355a563aa62c135cb33", size = 1068235, upload-time = "2026-09-24T16:58:43.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/92/a02a4108ca4faf05bca394d65dcebc7b84b0248cff23aa89927c81a0d5dd/pymongo-4.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecde9e735e1f86559e0a71a5bbec671e59a35549bfd8c60968d9f032b05013de", size = 1049896, upload-time = "2026-09-24T16:58:44.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/93/65ef17eecdd86c153f7907b2d490d85eb655d3a5eb35a8494e7e09376353/pymongo-4.18.2-cp312-cp312-win32.whl", hash = "sha256:a463c3fc072655b7fe9e9ded4601876bef7f1df98f6f3faeed07fa7f11309fff", size = 815124, upload-time = "2026-09-24T16:58:46.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/cc/fd66aa9d8956e8269ea63b2977a7fd38fbec45795d38b8067bfafc98992f/pymongo-4.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:cbb67901ba9b6334fa94643d66587a30ffe5be05061af287364e6b5f7d239dc5", size = 821996, upload-time = "2026-09-24T16:58:47.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/21/983d340cfe226c987014f1caf9568b176cbd7f25dd87536379a609e6371e/pymongo-4.18.2-cp312-cp312-win_arm64.whl", hash = "sha256:94c5e0fe9af4c05f382ae7c4addd0255a8ddcc91b3d2a6bc09b34230cb28290b", size = 815707, upload-time = "2026-09-24T16:58:49.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/a6/63bdeb527d98998b8ea2c667eca00d48f22dd42af281e9a2d7090632d54d/pymongo-4.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f00cb357d7cc7f2798116e2377732a409c43a6dc882f0241eafed7ffed50655", size = 818805, upload-time = "2026-10-08T19:42:09.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/e9/35602972d9fa98b894d1e5feef4db2f5d275298430e99b54b39f01125efa/pymongo-4.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fe2ef9c6eb6b75689e10b20a3d8119da87302481b0a7029f9399b35142adfd8", size = 819303, upload-time = "2026-10-08T19:42:10.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/e1/468f2c69b32565c93a56623535f248b091cf52e83c3509fa4b791427889f/pymongo-4.18.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba6090d4bed582c97e38fa818c0a2b7443f203cb28882900b433ff713465f158", size = 1023754, upload-time = "2026-10-08T19:42:12.683Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/24/8af75e8af2427a47cfcc996444df934990ac4890d855f2ec064409d8b94b/pymongo-4.18.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f9903d0a089317422f52bbc25f5827e6656f0c42c43ed7d799bd02748e79a1", size = 1031920, upload-time = "2026-10-08T19:42:14.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/d0/96fa79fb7cb47e6f09e58ccbe1a726330b5d052f395b65d57e286849ed43/pymongo-4.18.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ac9bf2304c2b092ccf04261ab0cddb7fd65df1cc1ae0fa57312b03396c00d28c", size = 1053859, upload-time = "2026-10-08T19:42:16.315Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/99/1b3f48bd3580c53e4a0e89bdc8cd8c15af94ca944f9de96582cb3ebfa5d0/pymongo-4.18.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5f37095428af3042f6bb1ebe269fedcbb645d9e0642b274e1cff026d3979500b", size = 1047418, upload-time = "2026-10-08T19:42:18.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/1e/ab9148b15dcefd3d02852a65e4ac3a1df86248c227dd532dc53b41f33697/pymongo-4.18.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:16ade5053ab6c712fd25d3f878e38441b169d607d1326d708844a131911d029f", size = 1033358, upload-time = "2026-10-08T19:42:19.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/93/bbbd0edfa33b10e7a86c864478dbb12163c946c142fd1e7b1bd0b6490353/pymongo-4.18.3-cp311-cp311-win32.whl", hash = "sha256:463c09e2cc208a65d35a1af3c613360cff6d58c8aef652273da07250bb214dba", size = 814425, upload-time = "2026-10-08T19:42:21.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/13/7515f91ed9e80968cc5dc9321ac97fafde07afb75123adb9e294f50c8a10/pymongo-4.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:1d7d0474012def6113c224b167aae661b926ac3b788219426830013ea25acd33", size = 821387, upload-time = "2026-10-08T19:42:23.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/59/f54d5ee7d95ec4ed1f31bff014a0f61caa3e888d7a89a0585f3eb4be164b/pymongo-4.18.3-cp311-cp311-win_arm64.whl", hash = "sha256:83dff65baa6f2423857598ffc371d7412fa4d2a07c618bdc8d5053ade65de664", size = 817697, upload-time = "2026-10-08T19:42:25.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/d5/4775a2891396ad125545e23b3024adae4bfac9553b70c924f1f372269dbf/pymongo-4.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ea78719dd05de3a919a52b94bec790c0d0cb7d07d2f7271711832664502a0782", size = 819374, upload-time = "2026-10-08T19:42:26.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/0b/89ad56f43c3da6cbde100699f6b99528e78eba3c6740d8dad4ea2516aa45/pymongo-4.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6029d14761ba7243e6c5e464592013b519ad4dd3e4cfb75ddec39f4b5910711b", size = 819694, upload-time = "2026-10-08T19:42:28.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/b4/b68ffc205441b0a6d36d6299e35e063a5d0d3264fd685428920e1f82b634/pymongo-4.18.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9536fb3820f721290f03ad07472ec2266d8f364f91de628679a7146c9c1dbe35", size = 1040136, upload-time = "2026-10-08T19:42:30.852Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/40/e779ff3d9165316c35a2f9742a42b9c3e3a678e9e2a9f6fe4128b7c551eb/pymongo-4.18.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e461bfca4861057929efa4215730b28b93b2adb4d07828d0b65475755bbf63f5", size = 1051430, upload-time = "2026-10-08T19:42:32.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/9b/443ee038a739cc65a75f2078c9ef725c1cb4881545d2e9d7941c46f64a6c/pymongo-4.18.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1fef248623ed5e7406902a68d49dc0b1db434f19489f8d2fc9fe512c3c08bb1", size = 1075464, upload-time = "2026-10-08T19:42:34.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/4b/d80518f675cd4c1215b770444bb83002454574dae0e69760af10703ed1e8/pymongo-4.18.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:213eaed8fc4f2b0f9c84323a229dea699e01e18b8fb39723f430123b6ee77813", size = 1069046, upload-time = "2026-10-08T19:42:36.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/77/f2e9648c62e423c3b9dab1e16491a6c33250487c819e5c75784b35d16047/pymongo-4.18.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa6f363ff648bf061335d2190dd580cbf465b1308a7e6acb992d128d6a16a3bd", size = 1050321, upload-time = "2026-10-08T19:42:38.052Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/e4/3236e3a87b29fc4c502ad7dd1521c20d4a29faf1db6fde6ae94d05c5ec27/pymongo-4.18.3-cp312-cp312-win32.whl", hash = "sha256:28ba8cae86ea02d7ffdf0eea81be69be80d35d6a4a3eba4dc436d3194341805a", size = 815300, upload-time = "2026-10-08T19:42:40.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/18/3fa9d86ba32386c02ea991f10875a6a066dd5e5d80790243be3c141b0e73/pymongo-4.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:dc8ccf72b76c99a6b9fd05f8b89fe4a693128c5cfdba70f70e5792a6a563f6b0", size = 822079, upload-time = "2026-10-08T19:42:42.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/50/65a7cefd3891b77994841992b2c5b59394667df64ef21377b8ac7ecdef47/pymongo-4.18.3-cp312-cp312-win_arm64.whl", hash = "sha256:4a1f7c7dc1d554449a1695d897eb42b6080a2f1e9ccd81385dfa00204979c54d", size = 817935, upload-time = "2026-10-08T19:42:43.98Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
"name": "prowler",
|
||||
"displayName": "Prowler Cloud",
|
||||
"version": "0.1.0",
|
||||
"description": "Prowler Cloud security and compliance skills for Claude, powered by the Prowler MCP server. Triage compliance gaps and remediate findings on AWS, Azure, GCP, Kubernetes, Microsoft 365, GitHub and many more providers listed at https://registry.prowler.com, plus custom providers, until your account meets the framework you choose.",
|
||||
"description": "Connect Claude to Prowler Cloud through the Prowler MCP server. Reads providers, scans, findings, compliance results and resources for AWS, Azure, GCP, Kubernetes, Microsoft 365, GitHub and the other providers listed at https://registry.prowler.com, and returns remediation guidance for failed checks. Writes are limited to Prowler Cloud itself: triggering scans, finding triage, mute rules and the mutelist, providers and integrations. Your cloud resources are not changed by the connector.",
|
||||
"author": {
|
||||
"name": "Prowler",
|
||||
"email": "support@prowler.com",
|
||||
"url": "https://prowler.com"
|
||||
},
|
||||
"homepage": "https://docs.prowler.com",
|
||||
"homepage": "https://docs.prowler.com/user-guide/ai-agents",
|
||||
"repository": "https://github.com/prowler-cloud/prowler",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Prowler for Claude Code
|
||||
# Prowler Cloud
|
||||
|
||||
End-to-end cloud security and compliance from inside [Claude Code](https://www.claude.com/product/claude-code), powered by the [Prowler MCP server](https://docs.prowler.com/projects/prowler-mcp/). The plugin lets Claude walk a Prowler Cloud-connected account through a compliance assessment and remediate findings until the chosen security or industry framework is compliant.
|
||||
Cloud security and compliance in Claude, powered by the [Prowler MCP server](https://docs.prowler.com/projects/prowler-mcp/). Claude reads your Prowler Cloud providers, scans, findings and compliance results, returns Prowler's remediation guidance for each failed check, and manages triage and mute state. The connector never changes your cloud resources: any fix is applied with your own tools (Terraform, a cloud CLI or the console), and only after you approve it.
|
||||
|
||||
> **Preview**: this plugin is under active development. Report issues at <https://github.com/prowler-cloud/prowler/issues> or join the [Slack community](https://goto.prowler.com/slack).
|
||||
|
||||
@@ -70,7 +70,7 @@ Claude tracks progress in a markdown report under `.prowler/` at your project ro
|
||||
|
||||
## Data and privacy
|
||||
|
||||
The plugin stores nothing itself. It connects to the Prowler MCP server at `https://mcp.prowler.com/mcp`, authenticating with the API key you provide (kept in your system keychain and sent only as a bearer token to that server). Through it, Claude reads your Prowler Cloud providers, scans, findings, compliance results and resource metadata, and can trigger scans. It also queries the public Prowler Hub and Prowler documentation. The remediation report is written locally under `.prowler/` in your project. Nothing is sent to any other service. See the [Prowler privacy policy](https://prowler.com/privacy-policy) for how Prowler Cloud handles account data.
|
||||
The plugin stores nothing itself. It connects to the Prowler MCP server at `https://mcp.prowler.com/mcp`, authenticating with the API key you provide (kept in your system keychain and sent only as a bearer token to that server). Through it, Claude reads your Prowler Cloud providers, scans, findings, compliance results and resource metadata. It can also write to Prowler Cloud: trigger scans, set finding triage, create mute rules, replace the mutelist, and add or remove providers and integrations. Mutes and deletions are marked as destructive, so your client can ask before running them. It also queries the public Prowler Hub and Prowler documentation. The remediation report is written locally under `.prowler/` in your project. Nothing is sent to any other service. See the [Prowler privacy policy](https://prowler.com/privacy-policy) for how Prowler Cloud handles account data.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
|
||||
@@ -1,28 +1,41 @@
|
||||
# Importing Packages
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import click
|
||||
import dash
|
||||
import dash_bootstrap_components as dbc
|
||||
from colorama import Fore, Style
|
||||
from colorama import init as colorama_init
|
||||
from dash import dcc, html
|
||||
from dash.dependencies import Input, Output
|
||||
|
||||
from dashboard.config import folder_path_overview
|
||||
from prowler.config.config import orange_color
|
||||
from prowler.lib.banner import print_banner
|
||||
from dashboard.lib.startup import (
|
||||
install_server_ready_hook,
|
||||
silence_dash_running_line,
|
||||
)
|
||||
from prowler.lib.banner import (
|
||||
print_dashboard_banner,
|
||||
print_dashboard_loading,
|
||||
print_dashboard_promotion,
|
||||
)
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
# Honour the NO_COLOR convention: the dashboard parser has no --no-color flag.
|
||||
if os.environ.get("NO_COLOR"):
|
||||
colorama_init(strip=True)
|
||||
|
||||
# Compact startup output: identity, one hand-off block and the CSV directory.
|
||||
# The local URL is printed by ``install_server_ready_hook`` once the server is
|
||||
# actually listening, so nothing claims to be ready before it is. S3 download
|
||||
# instructions live in the dashboard documentation instead of every startup.
|
||||
cli = sys.modules["flask.cli"]
|
||||
print_banner()
|
||||
print(
|
||||
f"{Fore.GREEN}Loading all CSV files from the folder {folder_path_overview} ...\n{Style.RESET_ALL}"
|
||||
)
|
||||
cli.show_server_banner = lambda *_: click.echo(
|
||||
f"{Fore.YELLOW}NOTE:{Style.RESET_ALL} If you are using {Fore.GREEN}{Style.BRIGHT}Prowler Cloud{Style.RESET_ALL} with the S3 integration or that integration \nfrom {Fore.CYAN}{Style.BRIGHT}Prowler CLI{Style.RESET_ALL} and you want to use your data from your S3 bucket,\nrun: `{orange_color}aws s3 cp s3://<your-bucket>/output/csv ./output --recursive{Style.RESET_ALL}`\nand then run `prowler dashboard` again to load the new files."
|
||||
)
|
||||
print_dashboard_banner()
|
||||
print_dashboard_promotion()
|
||||
print_dashboard_loading(folder_path_overview)
|
||||
cli.show_server_banner = lambda *_: None
|
||||
install_server_ready_hook()
|
||||
|
||||
# Initialize the app - incorporate css
|
||||
dashboard = dash.Dash(
|
||||
@@ -30,8 +43,10 @@ dashboard = dash.Dash(
|
||||
external_stylesheets=[dbc.themes.FLATLY],
|
||||
use_pages=True,
|
||||
suppress_callback_exceptions=True,
|
||||
title="Prowler Dashboard",
|
||||
title="Prowler Local Dashboard",
|
||||
)
|
||||
# The ready block already announces the URL; drop Dash's pre-bind duplicate.
|
||||
silence_dash_running_line(dashboard)
|
||||
|
||||
# ``use_pages`` above already imported dashboard/pages/cloud.py and registered
|
||||
# every /cloud/* route. Import its metadata now (after app instantiation) so
|
||||
@@ -70,7 +85,10 @@ DASHBOARD_ITEMS = [
|
||||
# Gated navigation groups reference the shared feature metadata by slug so the
|
||||
# sidebar and the informational pages never drift apart.
|
||||
GATED_GROUPS = [
|
||||
("Upgrade to Prowler Cloud", ["lighthouse-ai", "attack-paths", "findings"]),
|
||||
(
|
||||
"Upgrade to Prowler Cloud",
|
||||
["findings", "exploitable-issues", "lighthouse-ai", "mcp", "attack-paths"],
|
||||
),
|
||||
("Configuration", ["alerts", "mutelist", "integrations"]),
|
||||
("Workspace", ["organization"]),
|
||||
]
|
||||
|
||||
@@ -300,6 +300,18 @@
|
||||
margin: 0 0 26px;
|
||||
}
|
||||
|
||||
/* Prowler Cloud screenshot shown on each gated page. */
|
||||
.pc-shot {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 760px;
|
||||
height: auto;
|
||||
border: 1px solid var(--pc-border);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 30px rgba(2, 6, 23, 0.08);
|
||||
margin: 0 0 28px;
|
||||
}
|
||||
|
||||
.pc-benefits {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
@@ -350,6 +362,111 @@
|
||||
background: var(--pc-btn-press);
|
||||
}
|
||||
|
||||
/* ------------------------------------------- Findings hand-off additions */
|
||||
|
||||
/* Secondary "how to" block rendered between the message and the CTA. */
|
||||
.pc-steps {
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
text-align: left;
|
||||
background: #f8fafc;
|
||||
border: 1px solid var(--pc-border);
|
||||
border-radius: 12px;
|
||||
padding: 18px 20px;
|
||||
margin: 0 0 26px;
|
||||
}
|
||||
|
||||
.pc-steps-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--pc-text);
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.pc-steps-list {
|
||||
/* Tailwind's preflight strips list markers; restore the numbering. */
|
||||
list-style: decimal outside;
|
||||
margin: 0 0 12px;
|
||||
padding-left: 22px;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
color: var(--pc-text-2);
|
||||
}
|
||||
|
||||
.pc-steps-note {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
color: #52525b;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.pc-steps-note:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.pc-cta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px 20px;
|
||||
}
|
||||
|
||||
.pc-link-secondary {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--pc-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.pc-link-secondary:hover {
|
||||
color: var(--pc-btn-press);
|
||||
}
|
||||
|
||||
.pc-note {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
color: #52525b;
|
||||
margin: 14px 0 0;
|
||||
}
|
||||
|
||||
/* Compact hand-off card used at the end of the Compliance page. */
|
||||
.pc-handoff {
|
||||
min-width: 0;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--pc-border);
|
||||
border-radius: 14px;
|
||||
padding: 24px 28px;
|
||||
}
|
||||
|
||||
.pc-handoff .pc-avail {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.pc-handoff-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--pc-text);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.pc-handoff-desc {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.pc-handoff-benefits {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.pc-handoff .pc-cta-row {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.pc-handoff .pc-note {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------- Responsive collapse */
|
||||
|
||||
@media (max-width: 900px) {
|
||||
|
||||
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 78 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="22" x2="18" y1="12" y2="12"/><line x1="6" x2="2" y1="12" y2="12"/><line x1="12" x2="12" y1="6" y2="2"/><line x1="12" x2="12" y1="22" y2="18"/></svg>
|
||||
|
After Width: | Height: | Size: 341 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8v5a6 6 0 0 1-12 0V8z"/></svg>
|
||||
|
After Width: | Height: | Size: 252 B |
@@ -1,5 +1,7 @@
|
||||
from dash import dcc, html
|
||||
|
||||
from dashboard.pages.cloud import build_compliance_handoff_card
|
||||
|
||||
|
||||
def create_layout_overview(
|
||||
account_dropdown: html.Div,
|
||||
@@ -194,6 +196,7 @@ def create_layout_compliance(
|
||||
className="text-prowler-stone-900 text-lg font-bold",
|
||||
),
|
||||
html.Div(className="flex flex-wrap", id="output"),
|
||||
build_compliance_handoff_card(),
|
||||
],
|
||||
className="grid gap-x-8 gap-y-8 2xl:container mx-auto",
|
||||
)
|
||||
@@ -0,0 +1,112 @@
|
||||
"""Startup output helpers for `prowler dashboard`.
|
||||
|
||||
The local URL is announced only once the HTTP server is listening. Werkzeug
|
||||
calls ``BaseWSGIServer.log_startup`` right after it binds the socket, so a
|
||||
thin wrapper around that method reads the effective host and port from the
|
||||
bound server and prints the ready message. Dash silences the Werkzeug logger
|
||||
below ERROR while in debug mode, so hooking the method (not the log) is what
|
||||
makes the announcement reliable. Werkzeug's own behaviour is untouched: the
|
||||
original method still runs and no logger is reconfigured.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from colorama import Fore, Style
|
||||
from werkzeug.serving import BaseWSGIServer, get_interface_ip
|
||||
|
||||
from prowler.config.config import banner_color
|
||||
|
||||
_WILDCARD_HOSTS = {"0.0.0.0": "127.0.0.1", "::": "[::1]"}
|
||||
|
||||
|
||||
def server_urls(host, port, scheme="http"):
|
||||
"""Return the browser URL and the other reachable URLs for a bound server.
|
||||
|
||||
A wildcard host (``0.0.0.0`` or ``::``) is never presented as a browser
|
||||
destination: the loopback address is used instead and the interface
|
||||
address is listed as also reachable.
|
||||
"""
|
||||
if host in _WILDCARD_HOSTS:
|
||||
primary = f"{scheme}://{_WILDCARD_HOSTS[host]}:{port}/"
|
||||
family = socket.AF_INET if host == "0.0.0.0" else socket.AF_INET6
|
||||
try:
|
||||
interface_ip = get_interface_ip(family)
|
||||
except OSError:
|
||||
interface_ip = None
|
||||
others = []
|
||||
if interface_ip:
|
||||
if ":" in interface_ip:
|
||||
interface_ip = f"[{interface_ip}]"
|
||||
others.append(f"{scheme}://{interface_ip}:{port}/")
|
||||
return primary, others
|
||||
display_host = f"[{host}]" if ":" in host and not host.startswith("[") else host
|
||||
return f"{scheme}://{display_host}:{port}/", []
|
||||
|
||||
|
||||
def format_ready_message(host, port, scheme="http"):
|
||||
"""Render the ready block, styled like the CLI banner, for a listening server."""
|
||||
primary, others = server_urls(host, port, scheme)
|
||||
bar = f"{banner_color}│{Style.RESET_ALL}"
|
||||
check = f"{Fore.GREEN}✓{Style.RESET_ALL}"
|
||||
lines = [
|
||||
f"{bar} {check} {Style.BRIGHT}Prowler Local Dashboard now is running on{Style.RESET_ALL} "
|
||||
f"{banner_color}{primary}{Style.RESET_ALL}"
|
||||
]
|
||||
if host in _WILDCARD_HOSTS:
|
||||
lines.append(f"{bar} Listening on all interfaces ({host}).")
|
||||
if others:
|
||||
lines.append(f"{bar} Also reachable at: " + ", ".join(others))
|
||||
lines.append(f"{bar} Press Ctrl+C to stop.")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class DashRunningLineFilter(logging.Filter):
|
||||
"""Drop Dash's own ``Dash is running on ...`` line from the app logger.
|
||||
|
||||
The ready block printed by ``install_server_ready_hook`` already states the
|
||||
effective URL once the server is listening, so Dash's pre-bind line would
|
||||
only repeat it. Every other record on the logger still passes.
|
||||
"""
|
||||
|
||||
def filter(self, record):
|
||||
return not record.getMessage().startswith("Dash is running on")
|
||||
|
||||
|
||||
def silence_dash_running_line(app):
|
||||
"""Attach ``DashRunningLineFilter`` to a Dash app's logger once."""
|
||||
if not any(isinstance(f, DashRunningLineFilter) for f in app.logger.filters):
|
||||
app.logger.addFilter(DashRunningLineFilter())
|
||||
|
||||
|
||||
def _print_now(message):
|
||||
"""Print and flush so the ready message shows even when stdout is piped."""
|
||||
print(message, flush=True)
|
||||
|
||||
|
||||
def install_server_ready_hook(announce=_print_now):
|
||||
"""Announce the effective address right after Werkzeug binds the socket.
|
||||
|
||||
Wraps ``BaseWSGIServer.log_startup`` once per process. The wrapper calls
|
||||
the original method first, so Werkzeug's own startup output is unchanged,
|
||||
then prints the ready block a single time. A failed bind never reaches
|
||||
``log_startup``, so a startup error is never announced as ready.
|
||||
"""
|
||||
if getattr(BaseWSGIServer.log_startup, "_prowler_ready_hook", False):
|
||||
return BaseWSGIServer.log_startup
|
||||
|
||||
original = BaseWSGIServer.log_startup
|
||||
state = {"announced": False}
|
||||
|
||||
def log_startup(self):
|
||||
original(self)
|
||||
if state["announced"]:
|
||||
return
|
||||
state["announced"] = True
|
||||
scheme = "http" if self.ssl_context is None else "https"
|
||||
announce("\n" + format_ready_message(self.host, self.port, scheme) + "\n")
|
||||
|
||||
log_startup._prowler_ready_hook = True
|
||||
log_startup._prowler_original = original
|
||||
BaseWSGIServer.log_startup = log_startup
|
||||
return log_startup
|
||||
@@ -21,12 +21,58 @@ CLOUD_CTA_BASE = (
|
||||
# Path to the recolorable checkmark mask used for benefit bullets.
|
||||
CHECK_ICON = "/assets/images/icons/cloud/check.svg"
|
||||
|
||||
# Prowler Cloud screenshots shown on each gated page. They come from the
|
||||
# product documentation (docs/images) and are downscaled for the dashboard.
|
||||
SCREENSHOT_DIR = "/assets/images/cloud/screenshots"
|
||||
|
||||
# Prowler Cloud pricing page used by the "Compare editions" secondary link.
|
||||
CLOUD_PRICING_URL = "https://prowler.com/pricing"
|
||||
|
||||
# Findings hand-off CTA. Shares the ``report-handoff`` campaign and ``oss``
|
||||
# medium with the OSS HTML report CTA; ``utm_source`` identifies this entry
|
||||
# point and ``intent`` expresses the user's goal. The URL is static: no
|
||||
# assessment data (accounts, resources, findings, paths) is ever appended.
|
||||
FINDINGS_HANDOFF_CTA_URL = (
|
||||
"https://cloud.prowler.com/sign-up"
|
||||
"?utm_source=prowler-local-dashboard&utm_medium=oss"
|
||||
"&utm_campaign=report-handoff&utm_content=findings&intent=import-findings"
|
||||
)
|
||||
|
||||
# Same hand-off for the Compliance page; only ``utm_content`` differs.
|
||||
COMPLIANCE_HANDOFF_CTA_URL = (
|
||||
"https://cloud.prowler.com/sign-up"
|
||||
"?utm_source=prowler-local-dashboard&utm_medium=oss"
|
||||
"&utm_campaign=report-handoff&utm_content=compliance&intent=import-findings"
|
||||
)
|
||||
|
||||
# Shared hand-off copy (identical to the OSS HTML report CTA).
|
||||
HANDOFF_TITLE = "Keep scanning locally. Bring your findings to Prowler Cloud."
|
||||
HANDOFF_DESCRIPTION = (
|
||||
"Import your accompanying OCSF report to explore managed security workflows."
|
||||
)
|
||||
HANDOFF_CTA_LABEL = "Explore Prowler Cloud"
|
||||
HANDOFF_SECONDARY_LABEL = "Compare editions"
|
||||
HANDOFF_NOTE = "Nothing is uploaded automatically."
|
||||
|
||||
# What Prowler Cloud adds on top of the local compliance view, as documented in
|
||||
# the Compliance, Cross-Provider Compliance and Cross-Provider Type Compliance
|
||||
# tutorials. Keep these verifiable; do not add unreleased capabilities.
|
||||
COMPLIANCE_HANDOFF_BENEFITS = [
|
||||
"Compliance Watchlist to pin the frameworks you track",
|
||||
"Cross-account and cross-provider roll-ups of a framework, with a per-provider breakdown",
|
||||
"Downloadable CSV and PDF compliance reports",
|
||||
]
|
||||
|
||||
|
||||
# Normative feature definitions. ``icon`` points to a local mask asset so no
|
||||
# external requests are needed and the glyph recolors per context.
|
||||
CLOUD_FEATURES = [
|
||||
{
|
||||
"slug": "lighthouse-ai",
|
||||
"screenshot": {
|
||||
"file": "lighthouse-ai.jpg",
|
||||
"alt": "Lighthouse AI chat in Prowler Cloud",
|
||||
},
|
||||
"route": "/cloud/lighthouse-ai",
|
||||
"nav_label": "Lighthouse AI",
|
||||
"page_title": "Lighthouse AI",
|
||||
@@ -43,8 +89,64 @@ CLOUD_FEATURES = [
|
||||
"utm_content": "lighthouse-ai",
|
||||
"icon": "/assets/images/icons/cloud/lighthouse-ai.svg",
|
||||
},
|
||||
{
|
||||
# Wording mirrors the Prowler Cloud issues model: an issue folds the
|
||||
# findings of one attack path and carries a verdict, legs and a fix.
|
||||
"slug": "exploitable-issues",
|
||||
"route": "/cloud/exploitable-issues",
|
||||
"nav_label": "Exploitable Issues",
|
||||
"page_title": "Exploitable Issues",
|
||||
"card_title": "Unlock Exploitable Issues",
|
||||
"description": (
|
||||
"See which findings combine into an exploitable attack path, from "
|
||||
"where a threat actor starts to what they would gain, so the toxic "
|
||||
"combinations get fixed first."
|
||||
),
|
||||
"benefits": [
|
||||
"Each issue folds the related findings of one attack path into a single verdict: exploitable, likely, conditional or not exploitable",
|
||||
"Reachability, access, target, controls and verification are assessed separately, with the evidence behind each",
|
||||
"Outcome and starting point are spelled out, from the internet to a leaked credential, together with the fix and a triage status",
|
||||
],
|
||||
"utm_content": "exploitable-issues",
|
||||
"icon": "/assets/images/icons/cloud/exploitable-issues.svg",
|
||||
"note": "Exploitable Issues is rolling out to Prowler Cloud tenants.",
|
||||
},
|
||||
{
|
||||
# Capabilities below mirror the Prowler MCP documentation (Overview and
|
||||
# Cloud MCP Server configuration). The hosted server is in preview.
|
||||
"slug": "mcp",
|
||||
"screenshot": {
|
||||
"file": "mcp.jpg",
|
||||
"alt": "Claude Code querying Prowler through the Prowler MCP Server",
|
||||
},
|
||||
"route": "/cloud/mcp",
|
||||
"nav_label": "MCP Server",
|
||||
"page_title": "Prowler MCP",
|
||||
"card_title": "Unlock Prowler MCP",
|
||||
"description": (
|
||||
"Connect your AI assistant to Prowler through the hosted MCP Server "
|
||||
"at mcp.prowler.com, with no installation, using a Prowler Cloud "
|
||||
"API key."
|
||||
),
|
||||
"benefits": [
|
||||
"Ask about findings, resources and compliance in natural language from Claude Code, Cursor, VS Code or Codex",
|
||||
"Trigger scans and manage providers, mute rules and integrations from your AI agent",
|
||||
"Cloud-only tools for findings triage, scan scheduling, scan configurations and alerts",
|
||||
],
|
||||
"utm_content": "mcp",
|
||||
"icon": "/assets/images/icons/cloud/mcp.svg",
|
||||
"secondary_link": {
|
||||
"label": "Connect your AI agent",
|
||||
"href": "https://docs.prowler.com/user-guide/ai-agents/",
|
||||
},
|
||||
"note": "Prowler MCP Server is currently a preview feature.",
|
||||
},
|
||||
{
|
||||
"slug": "attack-paths",
|
||||
"screenshot": {
|
||||
"file": "attack-paths.jpg",
|
||||
"alt": "Attack path graph in Prowler Cloud",
|
||||
},
|
||||
"route": "/cloud/attack-paths",
|
||||
"nav_label": "Attack Paths",
|
||||
"page_title": "Attack Paths",
|
||||
@@ -62,25 +164,53 @@ CLOUD_FEATURES = [
|
||||
"icon": "/assets/images/icons/cloud/attack-paths.svg",
|
||||
},
|
||||
{
|
||||
# Findings is a voluntary hand-off, not an upsell: the dashboard keeps
|
||||
# showing findings from CSV, and this page explains how to bring the
|
||||
# OCSF report to Prowler Cloud. Copy is shared with the OSS HTML report.
|
||||
"slug": "findings",
|
||||
"screenshot": {
|
||||
"file": "findings.jpg",
|
||||
"alt": "Findings table with triage in Prowler Cloud",
|
||||
},
|
||||
"route": "/cloud/findings",
|
||||
"nav_label": "Findings",
|
||||
"page_title": "Findings",
|
||||
"card_title": "Unlock Findings",
|
||||
"card_title": "Keep scanning locally. Bring your findings to Prowler Cloud.",
|
||||
"description": (
|
||||
"Filter, investigate, and prioritize security findings across "
|
||||
"providers and scans from one workspace."
|
||||
"Import your accompanying OCSF report to explore managed security "
|
||||
"workflows."
|
||||
),
|
||||
"benefits": [
|
||||
"Search and filter findings across all connected accounts",
|
||||
"Track status, severity, ownership, and remediation context",
|
||||
"Triage findings and share a consistent source of truth with your security team",
|
||||
],
|
||||
"benefits": [],
|
||||
"utm_content": "findings",
|
||||
"icon": "/assets/images/icons/cloud/findings.svg",
|
||||
"cta_label": "Explore Prowler Cloud",
|
||||
"cta_url": FINDINGS_HANDOFF_CTA_URL,
|
||||
"secondary_link": {"label": "Compare editions", "href": CLOUD_PRICING_URL},
|
||||
"note": "Nothing is uploaded automatically.",
|
||||
"steps": {
|
||||
"title": "How to bring your results",
|
||||
"items": [
|
||||
"Create or sign in to your Prowler Cloud account.",
|
||||
"In Prowler Cloud, open Scans \u2192 Import Findings.",
|
||||
"Select the accompanying .ocsf.json report and start the import.",
|
||||
],
|
||||
"notes": [
|
||||
"This dashboard reads CSV files. Cloud import requires the OCSF "
|
||||
"JSON report, not the CSV or HTML report.",
|
||||
"If you did not generate an OCSF report, include json-ocsf in "
|
||||
"your output formats on your next scan.",
|
||||
"Import Findings is available to roles with the Manage Ingestions "
|
||||
"permission on an active Prowler Cloud subscription; each cloud "
|
||||
"account in the report counts as a provider.",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"slug": "alerts",
|
||||
"screenshot": {
|
||||
"file": "alerts.jpg",
|
||||
"alt": "Alert rules list in Prowler Cloud",
|
||||
},
|
||||
"route": "/cloud/alerts",
|
||||
"nav_label": "Alerts",
|
||||
"page_title": "Alerts",
|
||||
@@ -99,6 +229,10 @@ CLOUD_FEATURES = [
|
||||
},
|
||||
{
|
||||
"slug": "mutelist",
|
||||
"screenshot": {
|
||||
"file": "mutelist.jpg",
|
||||
"alt": "Mutelist configuration in Prowler Cloud",
|
||||
},
|
||||
"route": "/cloud/mutelist",
|
||||
"nav_label": "Mutelist",
|
||||
"page_title": "Mutelist",
|
||||
@@ -117,6 +251,10 @@ CLOUD_FEATURES = [
|
||||
},
|
||||
{
|
||||
"slug": "integrations",
|
||||
"screenshot": {
|
||||
"file": "integrations.jpg",
|
||||
"alt": "Integrations page in Prowler Cloud",
|
||||
},
|
||||
"route": "/cloud/integrations",
|
||||
"nav_label": "Integrations",
|
||||
"page_title": "Integrations",
|
||||
@@ -135,6 +273,10 @@ CLOUD_FEATURES = [
|
||||
},
|
||||
{
|
||||
"slug": "organization",
|
||||
"screenshot": {
|
||||
"file": "organization.jpg",
|
||||
"alt": "Organization users page in Prowler Cloud",
|
||||
},
|
||||
"route": "/cloud/organization",
|
||||
"nav_label": "Organization",
|
||||
"page_title": "Organization",
|
||||
@@ -178,9 +320,114 @@ def _benefit_item(text):
|
||||
)
|
||||
|
||||
|
||||
def _handoff_links(cta_url):
|
||||
"""Return the primary CTA and the "Compare editions" link for a hand-off."""
|
||||
return html.Div(
|
||||
[
|
||||
html.A(
|
||||
HANDOFF_CTA_LABEL,
|
||||
href=cta_url,
|
||||
target="_blank",
|
||||
rel="noopener noreferrer",
|
||||
className="pc-cta",
|
||||
),
|
||||
html.A(
|
||||
HANDOFF_SECONDARY_LABEL,
|
||||
href=CLOUD_PRICING_URL,
|
||||
target="_blank",
|
||||
rel="noopener noreferrer",
|
||||
className="pc-link-secondary",
|
||||
),
|
||||
],
|
||||
className="pc-cta-row",
|
||||
)
|
||||
|
||||
|
||||
def build_compliance_handoff_card():
|
||||
"""Build the compact hand-off card shown at the end of the Compliance page.
|
||||
|
||||
The local compliance view keeps working from CSV; this card only explains
|
||||
what Prowler Cloud adds for compliance reporting. Static links only: no
|
||||
upload control, no request, no assessment data in URLs.
|
||||
"""
|
||||
return html.Div(
|
||||
[
|
||||
html.Div("Available in Prowler Cloud", className="pc-avail"),
|
||||
html.H2(HANDOFF_TITLE, className="pc-handoff-title"),
|
||||
html.P(HANDOFF_DESCRIPTION, className="pc-card-desc pc-handoff-desc"),
|
||||
html.Ul(
|
||||
[_benefit_item(benefit) for benefit in COMPLIANCE_HANDOFF_BENEFITS],
|
||||
className="pc-benefits pc-handoff-benefits",
|
||||
),
|
||||
_handoff_links(COMPLIANCE_HANDOFF_CTA_URL),
|
||||
html.P(HANDOFF_NOTE, className="pc-note"),
|
||||
],
|
||||
className="pc-handoff pc-font",
|
||||
role="complementary",
|
||||
)
|
||||
|
||||
|
||||
def _screenshot(screenshot):
|
||||
"""Render the Prowler Cloud screenshot that shows the gated feature."""
|
||||
return html.Img(
|
||||
src=f"{SCREENSHOT_DIR}/{screenshot['file']}",
|
||||
alt=screenshot["alt"],
|
||||
className="pc-shot",
|
||||
)
|
||||
|
||||
|
||||
def _steps_block(steps):
|
||||
"""Build the optional secondary "how to" block rendered below the message.
|
||||
|
||||
It is plain text and links only: no file selector, upload control or
|
||||
request is introduced, since the browser cannot read local report files.
|
||||
"""
|
||||
return html.Div(
|
||||
[
|
||||
html.H3(steps["title"], className="pc-steps-title"),
|
||||
html.Ol(
|
||||
[html.Li(item) for item in steps["items"]],
|
||||
className="pc-steps-list",
|
||||
),
|
||||
*[html.P(note, className="pc-steps-note") for note in steps["notes"]],
|
||||
],
|
||||
className="pc-steps",
|
||||
)
|
||||
|
||||
|
||||
def build_cloud_layout(feature):
|
||||
"""Build the reusable gated informational page for a single feature."""
|
||||
cta_url = f"{CLOUD_CTA_BASE}{feature['utm_content']}"
|
||||
"""Build the reusable gated informational page for a single feature.
|
||||
|
||||
Features may override the CTA (``cta_label``/``cta_url``) and add an
|
||||
optional ``screenshot``, ``steps`` block, ``secondary_link`` and ``note``.
|
||||
Features that do not set them render exactly as before.
|
||||
"""
|
||||
cta_url = feature.get("cta_url") or f"{CLOUD_CTA_BASE}{feature['utm_content']}"
|
||||
cta_label = feature.get("cta_label", "Upgrade to Prowler Cloud")
|
||||
steps = feature.get("steps")
|
||||
screenshot = feature.get("screenshot")
|
||||
secondary_link = feature.get("secondary_link")
|
||||
note = feature.get("note")
|
||||
|
||||
cta_links = [
|
||||
html.A(
|
||||
cta_label,
|
||||
href=cta_url,
|
||||
target="_blank",
|
||||
rel="noopener noreferrer",
|
||||
className="pc-cta",
|
||||
)
|
||||
]
|
||||
if secondary_link:
|
||||
cta_links.append(
|
||||
html.A(
|
||||
secondary_link["label"],
|
||||
href=secondary_link["href"],
|
||||
target="_blank",
|
||||
rel="noopener noreferrer",
|
||||
className="pc-link-secondary",
|
||||
)
|
||||
)
|
||||
|
||||
return html.Div(
|
||||
html.Div(
|
||||
@@ -227,6 +474,7 @@ def build_cloud_layout(feature):
|
||||
feature["description"],
|
||||
className="pc-card-desc",
|
||||
),
|
||||
*([_screenshot(screenshot)] if screenshot else []),
|
||||
html.Ul(
|
||||
[
|
||||
_benefit_item(benefit)
|
||||
@@ -234,13 +482,13 @@ def build_cloud_layout(feature):
|
||||
],
|
||||
className="pc-benefits",
|
||||
),
|
||||
html.A(
|
||||
"Upgrade to Prowler Cloud",
|
||||
href=cta_url,
|
||||
target="_blank",
|
||||
rel="noopener noreferrer",
|
||||
className="pc-cta",
|
||||
*([_steps_block(steps)] if steps else []),
|
||||
*(
|
||||
[html.Div(cta_links, className="pc-cta-row")]
|
||||
if secondary_link
|
||||
else cta_links
|
||||
),
|
||||
*([html.P(note, className="pc-note")] if note else []),
|
||||
],
|
||||
className="pc-card-body",
|
||||
),
|
||||
@@ -260,6 +508,6 @@ for _feature in CLOUD_FEATURES:
|
||||
f"cloud_{_feature['slug'].replace('-', '_')}",
|
||||
path=_feature["route"],
|
||||
name=_feature["nav_label"],
|
||||
title=f"Prowler Dashboard - {_feature['page_title']}",
|
||||
title=f"Prowler Local Dashboard - {_feature['page_title']}",
|
||||
layout=build_cloud_layout(_feature),
|
||||
)
|
||||
@@ -80,6 +80,8 @@ Prowler Features are considered proper nouns. They are to be referenced without
|
||||
|
||||
Prowler ships two product families. Use these names exactly; the former names Prowler App (now Prowler Local Server) and Prowler Enterprise (now Prowler Private Cloud) must not appear in new writing. The only allowed former-name notes are on the Prowler Product Families page (`getting-started/products/index.mdx`) and in the site-wide banner, which document the mapping.
|
||||
|
||||
Web UI guides are written for Prowler Cloud. Steps that open the application link to `https://cloud.prowler.com/sign-in`, and the scope is stated once at the top of the section with the AppliesTo banner (see [AppliesTo Banner for Product Scope](#appliesto-banner-for-product-scope)). Do not offer Prowler Local Server as an inline alternative in a step list ("Go to Prowler Cloud or launch Prowler Local Server"). Mention Prowler Local Server inline only where its behavior differs, for example a feature that is not available or an environment variable that must be set.
|
||||
|
||||
Prowler Products:
|
||||
|
||||
* **Prowler Cloud**
|
||||
@@ -573,7 +575,7 @@ The default covers Prowler Cloud, Prowler Private Cloud, and Prowler Local Serve
|
||||
<AppliesTo products={["Prowler Cloud", "Prowler Private Cloud"]} />
|
||||
```
|
||||
|
||||
Place it on its own line below the Version Badge when one is present, otherwise directly after the imports.
|
||||
Place it on its own line below the Version Badge when one is present, otherwise directly after the imports. On a page that also covers Prowler CLI, such as the provider getting-started guides, place it under the `## Prowler Cloud` heading instead, so it scopes only the web UI section.
|
||||
|
||||
### Cloud Marker for Subscription Content
|
||||
|
||||
|
||||
@@ -4,6 +4,149 @@ description: "New features and improvements in each Prowler release"
|
||||
rss: true
|
||||
---
|
||||
|
||||
<Update label="v5.47.0" description="October 29, 2026">
|
||||
### 🧭 Add Provider — One Step to Connect Any Provider
|
||||
|
||||
The add-provider wizard follows the same model for every provider: one connect step, then the launch step. Adding an Azure subscription used to take six screens; now the account, its alias and its credentials are one form, and a single submit registers the account, stores the secret and tests the connection.
|
||||
|
||||
- A failed connection test shows inline, keeps the values and turns the action into **Retry connection** without registering the account twice.
|
||||
- Access methods show as cards on the same step for AWS, GCP, Microsoft 365, GitHub, Alibaba Cloud and Cloudflare, and AWS, Azure and GCP switch to their organization flow through tabs.
|
||||
- Users with the Manage Account permission can invite a teammate to finish the connection for every provider, not only AWS.
|
||||
- **Ask Lighthouse AI** opens beside the wizard without closing it. It guides the connection from the Prowler docs by default and, when asked, connects the provider itself after confirming the provider, UID and credential field names, never echoing a secret back.
|
||||
- Product tours and the guided onboarding sequence are removed; new tenants still land on the add-provider wizard.
|
||||
|
||||
### 🎯 Issues — Verdicts Explained in the List
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
- The issue list leads with the verdict, shows the attack path under each title and adds a **Why** column with the five evidence legs, so the reason for a verdict reads without opening the issue. The summary tiles become one line with the folded findings and the findings already covered by a control.
|
||||
- A row opens in the side panel, next to Lighthouse AI, with the verdict, legs, remediation, triage and an **Attack path** tab with the issue graph, instead of expanding the issue inline.
|
||||
- The menu entry is renamed to **Exploitability issues**.
|
||||
|
||||
### ☁️ Azure and AWS Checks
|
||||
|
||||
- `network_application_gateway_waf_enabled` and `network_application_gateway_http2_enabled` cover Azure Application Gateways and their WAF policies, mapped to CIS 5.0 and 6.0. Contributed by @BabafemiB. Thank you!
|
||||
- `iam_subscription_owners_between_2_and_3` verifies that each Azure subscription has between two and three Owners, counting owners inherited from management groups, mapped to CIS 5.0 and 6.0. Contributed by @BabafemiB. Thank you!
|
||||
- The AWS security group internet exposure checks treat CIDRs in one ingress rule that together cover `0.0.0.0/0` or `::/0`, such as `0.0.0.0/1` plus `128.0.0.0/1`, as open to the internet; such a rule used to pass `ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22` and its siblings. Contributed by @harshrajdebug. Thank you!
|
||||
|
||||
Explore all checks at [Prowler Hub](https://hub.prowler.com/check).
|
||||
|
||||
### 🛠️ Improvements and Fixes
|
||||
|
||||
- Every MCP Server tool declares `readOnlyHint` and `destructiveHint`, so clients can tell a listing from a write when deciding whether to ask the user. Mutelist and mute rule writes are marked destructive because they hide findings for good.
|
||||
- The cross-provider compliance tab keeps the frameworks that loaded, with a warning, when one catalog page or framework fails, instead of going blank.
|
||||
- The NATO D32 compliance framework has its own icon on the compliance cards.
|
||||
- Lighthouse AI replies no longer blink when the response finishes streaming, and the user avatar no longer requests the current page as an image, which rendered every page twice.
|
||||
- The Invitations page uses **Status** instead of **State**, and the Invitations and Roles filters show capitalized options.
|
||||
- **Prowler Cloud:** actions refused because the tenant is over its plan's usage limit, such as launching a scan, saving a schedule, adding a provider or triaging a finding, now link to the plans page.
|
||||
- **Prowler Cloud:** creating API keys is a Prowler Cloud feature; on Local Server the button opens the upgrade dialog, and existing keys can still be edited and revoked.
|
||||
- **Prowler Cloud:** issue evaluation after a scan reads the scan's findings through their indexes instead of a server-side cursor that took hours on large scans, so the evaluations no longer queue up behind each other.
|
||||
- **Prowler Private Cloud:** Lighthouse AI can use Amazon Bedrock with only a region, relying on the installation's own AWS role, when the API allows it.
|
||||
|
||||
### 🔐 Security Updates
|
||||
|
||||
- `pymongo` upgraded to 4.18.3, `dnspython` to 2.9.0 and `oauthlib` to 4.0.0 in the SDK and the API container image, patching the BSON decoding and DNS compression pointer denial of service advisories and the OAuth device flow token disclosure. The API also moves `django-allauth` to 65.19.7, since earlier releases cap `oauthlib` below 4.
|
||||
- `Authlib` upgraded to 1.9.1 and `dnspython` to 2.9.0 in the MCP Server container image, patching CVE-2026-96760 (critical), CVE-2026-114615, CVE-2026-114616 and CVE-2026-114605.
|
||||
|
||||
### 🙌 External Contributors
|
||||
|
||||
Thank you to our community contributors for this release!
|
||||
|
||||
- @BabafemiB: Azure `network_application_gateway_waf_enabled`, `network_application_gateway_http2_enabled` and `iam_subscription_owners_between_2_and_3` checks ([#13160](https://github.com/prowler-cloud/prowler/pull/13160), [#13145](https://github.com/prowler-cloud/prowler/pull/13145))
|
||||
- @harshrajdebug: AWS security group internet exposure fix for split CIDRs ([#13191](https://github.com/prowler-cloud/prowler/pull/13191))
|
||||
|
||||
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.47.0) for the complete list of changes.
|
||||
</Update>
|
||||
|
||||
<Update label="v5.46.0" description="October 26, 2026">
|
||||
### 🎯 Issues — Attack Paths with an Exploitability Verdict
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
A scan returns a list of failed checks, and most of them are one problem seen from several angles. The new **Issues** page folds the failed findings of each account into attack paths and gives each one a verdict: `exploitable`, `likely`, `conditional`, `not_exploitable` or `unknown`. The verdict is drawn from five legs of evidence (reachability, access, target, controls and verification), each naming the checks and resources it was read from, so a reader can disagree with it instead of having to trust it. A control in force moves a verdict one rung down and the issue says which control did it; a check that did not run reads as `unknown`, never as safe.
|
||||
|
||||
- Patterns cover AWS, Azure, GCP and Microsoft 365 and are keyed on Prowler check IDs, so detection is never reimplemented. Six more AWS paths ship in this release: Lambda functions anyone can invoke, snapshots shared with every AWS account, instances that can leak their role's credentials, database ports open to the internet, stale or weakly protected IAM user credentials, and roles another AWS account can use.
|
||||
- Each issue says what the path buys an attacker and where the attacker has to start, lists the compliance frameworks its findings put at risk, and can be sent to Jira as one grouped ticket.
|
||||
- An attack path can be triaged with a status and notes from its detail. The decision is kept per account and pattern, so it survives the next scan, which marks it resolved when the path is gone and reopened when it comes back.
|
||||
- The issue list opens with summary tiles and names the account of each issue, so two accounts with the same misconfiguration no longer read as one row. Each issue shows its risk factors, the triage status of every folded finding and an issue graph that starts internet-facing paths at the Internet node, drawn only where a reachability check failed on the resource, and has a page of its own to share.
|
||||
- The Overview shows a verdict share card and the top issues with their risk factors, with counts scoped to the latest completed scan of each provider.
|
||||
- **API:** `GET /api/v1/issues`, `GET /api/v1/issues/{id}` and `GET /api/v1/issues/overview`, with `filter[outcome__in]`, `filter[attacker_start__in]` and `filter[triage_status__in]`; triage lives at `/api/v1/issues/{id}/triage` and `/api/v1/issues/{id}/triage/notes`.
|
||||
|
||||
### 🐘 PHP — Coverage After the October Security Release
|
||||
|
||||
The PHP project shipped an emergency release on October 21 fixing two unauthenticated remote code execution flaws (CVE-2026-109618, CVE-2026-109619), with no fix for 8.1 and older branches. Four new checks cover how each cloud exposes the runtime, and the Azure runtime checks no longer fail every version newer than the configured one.
|
||||
|
||||
- **AWS:** `elasticbeanstalk_environment_php_platform_updated` flags PHP environments on branches without security fixes or on platform versions older than the one bundling the fixed release, `lightsail_instance_php_blueprint_updated` reports instances created from PHP blueprints for manual verification, and `wafv2_webacl_managed_rule_groups_enabled` verifies that a Web ACL references at least one managed rule group.
|
||||
- **GCP:** new `appengine` service and `appengine_version_php_runtime_supported` check, flagging App Engine serving versions on PHP runtimes without security fixes.
|
||||
- **Azure:** `app_ensure_php_version_is_latest`, `app_ensure_python_version_is_latest` and `app_ensure_java_version_is_latest` now pass any runtime at or above the configured minimum, and the default `php_latest_version` is raised from `8.2` to `8.5` in the SDK and in the scan configuration template of Prowler App.
|
||||
|
||||
The version thresholds are configurable; see the [configuration file documentation](https://docs.prowler.com/user-guide/cli/tutorials/configuration_file).
|
||||
|
||||
### 🧠 GCP — Vertex AI Workbench Coverage
|
||||
|
||||
A new Vertex AI service audits Workbench instances with five checks, all contributed by @rajeshnadiminti. Thank you!
|
||||
|
||||
- `vertexai_workbench_instance_public_ip_disabled` and `vertexai_workbench_instance_root_access_disabled`, mapped to PCI DSS 4.0.
|
||||
- `vertexai_workbench_instance_terminal_access_disabled`, `vertexai_workbench_instance_file_download_disabled` and `vertexai_workbench_instance_auto_upgrade_enabled`, with the Workbench checks mapped to the CCC MLDE requirements.
|
||||
|
||||
### 🤖 AWS — Bedrock and Cognito Checks
|
||||
|
||||
- `bedrock_guardrail_sensitive_information_filter_enforced` flags guardrails whose PII entities or custom regexes take no action on the output path, so a detected match is reported in the trace and still delivered to the caller. Contributed by @tamg-aws. Thank you!
|
||||
- `bedrock_model_invocation_logging_text_data_enabled` verifies that model invocation logging delivers prompt and response text rather than naming a destination while excluding request and response bodies. Contributed by @tamg-aws. Thank you!
|
||||
- `cognito_user_pool_account_recovery_sms_not_preferred` verifies that user pools do not rank SMS first for account recovery. Contributed by @UTKARSH698. Thank you!
|
||||
- `bedrock_api_key_no_long_term_credentials` now reports long-term API keys that never expire; IAM only returns `ExpirationDate` for keys created with an expiration period, so keys created without one produced no finding at all. Also contributed by @tamg-aws.
|
||||
|
||||
Explore all checks at [Prowler Hub](https://hub.prowler.com/check).
|
||||
|
||||
### 📬 Google Workspace — Policy Precedence and Edition Context
|
||||
|
||||
- Checks now read the policy Google applies (the greatest `sortOrder`) instead of the last policy the Cloud Identity Policy API lists, which reported default values over admin-configured ones.
|
||||
- Checks for Chat, Drive, Gmail, Calendar and Groups pass with the reason stated when the app is turned off for everyone and no organizational unit or group turns it back on. Drive sharing settings and the settings of existing groups keep being evaluated, because existing shares and groups stay reachable.
|
||||
- Findings state when a feature is not sold on the tenant edition, such as Drive DLP rules, Google session control and the shared drive sharing settings on Business Starter. Reading editions needs the optional `apps.licensing` scope; see the [authentication documentation](https://docs.prowler.com/user-guide/providers/googleworkspace/authentication).
|
||||
- The Gmail spoofing checks read each protection action from its own Policy API field: the inbound domain spoofing check could never pass and the similar domain names check reported the inbound action.
|
||||
- `drive_publishing_files_disabled` passes when sharing outside the organization is limited to allowlisted domains or off, and `drive_desktop_access_disabled` remediation points to the Google Drive for desktop card in the Admin console.
|
||||
|
||||
### 🛠️ Improvements and Fixes
|
||||
|
||||
- Scan report generation parses each resource's metadata once per batch, sanitizes it once per finding and releases each batch before building the next one, so providers with very large resource metadata no longer exhaust the worker memory.
|
||||
- Scan ingestion compares the serialized resource metadata with the stored value and serializes sets in a stable order, so unchanged resources no longer get their metadata, details and `updated_at` rewritten on every scan.
|
||||
- The compliance report PDF cover table wraps every value, so long account IDs such as OCI tenancy OCIDs no longer overflow their cell.
|
||||
- **Launch an initial scan now** is ticked by default on the provider and organization setup launch steps, so saving a schedule also runs a first scan unless the box is unticked.
|
||||
- The SAML SSO integration card on the profile page is shown to roles with the Manage Account permission, matching the API requirement.
|
||||
- **Prowler Cloud:** the scan-reports worker replaces each Celery child process after one report, so the memory a large report needs is returned instead of pushing the task towards an out-of-memory kill, and a container health check replaces a worker that stops consuming instead of leaving it in steady state.
|
||||
- **Prowler Cloud:** persistent report, compliance and overview workers no longer shut themselves down when a task named like a scan hits its time limit or is revoked; the one-shot shutdown only applies to the ephemeral workers of the `scans` queue.
|
||||
|
||||
### 🔐 Security Updates
|
||||
|
||||
- Resource metadata attached to findings no longer carries the values that secrets checks scan (CloudFormation outputs, Lambda environment variables, EC2 user data and similar fields); they are replaced with `REDACTED` before reaching the Evidence tab, the API and the output files.
|
||||
- SAML SSO configuration endpoints require the `manage_account` permission instead of `manage_integrations`, since the configured IdP controls who signs in and which role they receive.
|
||||
- Attack Paths custom queries strip block comments before the blocked-operation check and drop APOC virtual nodes and relationships from results, so `LOAD CSV` can no longer be split with a comment nor its response returned under a forged provider label.
|
||||
- `GET /resources/metadata`, `GET /resources/metadata/latest`, `GET /findings/metadata` and `GET /findings/metadata/latest` honour the provider groups of the caller role instead of aggregating every provider in the tenant.
|
||||
- OpenStack `auth_url` values resolving to loopback, private or otherwise non-public hosts are rejected before the SDK connects, on both the connection test and the scan.
|
||||
- `djangorestframework` upgraded to 3.18.3 in the API, patching the JSON parser charset and Accept header denial of service and the TokenProxy revocation bypass.
|
||||
- `Pillow` upgraded to 13.0.0 in the API, patching a JPEG decoder heap overflow.
|
||||
- `certifi` upgraded to 2026.10.19 in the SDK, the API and the MCP Server, dropping the emSign root CAs whose intermediate keys were compromised, and `uv` to 0.13.4 in the SDK and API images for the same reason.
|
||||
- `aiohttp` upgraded to 3.14.5 with `yarl` 1.25.1 in the SDK and the API, patching the aiohttp resource exhaustion advisories.
|
||||
- `black` upgraded to 26.10.1 and `graphql-core` to 3.2.12 in the SDK, patching CVE-2026-114134, CVE-2026-75507 and CVE-2026-75508, and `libbrotli1` to 1.1.0-2+deb13u1 in its container image, patching CVE-2026-111497.
|
||||
- Trivy upgraded to 0.76.1 in the SDK and API images, patching the HTTP/2, TLS and Go 1.27.2 stdlib CVEs bundled in Trivy 0.75.0, and the SDK and API images no longer ship the MicrosoftTeams build SBOM that Trivy read as installed `zod` and `body-parser`.
|
||||
- `next` upgraded to 16.4.1 in the UI, patching two unauthenticated remote code executions (Image Optimization with SVG files, GHSA-2464-fhr4-w9xw; `use cache` functions returning typed arrays, GHSA-673q-mjm6-r5xr), `@sentry/nextjs` to 10.75.2 for the middleware bypass through the tunnel route (GHSA-mchm-gjx6-7h55) and DOMPurify to 3.4.17 for the `SAFE_FOR_TEMPLATES` bypass (GHSA-cg2g-xh64-v4q7).
|
||||
- Node.js upgraded to 24.22.1 LTS in the UI container image, patching the eight CVEs of the October 2026 security release that affect the 24.x line, including the Brotli decoder remote code execution (CVE-2026-111497); the base image moves to Alpine 3.24.
|
||||
|
||||
### 🙌 External Contributors
|
||||
|
||||
Thank you to our community contributors for this release!
|
||||
|
||||
- @rajeshnadiminti: GCP Vertex AI service and its Workbench checks ([#13062](https://github.com/prowler-cloud/prowler/pull/13062), [#13077](https://github.com/prowler-cloud/prowler/pull/13077))
|
||||
- @tamg-aws: AWS `bedrock_guardrail_sensitive_information_filter_enforced` and `bedrock_model_invocation_logging_text_data_enabled` checks and the `bedrock_api_key_no_long_term_credentials` fix ([#12663](https://github.com/prowler-cloud/prowler/pull/12663))
|
||||
- @UTKARSH698: AWS `cognito_user_pool_account_recovery_sms_not_preferred` check ([#12251](https://github.com/prowler-cloud/prowler/pull/12251))
|
||||
|
||||
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.46.0) for the complete list of changes.
|
||||
</Update>
|
||||
|
||||
<Update label="v5.45.0" description="October 15, 2026">
|
||||
### 🤖 Azure — AI Services Coverage
|
||||
|
||||
|
||||
@@ -3251,7 +3251,7 @@ If your provider has specific fields, add them to the finding transformation:
|
||||
**File:** `prowler/lib/outputs/finding.py`
|
||||
|
||||
```python
|
||||
def transform_api_finding(cls, finding, provider) -> "Finding":
|
||||
def transform_api_finding(cls, finding, provider, metadata_cache=None) -> "Finding":
|
||||
# ... existing code ...
|
||||
|
||||
# Your provider specific field
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
title: "Installation"
|
||||
---
|
||||
|
||||
<Tip>
|
||||
[Prowler Cloud](https://cloud.prowler.com/sign-up) runs the same application with no installation or maintenance. Follow the [Prowler Cloud guide](/user-guide/tutorials/prowler-app) to start scanning right away.
|
||||
</Tip>
|
||||
|
||||
### Installation
|
||||
|
||||
Prowler Local Server offers flexible installation methods tailored to various environments.
|
||||
|
||||
Refer to the [Prowler Cloud guide](/user-guide/tutorials/prowler-app) for detailed usage instructions.
|
||||
Refer to the [Prowler Cloud guide](/user-guide/tutorials/prowler-app) for detailed usage instructions, which apply to Prowler Local Server as well.
|
||||
|
||||
<Warning>
|
||||
Prowler configuration is based on `.env` files. Every version of Prowler can have differences on that file, so, please, use the file that corresponds with that version or repository branch or tag.
|
||||
@@ -128,8 +132,8 @@ To update the environment file:
|
||||
Edit the `.env` file and change version values:
|
||||
|
||||
```env
|
||||
PROWLER_UI_VERSION="5.45.0"
|
||||
PROWLER_API_VERSION="5.45.0"
|
||||
PROWLER_UI_VERSION="5.47.0"
|
||||
PROWLER_API_VERSION="5.47.0"
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: 'Overview'
|
||||
---
|
||||
|
||||
Prowler Local Server is a self-hosted web application that simplifies running Prowler. It provides:
|
||||
Prowler Local Server is a self-hosted web application that simplifies running Prowler. It is the open source counterpart of [Prowler Cloud](/getting-started/products/prowler-cloud), which runs the same application as a managed service. It provides:
|
||||
|
||||
- **User-friendly interface** for configuring and executing scans
|
||||
- Dashboard to **view results** and manage **security findings**
|
||||
|
||||
|
Before Width: | Height: | Size: 210 KiB |
@@ -4,6 +4,10 @@
|
||||
|
||||

|
||||
|
||||
<Card title="Get started with Prowler Cloud" icon="cloud" href="https://cloud.prowler.com/sign-up">
|
||||
The fastest way to run Prowler: create an account, connect a provider, and get findings without installing anything. No billing details are required during the trial period.
|
||||
</Card>
|
||||
|
||||
Prowler ships two product families: Prowler Products, operated or licensed by the Prowler team, and Open Source projects, free to run and extend. See [Prowler product families](/getting-started/products) for every official name, including former names.
|
||||
|
||||
### Prowler Products
|
||||
@@ -25,6 +29,8 @@ Prowler ships two product families: Prowler Products, operated or licensed by th
|
||||
|
||||
### Open Source
|
||||
|
||||
Free to run and extend. Prefer the Open Source projects to run Prowler from a terminal, in CI/CD, or on infrastructure you operate yourself.
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Prowler CLI" icon="terminal" href="/getting-started/products/prowler-cli">
|
||||
Command line tool to run security scans across all supported providers.
|
||||
|
||||
@@ -24,7 +24,13 @@ Make sure you update the `/your/local/dir/prowler-output` to match the path that
|
||||
**Remember that the `dashboard` server is not authenticated. If you expose it to the Internet, do it at your own risk.**
|
||||
|
||||
</Note>
|
||||
The banner and additional info about the dashboard will be shown on your console: <img src="/images/cli/dashboard/dashboard-banner.png" />
|
||||
The console shows the dashboard version, the directory the CSV files are loaded from, the local URL once the server is listening, and a short note about bringing your findings to Prowler Cloud. Set `PROWLER_NO_PROMOTIONS=1` to omit that note:
|
||||
|
||||
```sh
|
||||
PROWLER_NO_PROMOTIONS=1 prowler dashboard
|
||||
```
|
||||
|
||||
Set `NO_COLOR=1` to print the startup output without colors.
|
||||
|
||||
## Overview Page
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: 'Compliance'
|
||||
sidebarTitle: 'Overview'
|
||||
description: 'Run security checks against compliance frameworks, review posture across providers, use Compliance Watchlist to pin frameworks, and download CSV or PDF reports from Prowler Cloud and Prowler Local Server, or CSV reports from Prowler CLI.'
|
||||
description: 'Run security checks against compliance frameworks, review posture across providers, use Compliance Watchlist to pin frameworks, and download CSV or PDF reports from Prowler Cloud, or CSV reports from Prowler CLI.'
|
||||
---
|
||||
|
||||
import { SubscriptionBanner } from "/snippets/subscription-banner.mdx"
|
||||
@@ -24,15 +24,15 @@ For the unified compliance score methodology used across frameworks, see [Prowle
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Prowler Cloud and Prowler Local Server
|
||||
## Prowler Cloud
|
||||
|
||||
The Compliance section in Prowler Cloud and Prowler Local Server centralizes compliance posture across every connected provider. It aggregates scan results, surfaces Prowler ThreatScore, and exposes detailed requirement-level evidence for each supported framework.
|
||||
The Compliance section in Prowler Cloud centralizes compliance posture across every connected provider. It aggregates scan results, surfaces Prowler ThreatScore, and exposes detailed requirement-level evidence for each supported framework.
|
||||
|
||||
### Accessing the Compliance Section
|
||||
|
||||
To open the compliance overview, follow these steps:
|
||||
|
||||
1. Sign in to Prowler Cloud at [cloud.prowler.com](https://cloud.prowler.com/sign-in) or to a Prowler Local Server instance.
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in).
|
||||
2. Select **Compliance** from the left navigation.
|
||||
|
||||
The Compliance page is organized into two tabs:
|
||||
@@ -42,10 +42,10 @@ The Compliance page is organized into two tabs:
|
||||
|
||||
<SubscriptionBanner label="Multiple Scans tab" />
|
||||
|
||||
<img src="/images/compliance/prowler-app-compliance-overview.png" alt="Compliance overview page in Prowler Cloud and Prowler Local Server showing filters, the Prowler ThreatScore card, and the framework grid" width="900" />
|
||||
<img src="/images/compliance/prowler-app-compliance-overview.png" alt="Compliance overview page in Prowler Cloud showing filters, the Prowler ThreatScore card, and the framework grid" width="900" />
|
||||
|
||||
<Note>
|
||||
Compliance results require at least one completed scan. If no scan has finished yet, Prowler Cloud and Prowler Local Server display a notice prompting to launch or wait for a scan to complete.
|
||||
Compliance results require at least one completed scan. If no scan has finished yet, Prowler Cloud displays a notice prompting to launch or wait for a scan to complete.
|
||||
</Note>
|
||||
|
||||
### Filtering Compliance Results
|
||||
@@ -196,7 +196,7 @@ Frameworks without a custom layout fall back to the generic details panel, which
|
||||
|
||||
### Downloading Compliance Reports
|
||||
|
||||
Prowler Cloud and Prowler Local Server expose two formats:
|
||||
Prowler Cloud exposes two formats:
|
||||
|
||||
* **CSV report:** Every requirement, every check, and every finding for the selected scan and filters. Available for all supported frameworks.
|
||||
* **PDF report:** Curated executive-style report. Currently supported for Prowler ThreatScore, ENS RD2022, NIS2, and CSA CCM. Additional PDF reports are added in subsequent Prowler releases.
|
||||
@@ -234,7 +234,7 @@ Use the API to integrate compliance evidence into ticketing systems, executive d
|
||||
|
||||
## Prowler CLI
|
||||
|
||||
Prowler CLI evaluates the same compliance frameworks as Prowler Cloud and Prowler Local Server, and produces detailed CSV outputs alongside the standard scan results. By default, it runs every supported framework and prints a status summary at the end of the scan:
|
||||
Prowler CLI evaluates the same compliance frameworks as Prowler Cloud, and produces detailed CSV outputs alongside the standard scan results. By default, it runs every supported framework and prints a status summary at the end of the scan:
|
||||
|
||||
<img src="/images/cli/compliance/compliance.png" />
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ title: 'Getting Started With Alibaba Cloud on Prowler'
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
Prowler supports Alibaba Cloud both from the CLI and from Prowler Cloud. This guide walks you through the requirements, how to connect the provider in the UI, and how to run scans from the command line.
|
||||
|
||||
@@ -29,6 +30,8 @@ Before you begin, make sure you have:
|
||||
|
||||
<VersionBadge version="5.18.0" />
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Step 1: Get Your Alibaba Cloud Account ID
|
||||
|
||||
1. Log in to the [Alibaba Cloud Console](https://home.console.alibabacloud.com/)
|
||||
@@ -39,7 +42,7 @@ Before you begin, make sure you have:
|
||||
|
||||
### Step 2: Access Prowler Cloud
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up)
|
||||
2. Go to "Configuration" > "Providers"
|
||||
|
||||

|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
title: 'Getting Started With AWS on Prowler'
|
||||
---
|
||||
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
## Prowler Cloud
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
<iframe width="560" height="380" src="https://www.youtube-nocookie.com/embed/RPgIWOCERzY" title="Prowler Cloud Onboarding AWS" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="1"></iframe>
|
||||
|
||||
> Walkthrough video onboarding an AWS Account using Assumed Role.
|
||||
@@ -18,7 +22,7 @@ title: 'Getting Started With AWS on Prowler'
|
||||
|
||||
### Step 2: Access Prowler Cloud
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up)
|
||||
2. Go to "Configuration" > "Providers"
|
||||
|
||||

|
||||
@@ -75,7 +79,7 @@ For detailed instructions on how to create the role, see [Authentication > Assum
|
||||
|
||||

|
||||
|
||||
9. Paste the ARN into the corresponding field in Prowler Cloud or Prowler Local Server
|
||||
9. Paste the ARN into the corresponding field in Prowler Cloud
|
||||
|
||||

|
||||
|
||||
@@ -110,7 +114,7 @@ AWS accounts can also be configured using static credentials (not recommended fo
|
||||
|
||||
For detailed instructions on how to create the credentials, see [Authentication > Credentials](/user-guide/providers/aws/authentication#credentials).
|
||||
|
||||
1. Complete the form in Prowler Cloud or Prowler Local Server and click "Next"
|
||||
1. Complete the form in Prowler Cloud and click "Next"
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
title: 'Getting Started With Azure on Prowler'
|
||||
---
|
||||
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
## Prowler Cloud
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
<iframe width="560" height="380" src="https://www.youtube-nocookie.com/embed/v1as8vTFlMg" title="Prowler Cloud Onboarding Azure" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="1"></iframe>
|
||||
> Walkthrough video onboarding an Azure Subscription using Service Principal.
|
||||
|
||||
@@ -34,7 +38,7 @@ For detailed instructions on how to create the Service Principal and configure p
|
||||
|
||||
### Step 2: Access Prowler Cloud
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up)
|
||||
2. Navigate to `Configuration` > `Providers`
|
||||
|
||||

|
||||
|
||||
@@ -3,6 +3,7 @@ title: 'Getting Started With Cloudflare on Prowler'
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
Prowler for Cloudflare scans zones for security misconfigurations, including SSL/TLS settings, DNSSEC, HSTS, WAF rules, DNS records, and more.
|
||||
|
||||
@@ -36,6 +37,8 @@ Both links open the Cloudflare Dashboard with the seven required read-only scope
|
||||
|
||||
<VersionBadge version="5.19.0" />
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Step 1: Locate the Account ID
|
||||
|
||||
1. Log into the [Cloudflare Dashboard](https://dash.cloudflare.com).
|
||||
@@ -50,7 +53,7 @@ The Account ID is a 32-character hexadecimal string (e.g., `372e67954025e0ba6aaa
|
||||
|
||||
### Step 2: Open Prowler Cloud
|
||||
|
||||
1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up).
|
||||
2. Navigate to "Configuration" > "Providers".
|
||||
|
||||

|
||||
|
||||
@@ -89,7 +89,7 @@ This method uses the Google Cloud CLI to authenticate and is suitable for develo
|
||||
|
||||

|
||||
|
||||
8. Extract the following values for Prowler Cloud or Prowler Local Server:
|
||||
8. Extract the following values for Prowler Cloud:
|
||||
|
||||
- `client_id`
|
||||
- `client_secret`
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
title: 'Getting Started With GCP on Prowler'
|
||||
---
|
||||
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
## Prowler Cloud
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Step 1: Get the GCP Project ID
|
||||
|
||||
1. Go to the [GCP Console](https://console.cloud.google.com/)
|
||||
@@ -13,7 +17,7 @@ title: 'Getting Started With GCP on Prowler'
|
||||
|
||||
### Step 2: Access Prowler Cloud
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up)
|
||||
2. Go to "Configuration" > "Providers"
|
||||
|
||||

|
||||
|
||||
@@ -272,7 +272,7 @@ Store the `.pem` private key securely. Anyone with this key can authenticate as
|
||||
|
||||
## Prowler Cloud Authentication
|
||||
|
||||
For step-by-step setup instructions for Prowler Cloud, see the [Getting Started Guide](/user-guide/providers/github/getting-started-github#prowler-cloud-and-prowler-local-server).
|
||||
For step-by-step setup instructions for Prowler Cloud, see the [Getting Started Guide](/user-guide/providers/github/getting-started-github#prowler-cloud).
|
||||
|
||||
### Using Personal Access Token
|
||||
|
||||
@@ -302,7 +302,7 @@ For step-by-step setup instructions for Prowler Cloud, see the [Getting Started
|
||||
|
||||
3. Enter your GitHub App ID and upload the private key (`.pem` file).
|
||||
|
||||
For complete step-by-step instructions, see the [Getting Started Guide](/user-guide/providers/github/getting-started-github#prowler-cloud-and-prowler-local-server).
|
||||
For complete step-by-step instructions, see the [Getting Started Guide](/user-guide/providers/github/getting-started-github#prowler-cloud).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: 'Getting Started with GitHub'
|
||||
---
|
||||
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
This guide covers setting up GitHub security scanning with Prowler. Choose a preferred interface below:
|
||||
|
||||
<Note>
|
||||
@@ -16,7 +18,7 @@ Prowler can scan either:
|
||||
</Note>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Prowler Cloud & Local Server" icon="cloud" href="#prowler-cloud-and-prowler-local-server">
|
||||
<Card title="Prowler Cloud" icon="cloud" href="#prowler-cloud">
|
||||
Web-based interface with centralized management
|
||||
</Card>
|
||||
<Card title="Prowler CLI" icon="terminal" href="#prowler-cli">
|
||||
@@ -26,7 +28,9 @@ Prowler can scan either:
|
||||
|
||||
---
|
||||
|
||||
## Prowler Cloud and Prowler Local Server
|
||||
## Prowler Cloud
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
<iframe width="560" height="380" src="https://www.youtube-nocookie.com/embed/9ETI84Xpu2g" title="Prowler Cloud Onboarding Github" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="1"></iframe>
|
||||
|
||||
@@ -34,7 +38,7 @@ Prowler can scan either:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before adding GitHub to Prowler Cloud or Prowler Local Server, ensure you have:
|
||||
Before adding GitHub to Prowler Cloud, ensure you have:
|
||||
|
||||
1. **GitHub Account Access**
|
||||
- Personal GitHub account, OR
|
||||
@@ -46,9 +50,9 @@ Before adding GitHub to Prowler Cloud or Prowler Local Server, ensure you have:
|
||||
- OAuth App Token
|
||||
- GitHub App Credentials (Not Recommended - limited data access)
|
||||
|
||||
### Step 1: Access Prowler Cloud or Prowler Local Server
|
||||
### Step 1: Access Prowler Cloud
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up)
|
||||
2. Go to **Configuration** → **Providers**
|
||||
|
||||

|
||||
|
||||
@@ -3,6 +3,7 @@ title: 'Getting Started With Google Workspace on Prowler'
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
Prowler for Google Workspace audits the organization's Google Workspace environment for security misconfigurations, including super administrator account hygiene, domain settings, and more.
|
||||
|
||||
@@ -28,6 +29,8 @@ Set up authentication for Google Workspace with the [Google Workspace Authentica
|
||||
|
||||
<VersionBadge version="5.21.0" />
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Step 1: Locate the Customer ID
|
||||
|
||||
1. Log into the [Google Workspace Admin Console](https://admin.google.com).
|
||||
@@ -42,7 +45,7 @@ The Customer ID starts with the letter "C" followed by alphanumeric characters (
|
||||
|
||||
### Step 2: Open Prowler Cloud
|
||||
|
||||
1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up).
|
||||
2. Navigate to "Configuration" > "Providers".
|
||||
|
||||

|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: "Getting Started with the IaC Provider"
|
||||
---
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
Prowler's Infrastructure as Code (IaC) provider enables scanning of local or remote infrastructure code for security and compliance issues using [Trivy](https://trivy.dev/). This provider supports a wide range of IaC frameworks, allowing assessment of code before deployment.
|
||||
|
||||
@@ -35,13 +36,15 @@ Prowler IaC provider scans the following Infrastructure as Code configurations f
|
||||
|
||||
<VersionBadge version="5.14.0" />
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Supported Scanners
|
||||
|
||||
Scanner selection is not configurable in Prowler Cloud. Default scanners, misconfig and secret, run automatically during each scan.
|
||||
|
||||
### Step 1: Access Prowler Cloud or Prowler Local Server
|
||||
### Step 1: Access Prowler Cloud
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up)
|
||||
2. Go to "Configuration" > "Providers"
|
||||
|
||||

|
||||
|
||||
@@ -3,6 +3,7 @@ title: "Getting Started with the Image Provider"
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
Prowler's Image provider enables comprehensive container image security scanning by integrating with [Trivy](https://trivy.dev/). This provider detects vulnerabilities, exposed secrets, and misconfigurations in container images, converting Trivy findings into Prowler's standard reporting format for unified security assessment.
|
||||
|
||||
@@ -27,13 +28,15 @@ Prowler's Image provider enables comprehensive container image security scanning
|
||||
|
||||
<VersionBadge version="5.21.0" />
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Supported Scanners
|
||||
|
||||
Prowler Cloud does not support scanner selection. The vulnerability, secret, and misconfiguration scanners run automatically during each scan.
|
||||
|
||||
### Step 1: Access Prowler Cloud
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up)
|
||||
2. Navigate to "Configuration" > "Providers"
|
||||
|
||||

|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
title: 'Getting Started with Kubernetes'
|
||||
---
|
||||
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
## Prowler Cloud
|
||||
|
||||
### Step 1: Access Prowler Cloud or Prowler Local Server
|
||||
<AppliesTo />
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
|
||||
### Step 1: Access Prowler Cloud
|
||||
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up)
|
||||
2. Go to "Configuration" > "Providers"
|
||||
|
||||

|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: 'Getting Started With Microsoft 365 on Prowler'
|
||||
---
|
||||
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
<Note>
|
||||
**Government Cloud Support**
|
||||
|
||||
@@ -29,6 +31,8 @@ Set up authentication for Microsoft 365 with the [Microsoft 365 Authentication](
|
||||
|
||||
## Prowler Cloud
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Step 1: Locate the Domain ID
|
||||
|
||||
1. Open the Entra ID portal, then search for "Domain" or go to Identity > Settings > Domain Names.
|
||||
@@ -41,7 +45,7 @@ Set up authentication for Microsoft 365 with the [Microsoft 365 Authentication](
|
||||
|
||||
### Step 2: Open Prowler Cloud
|
||||
|
||||
1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up).
|
||||
2. Navigate to "Configuration" > "Providers".
|
||||
|
||||

|
||||
|
||||
@@ -15,7 +15,7 @@ Before you begin, make sure you have:
|
||||
3. An **API Key pair** (public and private keys) with appropriate permissions:
|
||||
- **Organization Read Only**: Provides read-only access to everything in the organization, including all projects in the organization. This permission is sufficient for most security checks.
|
||||
- **Organization Owner**: Required to audit the [Auditing configuration](https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/group/endpoint-auditing) for projects. Database auditing tracks database operations and security events, including authentication attempts, data definition language (DDL) changes, user and role modifications, and privilege grants. This configuration is essential for security monitoring, forensics, and compliance. Without **Organization Owner** permission, the `projects_auditing_enabled` check cannot retrieve the audit configuration status.
|
||||
4. Access to Prowler Cloud or Prowler Local Server, or Prowler CLI (`pip install prowler`).
|
||||
4. A [Prowler Cloud](https://cloud.prowler.com/sign-up) account, or Prowler CLI (`pip install prowler`).
|
||||
|
||||
For detailed instructions on creating API keys, see the [MongoDB Atlas authentication guide](./authentication.mdx).
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
title: 'Getting Started with Oracle Cloud Infrastructure (OCI)'
|
||||
---
|
||||
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
Prowler supports security scanning of Oracle Cloud Infrastructure (OCI) environments. This guide will help you get started with using Prowler to audit your OCI tenancy.
|
||||
|
||||
## Prowler Cloud
|
||||
|
||||
The following steps apply to Prowler Cloud and Prowler Local Server.
|
||||
<AppliesTo />
|
||||
|
||||
### Step 1: Collect OCI Identifiers
|
||||
1. Sign in to the [OCI Console](https://cloud.oracle.com/) and open **Tenancy Details** to copy the Tenancy OCID.
|
||||
@@ -15,7 +17,7 @@ The following steps apply to Prowler Cloud and Prowler Local Server.
|
||||
4. In **Tenancy Details**, note the **Home Region** identifier (for example, `me-abudhabi-1`). Any other region the tenancy is subscribed to also works.
|
||||
|
||||
### Step 2: Access Prowler Cloud
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up).
|
||||
2. Go to **Configuration** → **Providers** and click **Add Provider**.
|
||||

|
||||
3. Select **Oracle Cloud** and enter the **Tenancy OCID** and an optional alias, then choose **Next**.
|
||||
@@ -342,7 +344,7 @@ prowler oci \
|
||||
|
||||
**Error: "OCI credential validation failed" with `401 NotAuthenticated` on `get_tenancy`**
|
||||
- The tenancy is not subscribed to the region used for validation (by default `us-ashburn-1`)
|
||||
- In Prowler Cloud or Prowler Local Server, update the provider credentials and select the tenancy home region
|
||||
- In Prowler Cloud, update the provider credentials and select the tenancy home region
|
||||
- In Prowler CLI, set `region` in `~/.oci/config` to the home region. All subscribed regions are still scanned; `--region` also works but limits the scan to that region
|
||||
|
||||
**Error: "Invalid region"**
|
||||
|
||||
@@ -3,6 +3,7 @@ title: 'Getting Started With Okta on Prowler'
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
Prowler for Okta scans an Okta organization for identity and session-management misconfigurations. The provider authenticates as a service application using **OAuth 2.0 with a private-key JWT** (Client Credentials grant) — no end-user login, read-only by scope.
|
||||
|
||||
@@ -28,9 +29,11 @@ Set up authentication for Okta with the [Okta Authentication](/user-guide/provid
|
||||
|
||||
<VersionBadge version="5.28.0" />
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Step 1: Add the Provider
|
||||
|
||||
1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up).
|
||||
2. Navigate to "Configuration" > "Providers".
|
||||
|
||||

|
||||
|
||||
@@ -611,6 +611,40 @@ If specific checks fail due to insufficient permissions:
|
||||
Using Public Cloud credentials can limit Keystone API access, so the command above may not work. Verify permissions in the provider's control panel instead.
|
||||
</Warning>
|
||||
|
||||
### Private Keystone Endpoints
|
||||
|
||||
Most OpenStack deployments keep Keystone on an internal network, which is the normal case rather than the exception.
|
||||
|
||||
**Prowler CLI scans a private cloud with no extra configuration:**
|
||||
|
||||
```console
|
||||
export OS_AUTH_URL="https://keystone.internal:5000/v3"
|
||||
prowler openstack
|
||||
```
|
||||
|
||||
On the CLI the operator supplies the `auth_url` themselves, so there is nothing to defend against and the check below does not apply.
|
||||
|
||||
**Prowler App rejects an `auth_url` that resolves to a non-public address**, as an SSRF defense, before the SDK connects and on both the connection test and the scan. There the URL arrives from a tenant and the worker must not be used to reach the internal network it runs in. To scan a private cloud 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 IaC providers. The Image provider has its own, `PROWLER_IMAGE_PROVIDER_ALLOWED_PRIVATE_NETWORKS`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Getting Started with OpenStack](/user-guide/providers/openstack/getting-started-openstack) - Run your first scan
|
||||
|
||||
@@ -3,6 +3,7 @@ title: "Getting Started With Vercel on Prowler"
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
Prowler for Vercel scans teams and projects for security misconfigurations, including deployment protection, environment variable exposure, WAF rules, domain configuration, team access controls, and more.
|
||||
|
||||
@@ -26,9 +27,11 @@ Set up authentication for Vercel with the [Vercel Authentication](/user-guide/pr
|
||||
|
||||
<VersionBadge version="5.23.0" />
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
### Step 1: Add the Provider
|
||||
|
||||
1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in) or [create an account](https://cloud.prowler.com/sign-up).
|
||||
2. Navigate to "Configuration" > "Providers".
|
||||
|
||||

|
||||
|
||||
@@ -99,7 +99,7 @@ export PROWLER_API_KEY="pk_example-api-key"
|
||||
|
||||
To create an API key:
|
||||
|
||||
1. Log in to Prowler Cloud or Prowler Local Server
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in)
|
||||
2. Click **Profile** → **Account**
|
||||
3. Click **Create API Key**
|
||||
4. Provide a descriptive name and optionally set an expiration date
|
||||
|
||||
@@ -51,7 +51,7 @@ export PROWLER_API_KEY="pk_example-api-key"
|
||||
|
||||
To create an API key:
|
||||
|
||||
1. Log in to Prowler Cloud or Prowler Local Server
|
||||
1. Sign in to [Prowler Cloud](https://cloud.prowler.com/sign-in)
|
||||
2. Click **Profile** → **Account**
|
||||
3. Click **Create API Key**
|
||||
4. Provide a descriptive name and optionally set an expiration date
|
||||
|
||||
@@ -4,22 +4,15 @@ sidebarTitle: 'Getting Started'
|
||||
---
|
||||
|
||||
import { ProviderCards } from "/snippets/provider-cards.mdx"
|
||||
import { AppliesTo } from "/snippets/applies-to.mdx"
|
||||
|
||||
<AppliesTo />
|
||||
|
||||
**Prowler Cloud** is a web application that simplifies running Prowler. This tutorial will guide you through setting up and using it.
|
||||
|
||||
**Prowler Local Server** is the self-hosted version of **Prowler Cloud**. See [Prowler product families](/getting-started/products) for every official product name.
|
||||
|
||||
## Accessing Prowler Cloud and API Documentation
|
||||
|
||||
If you are a [Prowler Cloud](https://cloud.prowler.com/sign-in) user, you can access API docs at [https://api.prowler.com/api/v1/docs](https://api.prowler.com/api/v1/docs)
|
||||
|
||||
<Note>
|
||||
**For Prowler Local Server users**
|
||||
|
||||
After [installing](/getting-started/installation/prowler-app) **Prowler Local Server**, access it at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
To view the auto-generated **Prowler API** documentation, navigate to [http://localhost:8080/api/v1/docs](http://localhost:8080/api/v1/docs). This documentation provides details on available endpoints, parameters, and responses.
|
||||
</Note>
|
||||
Sign in at [cloud.prowler.com](https://cloud.prowler.com/sign-in). The API documentation is available at [https://api.prowler.com/api/v1/docs](https://api.prowler.com/api/v1/docs).
|
||||
|
||||
## Step 1: Sign Up
|
||||
### Sign Up with Email
|
||||
@@ -85,20 +78,14 @@ For detailed instructions on configuring credentials for each provider, refer to
|
||||
<ProviderCards />
|
||||
|
||||
## Step 5: Test Connection
|
||||
After adding your cloud account credentials, click the `Check connection` button to verify that Prowler can successfully connect to your provider:
|
||||
|
||||
<img src="/images/test-connection-button.png" alt="Test Connection" width="700" />
|
||||
To verify the credentials, click the connect action at the bottom of the same step (for example, "Connect account", "Connect subscription" or "Connect project"). Prowler saves the provider and its credentials and tests the connection in that single action. When the test succeeds, the wizard moves straight to launching the scan. When it fails, the reason appears above the form and the values stay in place: click "Retry connection" to test again, or correct a field, which brings back the connect action, and click it. Neither adds the provider twice.
|
||||
|
||||
<Note>
|
||||
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.
|
||||
To delegate the connection, select "I don't have access, invite a teammate" on the same step when you cannot create the credentials yourself. The option is available for every provider to users who can manage the account. Prowler Local Server adds the invitation to the tenant and shows the link to share. Prowler Cloud also emails it.
|
||||
</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:
|
||||
Once the connection test succeeds, the wizard moves to the launch step, since the provider is already saved. To start the first scan, click "Launch scan", or "Save and launch scan" when scanning on a schedule. The `Scans` section shows the scan in progress:
|
||||
|
||||
<img src="/images/provider-added.png" alt="Start Now" width="700" />
|
||||
|
||||
@@ -177,3 +164,9 @@ Once your scan has finished, you don’t need to grab the entire ZIP—just pull
|
||||
To fetch a single compliance report via API, see the Retrieve compliance report as CSV endpoint in the Prowler API Reference. [Prowler API Reference - Retrieve compliance report as CSV](https://api.prowler.com/api/v1/docs#tag/Scan/operation/scans_compliance_retrieve)
|
||||
|
||||
</Note>
|
||||
|
||||
## Running the Same Steps on Prowler Local Server
|
||||
|
||||
**Prowler Local Server** is the self-hosted version of **Prowler Cloud**, and every step in this guide works the same way on it. See [Prowler product families](/getting-started/products) for every official product name.
|
||||
|
||||
After [installing](/getting-started/installation/prowler-app) **Prowler Local Server**, access it at [http://localhost:3000](http://localhost:3000) and the auto-generated **Prowler API** documentation at [http://localhost:8080/api/v1/docs](http://localhost:8080/api/v1/docs).
|
||||
@@ -4,6 +4,26 @@ All notable changes to the **Prowler MCP Server** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
|
||||
## [0.13.0] (Prowler v5.47.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- `readOnlyHint` and `destructiveHint` annotations on every MCP tool, with mute rule and mutelist writes marked destructive [(#13196)](https://github.com/prowler-cloud/prowler/pull/13196)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- Authlib from 1.7.2 to 1.9.1 and dnspython from 2.8.0 to 2.9.0 in the MCP Server container image, patching CVE-2026-96760 (critical), CVE-2026-114615, CVE-2026-114616 and CVE-2026-114605 [(#13186)](https://github.com/prowler-cloud/prowler/pull/13186)
|
||||
|
||||
---
|
||||
|
||||
## [0.12.4] (Prowler v5.46.0)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- certifi from 2026.4.22 to 2026.10.19 in the MCP Server container image, dropping the emSign root CAs whose intermediate keys were compromised [(#13107)](https://github.com/prowler-cloud/prowler/pull/13107)
|
||||
|
||||
---
|
||||
|
||||
## [0.12.3] (Prowler v5.45.0)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
certifi from 2026.4.22 to 2026.10.19 in the MCP Server container image, dropping the emSign root CAs whose intermediate keys were compromised
|
||||
@@ -30,6 +30,8 @@ class AttackPathsTools(BaseTool):
|
||||
- prowler_run_attack_paths_query: Execute query and analyze attack paths
|
||||
"""
|
||||
|
||||
read_only_tools = frozenset({"run_attack_paths_query"})
|
||||
|
||||
async def list_attack_paths_scans(
|
||||
self,
|
||||
provider_id: list[str] = Field(
|
||||
|
||||
@@ -2,12 +2,16 @@ import inspect
|
||||
from abc import ABC
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from mcp.types import ToolAnnotations
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastmcp import FastMCP
|
||||
|
||||
from prowler_mcp_server.lib.logger import logger
|
||||
from prowler_mcp_server.prowler_app.utils.api_client import ProwlerAPIClient
|
||||
|
||||
READ_ONLY_PREFIXES = ("get_", "list_", "search_")
|
||||
|
||||
|
||||
class BaseTool(ABC):
|
||||
"""Abstract base class for all MCP tools.
|
||||
@@ -24,6 +28,10 @@ class BaseTool(ABC):
|
||||
Attributes:
|
||||
_api_client: Singleton instance of ProwlerAPIClient for API requests
|
||||
_logger: Logger instance for structured logging
|
||||
read_only_tools: Tools that change nothing although their name does not
|
||||
start with one of READ_ONLY_PREFIXES
|
||||
destructive_tools: Tools that remove, replace or hide data although their
|
||||
name does not start with "delete_"
|
||||
|
||||
Example:
|
||||
class FindingsTools(BaseTool):
|
||||
@@ -37,6 +45,9 @@ class BaseTool(ABC):
|
||||
return response
|
||||
"""
|
||||
|
||||
read_only_tools: frozenset[str] = frozenset()
|
||||
destructive_tools: frozenset[str] = frozenset()
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the tool.
|
||||
|
||||
@@ -93,10 +104,24 @@ class BaseTool(ABC):
|
||||
|
||||
# Check if the method is a coroutine function (async)
|
||||
if inspect.iscoroutinefunction(method):
|
||||
mcp.tool(method)
|
||||
mcp.tool(method, annotations=self._annotations_for(name))
|
||||
registered_count += 1
|
||||
self.logger.debug(f"Auto-registered tool: {name}")
|
||||
|
||||
self.logger.info(
|
||||
f"Auto-registered {registered_count} tools from {self.__class__.__name__}"
|
||||
)
|
||||
|
||||
def _annotations_for(self, name: str) -> ToolAnnotations:
|
||||
"""Return the MCP hints a client reads to decide whether to ask the user.
|
||||
|
||||
Every tool states both hints: left out, the MCP spec treats a tool as
|
||||
destructive, so a client could not tell a listing from a deletion.
|
||||
"""
|
||||
if name.startswith(READ_ONLY_PREFIXES) or name in self.read_only_tools:
|
||||
return ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
||||
return ToolAnnotations(
|
||||
readOnlyHint=False,
|
||||
destructiveHint=name.startswith("delete_")
|
||||
or name in self.destructive_tools,
|
||||
)
|
||||
@@ -29,6 +29,12 @@ class MutingTools(BaseTool):
|
||||
- Managing mute rules (finding-specific muting)
|
||||
"""
|
||||
|
||||
# A wrong mute hides findings silently and for good, so it is flagged like a deletion.
|
||||
# update_mute_rule is included because re-enabling a rule mutes its findings again.
|
||||
destructive_tools = frozenset(
|
||||
{"set_mutelist", "create_mute_rule", "update_mute_rule"}
|
||||
)
|
||||
|
||||
# ===== MUTELIST TOOLS =====
|
||||
|
||||
async def _get_mutelist_raw(self) -> dict[str, Any] | None:
|
||||
|
||||
@@ -34,6 +34,8 @@ class RolesTools(BaseTool):
|
||||
- prowler_set_user_role: Set the role a user holds (idempotent)
|
||||
"""
|
||||
|
||||
destructive_tools = frozenset({"set_user_role"})
|
||||
|
||||
async def list_roles(
|
||||
self,
|
||||
page_size: int = Field(
|
||||
|
||||
@@ -14,7 +14,7 @@ docs_mcp_server = FastMCP("prowler-docs", mask_error_details=True)
|
||||
prowler_docs_search_engine = ProwlerDocsSearchEngine()
|
||||
|
||||
|
||||
@docs_mcp_server.tool()
|
||||
@docs_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
def search(
|
||||
term: NonBlankStr = Field(
|
||||
description="The term to search for in the documentation"
|
||||
@@ -41,7 +41,7 @@ def search(
|
||||
return prowler_docs_search_engine.search(term, page_size) # type: ignore In the hint we cannot put SearchResult type because JSON API MCP Generator cannot handle Pydantic models yet
|
||||
|
||||
|
||||
@docs_mcp_server.tool()
|
||||
@docs_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
def get_document(
|
||||
doc_path: NonBlankStr = Field(
|
||||
description="Path to the documentation file to retrieve. It is the same as the 'path' field of the search results. Use `prowler_docs_search` to find the path first."
|
||||
|
||||
@@ -167,7 +167,7 @@ def _explain_missing_check_file(
|
||||
|
||||
|
||||
# Security Check Tools
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def list_checks(
|
||||
providers: list[str] = Field(
|
||||
default=[],
|
||||
@@ -251,7 +251,7 @@ async def list_checks(
|
||||
return {"count": len(checks), "checks": checks_list}
|
||||
|
||||
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def semantic_search_checks(
|
||||
term: NonBlankStr = Field(
|
||||
description="Search term. Examples: 'public access', 'encryption', 'MFA', 'logging'.",
|
||||
@@ -303,7 +303,7 @@ async def semantic_search_checks(
|
||||
return {"count": len(checks), "checks": checks_list}
|
||||
|
||||
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def get_check_details(
|
||||
check_id: NonBlankStr = Field(
|
||||
description="The check ID to retrieve details for. Example: 's3_bucket_level_public_access_block'"
|
||||
@@ -441,7 +441,7 @@ async def get_check_details(
|
||||
return result
|
||||
|
||||
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def get_check_code(
|
||||
provider_id: NonBlankStr = Field(
|
||||
description="Prowler Provider ID. Example: 'aws', 'azure', 'gcp', 'kubernetes'. Use `prowler_hub_list_providers` to get available provider IDs.",
|
||||
@@ -494,7 +494,7 @@ async def get_check_code(
|
||||
}
|
||||
|
||||
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def get_check_fixer(
|
||||
provider_id: NonBlankStr = Field(
|
||||
description="Prowler Provider ID. Example: 'aws', 'azure', 'gcp', 'kubernetes'. Use `prowler_hub_list_providers` to get available provider IDs.",
|
||||
@@ -550,7 +550,7 @@ async def get_check_fixer(
|
||||
|
||||
|
||||
# Compliance Framework Tools
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def list_compliances(
|
||||
provider: list[str] = Field(
|
||||
default=[],
|
||||
@@ -610,7 +610,7 @@ async def list_compliances(
|
||||
return {"count": len(compliances), "compliances": compliances_list}
|
||||
|
||||
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def semantic_search_compliances(
|
||||
term: NonBlankStr = Field(
|
||||
description="Search term. Examples: 'CIS', 'HIPAA', 'PCI', 'GDPR', 'SOC2', 'NIST'.",
|
||||
@@ -654,7 +654,7 @@ async def semantic_search_compliances(
|
||||
return {"count": len(compliances), "compliances": compliances_list}
|
||||
|
||||
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def get_compliance_details(
|
||||
compliance_id: NonBlankStr = Field(
|
||||
description="The compliance framework ID to retrieve details for. Example: 'cis_4.0_aws'. Use `prowler_hub_list_compliances` or `prowler_hub_semantic_search_compliances` to find available compliance IDs.",
|
||||
@@ -743,7 +743,7 @@ async def get_compliance_details(
|
||||
|
||||
|
||||
# Provider Tools
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def list_providers() -> dict:
|
||||
"""List all providers supported by Prowler.
|
||||
|
||||
@@ -784,7 +784,7 @@ async def list_providers() -> dict:
|
||||
return {"count": len(providers), "providers": providers_list}
|
||||
|
||||
|
||||
@hub_mcp_server.tool()
|
||||
@hub_mcp_server.tool(annotations={"readOnlyHint": True, "destructiveHint": False})
|
||||
async def get_provider_services(
|
||||
provider_id: NonBlankStr = Field(
|
||||
description="The provider ID to get services for. Example: 'aws', 'azure', 'gcp', 'kubernetes'. Use `prowler_hub_list_providers` to get available provider IDs.",
|
||||
|
||||
@@ -74,13 +74,14 @@ extend-select = [
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
|
||||
# Transitive pins fastmcp does not raise on its own; each carries a known HIGH.
|
||||
constraint-dependencies = [
|
||||
"authlib==1.9.1",
|
||||
"cryptography==50.0.0",
|
||||
"dnspython==2.9.0",
|
||||
"joserfc==1.6.8",
|
||||
"mcp==1.28.1",
|
||||
"pyjwt==2.14.0",
|
||||
"python-multipart==0.0.30"
|
||||
]
|
||||
package = true
|
||||
@@ -49,3 +49,36 @@ async def test_every_tool_and_parameter_is_described(mcp_root_server):
|
||||
|
||||
for tool in tools:
|
||||
assert_tool_contract(tool)
|
||||
|
||||
|
||||
async def test_every_tool_says_whether_it_is_read_only_or_destructive(mcp_root_server):
|
||||
"""Clients decide whether to ask the user from these hints.
|
||||
|
||||
Left unset, the MCP spec treats a tool as destructive, so a listing would look
|
||||
exactly like a deletion.
|
||||
"""
|
||||
async with Client(mcp_root_server) as client:
|
||||
tools = {tool.name: tool.annotations for tool in await client.list_tools()}
|
||||
|
||||
for name, annotations in tools.items():
|
||||
assert annotations is not None, f"{name} has no annotations"
|
||||
assert annotations.readOnlyHint is not None, f"{name} has no readOnlyHint"
|
||||
assert annotations.destructiveHint is not None, f"{name} has no destructiveHint"
|
||||
|
||||
destructive = {name for name, a in tools.items() if a.destructiveHint}
|
||||
assert destructive == {
|
||||
"prowler_create_mute_rule",
|
||||
"prowler_update_mute_rule",
|
||||
"prowler_set_mutelist",
|
||||
"prowler_delete_mutelist",
|
||||
"prowler_delete_mute_rule",
|
||||
"prowler_delete_provider",
|
||||
"prowler_delete_integration",
|
||||
"prowler_set_user_role",
|
||||
}
|
||||
assert tools["prowler_run_attack_paths_query"].readOnlyHint
|
||||
assert tools["prowler_hub_list_checks"].readOnlyHint
|
||||
assert tools["prowler_docs_search"].readOnlyHint
|
||||
assert tools["prowler_get_mutelist"].readOnlyHint
|
||||
assert tools["prowler_hub_get_check_code"].readOnlyHint
|
||||
assert not tools["prowler_trigger_scan"].readOnlyHint
|
||||
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
revision = 5
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
@@ -10,7 +10,9 @@ resolution-markers = [
|
||||
|
||||
[manifest]
|
||||
constraints = [
|
||||
{ name = "authlib", specifier = "==1.9.1" },
|
||||
{ name = "cryptography", specifier = "==50.0.0" },
|
||||
{ name = "dnspython", specifier = "==2.9.0" },
|
||||
{ name = "joserfc", specifier = "==1.6.8" },
|
||||
{ name = "mcp", specifier = "==1.28.1" },
|
||||
{ name = "pyjwt", specifier = "==2.14.0" },
|
||||
@@ -62,15 +64,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "authlib"
|
||||
version = "1.7.2"
|
||||
version = "1.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "joserfc" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/dc/087c08741a98d234c9b98beef8a28d3c309f814d14b6156422c5815bf6d6/authlib-1.9.1.tar.gz", hash = "sha256:5c7d9848f47cac340f060f76ae0bc09b59f5c8e89cc1bb032d4370a0c55b5974", size = 182700, upload-time = "2026-10-14T14:17:52.307Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/44/a46089266de32a0d809023b6bf31b80a1f26a46954446a7ce695a565cbea/authlib-1.9.1-py2.py3-none-any.whl", hash = "sha256:8b9be8b1e5174dcbf5520e984f1a22dc2edae5b6e40a61d4cedbf86b97e9e69b", size = 266042, upload-time = "2026-10-14T14:17:50.695Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -350,11 +352,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/4a/50822184bd67cc6493f0fb6a880749158fcd31ab3fa07409acfd91f9fc85/dnspython-2.9.0.tar.gz", hash = "sha256:b44dc6b18f07a8b1c56676a19fbfdb5209415b046a9cece286baafa87ff3f7f1", size = 423560, upload-time = "2026-10-09T00:07:24.352Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/02/cdcc9b7c051786a103c3b09e1003a82fa0c66bcb91ffbdabcfbf7b4163b9/dnspython-2.9.0-py3-none-any.whl", hash = "sha256:9a4aedb833c3c1b49214d04d44d3032ab7a9135f7c1d29a549b4ff78fd82fda9", size = 354822, upload-time = "2026-10-09T00:07:22.622Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1293,8 +1295,8 @@ name = "secretstorage"
|
||||
version = "3.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography", marker = "sys_platform != 'win32'" },
|
||||
{ name = "jeepney", marker = "sys_platform != 'win32'" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "jeepney" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
|
||||
wheels = [
|
||||
|
||||
@@ -4,6 +4,63 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
|
||||
## [5.47.0] (Prowler v5.47.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- `iam_subscription_owners_between_2_and_3` check for Azure provider, verifying each subscription has between 2 and 3 principals with the Owner role, including owners inherited from management groups, mapped to CIS 5.0 and 6.0 [(#13145)](https://github.com/prowler-cloud/prowler/pull/13145)
|
||||
- `network_application_gateway_waf_enabled` and `network_application_gateway_http2_enabled` checks for Azure provider, with Application Gateway and WAF policy collection in the network service, mapped to CIS 5.0 and 6.0 [(#13160)](https://github.com/prowler-cloud/prowler/pull/13160)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Security group internet exposure checks now treat CIDRs in one ingress rule that together cover 0.0.0.0/0 or ::/0 as open to the internet [(#13191)](https://github.com/prowler-cloud/prowler/pull/13191)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- pymongo to 4.18.3, dnspython to 2.9.0 and oauthlib to 4.0.0 in the SDK, patching the BSON decoding and DNS compression pointer denial of service advisories and the OAuth device flow token disclosure; dnspython stays on 2.8.0 for Python 3.10, which 2.9.0 no longer supports [(#13185)](https://github.com/prowler-cloud/prowler/pull/13185)
|
||||
|
||||
---
|
||||
|
||||
## [5.46.0] (Prowler v5.46.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- `cognito_user_pool_account_recovery_sms_not_preferred` check verifying Cognito user pools do not rank SMS first for account recovery [(#12251)](https://github.com/prowler-cloud/prowler/pull/12251)
|
||||
- `bedrock_guardrail_sensitive_information_filter_enforced` check for AWS provider, flagging Bedrock guardrails whose PII entities or custom regexes take no action on the output path, so a detected match is reported in the trace and still delivered to the caller [(#12663)](https://github.com/prowler-cloud/prowler/pull/12663)
|
||||
- `bedrock_model_invocation_logging_text_data_enabled` check for AWS provider, verifying that model invocation logging delivers prompt and response text rather than naming a destination while excluding request and response bodies [(#12663)](https://github.com/prowler-cloud/prowler/pull/12663)
|
||||
- Vertex AI service for GCP provider, including the `vertexai_workbench_instance_public_ip_disabled` and `vertexai_workbench_instance_root_access_disabled` checks mapped to PCI DSS 4.0 [(#13062)](https://github.com/prowler-cloud/prowler/pull/13062)
|
||||
- `vertexai_workbench_instance_terminal_access_disabled`, `vertexai_workbench_instance_file_download_disabled` and `vertexai_workbench_instance_auto_upgrade_enabled` checks for GCP provider, with Vertex AI Workbench checks mapped to CCC MLDE requirements [(#13077)](https://github.com/prowler-cloud/prowler/pull/13077)
|
||||
- Google Workspace findings state when a feature is not sold on the tenant edition: Drive DLP rules, Google session control and the shared drive sharing settings on Business Starter; reading editions needs the optional `apps.licensing` scope [(#13113)](https://github.com/prowler-cloud/prowler/pull/13113)
|
||||
- `wafv2_webacl_managed_rule_groups_enabled` check to verify WAFv2 Web ACLs reference at least one managed rule group [(#13127)](https://github.com/prowler-cloud/prowler/pull/13127)
|
||||
- `appengine` GCP service and `appengine_version_php_runtime_supported` check to flag App Engine serving versions on PHP runtimes without security fixes [(#13127)](https://github.com/prowler-cloud/prowler/pull/13127)
|
||||
- `elasticbeanstalk_environment_php_platform_updated` check to flag Elastic Beanstalk PHP environments on branches without security fixes or on platform versions older than the one bundling the fixed PHP release [(#13127)](https://github.com/prowler-cloud/prowler/pull/13127)
|
||||
- `lightsail_instance_php_blueprint_updated` check to flag Lightsail instances created from PHP blueprints for manual verification of the PHP release [(#13127)](https://github.com/prowler-cloud/prowler/pull/13127)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- Google Workspace checks for Chat, Drive, Gmail, Calendar and Groups pass with the reason stated when the app is turned off for everyone and no organizational unit or group turns it back on. The rule covers settings that only act while the app is on: Drive sharing settings and the settings of existing groups keep being evaluated because existing shares and groups stay reachable, and only group creation is exempt when Groups for Business is off [(#13117)](https://github.com/prowler-cloud/prowler/pull/13117)
|
||||
- Default `php_latest_version` for `app_ensure_php_version_is_latest` raised from `8.2` to `8.5`, the newest PHP runtime on Azure App Service for Linux, following the PHP2Shell (CVE-2026-109618, CVE-2026-109619) security release [(#13128)](https://github.com/prowler-cloud/prowler/pull/13128)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- `bedrock_api_key_no_long_term_credentials` now reports a Bedrock long-term API key that never expires; IAM only returns `ExpirationDate` for keys created with an expiration period, so keys created without one were skipped and produced no finding at all [(#12663)](https://github.com/prowler-cloud/prowler/pull/12663)
|
||||
- `drive_desktop_access_disabled` remediation steps for Google Workspace now point to the Google Drive for desktop card in the Admin console instead of Features and Applications [(#13113)](https://github.com/prowler-cloud/prowler/pull/13113)
|
||||
- Gmail spoofing checks read each protection action from its own Policy API field; the inbound domain spoofing check could never pass and the similar domain names check reported the inbound action [(#13114)](https://github.com/prowler-cloud/prowler/pull/13114)
|
||||
- Google Workspace `drive_publishing_files_disabled` passes when sharing outside the organization is limited to allowlisted domains or off, since publishing to the web only exists with sharing on [(#13115)](https://github.com/prowler-cloud/prowler/pull/13115)
|
||||
- Google Workspace checks now read the policy Google applies (greatest sortOrder) instead of the last policy the Cloud Identity Policy API lists, which reported default values over admin-configured ones [(#13117)](https://github.com/prowler-cloud/prowler/pull/13117)
|
||||
- Azure App Service runtime checks (`app_ensure_php_version_is_latest`, `app_ensure_python_version_is_latest`, `app_ensure_java_version_is_latest`) now pass any runtime at or above the configured minimum instead of failing every version newer than the configured one [(#13127)](https://github.com/prowler-cloud/prowler/pull/13127)
|
||||
- Scan report generation parses each resource's metadata once per batch and sanitizes it once per finding, so providers with very large resource metadata no longer exhaust the worker memory [(#13149)](https://github.com/prowler-cloud/prowler/pull/13149)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- OpenStack auth_url resolving to loopback, private or otherwise non-public hosts rejected before the SDK connects, on both the connection test and the scan [(#12962)](https://github.com/prowler-cloud/prowler/pull/12962)
|
||||
- Trivy from 0.75.0 to 0.76.0 in the SDK container image, built with Go 1.27.2 and x/net 0.60.0, patching the HTTP/2 and TLS CVEs bundled in Trivy 0.75.0 [(#13064)](https://github.com/prowler-cloud/prowler/pull/13064)
|
||||
- certifi to 2026.10.19 and aiohttp to 3.14.5 with yarl 1.25.1 in the SDK, plus uv 0.13.4 and Trivy 0.76.1 in its container image, patching the compromised emSign root CAs bundled in certifi and uv, the aiohttp resource exhaustion advisories and the Go 1.27.2 stdlib CVE in the Trivy binary [(#13107)](https://github.com/prowler-cloud/prowler/pull/13107)
|
||||
- Resource metadata attached to findings no longer carries the values that secrets checks scan (CloudFormation outputs, Lambda environment variables, EC2 user data and similar fields); they are replaced with `REDACTED` before reaching the Evidence tab, the API and the output files [(#13129)](https://github.com/prowler-cloud/prowler/pull/13129)
|
||||
- black from 26.3.1 to 26.10.1 and graphql-core from 3.2.8 to 3.2.12 in the SDK, and libbrotli1 1.1.0-2+deb13u1 in its container image, which no longer ships the MicrosoftTeams build SBOM that Trivy read as installed zod and body-parser [(#13135)](https://github.com/prowler-cloud/prowler/pull/13135)
|
||||
|
||||
---
|
||||
|
||||
## [5.45.0] (Prowler v5.45.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
@@ -698,6 +698,8 @@ def prowler():
|
||||
html_output = HTML(
|
||||
findings=finding_outputs,
|
||||
file_path=f"{filename}{html_file_suffix}",
|
||||
# Only the OSS CLI shows the Prowler Cloud hand-off block
|
||||
show_cloud_cta=True,
|
||||
)
|
||||
generated_outputs["regular"].append(html_output)
|
||||
html_output.batch_write_data_to_file(
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
`elasticbeanstalk_environment_php_platform_updated` check to flag Elastic Beanstalk PHP environments on branches without security fixes or on platform versions older than the one bundling the fixed PHP release
|
||||
@@ -1 +0,0 @@
|
||||
`lightsail_instance_php_blueprint_updated` check to flag Lightsail instances created from PHP blueprints for manual verification of the PHP release
|
||||
@@ -1 +0,0 @@
|
||||
`wafv2_webacl_managed_rule_groups_enabled` check to verify WAFv2 Web ACLs reference at least one managed rule group
|
||||
@@ -1 +0,0 @@
|
||||
Azure App Service runtime checks (`app_ensure_php_version_is_latest`, `app_ensure_python_version_is_latest`, `app_ensure_java_version_is_latest`) now pass any runtime at or above the configured minimum instead of failing every version newer than the configured one
|
||||
@@ -1 +0,0 @@
|
||||
Default `php_latest_version` for `app_ensure_php_version_is_latest` raised from `8.2` to `8.5`, the newest PHP runtime on Azure App Service for Linux, following the PHP2Shell (CVE-2026-109618, CVE-2026-109619) security release
|
||||
@@ -1 +0,0 @@
|
||||
`bedrock_api_key_no_long_term_credentials` now reports a Bedrock long-term API key that never expires; IAM only returns `ExpirationDate` for keys created with an expiration period, so keys created without one were skipped and produced no finding at all
|
||||
@@ -1 +0,0 @@
|
||||
`bedrock_guardrail_sensitive_information_filter_enforced` check for AWS provider, flagging Bedrock guardrails whose PII entities or custom regexes take no action on the output path, so a detected match is reported in the trace and still delivered to the caller
|
||||
@@ -1 +0,0 @@
|
||||
`bedrock_model_invocation_logging_text_data_enabled` check for AWS provider, verifying that model invocation logging delivers prompt and response text rather than naming a destination while excluding request and response bodies
|
||||
@@ -1 +0,0 @@
|
||||
`cognito_user_pool_account_recovery_sms_not_preferred` check verifying Cognito user pools do not rank SMS first for account recovery
|
||||
@@ -0,0 +1 @@
|
||||
`prowler dashboard` startup output now shows the Local Dashboard logo, the CSV directory, a brief Prowler Cloud hand-off block that `PROWLER_NO_PROMOTIONS=1` disables, and the local URL once the server is listening
|
||||