# Technical Documentation: prowler-cloud/prowler

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/prowler-cloud/prowler)

### Overview (README.md)

<p align="center">
  <img align="center" alt="Prowler logo" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-black.png#gh-light-mode-only" width="50%" height="50%">
  <img align="center" alt="Prowler logo" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-white.png#gh-dark-mode-only" width="50%" height="50%">
</p>
<p align="center">
  <b><i>Prowler</b> is the Open Cloud Security Platform trusted by thousands to automate security and compliance in any cloud environment. With thousands of ready-to-use checks and compliance frameworks, Prowler delivers real-time, customizable monitoring and seamless integrations, making cloud security simple, scalable, and cost-effective for organizations of any size.
</p>
<p align="center">
<b>The Agentic Cloud Defender</i></b>
</p>
<p align="center">
<a href="https://cloud.prowler.com/sign-up">Try Prowler Cloud</a>
</p>

<p align="center">
<a href="https://goto.prowler.com/slack"><img width="30" height="30" alt="Prowler community on Slack" src="https://github.com/prowler-cloud/prowler/assets/38561120/3c8b4ec5-6849-41a5-b5e1-52bbb94af73a"></a>
  <br>
  <a href="https://goto.prowler.com/slack">Join our Prowler community!</a>
</p>
<hr>
<p align="center">
  <a href="https://goto.prowler.com/slack"><img alt="Slack Shield" src="https://img.shields.io/badge/slack-prowler-brightgreen.svg?logo=slack"></a>
  <a href="https://pypi.org/project/prowler/"><img alt="Python Version" src="https://img.shields.io/pypi/v/prowler.svg"></a>
  <a href="https://pypi.python.org/pypi/prowler/"><img alt="Python Version" src="https://img.shields.io/pypi/pyversions/prowler.svg"></a>
  <a href="https://pypistats.org/packages/prowler"><img alt="PyPI Downloads" src="https://img.shields.io/pypi/dw/prowler.svg?label=downloads"></a>
  <a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/toniblyx/prowler"></a>
  <a href="https://gallery.ecr.aws/prowler-cloud/prowler"><img width="120" height="19" alt="AWS ECR Gallery" src="https://user-images.githubusercontent.com/3985464/151531396-b6535a68-c907-44eb-95a1-a09508178616.png"></a>
  <a href="https://codecov.io/gh/prowler-cloud/prowler"><img alt="Codecov coverage" src="https://codecov.io/gh/prowler-cloud/prowler/graph/badge.svg?token=OflBGsdpDl"/></a>
  <a href="https://insights.linuxfoundation.org/project/prowler-cloud-prowler"><img alt="Linux Foundation insights health score" src="https://insights.linuxfoundation.org/api/badge/health-score?project=prowler-cloud-prowler"/></a>
</p>
<p align="center">
    <a href="https://github.com/prowler-cloud/prowler/releases"><img alt="Version" src="https://img.shields.io/github/v/release/prowler-cloud/prowler"></a>
  <a href="https://github.com/prowler-cloud/prowler/releases"><img alt="Version" src="https://img.shields.io/github/release-date/prowler-cloud/prowler"></a>
  <a href="https://github.com/prowler-cloud/prowler"><img alt="Contributors" src="https://img.shields.io/github/contributors-anon/prowler-cloud/prowler"></a>
  <a href="https://github.com/prowler-cloud/prowler/issues"><img alt="Issues" src="https://img.shields.io/github/issues/prowler-cloud/prowler"></a>
  <a href="https://github.com/prowler-cloud/prowler"><img alt="License" src="https://img.shields.io/github/license/prowler-cloud/prowler"></a>
  <a href="https://twitter.com/ToniBlyx"><img alt="Twitter" src="https://img.shields.io/twitter/follow/toniblyx?style=social"></a>
  <a href="https://twitter.com/prowlercloud"><img alt="Twitter" src="https://img.shields.io/twitter/follow/prowlercloud?style=social"></a>
</p>
<hr>
<p align="center">
  <img align="center" alt="Prowler Cloud demo" src="/docs/img/prowler-cloud.gif" width="100%" height="100%">
</p>

# Description

**Prowler** is the world’s most widely used _Open-Source Cloud Security Platform_ that automates security and compliance across **any cloud environment**. With thousands of ready-to-use security checks, remediation guidance, and compliance frameworks, Prowler is built to _“Secure ANY Cloud at AI Speed”_. Prowler delivers **AI-driven**, **customizable**, and **easy-to-use** assessments, dashboards, reports, and integrations, making cloud security **simple**, **scalable**, and **cost-effective** for organizations of any size.

Prowler includes hundreds of built-in controls to ensure compliance with standards and frameworks, including:

- **Prowler ThreatScore:** Weighted risk prioritization scoring that helps you focus on the most critical security findings first
- **Industry Standards:** CIS, NIST 800, NIST CSF, CISA, and MITRE ATT&CK
- **Regulatory Compliance and Governance:** RBI, FedRAMP, PCI-DSS, and NIS2
- **Frameworks for Sensitive Data and Privacy:** GDPR, HIPAA, and FFIEC
- **Frameworks for Organizational Governance and Quality Control:** SOC2, GXP, and ISO 27001
- **Cloud-Specific Frameworks:** AWS Foundational Technical Review (FTR), AWS Well-Architected Framework, and BSI C5
- **National Security Standards:** ENS (Spanish National Security Scheme) and KISA ISMS-P (Korean)
- **Custom Security Frameworks:** Tailored to your needs

## Prowler Cloud & Prowler Local Server

[Prowler Cloud](https://cloud.prowler.com/sign-up) and Prowler Local Server, its self-hosted open-source version, are web applications that simplify running Prowler across your cloud provider accounts. They provide a user-friendly interface to visualize the results and streamline your security assessments.






>For more details, refer to the [Prowler Local Server documentation](https://docs.prowler.com/getting-started/installation/prowler-app)

## Prowler CLI

```console
prowler <provider>
```



## Prowler Local Dashboard

```console
prowler dashboard
```



## Attack Paths

Attack Paths automatically extends every completed AWS scan with a graph that combines Cartography's cloud inventory with Prowler findings. The feature runs in the API worker after each scan.

Two graph backends are supported as the long-lived sink:

- **Neo4j** (default; the Docker Compose files already ship a `neo4j` service).
- **Amazon Neptune** (cloud-managed; opt-in).

Select the sink with `ATTACK_PATHS_SINK_DATABASE` (`neo4j` or `neptune`; default `neo4j`).

> Note: Cartography ingestion always uses a temporary Neo4j database, regardless of the configured sink. The `NEO4J_*` variables below must remain set even when `ATTACK_PATHS_SINK_DATABASE=neptune`.

### Neo4j sink

| Variable | Description | Default |
| --- | --- | --- |
| `NEO4J_HOST` | Hostname used by the API containers. | `neo4j` |
| `NEO4J_PORT` | Bolt port exposed by Neo4j. | `7687` |
| `NEO4J_USER` / `NEO4J_PASSWORD` | Credentials with rights to create per-tenant databases. | `neo4j` / `neo4j_password` |

### Neptune sink

| Variable | Description | Default |
| --- | --- | --- |
| `NEPTUNE_WRITER_ENDPOINT` | Bolt host for the Neptune writer instance. Required when sink is `neptune`. | _empty_ |
| `NEPTUNE_READER_ENDPOINT` | Optional reader endpoint for read-only queries. Falls back to the writer when unset. | _empty_ |
| `NEPTUNE_PORT` | Bolt port exposed by Neptune. | `8182` |
| `AWS_REGION` | Region the Neptune cluster lives in. Required when sink is `neptune`. | _empty_ |

Neptune authenticates with SigV4 using the standard boto3 credential chain. The worker's IAM role (or `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`) supplies the credentials. There is no Neptune password variable.

Every AWS provider scan will enqueue an Attack Paths ingestion job automatically. Other cloud providers will be added in future iterations.


# Prowler at a Glance
> [!Tip]
> For the most accurate and up-to-date information about checks, services, frameworks, and categories, visit [**Prowler Hub**](https://hub.prowler.com).


| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/user-guide/compliance/tutorials/compliance) | [Categories](https://docs.prowler.com/user-guide/cli/tutorials/misc#categories) | Support | Interface |
|---|---|---|---|---|---|---|
| AWS | 662 | 86 | 50 | 19 | Official | UI, API, CLI |
| Azure | 191 | 22 | 25 | 16 | Official | UI, API, CLI |
| GCP | 110 | 20 | 22 | 12 | Official | UI, API, CLI |
| Kubernetes | 92 | 7 | 11 | 11 | Official | UI, API, CLI |
| GitHub | 24 | 3 | 2 | 5 | Official | UI, API, CLI |
| M365 | 144 | 10 | 9 | 10 | Official | UI, API, CLI |
| OCI | 52 | 14 | 5 | 10 | Official | UI, API, CLI |
| Alibaba Cloud | 63 | 9 | 6 | 9 | Official | UI, API, CLI |
| Cloudflare | 29 | 3 | 2 | 5 | Official | UI, API, CLI |
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI |
| MongoDB Atlas | 10 | 3 | 1 | 8 | Official | UI, API, CLI |
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI |
| Image | N/A | N/A | N/A | N/A | Official | UI, API, CLI |
| Google Workspace | 65 | 11 | 4 | 6 | Official | UI, API, CLI |
| OpenStack | 34 | 5 | 1 | 9 | Official | UI, API, CLI |
| Vercel | 26 | 6 | 1 | 8 | Official | UI, API, CLI |
| Okta | 29 | 8 | 2 | 2 | Official | UI, API, CLI |
| Linode [Contact us](https://prowler.com/contact) | 10 | 3 | 1 | 4 | Unofficial | CLI |
| Huawei Cloud [Contact us](https://prowler.com/contact) | 25 | 10 | 1 | 6 | Unofficial | CLI |
| E2E Networks [Contact us](https://prowler.com/contact) | 27 | 6 | 0 | 2 | Unofficial | CLI |
| Scaleway [Contact us](https://prowler.com/contact) | 1 | 1 | 1 | 1 | Unofficial | CLI |
| StackIT [Contact us](https://prowler.com/contact) | 8 | 2 | 1 | 3 | Unofficial | CLI |
| NHN | 6 | 2 | 2 | 0 | Unofficial | CLI |

> [!Note]
> The numbers in the table are updated periodically.



> [!Note]
> Use the following commands to list Prowler's available checks, services, compliance frameworks, and categories:
> - `prowler <provider> --list-checks`
> - `prowler <provider> --list-services`
> - `prowler <provider> --list-compliance`
> - `prowler <provider> --list-categories`

# 💻 Installation

## Prowler Local Server

Prowler Local Server offers flexible installation methods tailored to various environments:

> For detailed instructions on using Prowler Local Server, refer to the [usage guide](https://docs.prowler.com/user-guide/tutorials/prowler-app).

### Docker Compose

#### Requirements

- `Docker Compose` installed: https://docs.docker.com/compose/install/.

#### Commands

_macOS/Linux:_

``` console
VERSION=$(curl -s https://api.github.com/repos/prowler-cloud/prowler/releases/latest | jq -r .tag_name)
curl -sLO "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/${VERSION}/docker-compose.yml"
# Environment variables can be customized in the .env file. Using default values in production environments is not recommended.
curl -sLO "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/${VERSION}/.env"
docker compose up -d
```

_Windows PowerShell:_

``` powershell
$VERSION = (Invoke-RestMethod -Uri "https://api.github.com/repos/prowler-cloud/prowler/releases/latest").tag_name
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/$VERSION/docker-compose.yml" -OutFile "docker-compose.yml"
# Environment variables can be customized in the .env file. Using default values in production environments is not recommended.
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/$VERSION/.env" -OutFile ".env"
docker compose up -d
```

> [!WARNING]
> 🔒 For a secure setup, the API auto-generates a unique key pair, `DJANGO_TOKEN_SIGNING_KEY` and `DJANGO_TOKEN_VERIFYING_KEY`, and stores it in `~/.config/prowler-api` (non-container) or the bound Docker volume in `_data/api` (container). Never commit or reuse static/default keys. To rotate keys, delete the stored key files and restart the API.

Once configured, access Prowler Local Server at http://localhost:3000. Sign up using your email and password to get started.

### Common Issues with Docker Pull Installation

> [!Note]
  If you want to use AWS role assumption (e.g., with the "Connect assuming IAM Role" option), you may need to mount your local `.aws` directory into the container as a volume (e.g., `- "${HOME}/.aws:/home/prowler/.aws:ro"`). There are several ways to configure credentials for Docker containers. See the [Troubleshooting](./docs/troubleshooting.mdx) section for more details and examples.

You can find more information in the [Troubleshooting](./docs/troubleshooting.mdx) section.


### From GitHub

#### Requirements

- `git` installed.
- `uv` installed: [uv installation](https://docs.astral.sh/uv/getting-started/installation/).
- `pnpm` installed: [pnpm installation](https://pnpm.io/installation).
- `Docker Compose` installed: https://docs.docker.com/compose/install/.

#### Commands to run the API

``` console
git clone https://github.com/prowler-cloud/prowler
cd prowler/api
uv sync
source .venv/bin/activate
set -a
source .env
docker compose up postgres valkey -d
cd src/backend
python manage.py migrate --database admin
gunicorn -c config/guniconf.py config.wsgi:application
```

> After completing the setup, access the API documentation at http://localhost:8080/api/v1/docs.

#### Commands to run the API Worker

``` console
git clone https://github.com/prowler-cloud/prowler
cd prowler/api
uv sync
source .venv/bin/activate
set -a
source .env
cd src/backend
python -m celery -A config.celery worker -l info -E
```

#### Commands to run the API Scheduler

``` console
git clone https://github.com/prowler-cloud/prowler
cd prowler/api
uv sync
source .venv/bin/activate
set -a
source .env
cd src/backend
python -m celery -A config.celery beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
```

#### Commands to run the UI

``` console
git clone https://github.com/prowler-cloud/prowler
cd prowler/ui
pnpm install
pnpm run build
pnpm start
```

> Once configured, access Prowler Local Server at http://localhost:3000. Sign up using your email and password to get started.

#### Pre-commit Hooks Setup

Some pre-commit hooks require tools installed on your system:

1. **Install [TruffleHog](https://github.com/trufflesecurity/trufflehog#install)** (secret scanning) — see the [official installation options](https://github.com/trufflesecurity/trufflehog#install).

2. **Install [Hadolint](https://github.com/hadolint/hadolint#install)** (Dockerfile linting) — see the [official installation options](https://github.com/hadolint/hadolint#install).

## Prowler CLI
### Pip package
Prowler CLI is available as a project in [PyPI](https://pypi.org/project/prowler-cloud/). Consequently, it can be installed using pip with Python >=3.10, <3.13:

```console
pip install prowler
prowler -v
```
>For further guidance, refer to [https://docs.prowler.com](https://docs.prowler.com/getting-started/installation/prowler-cli)

### Containers

#### Available Versions of Prowler CLI

The following versions of Prowler CLI are available, depending on your requirements:

- `latest`: Synchronizes with the `master` branch. Note that this version is not stable.
- `v4-latest`: Synchronizes with the `v4` branch. Note that this version is not stable.
- `v3-latest`: Synchronizes with the `v3` branch. Note that this version is not stable.
- `<x.y.z>` (release): Stable releases corresponding to specific versions. See the [complete list of Prowler releases](https://github.com/prowler-cloud/prowler/releases).
- `stable`: Always points to the latest release.
- `v4-stable`: Always points to the latest release for v4.
- `v3-stable`: Always points to the latest release for v3.

The container images are available here:
- Prowler CLI:
    - [DockerHub](https://hub.docker.com/r/prowlercloud/prowler/tags)
    - [AWS Public ECR](https://gallery.ecr.aws/prowler-cloud/prowler)
- Prowler Local Server:
    - [DockerHub - Prowler UI](https://hub.docker.com/r/prowlercloud/prowler-ui/tags)
    - [DockerHub - Prowler API](https://hub.docker.com/r/prowlercloud/prowler-api/tags)

### From GitHub

Python >=3.10, <3.13 is required with [uv](https://docs.astral.sh/uv/):

``` console
git clone https://github.com/prowler-cloud/prowler
cd prowler
uv sync
source .venv/bin/activate
python prowler-cli.py -v
```
> [!IMPORTANT]
> To clone Prowler on Windows, configure Git to support long file paths by running the following command: `git config core.longpaths true`.

# 🛡️ GitHub Action

The official **Prowler GitHub Action** runs Prowler scans in your GitHub workflows using the official [`prowlercloud/prowler`](https://hub.docker.com/r/prowlercloud/prowler) Docker image. Scans run on any [supported provider](https://docs.prowler.com/user-guide/providers/), with optional [`--push-to-cloud`](https://docs.prowler.com/user-guide/tutorials/prowler-import-findings) to send findings to Prowler Cloud and optional SARIF upload so findings show up in the repo's **Security → Code scanning** tab and as inline PR annotations.

```yaml
name: Prowler IaC Scan
on:
  pull_request:

permissions:
  contents: read
  security-events: write
  actions: read

jobs:
  prowler:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: prowler-cloud/prowler@5.25
        with:
          provider: iac
          output-formats: sarif json-ocsf
          upload-sarif: true
          flags: --severity critical high
```

Full configuration, per-provider authentication, and SARIF examples: [Prowler GitHub Action tutorial](docs/user-guide/tutorials/prowler-app-github-action.mdx). Marketplace listing: [Prowler Security Scan](https://github.com/marketplace/actions/prowler-security-scan).

# ✏️ High level architecture

## Prowler Local Server
**Prowler Local Server** is composed of four key components:

- **Prowler UI**: A web-based interface, built with Next.js, providing a user-friendly experience for executing Prowler scans and visualizing results.
- **Prowler API**: A backend service, developed with Django REST Framework, responsible for running Prowler scans and storing the generated results.
- **Prowler SDK**: A Python SDK designed to extend the functionality of the Prowler CLI for advanced capabilities.
- **Prowler MCP Server**: A Model Context Protocol server that provides AI tools for Lighthouse, the AI-powered security assistant. This is a critical dependency for Lighthouse functionality.

```mermaid
flowchart TB
    user([User / Security Team])
    cli([Prowler CLI])

    subgraph APP["Prowler Local Server"]
        ui["Prowler UI<br/>(Next.js)"]
        api["Prowler API<br/>(Django REST Framework)"]
        worker["API Worker<br/>(Celery)"]
        beat["API Scheduler<br/>(Celery Beat)"]
        mcp["Prowler MCP Server<br/>(Lighthouse AI tools)"]
    end

    sdk["Prowler SDK<br/>(Python)"]

    subgraph DATA["Data Layer"]
        pg[("PostgreSQL")]
        valkey[("Valkey / Redis")]
        neo4j[("Neo4j")]
    end

    providers["Providers"]

    user --> ui
    user --> cli
    ui -->|REST| api
    ui -->|MCP HTTP| mcp
    mcp -->|REST| api
    api --> pg
    api --> valkey
    beat -->|enqueue jobs| valkey
    valkey -->|dispatch| worker
    worker --> pg
    worker -->|Attack Paths| neo4j
    worker -->|invokes| sdk
    cli --> sdk

    sdk --> providers
```




## Prowler CLI

### Running Prowler

Prowler can be executed across various environments, offering flexibility to meet your needs. It can be run from:

- Your own workstation

- A Kubernetes Job

- Google Compute Engine

- Azure Virtual Machines (VMs)

- Amazon EC2 instances

- AWS Fargate or other container platforms

- CloudShell

And many more environments.



# 🤖 AI Skills for Development

Prowler includes a comprehensive set of **AI Skills** that help AI coding assistants understand Prowler's codebase patterns and conventions.

## What are AI Skills?

Skills are structured instructions that give AI assistants the context they need to write code that follows Prowler's standards. They include:

- **Coding patterns** for each component (SDK, API, UI, MCP Server)
- **Testing conventions** (pytest, Playwright)
- **Architecture guidelines** (Clean Architecture, RLS patterns)
- **Framework-specific rules** (React 19, Next.js 15, Django DRF, Tailwind 4)

## Available Skills

| Category | Skills |
|----------|--------|
| **Generic** | `typescript`, `react-19`, `nextjs-15`, `tailwind-4`, `playwright`, `pytest`, `django-drf`, `zod-4`, `zustand-5`, `ai-sdk-5` |
| **Prowler** | `prowler`, `prowler-api`, `prowler-ui`, `prowler-mcp`, `prowler-sdk-check`, `prowler-test-ui`, `prowler-test-api`, `prowler-test-sdk`, `prowler-compliance`, `prowler-provider`, `prowler-pr`, `prowler-docs` |

## Setup

```bash
./skills/setup.sh
```

This configures skills for AI coding assistants that follow the [agentskills.io](https://agentskills.io) standard:

| Tool | Configuration |
|------|---------------|
| **Claude Code** | `.claude/skills/` (symlink) |
| **OpenCode** | `.claude/skills/` (symlink) |
| **Codex (OpenAI)** | `.codex/skills/` (symlink) |
| **GitHub Copilot** | `.github/skills/` (symlink) |
| **Gemini CLI** | `.gemini/skills/` (symlink) |

> **Note:** Restart your AI coding assistant after running setup to load the skills.
> Gemini CLI requires `experimental.skills` enabled in settings.

# 📖 Documentation

For installation instructions, usage details, tutorials, and the Developer Guide, visit https://docs.prowler.com/

# 📃 License

Prowler is licensed under the Apache License 2.0.

A copy of the License is available at <http://www.apache.org/licenses/LICENSE-2.0>

---

### README (`docs/README.md`)

# Prowler Documentation

This repository contains the Prowler Open Source documentation powered by [Mintlify](https://mintlify.com).

## Documentation Structure

- **Getting Started**: Overview, installation, and basic usage guides
- **User Guide**: Comprehensive guides for Prowler Cloud, Prowler Local Server, Prowler CLI, providers, and compliance
- **Developer Guide**: Technical documentation for developers contributing to Prowler

## Local Development

Install a reviewed version of the [Mintlify CLI](https://www.npmjs.com/package/mint) to preview documentation changes locally:

```bash
npm install mint@4.2.689
```

Run the following command at the root of your documentation (where `mint.json` is located):

```bash
mint dev
```

View your local preview at `http://localhost:3000`.

## Publishing Changes

Changes pushed to the main branch are automatically deployed to production through Mintlify's GitHub integration.

## Documentation Guidelines

When contributing to the documentation, please follow the Prowler documentation style guide located in the `.claude` directory.

## Troubleshooting

- If your dev environment isn't running: Run `mint update` to ensure you have the most recent version of the CLI.
- If a page loads as a 404: Make sure you are running in a folder with a valid `mint.json` file and that the page path is correctly listed in the navigation.

## Resources

- [Prowler GitHub Repository](https://github.com/prowler-cloud/prowler)
- [Prowler Documentation](https://docs.prowler.com/)
- [Mintlify Documentation](https://mintlify.com/docs)
- [Mintlify Community](https://mintlify.com/community)

---

### Dockerhub README (`docs/dockerhub/README.md`)

<p align="center">
  <img src="https://raw.githubusercontent.com/prowler-cloud/prowler/master/docs/dockerhub/prowler-logo.svg" width="252" alt="Prowler">
</p>
<p align="center">
  <b>Prowler</b> is the Open Cloud Security platform trusted by thousands to automate security and compliance in any cloud environment — AWS, Azure, Google Cloud, Kubernetes, M365, GitHub and more.
</p>
<p align="center">
  <b>Learn more at <a href="https://prowler.com">prowler.com</a> · <a href="https://goto.prowler.com/slack">Join our Slack community</a></b>
</p>

<p align="center">
  <a href="https://github.com/prowler-cloud/prowler"><img alt="GitHub" src="https://img.shields.io/github/stars/prowler-cloud/prowler?style=social"></a>
  <a href="https://github.com/prowler-cloud/prowler/releases"><img alt="Version" src="https://img.shields.io/github/v/release/prowler-cloud/prowler"></a>
  <a href="https://pypi.org/project/prowler/"><img alt="PyPI" src="https://img.shields.io/pypi/v/prowler.svg"></a>
  <a href="https://github.com/prowler-cloud/prowler"><img alt="License" src="https://img.shields.io/github/license/prowler-cloud/prowler"></a>
</p>

---

# Prowler container images

All Prowler images are built from a single repository — [github.com/prowler-cloud/prowler](https://github.com/prowler-cloud/prowler) — and published together on every release.

| Image | What it is | Dockerfile |
|---|---|---|
| [`prowlercloud/prowler`](https://hub.docker.com/r/prowlercloud/prowler) | **Prowler CLI.** Runs scans from your terminal, a CI job, a Kubernetes Job or any container platform. | [`Dockerfile`](https://github.com/prowler-cloud/prowler/blob/master/Dockerfile) |
| [`prowlercloud/prowler-api`](https://hub.docker.com/r/prowlercloud/prowler-api) | **Prowler Local Server — API.** Django REST backend plus the Celery worker and scheduler that run scans and store results. | [`api/Dockerfile`](https://github.com/prowler-cloud/prowler/blob/master/api/Dockerfile) |
| [`prowlercloud/prowler-ui`](https://hub.docker.com/r/prowlercloud/prowler-ui) | **Prowler Local Server — UI.** Next.js web interface for launching scans and exploring findings. | [`ui/Dockerfile`](https://github.com/prowler-cloud/prowler/blob/master/ui/Dockerfile) |
| [`prowlercloud/prowler-mcp`](https://hub.docker.com/r/prowlercloud/prowler-mcp) | **Prowler MCP.** Gives AI assistants access to the Prowler ecosystem over the Model Context Protocol. | [`mcp_server/Dockerfile`](https://github.com/prowler-cloud/prowler/blob/master/mcp_server/Dockerfile) |
| [`toniblyx/prowler`](https://hub.docker.com/r/toniblyx/prowler) | **Legacy home of the Prowler CLI image.** Still mirrored on every release for backwards compatibility. New deployments should use `prowlercloud/prowler`. | [`Dockerfile`](https://github.com/prowler-cloud/prowler/blob/master/Dockerfile) |

All images are published for `linux/amd64` and `linux/arm64`.

## Tags

| Tag | Meaning |
|---|---|
| `stable` | Always points to the latest stable release. **Recommended for production.** |
| `<x.y.z>` | A specific release, e.g. `5.14.0`. Immutable. |
| `latest` | Built from the `master` branch on every merge. Not a stable version. |
| `<short-sha>` | A specific `master` commit (`prowler-api`, `prowler-ui` and `prowler-mcp` only). |

`v3-*` and `v4-*` tags on `prowlercloud/prowler` are frozen historical artifacts of Prowler v3/v4 and no longer receive updates.

## Other registries

The Prowler CLI image is also available on AWS Public ECR: [`public.ecr.aws/prowler-cloud/prowler`](https://gallery.ecr.aws/prowler-cloud/prowler).

---

# Quick start

## Prowler Local Server (UI + API)

```console
curl -LO https://raw.githubusercontent.com/prowler-cloud/prowler/refs/heads/master/docker-compose.yml
curl -LO https://raw.githubusercontent.com/prowler-cloud/prowler/refs/heads/master/.env
docker compose up -d
```

Then open http://localhost:3000 and sign up with your email and password.

Full guide: [Prowler Local Server installation](https://docs.prowler.com/getting-started/installation/prowler-app)

## Prowler CLI

```console
docker run -ti --rm \
  -v /your/local/dir/prowler-output:/home/prowler/output \
  --name prowler \
  --env AWS_ACCESS_KEY_ID \
  --env AWS_SECRET_ACCESS_KEY \
  --env AWS_SESSION_TOKEN \
  prowlercloud/prowler:stable aws
```

Swap `aws` for `azure`, `gcp`, `kubernetes`, `m365` or `github` to scan another provider. The CLI is also on PyPI: `pip install prowler`.

Full guide: [Prowler CLI installation](https://docs.prowler.com/getting-started/installation/prowler-cli)

## Prowler MCP

```console
# STDIO mode (for local MCP clients)
docker run --rm -i prowlercloud/prowler-mcp

# HTTP mode (for remote access)
docker run --rm -p 8000:8000 prowlercloud/prowler-mcp \
  --transport http --host 0.0.0.0 --port 8000
```

Full guide: [Prowler MCP installation](https://docs.prowler.com/getting-started/installation/prowler-mcp)

> **Note on architecture:** if your workstation's architecture is incompatible, set `DOCKER_DEFAULT_PLATFORM=linux/amd64` or pass `--platform linux/amd64` to your Docker command.

---

# What Prowler covers

Hundreds of built-in checks mapped to the frameworks you get audited against — CIS, NIST 800 / CSF, CISA, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, FedRAMP, RBI, AWS Well-Architected (Security Pillar), AWS FTR, ENS — plus your own custom frameworks.

For live check, service, framework and category counts, see [**Prowler Hub**](https://hub.prowler.com).

List what's available for any provider:

```console
prowler <provider> --list-checks
prowler <provider> --list-services
prowler <provider> --list-compliance
prowler <provider> --list-categories
```

# Documentation and support

- **Documentation:** [docs.prowler.com](https://docs.prowler.com/)
- **Source:** [github.com/prowler-cloud/prowler](https://github.com/prowler-cloud/prowler)
- **Issues:** [github.com/prowler-cloud/prowler/issues](https://github.com/prowler-cloud/prowler/issues)
- **Community:** [Prowler Slack](https://goto.prowler.com/slack)
- **Troubleshooting:** [docs.prowler.com/troubleshooting](https://docs.prowler.com/troubleshooting)

# License

Prowler is licensed under the Apache License 2.0. A copy is available at http://www.apache.org/licenses/LICENSE-2.0.

---

### Security Index (`docs/security/index.mdx`)

---
title: 'Security & Compliance'
---

**Prowler secures itself with Prowler.** As an open-source cloud security platform trusted by thousands of organizations, Prowler applies the same rigorous security standards internally that customers achieve externally.

All security tooling, configurations, and CI/CD pipelines are publicly available in the [Prowler GitHub repository](https://github.com/prowler-cloud/prowler). Transparency is fundamental to open-source security.

## Software Security

All Prowler code goes through the same security pipeline, whether running on Prowler Cloud or self-managed infrastructure: DAST, SAST, SCA, container scanning, and secrets detection on every build.

<Card title="Software Security" icon="code" href="/security/software-security">
  Security tools and practices applied to all Prowler code.
</Card>

## Prowler Cloud vs Prowler OSS (Self-Managed)

| | Prowler Cloud | Self-Managed |
|--|---------------|--------------|
| **Deployment** | Fully managed SaaS | Own infrastructure |
| **Region** | EU (Ireland) | Any region or provider |
| **Compliance** | SOC 2 Type II, AWS FTR | Organization responsibility |
| **Data Control** | Prowler managed | Full control |
| **Encryption** | AES-256 at rest, TLS 1.2+ in transit | Configurable |
| **Backups** | Automated | Organization responsibility |
| **Updates** | Automatic | Manual |

<Note>
Self-Managed includes Prowler Local Server and Prowler CLI. They can run anywhere — any cloud provider, any region, on-premises, or air-gapped environments. Full control over data residency and infrastructure decisions. See the [Prowler Local Server Installation Guide](/getting-started/installation/prowler-app) to get started.
</Note>

---

## Prowler Cloud

This section covers security and compliance for **Prowler Cloud**, the managed infrastructure.

### Trust & Compliance

Prowler Cloud holds compliance certifications and undergoes regular audits.

| Certification | Status |
|---------------|--------|
| **SOC 2 Type II** | [View on Trust Portal](https://trust.prowler.com) |
| **AWS Foundational Technical Review (FTR)** | Passed — [Details](https://aws.amazon.com/partners/foundational-technical-review/) |

Compliance data and reports: [trust.prowler.com](https://trust.prowler.com)

### Security

<Columns cols={3}>
  <Card title="Encryption" icon="lock" href="/security/encryption">
    Data encrypted at rest (AES-256) and in transit (TLS 1.2+).
  </Card>
  <Card title="Data Regions" icon="globe" href="/security/data-regions">
    EU-hosted infrastructure with high availability and disaster recovery.
  </Card>
  <Card title="Networking" icon="network-wired" href="/security/networking">
    Static egress IPs for firewall allowlisting.
  </Card>
</Columns>

### Privacy

Prowler Cloud is GDPR compliant in regard to the ["right to be forgotten"](https://gdpr.eu/right-to-be-forgotten/). When an account is deleted, user information is removed from online and backup systems within 10 calendar days.

---

## Report a Vulnerability

Found a security issue? Report it through the [responsible disclosure](https://prowler.com/.well-known/security.txt) process.

## Contact

For security inquiries or general support, visit the [Support page](/support).

---

### Getting Started Comparison Index (`docs/getting-started/comparison/index.mdx`)

---
title: 'Comparison'
---

Click to learn more about each cloud security provider and learn how Prowler is differentiated.

- [AWS Security Hub](/getting-started/comparison/awssecurityhub)
- [Microsoft Sentinel](/getting-started/comparison/microsoftsentinel)
- [Microsoft Defender](/getting-started/comparison/microsoftdefender)
- [Google Cloud Security Command Center](/getting-started/comparison/gcp)

---

### Getting Started Products Index (`docs/getting-started/products/index.mdx`)

---
title: 'Prowler Product Families'
description: 'Official names for Prowler Open Source projects and Prowler Products, including former product names.'
boost: 2
---

Prowler ships two product families: Prowler Products, operated or licensed by the Prowler team, and Open Source projects, free to run and extend. This page is the reference for every official name. If a page or blog post uses a former name, the [Former Names](#former-names) table maps it to the current one.

<Note>
Read the [public announcement of the Prowler product families](https://prowler-workspace.slack.com/archives/C03JUQVM33L/p1784120677833319) in our Slack community.
</Note>

## Prowler Products

| Name | Description |
|------|-------------|
| [Prowler Cloud](/getting-started/products/prowler-cloud) | Managed cloud security platform operated by the Prowler team. See [pricing](https://prowler.com/pricing). |
| Prowler Private Cloud | Prowler Cloud deployed in your own environment. Formerly Prowler Enterprise. See [pricing](https://prowler.com/pricing). |
| [Prowler Hub](https://hub.prowler.com) | Free public library of versioned checks, cloud service artifacts, and compliance frameworks. |
| [Prowler Lighthouse AI](/getting-started/products/prowler-cloud-lighthouse) | AI security analyst capabilities within Prowler Cloud and Prowler Private Cloud. |
| [Prowler MCP](/getting-started/products/prowler-mcp) | MCP server that connects AI assistants and agents to Prowler, including IDE plugins such as [Prowler for Claude Code](/user-guide/ai-agents/claude-code). |

{/* Unreleased products. Uncomment these rows in the Prowler Products table when announced:
| Prowler Registry | Distribution service for Prowler content such as checks and compliance frameworks. Free and paid tiers. |
| Prowler Local Registry | Prowler Registry running in your own environment. Paid. |
*/}

<Info>
Throughout this documentation, the green cloud icon in the sidebar marks sections and pages for capabilities that require a Prowler Cloud or Prowler Private Cloud [subscription](https://prowler.com/pricing).
</Info>

Products without a documentation page here are available through the Prowler team. [Contact us](https://prowler.com/contact) for details.

## Open Source Projects

| Name | Description |
|------|-------------|
| [Prowler CLI](/getting-started/products/prowler-cli) | Command line tool to run security scans across all supported providers. |
| [Prowler Local Server](/getting-started/products/prowler-app) | Self-hosted web application and API to run scans, visualize findings, and manage cloud providers. Formerly Prowler App. |
| [Prowler Local Dashboard](/user-guide/cli/tutorials/dashboard) | Local web dashboard to visualize scan results from Prowler CLI CSV outputs. Shipped with Prowler CLI. |
| [Prowler SDK](/getting-started/products/prowler-sdk) | Python library that powers Prowler CLI and Prowler Local Server. Part of the [prowler repository](https://github.com/prowler-cloud/prowler). |

## Former Names

| Former name | Current name |
|-------------|--------------|
| Prowler App | [Prowler Local Server](/getting-started/products/prowler-app) |
| Prowler Enterprise | Prowler Private Cloud |

## Prowler for MSPs and MSSPs

Prowler partners with managed service providers (MSPs) and managed security service providers (MSSPs) that operate Prowler for their customers. Visit [partners.prowler.com](https://partners.prowler.com) to become a partner.

---

### User Guide Ai Agents Index (`docs/user-guide/ai-agents/index.mdx`)

---
title: "Connect Your AI Agent to Prowler"
sidebarTitle: "Overview"
description: "Pick your AI agent and follow its guide to connect it to the Prowler Cloud MCP Server."
---

Connect your AI agent to the Prowler Cloud MCP Server at `https://mcp.prowler.com/mcp` so it can query findings, inspect security checks, and manage your Prowler providers.

Pick your agent below. Each guide is a full walkthrough with screenshots, verification steps, and the caveats specific to that client.

<CardGroup cols={2}>
  <Card title="Claude Code" icon="terminal" href="/user-guide/ai-agents/claude-code">
    Plugin and MCP-only choices, and which Claude surfaces work
  </Card>
  <Card title="Claude Desktop App (Chat)" icon="comment" href="/user-guide/ai-agents/claude-desktop">
    The Chat tab, via a local bridge
  </Card>
  <Card title="Codex / ChatGPT Desktop App" icon="code" href="/user-guide/ai-agents/codex">
    ChatGPT Desktop App, Codex CLI, the VS Code extension through same config file
  </Card>
  <Card title="Cursor" icon="arrow-pointer" href="/user-guide/ai-agents/cursor">
    Agentic code editor. Global and project scopes
  </Card>
  <Card title="VS Code / Copilot" icon="microsoft" href="/user-guide/ai-agents/vscode">
    Agent mode with secure key prompts
  </Card>
</CardGroup>

## Before You Start

All guides need the same two things:

- A **Prowler Cloud account** with at least one cloud provider connected. [Sign up](https://cloud.prowler.com) if you do not have one.
- A **Prowler API key**, created in Prowler Cloud. The key begins with `pk_` and is shown only once. See the [API Keys](/user-guide/tutorials/prowler-app-api-keys#creating-api-keys) guide.

<Note>
Using an agent that is not listed here? Any MCP-compatible client can connect. See the [generic configuration reference](/getting-started/basic-usage/prowler-mcp#cloud-mcp-server-configuration-recommended) for the raw connection details.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="What Is Prowler MCP" icon="cloud" href="/getting-started/products/prowler-mcp">
    How the MCP Server fits into Prowler
  </Card>
  <Card title="Configuration Reference" icon="gear" href="/getting-started/basic-usage/prowler-mcp">
    Cloud and local server options, all clients
  </Card>
</CardGroup>

---

### AGENTS (`docs/AGENTS.md`)

Always that you are writting documentation try to follow this text/communication style guide:

# Prowler's Brand Voice

Prowler is the open cloud security platform trusted by thousands of organizations automating security monitoring and compliance with hundreds of built-in security checks, remediation solutions, and compliance frameworks. With over 10 million downloads, thousands of contributors, and a vibrant global community, Prowler is driving the open-source cloud security movement by providing transparent, customizable, and user-friendly solutions that help teams secure AWS, Azure, GCP, Kubernetes, and Microsoft 365 environments. Leveraging open-source innovation and cost savings, the Prowler platform makes cloud security 10 times more cost-effective and accessible than alternatives.

These values must be demonstrated in all our conversations and communications.

---

## Unbiased Communication

Prowler aims to reach every person in the globe. Our communications must be as inclusive and diverse as possible every time. We are guided by the following principles:

### Avoid Gendered Pronouns

Reference to gendered pronouns (she/her/hers, he/his/his, they/them/theirs) must be avoided whenever possible.

* Use second person for communications (you/your/yours).
* Use a third-person reference instead of a gendered pronoun (the customer, the user).
* In case a gendered pronoun must be forcibly used, use they/them/theirs.
* Avoid double references like she/he, s/he, etc.

### Use Alternatives for Gendered Nouns

Avoid nouns that include gendered components. Examples:

* Businessman 🡪 Entrepreneur, businessperson, executive
* Salesman 🡪 Sales executive, sales representative
* Mankind 🡪 Humanity, people
* Penmanship 🡪 Calligraphy, handwriting
* Middleman 🡪 Intermediary, negotiator

### Diversity, Equity, and Inclusion

All communications must prioritize diversity and inclusivity. When incorporating examples, ensure representation across sex, gender, age, identity, race, culture, background, ability, and socioeconomic status. Strive for balanced and respectful depictions.

### Cultural and Geographical Awareness

Before referencing a region, country, culture, national status, political status, or socioeconomic realities, conduct thorough research. Maintain a respectful, informed approach and avoid unnecessary conflicts.

### Avoiding Generalizations

Avoid broad assumptions about gender, sex, race, sexual orientation, nationality, or culture. Generalizations can introduce bias and misrepresentation. Example to avoid: "Cybersecurity is of the utmost importance in the country, where corruption runs amok."

### Respectful Language

Derogatory terms must not be used. If uncertain about terminology, consult individuals from the relevant region or community to ensure accuracy and appropriateness.

### Clear and Accessible Language

* **Jargon:** Use technical terminology only when the audience is expected to understand it. If uncertain, opt for clear and universally accessible language.
* **Slang:** Minimize slang usage. Even when confident about the audience, prefer formal and neutral language to enhance clarity.

### Militaristic Language

Current tendencies in a topic as sensitive as cybersecurity avoid violent and militaristic references save for explicit reference to combat. These are some alternatives:

* Combat, fight, eliminate 🡪 Address, protect, safeguard, ward
* Kill chain 🡪 Cyberattack chain
* Attacker 🡪 Cyberattacker, bad actor, threat actor
* Defense-in-depth approach 🡪 Multilayered approach
* First line of defense, frontline 🡪 Security, protection, defense
* External attack surface 🡪 Vulnerabilities, point of access, external exposure

### Note on Safety and Security

“Safety” and “security” are terms often misunderstood. “Safety” is the microscopic, personal and individual term, while “security” is the macroscopic, broader, national term. Examples:

a. Seat belts are great for personal safety.
b. National security is of the utmost concern nowadays.

---

## Naming Conventions

### Prowler Features

Prowler Features are considered proper nouns. They are to be referenced without articles in all pieces of writing.

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.

Prowler Products:

* **Prowler Cloud**
* **Prowler Private Cloud** (formerly Prowler Enterprise)
* **Prowler Hub**
* **Prowler Lighthouse AI**
* **Prowler MCP**

Open Source projects:

* **Prowler CLI**
* **Prowler Local Server** (formerly Prowler App)
* **Prowler Local Dashboard** (the Prowler CLI dashboard)
* **Prowler SDK**

Other Prowler Features:

* **Built-in Compliance Checks**
* **Multi-cloud Security Scanning**
* **Autonomous Cloud Security Analyst (AI)**
* **Threat & Misconfiguration Detection**
* **Role-Based Access Control (RBAC)**
* **Identity & Access Risk Detection**
* **Tag-Based Scanning & Filtering**
* **Audit Logs & Security Reports**
* **Agentless & Works Anywhere**
* **Automated Scans & Continuous Monitoring**
* **Chat-based Security Querying (AI)**
* **AI-Generated Detections & Remediations**
* **Prowler Studio**
* **Custom Security Policies**
* **Open Source & Full APIs**

---

## Verbal Constructions in Technical Writing

Choosing verbal constructions (using verbs) over nominal (using nouns) constructions can significantly impact the clarity, conciseness and especially readability of the content.

Nominal constructions often introduce unnecessary complexity or vagueness. For example:

* Nominal: "The creation of the report was successful."
* Verbal: "The report was successfully created."

Verbal constructions also tend to use fewer words, resulting in a more polished and concise style:

* Nominal: "The implementation of the solution reduced system downtime."
* Verbal: "The solution reduced system downtime."

Verbal constructions are to be chosen over nominal constructions whenever possible.

---

### Addendum: Verbal Structures Actually State your Purpose

* **Example 1:** Recommendation for multiple subscriptions
* **Example 2:** Recommendation for Managing Multiple Subscriptions

Example 1 is vague and even potentially ambiguous. Verbs state your purpose and they must be used whenever possible.

---

## Avoiding The Second Person Except for Imperative Instructions

Explicit use of second-person pronouns (you) and possessives (your) should be minimized whenever possible. Those constructions are best reserved for cases when instructions are directly given in an imperative form:

### Example of Improvement Through Avoiding Second Person Pronouns

**Original:**
Prowler Local Server can be installed in different ways, depending on your environment:

**Improved Version:**
Prowler Local Server offers flexible installation methods tailored to various environments:

---

## Title-Case Capitalization

We use title case.

**Example:** This Is an Example on Title Case

Title case tends to be better for SEO because it improves readability and makes headlines more visually distinct, which can lead to higher click-through rates (CTR).

---

### Other Considerations on Capitalization

Follow these additional guidelines for capitalization:

### Inner Capitalization

Avoid internal capitalization of words in body text unless it is part of a proper name or brand denomination. Example: instead of E-mail and e-Book use email/e-mail and e-book.

### Acronym Capitalization

Do not capitalize the individual words of the spelled-out form of acronyms. Example: instead of CTI (Cyber Threat Intelligence) use CTI (cyber threat intelligence), but AWS (Amazon Web Services) is to be kept as is.

### Avoid Capitalization for Emphasis

Do not capitalize words in order to emphasize them.

### Capitalization of Languages and Standards

Check for the proper capitalization of language and standard names.

Language examples: HTML, JSON, YAML, XML, etc., must be capitalized.

Standard examples: standards follow title-case capitalization: Industrial Automation and Control Systems (IACS).

### Capitalization of Laws and Regulations

Laws and Regulations follow title-case capitalization. If referring to a non-domestic law or regulation, add the nationality and the original name.

**Example:** Code for the Cybersecurity Law published in the Spanish Official State Bulletin (BOE, Boletín Oficial del Estado).

Most UE Regulations have an official translation for all UE languages; please check it on EUR-Lex portal and choose the proper language: https://eur-lex.europa.eu/.

The different languages can be chosen on the portal under Languages, formats and link to OJ.

---

## Hyphenation

Hyphenation is to be used for noun modifiers in prenominal position, i.e., placed before nouns.

**Example:** Prowler is a world-leading company in open-source software.

It is not to be used for predicate adjectives in postnominal position.

**Example:** Prowler has many features built in.

### Note on Hyphenation and SEO

Google treats hyphens as word separators, as if they were blank spaces, i.e., the term `high quality checks` is treated as if it was the same term as `high-quality checks`. Hyphenation does not affect SEO on body text, thus the grammatically correct approach is recommended as sign of good writing. However, underscores (`_`) are treated as different words. This has implications particularly for URLs.

Hyphens are preferred for URLs as they improve readability and indexing.

**Example:**
* Better approach: `example.com/this-is-an-URL`
* Less ideal approach: `example.com/this_is_an_URL`

---

## Bullet Points

Bullet points offer several advantages:

* **Improved readability:** Bullet points make information scannable and enable vertical reading, allowing users to easily spot relevant details and breaking content into more digestible pieces.
* **Highlighting of relevant information:** They emphasize the most salient points, improving focus and enabling quick summarization at a glance.
* **Improved retention:** Bullet points enhance memory retention and contribute to a clearer, more polished final product.
* **Structured presentation:** They improve user experience through the logical organization of content.
* **SEO relevance (Search Engine Optimization):** Bullet points make content easier to consume and offer the following SEO benefits:
    * Reduced bounce rates
    * Increased time spent on page
    * Strategic keyword optimization
    * Improved chances of being featured in search engine snippets
    * Enhanced crawlability for search engines.

---

### When to Use Bullet Points

The use of bullet points is highly recommended when:

* Information can be logically divided into multiple categories, each sharing characteristics, features, or other relevant classifications.
* Items are significant enough as standalone concepts to deserve their own bullet point.

#### Example of Improvement Through Bullet Points

**Original:**
It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, FedRAMS, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, AWS Well-Architected Framework Security Pillar, AWS Foundational Technical Review (FTR), ENS (Spanish National Security Scheme), and your custom security frameworks.

**Improved with Bullet Points:**

**Prowler CLI Features:**
Prowler CLI includes hundreds of built-in controls to ensure compliance with standards and frameworks, including:

* **Industry standards:** CIS, NIST 800, NIST CSF, and CISA
* **Regulatory compliance and governance:** RBI, FedRAMP, and PCI-DSS
* **Frameworks for sensitive data and privacy:** GDPR, HIPAA, and FFIEC
* **Frameworks for organizational governance and quality control:** SOC2 and GXP
* **AWS-specific guidance:** AWS Foundational Technical Review (FTR) and AWS Well-Architected Framework (Security Pillar)
* **Regional compliance:** ENS (Spanish National Security Scheme)
* **Custom security frameworks:** Tailored to meet your organization’s specific needs

---

### Punctuation of Bullet Points

There are several options for punctuating bullet points. Regardless of the style chosen, it is imperative to maintain consistency throughout the text.

* **No punctuation (minimalistic):** This strategy is suitable when no verbs are involved and is best used to highlight products or features in isolation. For example:

    Prowler Local Server is composed of three key components:
    * Prowler UI
    * Prowler API
    * Prowler SDK

    This example highlights each element individually and fosters retention with a noise-free approach.

* **Periods for full sentences:** This approach works best when each bullet point forms a full sentence or includes verbs. For example:

    Prowler Local Server is composed of three key components:
    * Prowler UI, a web-based interface, built with Next.js, providing a user-friendly experience for executing Prowler scans and visualizing results.
    * Prowler API, a backend service, developed with Django REST Framework, responsible for running Prowler scans and storing the generated results.
    * Prowler SDK, a Python SDK designed to extend the functionality of the Prowler CLI for advanced capabilities.

    This example demonstrates a polished list using proper punctuation.

* **Semi-colons with final period:** This approach was traditionally used for those cases when bullet points were part of a continuous sentence or logical succession. However, it is being deprecated, consistent with the declining use of semi-colons in modern writing. It is to be avoided whenever possible. For example:

    Prowler UI:
    * is a web-based interface;
    * is built with Next.js;
    * provides a user-friendly experience for executing Prowler scans and visualizing results.

---

### Advantages of Adding Headers to Bullet Points

Adding headers to bullet points in technical writing is a powerful technique that enhances both the clarity and usability of the content. It has also advantages for SEO:

* Increased crawlability of search engines
* Enhanced keyword integration
* Improved user engagement
* Enhanced snippetting by search engines
* Reduced bounce rates

It is recommended to add headers to bullet points whenever possible.

---

## Quotation Marks

### Quotation Marks Usage in Technical Documentation

Proper use of quotation marks enhances clarity and consistency in technical writing. Below are key guidelines for using double and single quotation marks, following American English conventions.

### Double Quotation Marks

* Use for titles of books, movies, songs, and articles.
* Enclose direct quotes:
    * **Example:** The developer said, “We will try to fix this issue.”
* Capitalize the first word if quoting a full sentence.
* When quoting a phrase within a sentence, do not capitalize:
    * **Example:** The news portal called our product “one of the most efficient online help authoring tools.”
* Use scare quotes when words acquire a different or ironic meaning:
    * **Example:** The update is “scheduled” to release next week.
* To refer to a term without applying its meaning, use double quotes (or italics):
    * **Example:** Avoid terms like “don’t worry” in pop-ups to prevent user anxiety.

### Single Quotation Marks

* Used inside double quotes:
    * **Example:** He said, “I am not sure what ‘single sourcing’ means.”
* In British English, the order is often reversed (single quotation marks on the outside).

---

### Double Quoting in Software Documentation

Double quoting is to be used through software documentation where their use does not interfere with formatting restrictions.

1.  **Menu Items & UI Options**
    * Use double quotation marks when referring to selectable items in software interfaces.
    * **Example:** Click “File” and select “Save As” to export your document.
2.  **Buttons & Commands**
    * Use double quotes for labeled interface elements that users interact with.
    * **Example:** Select “Submit” to finalize the form.
3.  **Exact Input & User Actions**
    * If users need to enter exact text, enclose it in double quotes.
    * **Example:** Type “admin” in the username field.
4.  **Avoid Quoting Software Names**
    * Do not use quotation marks for software product names unless required for clarity.
    * **Correct:** Open Microsoft Excel.
    * **Incorrect:** Open “Microsoft Excel.”

---

## Interaction Verbs

The following are the correct verbs that must be used when referring to user interactions with the software.

### Mouse & Trackpad Actions (Desktop/Laptop)

* **Click:** Press and release the left mouse button or trackpad without moving the pointer.
    * **Example:** Click the “OK” button to confirm. 🡪 Transitive
* **Click on:** Often interchangeable with "Click," but less commonly used in technical writing for UI interactions.
    * **Example:** Click on the "Settings" icon to open preferences. (Less recommended—“Click” is preferred.)
* **Double-click:** Press and release twice in quick succession, usually to open files or applications.
    * **Example:** Double-click the document to open it. 🡪 Transitive
* **Right-click:** Press and release the right mouse button to open a context menu.
    * **Example:** Right-click the folder and select “Properties.” 🡪 Transitive

### Touchscreen Actions (Mobile & Touch)

* **Tap:** Touch the screen lightly with a finger or stylus, equivalent to "Click" on a mouse.
    * **Example:** Tap the “Sign in” button.
* **Double-tap:** Quickly touch the screen twice, often used for zooming or selecting text.
    * **Example:** Double-tap an image to zoom in.
* **Press and hold:** Touch and hold the screen for a moment to access additional options.
    * **Example:** Press and hold an app icon to see more actions. (Similar to “Right-click” in desktop environments.)

### Additional Actions

* **Drag:** Click or tap an item and move it while holding down the button or finger.
    * **Example:** Drag the file into the folder.
* **Swipe:** Move a finger across the touchscreen horizontally or vertically.
    * **Example:** Swipe left to dismiss the notification.
* **Pinch to zoom:** Use two fingers to zoom in or out.
    * **Example:** Pinch the screen to zoom in on the image.
* **Scroll:** Move the mouse wheel, swipe, or use the arrow keys to navigate up/down.
    * **Example:** Scroll down to see more results.

The widely-accepted terminology for gestures is Windows’: https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741

---

## Sentence Structure for Technical Writing and SEO

When writing technical documentation, clarity, conciseness, and searchability (SEO) are key factors. Let’s compare the following two sentence structures, extracted from Prowler’s documentation:

**Option 1:**
"Open a terminal and execute the following command to create a new custom role."

**Option 2:**
"To create a new custom role, open a terminal and execute the following command."

### SEO Optimization

* Search engines prioritize clear intent at the beginning of a sentence.
* Option 2 starts with the action users are likely to search for (e.g., "Create a custom role"), which improves SEO rankings and makes the content more likely to match search queries.
* Option 1 places the primary search term toward the end, making it less effective for keyword optimization.

### Technical Writing Best Practices

* Technical writing emphasizes clear objectives first, followed by actions.
* Option 2 follows this best practice by stating the goal first ("To create a new custom role") and then providing instructions.
* Option 1 is still acceptable for step-by-step guides, but Option 2 is more effective for tutorials, manuals, and documentation.

### Key Takeaways

* Draft trying to mimic the most likely way users are to find the information (“Ctrl + F approach”).
* Place keywords and key terms at the beginning of sentences so that they rank better SEO-wise.
* Rule of thumb: “In order to what” precedes the “what”. “What” must mirror the user’s most likely way of drafting or searching.

---

## Section Titles and Headers in Technical Writing

Effective headers and section titles enhance document readability and structure, making content more accessible to the reader. This chapter outlines best practices for crafting clear, consistent, and meaningful headings.

1.  **Purpose of Headers**
    Headers serve several key functions:
    * **Improve Navigation:** Allow users to quickly locate relevant information.
    * **Enhance Readability:** Break down complex topics into manageable sections.
    * **Establish Hierarchy:** Define the logical flow of content.
    * **SEO:** Headers impact SEO both directly and indirectly:
        * Search engines use headings to determine the hierarchy and relevance of content.
        * **H1:** The primary heading (should be unique and descriptive).
        * **H2-H6:** Subheadings that break down content logically.
        * Best practices for SEO-friendly headers:
            * Include keywords naturally in headings.
            * Avoid keyword stuffing—keep it clear and readable.
            * Use structured hierarchy (H1 → H2 → H3, etc.).
2.  **Header Levels and Formatting**
    Use a structured approach for organizing section titles. Common conventions include:
    * **Title:** The primary heading of the document (e.g., H1).
    * **Main Sections:** First-level headers (H2), introducing key content areas.
    * **Subsections:** Second-level headers (H3) to detail specific topics within sections.
    * **Subtopics:** Third-level headers (H4+) used sparingly for finer details.

    **Example:**

    ```markdown
    # Document Title (H1)
    ## Main Section (H2)
    ### Subsection (H3)
    #### Subtopic (H4)
    ```

3.  **Writing Effective Headers**
    When crafting headers and section titles, follow these guidelines:
    * **Be Descriptive:** Clearly indicate what the section covers.
        * **Poor:** Introduction (too vague)
        * **Good:** Introduction to AWS CloudShell Installation (informative)
    * **Keep It Concise:** Use precise language without unnecessary words.
    * **Maintain Consistency:** Apply uniform formatting and style conventions throughout.
    * **Avoid Special Characters:** Limit punctuation for clarity—avoid excessive symbols, dashes, or underscores.
4.  **Capitalization Rules**
    Use Title Case for headers to ensure a professional look:
    * **Good:** How to Clone and Install Prowler from GitHub
    * **Poor:** How to clone and install Prowler from GitHub

    For technical documentation, sentence case may be used for readability in subheadings. Please note this differs from headers and it is only a recommendation, but consistency is to be kept throughout the documentation:

    * **Example:**
        * How to Clone and Install Prowler from GitHub (header: Title case)
            * How to install uv dependencies (subheading: Sentence case)
5.  **Using Keywords in Headers**
    Headers should include relevant keywords to improve document searchability:
    * **Good:** Scanning AWS Accounts in Parallel
    * **Poor:** Ways to scan on AWS (vague and imprecise)
6.  **Consistency Across Documents**
    Ensure uniformity in section titles across related documentation:
    * **Standardized Header Naming:** Use consistent wording for common sections (e.g., "Installation," "Setup," "Configuration").
    * **Numbering Sections (If Necessary):** For structured guides, include numbering where appropriate (e.g., "Step 1: Install Prowler").

---

## Version Badge for Feature Documentation

The Version Badge component indicates when a specific feature or functionality was introduced in Prowler. This component is located at `docs/snippets/version-badge.mdx` and should be used consistently across the documentation.

### When to Use the Version Badge

Use the Version Badge when documenting:

* New features added in a specific version.
* New CLI options or flags.
* New API endpoints or SDK methods.
* New compliance frameworks or security checks.
* Breaking changes or deprecated features (with appropriate context).

### How to Use the Version Badge

1.  **Import the Component**

    At the top of the MDX file, import the snippet:

    ```mdx
    import { VersionBadge } from "/snippets/version-badge.mdx"
    ```

2.  **Place the Badge**

    Insert the badge immediately after the section header or feature title:

    ```mdx
    ## New Feature Name

    <VersionBadge version="4.5.0" />

    Description of the feature...
    ```

3.  **Version Format**

    Use semantic versioning format (e.g., `4.5.0`, `5.0.0`). Do not include the "v" prefix.

### Placement Guidelines

* Place the Version Badge on its own line, directly below the header.
* Leave a blank line after the badge before continuing with the content.
* For subsections, place the badge only if the subsection introduces something new independently from the parent section.

**Example:**

```mdx
## Tag-Based Scanning

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="4.3.0" />

Tag-Based Scanning allows filtering resources by AWS tags during security assessments...
```

---

## AppliesTo Banner for Product Scope

The AppliesTo component states which products a guide covers and links to the product families page. It is located at `docs/snippets/applies-to.mdx`.

### When to Use the AppliesTo Banner

Use it on web UI tutorial pages that apply to more than one product (for example, a guide written for Prowler Cloud whose steps also work on Prowler Local Server). Do not combine it with the SubscriptionBanner: pages carrying the SubscriptionBanner already state their availability.

### How to Use the AppliesTo Banner

```mdx
import { AppliesTo } from "/snippets/applies-to.mdx"

<AppliesTo />
```

The default covers Prowler Cloud, Prowler Private Cloud, and Prowler Local Server. Pass the `products` prop to narrow the scope:

```mdx
<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.

### Cloud Marker for Subscription Content

The custom green cloud glyph (`docs/images/icons/cloud-bold.svg`) marks content that requires a Prowler Cloud or Prowler Private Cloud subscription. It renders as a trailing `::after` element through the "Cloud marker" rules in `docs/style.css`, so sidebar labels stay left-aligned.

* Pages: add a `li[id="<page path>"] a span::after` selector to the Cloud marker rule in `docs/style.css` for every page whose content is subscription-gated. The `li` id equals the page URL path. Pages where only one section is gated (for example the Support page, where only the Support Desk carries the SubscriptionBanner) get no marker.
* Navigation groups: add a selector only when every page in the group is subscription-gated. One sanctioned exception: the Prowler MCP group is marked because the hosted server at `mcp.prowler.com` includes tools for Prowler Cloud-specific features, and its overview page explains the free local alternative. Nested groups render as `li[data-title="<group name>"]` with a button toggle, so use `li[data-title="<group name>"] > button span:first-child::after`. Top-level groups render as `h3` headings without `data-title`, which means name collisions with top-level groups resolve themselves; a gated top-level group (always expanded) is selected through its sibling list with `:has()`, like the Security tab group. Do not use `:has()` on child links of nested groups: collapsed groups do not render their children.

Do not use the `icon` field for this marker (icons render before the label and misalign the sidebar), and do not use `"tag"` on navigation groups (group-level tags crash `mint broken-links` with a stack overflow, verified with mint 4.2.689). The SVG stroke uses the fixed brand green `#10B981` on purpose: it must be visible on both themes without `currentColor` support.

The marker meaning is explained on the Prowler Product Families page at `getting-started/products/index.mdx`: keep that note in place.

---

## Avoid Assumptions Regarding Audience’s Expertise

### Understand Your Audience’s Expertise

Despite knowing your target audience, assumptions on target audience’s expertise or knowledge are to be avoided.

Adjust the level of detail based on expected reader proficiency, but make sure to be as explanatory as humanly possible.

### Define Key Terms and Acronyms on First Use

Even if your audience is technical, some domain-specific terms may vary.
* Introduce jargon only after defining it clearly.
* If using acronyms (e.g., IAM, MFA), spell them out on first mention:
    * AWS Identity and Access Management (IAM)
    * Multifactor Authentication (MFA)

### Don’t Assume Unwritten Knowledge

Even experienced readers may not know every prerequisite. If a process relies on prior steps, briefly reference them:

* Before configuring security groups, ensure VPC networking is set up.

### Use Consistent Formatting

### Provide as Many Examples as Deemed Right… and Then Some

### Anticipate Common Knowledge Gaps

### Avoid Excessive Notes

Notes are often omitted by readers and they clutter text, so use them sparingly and only for additional information that is not essential or prompts any error or mistake.

---

## Using Warnings and Danger Calls for High-Severity Information

In technical documentation, warnings and danger calls highlight critical risks, guiding users in preventing security breaches or system failures. Proper usage ensures clarity and actionable guidance.

1.  **Define Severity Levels**
    Before applying Note, Warning, or Danger, clearly define their significance:
    * **Note:** Provides general information or best practices (low severity).
    * **Warning:** Indicates potential issues if instructions are not followed (moderate severity).
    * **Danger:** Highlights actions that could result in severe consequences, such as system corruption or data loss (high severity).
2.  **Explain Consequences**
    Each warning or danger call should explicitly describe the impact of ignoring the caution:
    * **Good:** Disabling encryption may expose sensitive data to unauthorized access.
    * **Poor:** Avoid disabling encryption.
3.  **Provide Remediation and Troubleshooting**
    Whenever possible, direct users to troubleshooting guides or mitigation steps to resolve the issue.

    **Example:**
    **Danger:** Running this command will **permanently delete all data**. Refer to @Data Recovery Guide for restoration steps.

---

### Changelog (`docs/changelog.mdx`)

---
title: "Changelog"
description: "New features and improvements in each Prowler release"
rss: true
---

<Update label="v5.45.0" description="October 15, 2026">
  ### 🤖 Azure — AI Services Coverage

  A new Azure AI Services service audits the Azure AI and Azure OpenAI accounts of each subscription, with nine checks across network exposure, authentication, encryption, identity, logging and content safety, all contributed by @rajeshnadiminti. Thank you!

  - `aiservices_account_public_network_access_disabled`, `aiservices_account_network_acls_default_action_deny`, `aiservices_account_uses_private_endpoint` and `aiservices_account_outbound_network_restricted` cover how the account can be reached and what it can reach.
  - `aiservices_account_local_auth_disabled` and `aiservices_account_managed_identity_enabled` cover key-based access and the account's own identity.
  - `aiservices_account_encrypted_with_cmk` and `aiservices_account_diagnostic_logging_enabled` cover customer-managed key encryption and diagnostic logs.
  - `aiservices_deployment_content_filter_prompt_shield_enabled` verifies that model deployments have Prompt Shields enabled in their content filter.

  ### 🪪 Microsoft 365 — New Entra ID Checks

  Five new Entra ID checks, all contributed by @kinomakino. Thank you!

  - `entra_cross_tenant_access_default_outbound_restricted` verifies that the default cross-tenant access policy does not let every user reach every external application through B2B collaboration or B2B direct connect.
  - `entra_policy_admin_self_service_password_reset_disabled` verifies that Self-Service Password Reset is disabled for administrators.
  - `entra_policy_msol_powershell_blocked` verifies that the tenant authorization policy blocks the legacy MSOnline PowerShell module.
  - `entra_app_registration_redirect_uris_secure` flags app registrations with HTTP, wildcard, malformed or `azurewebsites.net` redirect URIs.
  - `entra_dynamic_group_member_of_rule_not_used` flags dynamic groups whose membership rule uses the `memberOf` operator, which Microsoft retires on November 3, 2026.

  Explore all checks at [Prowler Hub](https://hub.prowler.com/check).

  ### ☁️ OCI — Connect Tenancies Outside us-ashburn-1

  OCI API key credentials now take a home region, and the credentials form in Prowler App requires it. Identity calls that validate the credentials and discover the subscribed regions used to go to `us-ashburn-1` regardless of the tenancy, so a tenancy not subscribed to that region could not connect. The home region is only used for that bootstrap; scans still audit every subscribed region.

  Read more in the [Getting Started with OCI documentation](https://docs.prowler.com/user-guide/providers/oci/getting-started-oci).

  ### 🛠️ Improvements and Fixes

  - **AWS:** Organizations metadata is fetched from the partition of the audited account, using the region that answered the STS bootstrap, instead of the commercial `us-east-1` endpoint, which timed out in GovCloud.
  - API Gunicorn workers are recycled after 10,000 requests, plus up to 1,000 of jitter, to cap memory growth. Both values are configurable with `GUNICORN_MAX_REQUESTS` and `GUNICORN_MAX_REQUESTS_JITTER`.
  - The first-login redirect to the Add Provider wizard is retried on the next load when the navigation was cut short, instead of being written off after a single attempt.
  - The AWS connect step offers to invite a teammate when the user cannot access the account credentials.
  - **Prowler Cloud:** the Slack channel picker reads the channels the Prowler app is in before the public ones, so invited private channels show up in large workspaces, and the partial list message tells the user to invite the app to a missing channel.

  ### 🔐 Security Updates

  - Kubernetes kubeconfigs accept only inline credentials and known cluster fields. File-path fields, command-based authentication, `auth-provider` directives, proxy URLs and unknown keys are rejected by the API and the credentials form, and stored kubeconfigs are re-validated before each scan and connection test.
  - IaC repository URLs that resolve to loopback, private or otherwise non-public hosts are rejected before the connection test and the scan clone, including every destination a redirect points to.
  - CSV outputs prefix cells starting with `=`, `+`, `-`, `@`, tab or carriage return with a single quote so spreadsheet applications treat them as text, and the HTML report escapes the compliance column.
  - `GET /api/v1/users/{id}` no longer exposes another user's memberships, roles or tenant names from tenants other than the one in the request token.
  - API error reports sent to Sentry no longer include user identity, IP addresses, cookies, headers or request bodies.
  - PowerShell upgraded to 7.5.12 in the SDK and API container images, bundling .NET runtime 9.0.21.
  - Python base images upgraded to 3.12.15 in the SDK and API and to 3.13.16 in the MCP Server, and Trivy to 0.75.0, patching CVE-2026-19445, CVE-2026-19553 and the grpc-go and x/crypto CVEs bundled in Trivy.
  - `django` upgraded to 5.2.17 in the API, patching CVE-2026-15307.
  - `pyjwt` upgraded to 2.14.0 in the SDK, the API and the MCP Server, patching GHSA-ffc3-869f-jxw9.
  - `pymongo` upgraded to 4.18.2 and `urllib3` to 2.8.0 in the SDK and the API, patching CVE-2026-96748, CVE-2026-96749, CVE-2026-97687 and CVE-2026-97689.
  - `dulwich` upgraded to 1.2.9 in the SDK and the API, patching GHSA-5fqc-mrg8-w798, GHSA-8mcx-5rqc-vhmf, GHSA-8w8g-wq8h-fq33 and GHSA-cm62-gvxx-vmxx.
  - `next` upgraded to 16.3.8 in the UI, patching a remote code execution in `next/og` (GHSA-vcvr-r3jv-pc5j) and a server-side request forgery in Image Optimization (GHSA-cjq9-62q9-8jv4).
  - `sharp` upgraded to 0.35.5 and `proxy-addr` to 2.0.8 in the UI, patching GHSA-wq5f-xc86-pv6w and GHSA-jqcg-44mw-7w3h.

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @rajeshnadiminti: Azure AI Services service and its checks ([#12925](https://github.com/prowler-cloud/prowler/pull/12925), [#12926](https://github.com/prowler-cloud/prowler/pull/12926), [#12997](https://github.com/prowler-cloud/prowler/pull/12997))
  - @kinomakino: Microsoft 365 `entra_cross_tenant_access_default_outbound_restricted` ([#12991](https://github.com/prowler-cloud/prowler/pull/12991)), `entra_policy_admin_self_service_password_reset_disabled` ([#12990](https://github.com/prowler-cloud/prowler/pull/12990)), `entra_policy_msol_powershell_blocked` ([#13004](https://github.com/prowler-cloud/prowler/pull/13004)), `entra_app_registration_redirect_uris_secure` ([#12993](https://github.com/prowler-cloud/prowler/pull/12993)) and `entra_dynamic_group_member_of_rule_not_used` ([#13003](https://github.com/prowler-cloud/prowler/pull/13003)) checks

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.45.0) for the complete list of changes.
</Update>

<Update label="v5.44.0" description="September 29, 2026">
  ### 🔁 Findings — Re-check a Resource with a Partial Scan

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  A resource that has just been fixed can be confirmed from the Findings page without waiting for the next full scan. **Re-check resource** is available in the actions menu of every resource row and in the resource detail drawer, and a hint icon next to **Last seen** opens it directly. It launches a partial scan that runs again only the checks that last reported on that resource; its findings update when the scan completes, and every other resource keeps the results of the latest full scan. Roles need the Manage Scans permission, and a re-check is refused while the provider has a scan running or queued.

  Re-checked resources that now pass drop out of the finding groups list and its drill-down instead of opening a detail panel that still reports `FAIL`. Partial scans do not change overviews or compliance until the next full scan and produce no report files, so the Scans table marks them as **Partial**, offers no report download for them, and the per-scan Compliance selector leaves them out.

  Partial scans can also be launched outside the Findings page:

  - **API:** `POST /api/v1/scans` accepts up to 10 resources in `resource_uids`, and scans expose `is_partial` with a `filter[is_partial]` filter.
  - **MCP Server:** `prowler_trigger_scan` takes a `resource_uids` argument, and `prowler_list_scans` and `prowler_get_scan` return `is_partial`, with an `is_partial` filter on `prowler_list_scans`.
  - **Lighthouse AI:** can launch a partial scan to re-check specific resources, such as confirming a remediation.

  ### ☁️ AWS — Connect an Account in One Step

  The Add Provider wizard connects an AWS account in a single step. The account ID is read from the role ARN (or typed when using static access keys), the role is assumed with Prowler's own credentials, and the account, its credentials and the connection test are handled by one submit. A confirmed connection goes straight to the launch step. A refused connection stays on the form with the reason the API returned, so the fields can be fixed and retried without registering the account twice.

  New tenants without providers now land on this wizard on their first sign-in instead of a welcome modal, and the sidebar action reads **Add Provider** until the first provider is connected.

  Read more in the [Getting Started with AWS documentation](https://docs.prowler.com/user-guide/providers/aws/getting-started-aws).

  ### 🔌 Connection Tests No Longer Give Up Early

  The provider connection test no longer reports `Max retries exceeded` for checks that take longer than 30 seconds, such as networks where some AWS endpoints are unreachable. The UI now waits for the full time limit of the backend task, and if that is still exhausted it shows the provider's current connection state instead of a failure.

  On the SDK side, STS calls after a role assumption reuse the region that answered, so an unreachable partition region is waited on once instead of twice. The new `PROWLER_AWS_BOTO3_RETRIES_MAX_ATTEMPTS` environment variable sets the Boto3 retries for deployments that build the AWS provider without CLI flags, next to the existing timeout variables; `0` disables retries.

  Read more in the [Boto3 configuration documentation](https://docs.prowler.com/user-guide/providers/aws/boto3-configuration#retries-configuration).

  ### 🗄️ Self-Hosted — S3-Compatible Storage and Air-Gapped Deployments

  - `DJANGO_OUTPUT_S3_AWS_ENDPOINT_URL` points scan output uploads and downloads at S3-compatible object storage such as MinIO. Previously the only way to reach it was exporting process-wide AWS environment variables, which also hijacked unrelated AWS API calls such as role assumption for AWS providers.
  - Report downloads from a bucket with default SSE-KMS encryption no longer fail with `InvalidArgument`: when `DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION` is set, download URLs are signed with Signature Version 4 for that region.
  - Icons ship in the UI bundle instead of being fetched from `api.iconify.design`, so pages render correctly without internet access.
  - Celery worker fatal errors are logged instead of silenced, and every long-running service in `docker-compose.yml` restarts automatically after an unexpected crash.

  ### 📊 Consistent Latest Scan Across Endpoints

  Every endpoint now resolves a provider's latest completed scan the same way, so overlapping scans no longer make findings, compliance and mute rules read from different scans. Providers whose latest completed scan has no `completed_at` timestamp are no longer missing from those endpoints, and resources no longer keep a stale failed findings count when a scoped or imported scan completes after a full scan.

  ### 🛠️ Prowler App Fixes

  - API key authentication no longer locks the key row on every request, so a heavily used key no longer serializes all its requests; `last_used_at` is updated at most once per minute.
  - `POST /api/v1/scans` returns the new scan ID in `task_args` again.
  - Provider deletion no longer fails when the provider has Attack Paths scans recorded on a sink that is no longer configured, such as Neptune after moving back to Neo4j.
  - A periodic sweep drops orphaned Attack Paths temporary Neo4j databases left behind when a worker or Neo4j crashes mid-scan.
  - **Prowler Cloud:** imported findings no longer stay stuck in `pending` when the ingestion worker picks up the job before it is committed, and a failed enqueue marks the ingestion as failed.
  - **Prowler Cloud:** the Lighthouse AI connection check reports a network failure as one, naming the endpoint it could not reach, instead of hitting a time limit that looked the same as a bad key.
  - **Prowler Cloud:** the finding groups endpoints no longer query Manual Pass triages once per finding, and skip that overlay for tenants with no active Manual Pass.
  - The Findings page renders a skeleton at once and streams the table before the filters, and the **Finding Group** options load when the dropdown opens.
  - Mute rule creation errors show the API error message instead of the raw response body.
  - The sidebar no longer throws a hydration error on full page loads for users who last used the chat mode.

  ### 🔐 Security Updates

  - `DELETE /api/v1/tasks/{id}` requires the permission of the operation that queued the task and rejects provider deletions, and `GET /api/v1/tasks` hides tasks of providers outside the role's visibility.
  - The UI E2E workflow receives its AWS credentials through environment variables instead of template expansion.

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.44.0) for the complete list of changes.
</Update>

<Update label="v5.43.0" description="September 21, 2026">
  ### 🏛️ Compliance — FedRAMP 20x Consolidated Rules 2026

  The FedRAMP 20x Phase One pilot frameworks (`fedramp_20x_ksi_low_aws`, `fedramp_20x_ksi_low_azure` and `fedramp_20x_ksi_low_gcp`) are replaced by two universal frameworks built from the FedRAMP Consolidated Rules for 2026, each covering AWS, Azure, GCP, Kubernetes and Microsoft 365 from a single definition:

  - **FedRAMP 20x KSI** (`fedramp_20x_ksi_2026`): the 46 Key Security Indicators across 10 themes. There is one indicator catalog for every class instead of a separate Low fork; each indicator carries its class applicability and NIST SP 800-53 controls.
  - **FedRAMP 20x Class C FRR** (`fedramp_20x_frr_class_c_2026`): the 158 FedRAMP Rules of the Class C ruleset that bind cloud service providers. Most are program obligations (reports, notifications, certification package) and stay manual; checks are mapped only where they evidence part of the rule text. Configurable checks carry configuration requirements, so a relaxed `audit_config` cannot turn a requirement green.

  Automation or stored results that reference the pilot framework IDs need to move to `fedramp_20x_ksi_2026`. In Prowler App, both frameworks show the per-provider breakdown in the cross-provider compliance view and can be downloaded as OCSF.

  Read more in the [Compliance documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance).

  ### 🔎 AWS — Inspector Coverage, CISA KEV and FIPS Checks

  Seven new AWS checks back the vulnerability detection and cryptography rules of FedRAMP 20x Class C:

  - `inspector2_coverage_scan_status_active` and `inspector2_coverage_recently_scanned` report resources Amazon Inspector is not scanning, or last scanned more than `inspector2_max_days_since_last_scan` days ago (default 3).
  - `inspector2_active_findings_no_known_exploited_vulnerabilities` and `inspector2_active_findings_kev_within_due_date` report active findings whose CVE is in the CISA Known Exploited Vulnerabilities catalog, and those still open past the CISA due date. The KEV data comes from Inspector itself through `inspector2:BatchGetFindingDetails`, so no external feed is needed.
  - `inspector2_active_findings_within_max_age` reports active findings first observed more than `inspector2_active_finding_max_age_days` days ago (default 192).
  - `elbv2_listener_fips_tls_enabled` and `transfer_server_fips_security_policy_enabled` report HTTPS/TLS load balancer listeners and Transfer Family servers without a FIPS security policy.

  `inspector2:BatchGetFindingDetails` is not part of `SecurityAudit`, so it is now included in the Prowler additions policy and the CloudFormation scan role. Without it, the KEV checks report `MANUAL` naming the missing permission instead of a false `FAIL`.

  Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).

  ### ☁️ AWS — Partition Bootstrap Failover

  When `PROWLER_AWS_PARTITION` is set, the bootstrap STS calls (validating credentials, assuming a role and getting an MFA session token) now try up to two more regions of the partition if the first one cannot be reached. A GovCloud host whose configured region belongs to another partition was still sent to `us-gov-east-1`, and on a network that routes only to `us-gov-west-1` the connection check and the scan failed on perfectly valid credentials. Only connection errors and timeouts move on to the next region; credential errors are reported from the first one as before. Later STS calls reuse the region that answered, and nothing changes when `PROWLER_AWS_PARTITION` is unset.

  Read more in the [AWS Regions and Partitions documentation](https://docs.prowler.com/user-guide/providers/aws/regions-and-partitions).

  ### 🌐 Azure — Sovereign Cloud Endpoints for Defender and Key Vault

  Defender security contacts and Key Vault key rotation policies now use the endpoints of the cloud selected with `--azure-region` instead of the hardcoded `management.azure.com` and `vault.azure.net` hosts, so both work on `AzureUSGovernment` and `AzureChinaCloud`. Key Vault clients are built from the vault URI that Azure returns for each vault.

  Read more in the [Azure non-default cloud documentation](https://docs.prowler.com/user-guide/providers/azure/use-non-default-cloud).

  ### ✉️ Invitations — Expired Invitations No Longer Block Re-Invites

  A pending invitation past its expiry date is now reported as expired, and inviting the same email again marks it as expired and creates the new invitation instead of returning a generic error. The Invitations table disables Edit and Revoke on expired and revoked invitations, and `filter[state__in]` on the invitations endpoint no longer returns a server error.

  In Prowler Cloud, new organizations are offered an **Invite your team** step once the first provider is connected, and Prowler Private Cloud deployments can set `UI_SELF_REGISTRATION_ENABLED=false` to make sign-up invitation-only.

  Read more in the [Invitations documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-rbac#invitations).

  ### 📄 Reports — Downloads on Self-Hosted Storage

  Report downloads no longer depend on the storage host being the same inside and outside the container network. `DJANGO_OUTPUT_S3_AWS_PUBLIC_ENDPOINT_URL` signs the download URL against a browser-reachable host, so a deployment whose object storage answers only on an internal address serves the file instead of a link the browser cannot open. Leaving `DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION` unset, common on S3-compatible storage with no meaningful region, no longer makes the download fail with a server error.

  ### 🔍 Checks

  - **Huawei Cloud:** new `smn_topic_subscriptions` check reports SMN topics without any subscription.
  - **Cloudflare:** the API token links in the provider wizard request the SSL and Certificates, Bot Management and Zone WAF read permissions the checks need, so a token created from the wizard no longer produces failures on permissions it was never granted.
  - **Microsoft 365:** five Defender malware, anti-phishing and inbound anti-spam checks no longer fail with `KeyError` on tenants that use the Standard or Strict preset security policies, which dropped every finding of those checks. Preset policies are covered by `defender_strict_preset_security_policy_enabled`.
  - **Google Workspace:** `security_2sv_enforced` reports domain-wide 2-Step Verification failures as `FAIL` even when every failing setting is overridden for a group or organizational unit.

  Explore all checks at [Prowler Hub](https://hub.prowler.com/check).

  ### 🔐 Security Updates

  - `libsqlite3-0`, `gzip`, `perl-base`, `libssh2-1t64` and `libpcre2-8-0` upgraded in the SDK and API container images, patching high-severity Debian CVEs.
  - PowerShell upgraded to 7.5.11 in the SDK and API container images, bundling .NET runtime 9.0.20 and patching CVE-2026-62901.
  - `anyio` upgraded to 4.14.2 in the SDK, the API and the MCP Server, patching CVE-2026-63374.

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.43.0) for the complete list of changes.
</Update>

<Update label="v5.42.0" description="September 11, 2026">
  ### ☁️ AWS — ISO Partitions

  Prowler now resolves regions and services for the AWS ISO partitions (`aws-iso`, `aws-iso-b`, `aws-iso-e` and `aws-iso-f`) the same way it does for the commercial, China, European Sovereign Cloud and GovCloud partitions. The region matrix is filled from the endpoint metadata bundled with botocore, which needs no credentials or network access, so it covers partitions that are air-gapped from the internet. Scanning them no longer requires a hand-edited `aws_regions_by_service.json`: ISO regions such as `us-isob-east-1` are accepted by `--region` and `--excluded-region`.

  Deployments that declare `PROWLER_AWS_PARTITION` also keep their bootstrap STS calls in the configured region when it belongs to that partition. An install in `us-gov-west-1` that reaches AWS only through its own VPC endpoints is no longer sent to `us-gov-east-1`, where the connection check and the scan used to time out.

  Read more in the [AWS Regions and Partitions documentation](https://docs.prowler.com/user-guide/providers/aws/regions-and-partitions).

  ### ⏱️ AWS — Configurable Timeouts for Restricted Networks

  Scans from networks with restricted egress (VPC endpoints for only some services, GovCloud or private deployments) could take hours: Boto3 waits 60 seconds to connect by default and retries connection errors, so every service without a reachable endpoint cost up to four 60-second attempts in every region. Prowler now lowers the default connect timeout to 10 seconds, keeps the read timeout at 60 seconds, and exposes both through `--aws-connect-timeout` and `--aws-read-timeout`, or through the `PROWLER_AWS_BOTO3_CONNECT_TIMEOUT` and `PROWLER_AWS_BOTO3_READ_TIMEOUT` environment variables for deployments without a CLI. `--aws-retries-max-attempts 0` now disables retries instead of silently falling back to three, leaving a single attempt per call.

  Read more in the [Boto3 configuration documentation](https://docs.prowler.com/user-guide/providers/aws/boto3-configuration).

  ### 🐳 Image Provider — Reusable Vulnerability Database

  The Image provider now honors `TRIVY_CACHE_DIR`. When the variable names a directory, Trivy keeps its vulnerability database there and Prowler leaves the directory in place after the scan, so the database is downloaded once instead of on every scan. Hosts without internet access can now scan images by pointing `TRIVY_CACHE_DIR` at a pre-populated database and setting `TRIVY_SKIP_DB_UPDATE=true`. Without the variable, the temporary cache is created and removed as before.

  Read more in the [Image provider documentation](https://docs.prowler.com/user-guide/providers/image/getting-started-image#vulnerability-database-cache).

  ### 🎫 Jira Integration — Faster Connection Test

  Testing a Jira integration no longer reports a false failure on accounts with many projects. The connection test fetched the issue types of every project one request at a time, which could outlast the wait in the UI even when the check was about to succeed. Issue types are now fetched concurrently, a project whose issue types the integration user cannot see is no longer logged as an error, and the Integrations page keeps following the connection test instead of giving up after about a minute.

  Read more in the [Jira integration documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-jira-integration).

  ### 📚 Compliance — Catalog Integrity Fixes

  A new integrity test runs over every compliance framework, asserting unique requirement IDs, no check listed twice within a requirement, and that every referenced check exists for its provider. The fixes it drove span 42 frameworks across AWS, Azure, GCP, GitHub, Kubernetes and Microsoft 365:

  - **Duplicate requirement IDs:** identical copies are removed, and distinct requirements that shared an ID get their own, such as `1.10` in CIS AWS 5.0 and `rc_rp_1` for RC.RP-1 in NIST CSF 1.1. In Prowler ThreatScore for Azure, SQL auditing retention moves from `3.2.1` to `3.2.4`, and requirement `1.2.1` of Prowler ThreatScore for GCP now points to `iam_sa_no_user_managed_keys`.
  - **Stale check references:** checks that no longer exist are replaced with their current name when there is a direct equivalent, or removed so the requirement reports as manual. Most of these were in the FedRAMP 20x KSI frameworks.

  Renamed requirement IDs appear as new requirements for scans run after the upgrade.

  The compliance overview task that runs after every scan is also faster: ThreatScore mappings are read once from the compliance template instead of from every finding, and rows are inserted with time-ordered `uuid7` IDs grouped by framework and requirement.

  Read more in the [Compliance documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance).

  ### 🔍 Checks

  `rolesanywhere_profile_restricts_session_permissions`, `iam_role_service_trust_restricts_source_to_account` and `codebuild_project_uses_allowed_github_organizations` no longer crash with `TypeError` when the scanning role is denied `iam:ListRoles`, which dropped every finding of those checks for the account. Without the role inventory, an enabled IAM Roles Anywhere profile without session scoping reports `MANUAL`, and CodeBuild projects whose service role cannot be resolved are skipped.

  Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).

  ### 🔐 Security Updates

  - `next` upgraded to 16.3.3 in the UI, patching unauthenticated remote code execution through AVIF image optimization ([GHSA-2xp9-vwfh-vxw4](https://github.com/advisories/GHSA-2xp9-vwfh-vxw4)) and on Windows-hosted servers ([GHSA-p293-qw3h-jr36](https://github.com/advisories/GHSA-p293-qw3h-jr36)).
  - `sharp` upgraded to 0.35.4 in the UI, patching libheif image-decoding vulnerabilities ([GHSA-rgj7-g3m4-5g8c](https://github.com/advisories/GHSA-rgj7-g3m4-5g8c)).
  - `nanoid`, `js-yaml` and `postcss`, plus eleven transitive UI dependencies, upgraded to patched versions, resolving 40 npm audit advisories (21 high, 15 moderate, 4 low).
  - `libuuid` upgraded to 2.41.6-r1 in the MCP Server image, patching CVE-2026-53612, CVE-2026-53613, CVE-2026-53614, CVE-2026-76642, CVE-2026-78408 and CVE-2026-78410.

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.42.0) for the complete list of changes.
</Update>

<Update label="v5.41.0" description="September 2, 2026">
  ### 📥 Scans — Import Findings from the Browser

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Findings produced outside the platform, by the Prowler CLI or a CI pipeline, can now be brought into the app without leaving the browser. The Scans page gains an "Import Findings" dialog that takes a Prowler `.ocsf.json` report by drag-and-drop or file picker, hands it to the ingestion API and tracks the job to completion, reporting how many records were processed and how many were invalid. Files that are not a `.ocsf.json` report, or are empty, are refused before any upload starts, and a rejected upload or a failed status poll can be retried in place. The dialog is available to roles holding the Manage Ingestions permission.

  

  

  Read more in the [Import Findings documentation](https://docs.prowler.com/user-guide/tutorials/prowler-import-findings#using-the-ui).

  ### 🎫 Jira Integration — Finding Reference in Every Issue

  Every Jira issue created from a finding now carries a stable reference back to it. Issues are labeled `prowler`, `prowler-<provider>`, `prowler-<severity>`, `prowler-<check-id>` and `prowler-finding-<finding-uid>`, so they can be filtered, searched with JQL or matched by automation; labels are sanitized to Jira's limits so a long or unusual value never blocks issue creation. The issue also links back to the finding in Prowler, filtered by its UID so the link keeps working after later scans, and names the Prowler organization that sent it. Prowler Cloud always includes the link; Prowler Local Server enables it by setting `DJANGO_UI_BASE_URL` in the API environment.

  Read more in the [Jira integration documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-jira-integration).

  ### 📚 Compliance — CIS Google Workspace Foundations Benchmark v1.4.0

  Prowler now ships the CIS Google Workspace Foundations Benchmark v1.4.0. Alongside the new framework, the Google Workspace checks mapped to CIS were reworked to evaluate the benchmark's full audit procedure instead of a single condition, so Gmail spoofing actions, 2-Step Verification, password expiration and alert severity left on Google's defaults no longer pass. Expect new `FAIL` findings on domains that rely on those defaults. Three accuracy fixes also land:

  - `security_2sv_enforced` and `security_2sv_hardware_keys_admins` report `MANUAL` instead of judging domain-wide values that a group or a sub-organizational unit overrides; a domain-wide failure is still reported as such, with the override noted.
  - `rules_*_alert_configured` no longer passes a rule whose delivery to the alert center is disabled.
  - `security_password_policy_strong` no longer fails a domain that never touched the password strength setting, since Google enforces strong passwords by default.

  `security_login_challenges_configured` was unmapped from CIS Google Workspace requirement 4.1.4.1 (Post-SSO verification) and `security_2sv_enforced` from CISA SCuBA `GWS.COMMONCONTROLS.1.1` (phishing-resistant MFA), because neither check can prove what those requirements ask for.

  Read more in the [Compliance documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance).

  ### 🔍 Checks

  Ten new AWS checks land in this release, eight of them contributed by @tamg-aws. Thank you!

  #### Amazon Bedrock AgentCore

  - `iam_policy_passrole_to_bedrock_agentcore_restricted` flags customer-managed IAM policies that allow `iam:PassRole` over every role where the passed role can reach Bedrock AgentCore, so any principal holding the policy could run agent code under any role in the account.
  - `iam_policy_no_agentcore_workload_access_token_wildcard` flags customer-managed IAM policies that allow `bedrock-agentcore:GetWorkloadAccessToken`, `GetWorkloadAccessTokenForJWT` or `GetWorkloadAccessTokenForUserId` on resources reaching workload identities other than the caller's own.
  - `cloudwatch_log_group_agentcore_data_protection_policy_enabled` verifies that Bedrock AgentCore log groups mask sensitive data with a CloudWatch Logs data protection policy. The log group prefixes are configurable through `agentcore_log_group_name_prefixes` in `config.yaml`.

  #### Amazon GuardDuty

  - `guardduty_runtime_monitoring_enabled` flags detectors without unified Runtime Monitoring, the only feature that covers Amazon EC2 instances and Amazon ECS on AWS Fargate tasks in addition to Amazon EKS.
  - `guardduty_ai_protection_enabled` flags detectors without AI Protection, which analyzes CloudTrail data events from Amazon Bedrock, Amazon Bedrock AgentCore and Amazon SageMaker AI. A detector that does not report the feature is `MANUAL` rather than `FAIL`.

  `guardduty_eks_runtime_monitoring_enabled` no longer reports `FAIL` for detectors that use unified Runtime Monitoring, which is mutually exclusive with `EKS_RUNTIME_MONITORING` and already covers Amazon EKS.

  #### Amazon ECR and EKS

  - `ecr_registry_enhanced_scanning_enabled` verifies that the ECR registry scan type is enhanced (Amazon Inspector, covering programming language packages and continuous rescanning) instead of basic, reporting `MANUAL` when the registry scanning configuration cannot be read.
  - `eks_cluster_vpc_cni_network_policy_enforced` flags EKS clusters whose Amazon VPC CNI managed add-on does not enable Kubernetes network policy enforcement, reporting `MANUAL` where the EKS API cannot show the setting.

  #### AWS IAM, Elastic Beanstalk and MemoryDB

  - `iam_role_service_trust_restricts_source_to_account` flags IAM roles whose trust policy lets an AWS service principal assume the role without confining the request to a specific source account, including trust policies that `iam_role_cross_service_confused_deputy_prevention` does not evaluate.
  - `elasticbeanstalk_environment_no_secrets_in_configuration` scans the option settings of every Elastic Beanstalk environment for hardcoded secrets. Thanks to @haneul-24!
  - `memorydb_cluster_in_transit_encryption_enabled` verifies that MemoryDB clusters have in-transit encryption (TLS) enabled. Thanks to @UTKARSH698!

  Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).

  ### 🐳 Image Provider — On-Premises Registries

  Scanning registries that live on private networks is now supported end to end. `PROWLER_IMAGE_PROVIDER_ALLOWED_PRIVATE_NETWORKS` takes a comma-separated list of IPs and CIDRs the provider may reach, while every other non-public address, including link-local and loopback, stays blocked by the SSRF guard. Authentication negotiation is also more resilient: the provider falls back to Basic when a registry such as Harbor rejects the negotiated bearer token, and switches to a bearer token when the server answers a Basic or anonymous request with a Bearer challenge. `--registry-insecure` now propagates to Trivy through `TRIVY_INSECURE`, so images behind self-signed certificates can be pulled and scanned, not just enumerated. The flag now disables certificate validation for the image pull too, so keep it for trusted internal registries only.

  Registry scans also skip non-image OCI artifacts (Helm charts, cosign signatures, SBOM attestations), no longer abort the whole scan when Trivy fails on a single image, and enumerate repositories in parallel instead of one request at a time.

  Read more in the [Image provider documentation](https://docs.prowler.com/user-guide/providers/image/getting-started-image#on-premises-registries-and-private-networks).

  ### 🛠️ Prowler MCP Server — Tool Failures Reported as Errors

  Prowler Local Server tools now report a failure as an MCP tool execution error (`isError: true`, with the explanation in `content`) instead of a successful result carrying an `{"error": ...}` object, which clients and models read as a success. The Prowler Documentation and Prowler Hub tools follow the same rule: `prowler_docs_search` no longer reports a failed search as zero matches, `prowler_docs_get_document` no longer reports a failed fetch as a missing page, and `prowler_hub_get_check_code` and `prowler_hub_get_check_fixer` now name the provider a check ID actually belongs to instead of reporting it as nonexistent. `prowler_get_compliance_framework_state_details` also rejects a call that passes both `scan_id` and `provider_id` instead of silently ignoring the provider.

  Read more in the [Prowler MCP documentation](https://docs.prowler.com/getting-started/products/prowler-mcp).

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @tamg-aws: GuardDuty unified Runtime Monitoring and AI Protection checks ([#12564](https://github.com/prowler-cloud/prowler/pull/12564)), EKS VPC CNI network policy check ([#12661](https://github.com/prowler-cloud/prowler/pull/12661)), ECR enhanced scanning check ([#12660](https://github.com/prowler-cloud/prowler/pull/12660)), Bedrock AgentCore IAM and service trust checks ([#12664](https://github.com/prowler-cloud/prowler/pull/12664)), AgentCore log group data protection check ([#12662](https://github.com/prowler-cloud/prowler/pull/12662)), and fixes to ECR scan frequency ([#12560](https://github.com/prowler-cloud/prowler/pull/12560)), CloudWatch metric filters ([#12561](https://github.com/prowler-cloud/prowler/pull/12561)) and SageMaker direct internet access ([#12659](https://github.com/prowler-cloud/prowler/pull/12659))
  - @haneul-24: AWS `elasticbeanstalk_environment_no_secrets_in_configuration` check ([#12378](https://github.com/prowler-cloud/prowler/pull/12378))
  - @UTKARSH698: AWS `memorydb_cluster_in_transit_encryption_enabled` check ([#12246](https://github.com/prowler-cloud/prowler/pull/12246))
  - @ye11oc4t: GitHub repository discovery pagination for unscoped scans ([#12460](https://github.com/prowler-cloud/prowler/pull/12460))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.41.0) for the complete list of changes.
</Update>

<Update label="v5.40.0" description="August 28, 2026">
  ### 💬 Slack Integration — Alert Channel Destinations

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Alerts can now reach Slack. Connect a Slack workspace from the Integrations page, authorize one or several destination channels (the connection check confirms each channel with a one-time message and names any channel Slack refuses), and pick those channels in the alert modal's "Destination channels" selector, next to the "Recipients" selector for email. The alerts list summarizes both in a single "Destinations" column, showing a rule's email recipients and Slack channels at a glance. Disconnecting the workspace and recovering from revoked credentials are handled from the same page.

  

  

  Read more in the [Slack integration documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-slack-integration) and the [Alerts documentation](https://docs.prowler.com/user-guide/tutorials/prowler-alerts).

  ### 🤖 Lighthouse AI — Answer Feedback

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Every Lighthouse AI answer can now be rated with a thumbs up or thumbs down, with an optional field to describe what worked or what did not. Feedback is collected per answer, directly in the chat, and tells the team where Lighthouse should improve next.

  Read more in the [Lighthouse AI documentation](https://docs.prowler.com/getting-started/products/prowler-cloud-lighthouse).

  ### 📥 Providers — Imported Findings Indicator

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Providers whose findings were imported with the Prowler CLI now show an "Imported provider" indicator next to their connection status in the providers table. In accounts that mix connected providers with Import Findings uploads, the table now tells them apart at a glance.

  

  Read more in the [Import Findings documentation](https://docs.prowler.com/user-guide/tutorials/prowler-import-findings).

  ### 📚 Compliance — NCSC Cyber Essentials 3.3

  Cyber Essentials is the UK National Cyber Security Centre (NCSC) scheme certifying the baseline technical controls an organization must implement, and cloud services are explicitly in scope and cannot be excluded from an assessment. Prowler now includes NCSC Cyber Essentials: Requirements for IT Infrastructure v3.3 (April 2026) as a universal framework, with its 28 requirements organized in the five control themes: Firewalls, Secure Configuration, Security Update Management, User Access Control, and Malware Protection.

  Sixteen requirements map to Azure checks covering the controls the applicant organization owns under the shared responsibility model. The remaining twelve apply to end-user devices, on-premises network appliances, or organizational process, which cloud control-plane evidence cannot observe, so they are reported as Manual.

  Contributed by @m-khan-97. Thank you!

  Read more in the [Compliance documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance).

  ### 🔍 Checks

  Fifteen new checks land across seven providers in this release.

  #### AWS

  - `ecr_repository_image_no_secrets` scans the latest image of each ECR repository, both its configuration and its filesystem layers, for hardcoded secrets. Thanks to @esquaredsec!
  - Four new Amazon Bedrock checks, thanks to @tamg-aws!
    - `bedrock_guardrail_contextual_grounding_filter_enabled` verifies that guardrails enable both contextual grounding filters, blocking responses that are not supported by the retrieved source or do not answer the question asked.
    - `bedrock_custom_model_encrypted_with_cmk` verifies that custom models are encrypted at rest with a customer-managed KMS key instead of an AWS-owned key the organization cannot audit, rotate, or revoke.
    - `bedrock_knowledge_base_encrypted_with_cmk` verifies that each knowledge-base data source encrypts with a customer-managed KMS key the transient storage used while documents are chunked and embedded.
    - `bedrock_agent_role_not_shared_across_agents` verifies that every agent has a dedicated execution role, so no agent inherits another's permissions.
  - `rolesanywhere_profile_restricts_session_permissions` flags IAM Roles Anywhere profiles that reference an administrative role without scoping down the vended session with a session policy or managed policies.

  Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).

  #### GCP

  - `iam_workload_identity_pool_provider_attribute_condition` flags Workload Identity Federation providers that trust a multi-tenant issuer without an attribute condition restricting which external identities can impersonate federated principals.

  Explore all GCP checks at [Prowler Hub](https://hub.prowler.com/check?provider=gcp).

  #### GitHub

  Three new checks harden GitHub Actions defaults, all contributed by @Edneam. Thank you!

  - `organization_default_workflow_permissions_read_only` and `repository_default_workflow_permissions_read_only` verify that workflows get a read-only default `GITHUB_TOKEN` at the organization and repository level.
  - `organization_actions_pull_request_approval_disabled` verifies that organizations prevent GitHub Actions from creating and approving pull requests.

  Explore all GitHub checks at [Prowler Hub](https://hub.prowler.com/check?provider=github).

  #### Microsoft 365

  - `defender_domain_dmarc_records_published` checks that every Exchange Online domain publishes a DMARC record with an enforcing policy (`p=quarantine` or `p=reject`). Thanks to @Rishi943!

  Explore all Microsoft 365 checks at [Prowler Hub](https://hub.prowler.com/check?provider=m365).

  #### Alibaba Cloud

  - `oss_bucket_versioning_enabled` verifies that OSS buckets keep versioning enabled, allowing recovery from accidental or malicious object overwrite and deletion. Thanks to @abidedavana!
  - `oss_bucket_server_side_encryption_enabled` verifies that OSS buckets define a default server-side encryption rule, either AES256 or KMS. Thanks to @alexchen-sys!

  OSS bucket logging, versioning, default encryption, and ACL configurations are also now read correctly from the Alibaba Cloud SDK, so the checks reading them no longer report every bucket as unconfigured.

  Explore all Alibaba Cloud checks at [Prowler Hub](https://hub.prowler.com/check?provider=alibabacloud).

  #### Huawei Cloud

  - `vpc_security_group_open_egress` flags VPC security groups that allow open egress to the internet. Thanks to @tomitobio!

  Explore all Huawei Cloud checks at [Prowler Hub](https://hub.prowler.com/check?provider=huaweicloud).

  #### STACKIT

  - `ske_cluster_no_public_endpoint` flags SKE clusters whose Kubernetes API endpoint is reachable from the whole internet, because the ACL extension is disabled or its allowed CIDR list contains `0.0.0.0/0` or `::/0`. Thanks to @johannes-engler-mw!

  Explore all STACKIT checks at [Prowler Hub](https://hub.prowler.com/check?provider=stackit).

  ### 🔐 Security Updates

  - The API and SDK container images upgrade OpenSSL to 3.5.7-1~deb13u2, patching ten high CVEs; the UI image upgrades `libcrypto3` and `libssl3` to 3.5.8-r0, patching seven high CVEs; the MCP Server image patches CVE-2026-14456 (OpenSSL), CVE-2026-11822, and CVE-2026-11824 (SQLite).
  - `sqlparse` upgraded to 0.6.0 in the API, patching CVE-2026-54284, CVE-2026-59893, and CVE-2026-71491.

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @Edneam: GitHub `organization_default_workflow_permissions_read_only` ([#12122](https://github.com/prowler-cloud/prowler/pull/12122)), `repository_default_workflow_permissions_read_only` ([#12143](https://github.com/prowler-cloud/prowler/pull/12143)), and `organization_actions_pull_request_approval_disabled` ([#12394](https://github.com/prowler-cloud/prowler/pull/12394)) checks
  - @tamg-aws: four AWS Bedrock checks covering guardrail grounding, CMK encryption, and agent role isolation ([#12459](https://github.com/prowler-cloud/prowler/pull/12459))
  - @esquaredsec: AWS `ecr_repository_image_no_secrets` check ([#12123](https://github.com/prowler-cloud/prowler/pull/12123))
  - @Rishi943: Microsoft 365 `defender_domain_dmarc_records_published` check ([#11936](https://github.com/prowler-cloud/prowler/pull/11936))
  - @abidedavana: Alibaba Cloud `oss_bucket_versioning_enabled` check ([#11913](https://github.com/prowler-cloud/prowler/pull/11913))
  - @alexchen-sys: Alibaba Cloud `oss_bucket_server_side_encryption_enabled` check ([#11981](https://github.com/prowler-cloud/prowler/pull/11981))
  - @tomitobio: Huawei Cloud `vpc_security_group_open_egress` check ([#12209](https://github.com/prowler-cloud/prowler/pull/12209))
  - @johannes-engler-mw: STACKIT `ske_cluster_no_public_endpoint` check ([#11943](https://github.com/prowler-cloud/prowler/pull/11943))
  - @gabrielfrdev: cluster name in Kubernetes compliance report outputs ([#12506](https://github.com/prowler-cloud/prowler/pull/12506))
  - @jfgmesquita: AWS FSBP compliance mapping fix for IAM.9 and EKS.1 ([#12372](https://github.com/prowler-cloud/prowler/pull/12372))
  - @hackertwinten: `ec2_securitygroup_not_used` no longer flags security groups held only by scaled-down AWS Batch compute environments ([#12458](https://github.com/prowler-cloud/prowler/pull/12458))
  - @0xTaoZ: ECS task-definition checks no longer report PASS when `DescribeTaskDefinition` fails, shipped early in v5.39.1 ([#12217](https://github.com/prowler-cloud/prowler/pull/12217))
  - @ye11oc4t: `ses_identity_not_publicly_accessible` now evaluates every identity authorization policy, shipped early in v5.39.1 ([#12464](https://github.com/prowler-cloud/prowler/pull/12464))
  - @Zuhef: IaC provider raises typed exceptions instead of `sys.exit` when cloning the scanned repository or running Trivy fails ([#12227](https://github.com/prowler-cloud/prowler/pull/12227)), Kubernetes kubelet checks no longer disappear from the scan when a `kubelet-config` ConfigMap is broken ([#12225](https://github.com/prowler-cloud/prowler/pull/12225)), and the CLI `--slack` summary is sent for scans with no findings instead of failing with `ZeroDivisionError` ([#12229](https://github.com/prowler-cloud/prowler/pull/12229))
  - @m-khan-97: NCSC Cyber Essentials 3.3 compliance framework with Azure provider coverage across the five Cyber Essentials themes ([#11588](https://github.com/prowler-cloud/prowler/pull/11588))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.40.0) for the complete list of changes.
</Update>

<Update label="v5.39.0" description="August 13, 2026">
  ### 🤖 Lighthouse AI — Finding Skills

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Lighthouse AI now embeds a Skills menu on every finding, answering the questions an analyst actually asks. **Contextual Fix** produces the fix for the finding, **Triage Decision** judges whether it is real and closes it out when it is not, and **Systemic Scope** determines whether the problem is a one-off or everywhere. A free-form "Ask Lighthouse anything" prompt sits in the same menu, and each run shows its progress and offers follow-up actions such as creating a Jira issue or muting the finding.

  

  Read more in the [Lighthouse AI documentation](https://docs.prowler.com/getting-started/products/prowler-cloud-lighthouse).

  ### ☁️ Azure Management Group Onboarding

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Azure subscriptions no longer onboard one at a time. Choose "Add Multiple Subscriptions With Azure Management Group" in the add-provider wizard, enter the Microsoft Entra tenant ID, and authenticate once with a single tenant-wide service principal: Prowler discovers the entire management-group hierarchy under the tenant root, lets you select the subscriptions to onboard, and creates their providers with the management-group structure preserved. Azure now matches the one-step onboarding that AWS Organizations and GCP organizations already have.

  

  Read more in the [Azure Management Groups documentation](https://docs.prowler.com/user-guide/tutorials/prowler-cloud-azure-management-groups).

  ### ✅ Findings Triage — Verify MANUAL Findings as PASS

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Checks that require human judgment report `MANUAL` findings. For these findings, and only for them, the triage status selector now offers **Resolved**: choosing it asks for the required written evidence and verifies the finding as passing. The finding then reports an effective `PASS` while preserving the raw `MANUAL` scan result, across findings, finding groups, compliance reports, and scans, with the attestation's author, evidence, and validity always visible. Attestations expire automatically after 90 days, or as soon as a new scan reports a real failure, returning the finding to the review queue.

  

  

  Read more in the [Findings Triage documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-findings-triage#verify-a-manual-finding-as-pass).

  ### ☁️ Prowler Cloud MCP — Organizations Management and Grouped Jira Dispatch

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  The hosted Prowler Cloud MCP server adds eight organization tools, so an agent can onboard and manage entire cloud organizations end to end: create the organization, discover its accounts, subscriptions, and projects, apply the selection, and manage the resulting providers. The tools cover AWS Organizations, GCP organizations, and Azure tenant root management groups, and they are available to Lighthouse AI.

  `prowler_send_findings_to_jira` also gains Cloud-only dispatch capabilities: select failed findings by check IDs against the latest completed scan, and send them in grouped mode, one Jira work item per check listing up to 50 affected resources, with per-group error reporting.

  Read more in the [Prowler MCP tools documentation](https://docs.prowler.com/getting-started/basic-usage/prowler-mcp-tools) and its [Jira operations reference](https://docs.prowler.com/getting-started/basic-usage/prowler-mcp-tools#jira-operations).

  ### 🕸️ Attack Paths — Grouped Graph with Outcome Destinations

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  The Attack Paths graph now reads from source to destination. Resources of the same class collapse into a single expandable node with a count, clicking reveals its members, and every path terminates in an explicit outcome node naming the destination impact: code execution, privilege escalation, public exposure, or resource inventory. The per-account hub node is gone, and the clicked resource stays highlighted while its findings are expanded.

  

  Explore the full Attack Paths query catalog at [Prowler Hub](https://hub.prowler.com/attack-paths).

  Read more in the [Attack Paths documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths).

  ### 📚 New Compliance Framework — CMMC 2.0

  The Cybersecurity Maturity Model Certification (CMMC) is the certification the US Department of Defense requires from contractors and suppliers that handle federal contract data. Prowler now includes CMMC 2.0 as a universal framework with all 149 requirements defined by the CMMC Program rule (32 CFR Part 170), organized in its three levels:

  - **Level 1 (Foundational):** 15 requirements for the basic safeguarding of Federal Contract Information, from FAR 52.204-21.
  - **Level 2 (Advanced):** 110 requirements from NIST SP 800-171 Rev 2, protecting Controlled Unclassified Information.
  - **Level 3 (Expert):** 24 enhanced requirements from NIST SP 800-172 for the most sensitive programs.

  Requirements map to Prowler checks across AWS, Azure, GCP, Alibaba Cloud, Oracle Cloud, and Microsoft 365, so one framework reports the compliance posture of the whole estate.

  Read more in the [Compliance documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance).

  ### 🔍 Checks

  #### Microsoft 365

  Twenty new Entra ID checks expand the coverage of CIS Microsoft 365 Foundations Benchmark v7.0.0:

  - **Password protection:** custom banned password list, on-premises enforcement, and lockout threshold and duration.
  - **Default user permissions:** security group and Microsoft 365 group creation restricted, and guest invitations limited to allowed domains.
  - **Conditional Access:** high and medium sign-in risk blocked, authentication transfer blocked, untrusted locations blocked, trusted named locations defined, sign-in frequency enforced, and token protection enforced.
  - **Sessions and authentication methods:** idle session timeout configured, email one-time passcodes disabled, and Microsoft Authenticator context shown.
  - **PIM and access reviews:** approval required to activate the Global Administrator and Privileged Role Administrator roles, and access reviews configured for guest users and privileged roles.

  Explore all Microsoft 365 checks at [Prowler Hub](https://hub.prowler.com/check?provider=m365).

  #### AWS

  Two new checks detect hardcoded secrets:

  - `batch_job_definition_no_secrets` scans Batch job definition environment variables and command parameters. Thanks to @praneetrajv!
  - `awslambda_layer_no_secrets_in_content` scans Lambda layer package content. Thanks to @ganiganesh25!

  Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @praneetrajv: AWS `batch_job_definition_no_secrets` check ([#12117](https://github.com/prowler-cloud/prowler/pull/12117))
  - @ganiganesh25: AWS `awslambda_layer_no_secrets_in_content` check ([#12233](https://github.com/prowler-cloud/prowler/pull/12233))
  - @andoniaf: GitHub `organization_repository_creation_limited` now reports low severity when repository creation is limited to private or internal visibility ([#12164](https://github.com/prowler-cloud/prowler/pull/12164))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.39.0) for the complete list of changes.
</Update>

<Update label="v5.38.0" description="August 6, 2026">
  ### 📌 Compliance Watchlist

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Compliance Watchlist keeps the frameworks an organization tracks in one shared list. Pin frameworks from any compliance view, manage several at once through a searchable catalog, and filter the Compliance section to show only the pinned frameworks.

  The Overview page now reports the latest score for every pinned framework, while finding details highlight the watched frameworks associated with each check. Universal frameworks remain a single watchlist entry across provider views, keeping the organization's priorities consistent everywhere.

  

  Read more in the [Compliance Watchlist documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance#tracking-frameworks-with-the-compliance-watchlist).

  ### 🔐 SAML SSO - Multiple Email Domains

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  One SAML configuration can now authorize a primary email domain and up to 19 additional domains through the same Identity Provider. Every domain shares one stable Assertion Consumer Service (ACS) URL based on the primary domain, so subsidiaries, acquired companies, regional domains, and multiple brands no longer require separate tenants or duplicated SAML applications.

  Domain ownership remains tenant-bound throughout the authentication flow. During service provider-initiated sign-in, the discovery domain and the domain asserted by the Identity Provider must resolve to the same tenant before provisioning continues.

  

  Read more in the [SAML SSO documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-sso#add-multiple-saml-domains).

  ### 👥 User Sign-In Methods

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  The Users table now shows each account's sign-in methods as tags, including email/password, Google, GitHub, SAML with linked domains, and Partner SSO. Accounts without a reported method display a placeholder.

  

  ### 🕸️ Attack Paths - Expanded AWS Privilege-Escalation Coverage

  Attack Paths adds 20 AWS privilege-escalation queries from [pathfinding.cloud](https://pathfinding.cloud), while `iam_policy_allows_privilege_escalation` gains 22 additional escalation combinations.

  The new coverage includes service `iam:PassRole` paths across AWS Batch, Braket, Cognito Identity, ECS, EMR, EMR Serverless, GameLift, Glue, EC2 Image Builder, Kinesis Analytics, HealthOmics, EventBridge Scheduler, Systems Manager, and Step Functions. It also covers existing-resource abuse, permissions-boundary removal, role assumption, and IAM Identity Center permission-set policy injection.

  The query catalog now exposes each AWS query's outcome category, distinguishing code execution, privilege escalation, public exposure, and resource inventory.

  Explore the full Attack Paths query catalog at [Prowler Hub](https://hub.prowler.com/attack-paths).

  Read more in the [Attack Paths documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths).

  ### 🔍 Checks

  #### Microsoft 365

  Twelve new checks expand the coverage of CIS Microsoft 365 Foundations Benchmark v7.0.0:

  - **Admin Center:** Shared Bookings is disabled.
  - **Defender:** Priority account protection and strict preset security policies are enabled.
  - **Entra ID:** Six checks cover device registration restrictions, local administrator behavior, device limits, LAPS, and BitLocker key visibility.
  - **Exchange Online:** Personal accounts in Outlook on the web are disabled and Direct Send is rejected.
  - **Microsoft Teams:** External access from trial-only tenants is blocked.

  Explore all Microsoft 365 checks at [Prowler Hub](https://hub.prowler.com/check?provider=m365).

  ### 🔐 Security

  - Prowler API, UI, SDK, and MCP container images now publish per-architecture Software Bills of Materials (SBOMs) and build-provenance attestations. Prowler Cloud production and Prowler Private Cloud images carry the same attestations.
  - SDK and API container builds verify the checksums of downloaded PowerShell, Trivy, and zizmor binaries before installation.
  - Grype now complements Trivy across the container-image security gates, detecting components and vulnerabilities that manifest-based scanners can miss and blocking fixable high and critical findings.
  - `aiohttp` was upgraded to 3.14.3 to address CVE-2026-69244. `cryptography` was upgraded to 50.0.0 to address CVE-2026-69247 and CVE-2026-69249.

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.38.0) for the complete list of changes.
</Update>

<Update label="v5.37.0" description="August 3, 2026">
  ### 💬 Lighthouse AI — Context-Aware Chat and a Bigger Toolbox

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Lighthouse AI is now aware of your working context when in Prowler Cloud. Messages carry page-aware context — the page you are on, the finding or resource open in the side panel, and its metadata — so "explain this" just works, and each page offers concise contextual suggestions to start from.

  

  Lighthouse also gained access to every tool family the Prowler MCP server advertises: scan configurations, scan scheduling, finding triage, alert rules and recipients, integrations, users, and roles. Every action remains gated by RBAC: Lighthouse AI can only do what the user asking could do themselves.

  Read more in the [Lighthouse AI documentation](/getting-started/products/prowler-cloud-lighthouse).

  ### 🔌 Prowler MCP — Integrations, Users, and Roles

  Prowler MCP gained three tool families, available on both the Cloud and the self-hosted Local MCP Server:

  - **[Integrations](/getting-started/basic-usage/prowler-mcp-tools#integrations-management)** — manage where Prowler sends its results, with the full lifecycle for Amazon S3, AWS Security Hub, and Jira: create them, update credentials, configuration and attached providers, re-check connections, and delete them — plus turning findings into Jira work items directly from a conversation.
  - **[Users](/getting-started/basic-usage/prowler-mcp-tools#user-management)** — read-only tools to list the tenant users with their emails and identify the authenticated user.
  - **[Roles](/getting-started/basic-usage/prowler-mcp-tools#role-management)** — browse the RBAC roles defined in the tenant, inspect the capabilities each one grants, and set the role a user holds.

  ### ☁️ Prowler MCP — Cloud-Only Tools

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing). These tools are exposed only by the Cloud MCP Server at `https://mcp.prowler.com/mcp`; the self-hosted Local MCP Server **does not** include them.
  </Note>

  A new `prowler_cloud_*` namespace adds 32 tools so your AI assistant can run Prowler Cloud workflows end to end instead of only reading from them:

  - **[Alerts](/getting-started/basic-usage/prowler-mcp-tools#alerts)** — create and manage alert rules and email recipients, and browse the fired-alert history. Rule conditions can be dry-run before saving, so you can see what a rule would match without persisting anything.
  - **[Findings Triage](/getting-started/basic-usage/prowler-mcp-tools#findings-triage)** — set a finding's triage status and attach notes documenting the decision. Unlike muting, the finding stays visible.
  - **[Scan Scheduling](/getting-started/basic-usage/prowler-mcp-tools#scan-scheduling)** — configure daily, interval, weekly, or monthly recurring scans, one provider at a time or applied across many at once.
  - **[Scan Configurations](/getting-started/basic-usage/prowler-mcp-tools#scan-configurations)** — build reusable check and compliance selections and attach them to providers.

  Read more in the [Prowler MCP tools reference](/getting-started/basic-usage/prowler-mcp-tools#prowler-cloud-tools).

  ### 🧭 Compliance — Grouped by provider of the same type

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  One framework, every provider, a single answer. Building on the cross-provider-type roll-up, the Compliance section now groups compliance for all providers of the same type: a **single-provider framework** — CIS AWS, CIS GCP, ENS for Azure — is aggregated across the latest completed scan of every provider of that type. Each framework card rolls up into a consolidated posture with a per-provider breakdown, a findings drill-down, and a combined executive PDF report. Requirement status follows the same strict precedence (FAIL over PASS over MANUAL), so one failing provider flags the requirement for the whole estate.

  

  The Compliance tabs were also renamed to say what they aggregate: "Per Scan" is now **Single Scan**, "Cross-Provider" is now **Multiple Scans**, and Compliance lands on Multiple Scans by default.

  

  Read more in the [Cross-Provider Compliance documentation](/user-guide/compliance/tutorials/cross-provider-compliance).

  ### ☁️ GCP Organization Onboarding

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Onboarding an entire Google Cloud organization is now a single guided flow. Provide an organization-level credential and Prowler discovers the full hierarchy, every folder and project. Pick the folders and projects to onboard from a selection tree, set custom aliases, test the connection, and launch: each selected project is registered as a provider, with no need to add them one by one. Post-onboarding management is covered too, including credential replacement and organization-wide deletion.

  Read more in the [GCP Organizations documentation](/user-guide/tutorials/prowler-cloud-gcp-organizations).

  ### 🕸️ Attack Paths — More Privilege Escalation Queries

  Attack Paths adds four AWS privilege-escalation detection queries from [pathfinding.cloud](https://pathfinding.cloud). Thanks to @paramanandmallik!

  - **[STS-002](https://hub.prowler.com/attack-paths/aws-sts-privesc-cross-account-trust)** — cross-account role trust
  - **[STS-003](https://hub.prowler.com/attack-paths/aws-sts-privesc-wildcard-trust)** — wildcard role trust
  - **[IAM-022](https://hub.prowler.com/attack-paths/aws-iam-privesc-delete-user-permissions-boundary)** — user permissions-boundary removal
  - **[SSO-001](https://hub.prowler.com/attack-paths/aws-sso-privesc-permission-set-escalation)** — IAM Identity Center permission-set escalation

  The query info panel now links every query to its page on [Prowler Hub](https://hub.prowler.com), and the IAM privilege-escalation queries were reworked to run efficiently on accounts with many IAM roles, users, or groups, fixing runtime errors and timeouts on large graphs.

  Read more in the [Attack Paths documentation](/user-guide/tutorials/prowler-app-attack-paths).

  ### 🛡️ AWS Confidential Computing — Nitro Enclaves Checks

  Prowler adds the first CSPM coverage for confidential computing workloads on AWS, with **11 new checks** for [Nitro Enclaves](https://aws.amazon.com/ec2/nitro/nitro-enclaves/), developed together with [Guillermo Ruiz](https://www.linkedin.com/in/gruizesteban/) from AWS.

  - **Workload host environment (EC2)** — five `ec2_confidential_workload_host_*` checks for the parent instance: IMDSv2 not enforced, public IP exposure, unrestricted ingress, exposed vsock proxy ports, and hosts not running.
  - **KMS attestation policy** — six `kms_key_enclave_*` checks for the key policies gating enclave secrets: attestation not enforced or bypassable, missing deployment binding, debug-mode attestations, PCR mismatches, and unknown enclave images.

  All checks are fully passive, using AWS APIs and CloudTrail with no instance access or SSM agent required, and are mapped across 23 compliance frameworks, including NIST 800-53 Rev 5, PCI-DSS v4.0, ISO 27001:2022, SOC 2, HIPAA, and MITRE ATT&CK.

  Read more about it in this [blog post](https://prowler.com/blog/your-llm-runs-in-a-nitro-enclave-who-is-checking-the-enclave).

  Try them out now at [cloud.prowler.com](https://cloud.prowler.com/sign-up)!

  ### 🏢 New Provider — Huawei Cloud

  Prowler now scans [**Huawei Cloud**](https://www.huaweicloud.com/), with **25 checks** across ten services: CTS, ECS, ELB, EVS, IAM, KMS, OBS, RDS, VPC, and WAF, plus the CIS Huawei Cloud Foundations Benchmark 1.0 compliance framework. Thanks to @tomitobio for their 1st provider in Prowler!

  To scan a Huawei Cloud account, export the IAM user's access key credentials and run Prowler CLI:

  ```bash
  export HUAWEICLOUD_ACCESS_KEY_ID="your-access-key-id"
  export HUAWEICLOUD_SECRET_ACCESS_KEY="your-secret-access-key"

  prowler huaweicloud
  ```

  Read more in the [Huawei Cloud documentation](/user-guide/providers/huaweicloud/getting-started-huaweicloud). Explore all Huawei Cloud checks at [Prowler Hub](https://hub.prowler.com/check?provider=huaweicloud).

  ### 🔍 Checks

  #### AWS

  - `codecommit_repository_no_secrets`, alongside the new `codecommit` service, scans files tracked at the tip of each repository's default branch for hardcoded secrets. Thanks to @Sid-0602!
  - `glue_catalog_connection_no_secrets` detects secrets in Glue Data Catalog connection properties. Thanks to @l46983284-cpu, @Rishi943, and @UTKARSH698!
  - `ec2_instance_stopped_older_than_specific_days` detects EC2 instances stopped longer than a configurable number of days (default 30). Thanks to @Nithin078!
  - `sagemaker_endpoint_config_kms_encryption_enabled` verifies SageMaker endpoint configurations use a KMS key for storage volume encryption. Thanks to @Nithin078 and @l46983284-cpu!

  Read more in the [AWS documentation](/user-guide/providers/aws/getting-started-aws). Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).

  ### 📤 OCSF Output — MITRE ATT&CK Enrichment

  OCSF detection finding output now populates `finding_info.analytic` with the Prowler check rule and `finding_info.attacks` with MITRE ATT&CK technique and tactic objects for findings with MITRE ATT&CK compliance metadata. Thanks to @AlexanderSanin!

  ### 🐞 Fixed

  - AWS Security Hub integrations now persist successful recovery checks during finding delivery, keeping connection status and the last-checked time accurate.
  - Social sign-up now creates authentication, tenant, and membership records in a single transaction, fully rolling back failed provisioning to prevent incomplete accounts.
  - The SAML configuration form keeps the ACS URL field stable while generating the callback URL and exposes the copy action only after a valid URL is available.
  - SAML users without a `userType` attribute and without an existing role now receive a least-privilege `read_only` fallback role, so role-dependent operations continue to work without granting management permissions.

  ### 🔐 Security

  - Provider deletion, connection checks, scan creation, provider secrets, provider groups, and daily schedules now respect role provider-group visibility.
  - HTML reports escape provider-originated finding fields, preventing stored cross-site scripting through malicious cloud resource tags. https://github.com/prowler-cloud/prowler/security/advisories/GHSA-c2jg-2778-ggm4
  - Authentication with an API key whose owning user was deleted now returns `401`, and user deletion revokes the user's API keys across all their tenants.

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @tomitobio: Huawei Cloud provider with CIS 1.0 benchmark ([#11950](https://github.com/prowler-cloud/prowler/pull/11950))
  - @paramanandmallik: four AWS privilege-escalation Attack Paths queries ([#11460](https://github.com/prowler-cloud/prowler/pull/11460))
  - @Sid-0602: AWS `codecommit` service and `codecommit_repository_no_secrets` check ([#11846](https://github.com/prowler-cloud/prowler/pull/11846))
  - @l46983284-cpu, @Rishi943, and @UTKARSH698: AWS `glue_catalog_connection_no_secrets` check ([#11963](https://github.com/prowler-cloud/prowler/pull/11963))
  - @Nithin078: AWS `ec2_instance_stopped_older_than_specific_days` ([#12076](https://github.com/prowler-cloud/prowler/pull/12076)) and `sagemaker_endpoint_config_kms_encryption_enabled` ([#12118](https://github.com/prowler-cloud/prowler/pull/12118), co-authored with @l46983284-cpu) checks
  - @AlexanderSanin: MITRE ATT&CK enrichment in OCSF detection finding output ([#11492](https://github.com/prowler-cloud/prowler/pull/11492))
  - @stefanobaldo: GCP gen2 Cloud Functions IAM policy retrieval is now thread-safe ([#12107](https://github.com/prowler-cloud/prowler/pull/12107))
  - @rayair250-droid: GCP SSH and RDP firewall checks now detect exposed ports in any position within multi-port rules ([#12115](https://github.com/prowler-cloud/prowler/pull/12115))
  - @jbchief-dev: secret ignore patterns now use Kingfisher-compatible LF line indexing ([#12141](https://github.com/prowler-cloud/prowler/pull/12141))
  - @bmbferreira: Helm chart improvements — immutable chart versions on release ([#12056](https://github.com/prowler-cloud/prowler/pull/12056)) and capped Celery worker concurrency ([#12054](https://github.com/prowler-cloud/prowler/pull/12054))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.37.0) for the complete list of changes.
</Update>

<Update label="v5.36.0" description="July 24, 2026">
  ### 🎫 Finding Groups - Jira

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Selected Findings, Finding Groups, and mixed selections can now be sent to Jira. When you select multiple findings, choose between one grouped issue or separate issues. Generated issues keep their Prowler context with deep links and filter details, while the UI provides clear dispatch and failure feedback.

  

  Read more in the [Jira integration documentation](/user-guide/tutorials/prowler-app-jira-integration).

  ### 🕸️ Attack Paths - Queries

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Prowler Cloud now records which built-in Attack Paths queries returned data at the end of each scan. The query selector hides confirmed-empty queries for the selected scan, so you can focus on paths that exist without opening blank graph views. Errored, unknown, and parameterized queries remain available when they still require investigation or input.

  All Attack Paths queries are now published on [Prowler Hub](https://hub.prowler.com), where you can browse the full catalog.

  

  Read more in the [Attack Paths documentation](/user-guide/tutorials/prowler-app-attack-paths).

  ### 🧑‍🏫 New Tutorials: Connect Your AI Agents to Prowler Cloud

  <Note>
  This feature needs a Prowler Cloud API key, so it is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  New tutorials walk you through connecting your own AI agents to Prowler Cloud, so they can query your security posture and act on it programmatically.

  Read more in the [AI agents documentation](/user-guide/ai-agents/index).

  ### ☁️ Region-less Oracle Cloud Infrastructure Setup

  Oracle Cloud Infrastructure (OCI) provider credentials no longer require a region. Existing clients can still send the legacy `region` field for compatibility, but the API ignores it before storing credentials or starting a scan. This removes an unnecessary step from OCI onboarding.

  Read more in the [OCI documentation](/user-guide/providers/oci/getting-started-oci).

  ### 🔍 Checks

  #### AWS

  - `sagemaker_notebook_instance_no_secrets` scans the `OnCreate` and `OnStart` lifecycle scripts of SageMaker notebook instances for hardcoded API keys, passwords, tokens, connection strings, and other secrets. Thanks to @kiranrajsg!

  Read more in the [AWS documentation](/user-guide/providers/aws/getting-started-aws). Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).

  ### 🔐 Security

  - Integration responses and operations now respect provider visibility, preventing hidden-provider disclosure and blocking unauthorized attachment, connection checks, Jira dispatches, edits, and deletion.
  - Next.js was updated from 16.2.9 to 16.2.11, patching four high-severity and five medium-severity vulnerabilities.
  - The unused `npm` CLI was removed from the UI container image, eliminating the bundled `node-tar` CVE-2026-59873 and reducing exposure to future bundled npm vulnerabilities.
  - Vitest and its browser packages were updated from 4.1.8 to 4.1.10, resolving the critical `@vitest/browser` file-access permission bypass. These are development dependencies and have no runtime impact.
  - Kubernetes kubeconfig validation now blocks legacy `auth-provider.config.cmd-path` command authentication, closing a command-execution bypass.
  - `next-auth` was updated from 5.0.0-beta.30 to 5.0.0-beta.32, patching two critical Auth.js advisories: existence-based authorization checks that could fail open when a provider is misconfigured, and a homoglyph `@` bypass in email address normalization. The bump also pulls in the patched `@auth/core` 0.41.3 transitively.

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @kiranrajsg: AWS `sagemaker_notebook_instance_no_secrets` check ([#11843](https://github.com/prowler-cloud/prowler/pull/11843))
  - @owenchenxy: Alibaba Cloud SSH and RDP security group checks now handle capitalized `Policy="Accept"` values correctly ([#12049](https://github.com/prowler-cloud/prowler/pull/12049))
  - @rsaladra: S3 bucket name validation no longer raises an invalid escape sequence `SyntaxWarning` at startup ([#12041](https://github.com/prowler-cloud/prowler/pull/12041))
  - @SujayKulkarni-2211: Updated the AWS check count in the README ([#12011](https://github.com/prowler-cloud/prowler/pull/12011))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.36.0) for the complete list of changes.
</Update>

<Update label="v5.35.0" description="July 17, 2026">
  ### 💬 Lighthouse AI - Side Chat

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Lighthouse AI now lives in a side panel you can open from anywhere in the app. Ask about the findings you are looking at without leaving the page, and expand to the full-page chat at any time: your draft, messages, and streaming response come along. Finding and resource details share the same panel, with tabs to switch between Details and Lighthouse AI.

  

  Read more in the [Lighthouse AI documentation](/getting-started/products/prowler-cloud-lighthouse#side-panel).

  ### 🤖 Lighthouse AI - Take Action

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Lighthouse AI is no longer read-only. Ask it to do things and it will: connect or remove providers, trigger a scan, schedule daily scans, update scan settings, and manage your mutelist and mute rules, straight from the chat. Every action is gated by RBAC: Lighthouse can only do what the user asking could do themselves.

  Read more in the [Lighthouse AI capabilities](/getting-started/products/prowler-cloud-lighthouse#capabilities).

  ### ☁️ One-step AWS Organizations onboarding

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Onboarding an entire AWS Organization is now a single step. One CloudFormation quick-create link deploys the management account role and a service-managed StackSet that rolls the role out to every member account, replacing the manual StackSet console setup. Target the whole organization or a specific Organizational Unit or Root ID, and deploy from the management account or a delegated administrator. The S3 integration quick-create link also pre-fills the bucket owner account ID, preventing a stack validation error.

  

  Built on the full-organization CloudFormation template contributed by @jchrisfarris — thanks!

  Read more in the [AWS Organizations documentation](/user-guide/tutorials/prowler-cloud-aws-organizations).

  ### 🎯 Scan configurations: exclude checks and services

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Scan configurations now accept `excluded_checks` and `excluded_services` to narrow the execution scope. Skip individual checks or entire services per provider, and the scan does not run them at all: less noise, faster scans, and no findings you would mute anyway.

  Read more in the [Scan Configuration documentation](/user-guide/tutorials/prowler-app-scan-configuration#limiting-the-scan-scope).

  ### 🧭 Redesigned sidebar navigation

  The sidebar was redesigned around how you actually work: grouped sections for security, settings, and help, a Home/Chat switch at the top, collapsible configuration entries, clearer active states, and a responsive mobile overlay.

  

  ### 🔌 Prowler MCP tools renamed to `prowler_*`

  Core Prowler tools in Prowler MCP moved from the `prowler_app_*` prefix to the shorter `prowler_*` namespace, and the MCP documentation was restructured around it. Legacy `prowler_app_*` names keep working in Lighthouse AI, so existing setups are not broken.

  Read more in the [Prowler MCP tools reference](/getting-started/basic-usage/prowler-mcp-tools).

  ### 🔐 Security

  - Jira integration credentials now only accept bare Atlassian site names (letters, numbers, and hyphens), and Jira tenant information requests validate site names and no longer follow redirects.
  - Social account linking now requires a verified matching email from both the identity provider and the existing user account, and account connection notification emails are disabled.
  - 13 advisories reported by `pnpm audit` on the UI (3 high, 9 moderate, 1 low) are resolved with patched versions of `hono`, `ws`, `vite`, `dompurify`, `js-yaml`, `@opentelemetry/core`, and `@babel/core`, including `hono` CVE-2026-59896.

  ### 🙌 External Contributors

  No external contributors in this release.

  Special mention to @jchrisfarris, whose full-organization CloudFormation template from v5.34.0 powers the new one-step AWS Organizations onboarding ([#10403](https://github.com/prowler-cloud/prowler/pull/10403)).

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.35.0) for the complete list of changes.
</Update>

<Update label="v5.34.0" description="July 15, 2026">
  ### 🏷️ New product names

  The Prowler family has grown, and the names now say what each product is. Same products, clearer names:

  **Prowler products:**

  - **Prowler Cloud** — the managed cloud security platform operated by the Prowler team.
  - **Prowler Private Cloud** (formerly *Prowler Enterprise*) — the self-hosted deployment of Prowler Cloud in your own environment.
  - **Prowler Hub** — the free public library of versioned checks, cloud service artifacts, and compliance frameworks.
  - **Prowler Lighthouse AI** — The Agentic Cloud Defender in Prowler Cloud and Prowler Private Cloud.
  - **Prowler MCP** — the MCP server that connects AI assistants and agents to Prowler, including the IDE plugins.

  **Open source projects:**

  - **Prowler CLI** — the command-line scanner for all supported providers.
  - **Prowler Local Server** (formerly *Prowler App*) — the self-hosted web application and API to run scans, visualize findings, and manage providers.
  - **Prowler Local Dashboard** — the web dashboard for visualizing Prowler CLI scan results, distributed with the CLI.
  - **Prowler SDK** — the Python library behind Prowler CLI and Prowler Local Server.

  See the full family in the [Prowler products documentation](/getting-started/products).

  ### 🧭 Cross-Provider Compliance

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  One framework, every cloud, a single answer. The new **Cross-provider** tab in Compliance takes the most recent completed scan of every compatible provider and rolls them up into a single compliance posture per framework, with a per-provider breakdown and a combined executive PDF report. Requirement status follows strict precedence (FAIL over PASS over MANUAL), so one failing provider is enough to flag a requirement across your whole estate.

  

  Three universal frameworks support it today:

  - **CIS Controls 8.1** — AWS, Azure, Google Cloud, Microsoft 365, Kubernetes, GitHub, Google Workspace, Okta, Oracle Cloud, Alibaba Cloud, Cloudflare, MongoDB Atlas, OpenStack, and Vercel.
  - **CSA CCM 4.0** — AWS, Azure, Google Cloud, Alibaba Cloud, and Oracle Cloud.
  - **DORA 2022/2554** — AWS, Azure, Google Cloud, Alibaba Cloud, and Cloudflare.

  Filter by provider type, account, or provider group, drill into each framework's requirements, and export the combined PDF.

  

  Read more in the [Cross-Provider Compliance documentation](/user-guide/compliance/tutorials/cross-provider-compliance).

  ### 🏢 New Provider — E2E Networks

  Prowler now scans [**E2E Networks**](https://www.e2enetworks.com/), with **27 checks** spanning compute nodes, networking, security groups, load balancers, block and file storage, and managed databases. Thanks to @deepak7093 for their 1st provider in Prowler!

  Available in the Prowler CLI:

  ```bash
  export E2E_NETWORKS_API_KEY="your-api-key"
  export E2E_NETWORKS_AUTH_TOKEN="your-auth-token"
  export E2E_NETWORKS_PROJECT_ID="your-project-id"
  prowler e2enetworks
  ```

  Read more in the [E2E Networks documentation](/user-guide/providers/e2enetworks/getting-started-e2enetworks). Explore all E2E Networks checks at [Prowler Hub](https://hub.prowler.com/check?provider=e2enetworks).

  ### 🔐 Security

  User role relationship updates in the API are now limited to the active tenant, preserving the role assignments the same user holds in other tenants.

  ### 🔍 Checks

  #### AWS

  - `ec2_ami_account_block_public_access` — verifies AMI block public access is enabled at the account level in each Region, so AMIs cannot be shared publicly. Thanks to @goutham-hari!
  - `datapipeline_pipeline_no_secrets_in_definition` — scans Data Pipeline object fields, parameter objects, and parameter values for hardcoded secrets with Kingfisher. Thanks to @YinkaMetrics!
  - `elbv2_listener_pqc_tls_enabled` — verifies ELBv2 HTTPS/TLS listeners use post-quantum TLS security policies with TLS 1.2 or higher, helping reduce harvest-now-decrypt-later exposure.
  - `amplify_app_no_secrets_in_environment` — scans Amplify app and branch environment variables and build settings (buildSpec) for hardcoded secrets with Kingfisher. Thanks to @Deep070203!

  #### Azure

  - `app_function_ensure_http_is_redirected_to_https` — verifies that Function Apps enforce HTTPS-only traffic. Thanks to @amandalal007!

  #### Kubernetes

  - `core_minimize_hostpath_volume_mounts` — detects Pods that use `hostPath` volumes. Thanks to @0xTaoZ!
  - `core_readonly_root_filesystem_enabled` — verifies that every container in each Pod explicitly sets `readOnlyRootFilesystem: true` in its security context. Thanks to @Weedle02!

  #### STACKIT

  - `iaas_server_public_ip_attached` — flags IaaS servers that have a public IP address directly attached to a network interface. Thanks to @johannes-engler-mw!

  Explore all checks at [Prowler Hub](https://hub.prowler.com/check).

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @jchrisfarris — Deploy AWS Organizations with the CloudFormation template in one step ([#10403](https://github.com/prowler-cloud/prowler/pull/10403))
  - @deepak7093 — New E2E Networks provider: 27 checks across compute nodes, networking, security groups, load balancers, block/file storage, and managed databases ([#11654](https://github.com/prowler-cloud/prowler/pull/11654))
  - @goutham-hari — AWS `ec2_ami_account_block_public_access` check ([#11828](https://github.com/prowler-cloud/prowler/pull/11828))
  - @YinkaMetrics — AWS `datapipeline_pipeline_no_secrets_in_definition` check ([#11821](https://github.com/prowler-cloud/prowler/pull/11821))
  - @amandalal007 — Azure `app_function_ensure_http_is_redirected_to_https` check ([#11929](https://github.com/prowler-cloud/prowler/pull/11929))
  - @0xTaoZ — Kubernetes `core_minimize_hostpath_volume_mounts` check ([#11837](https://github.com/prowler-cloud/prowler/pull/11837))
  - @Weedle02 — Kubernetes `core_readonly_root_filesystem_enabled` check ([#11835](https://github.com/prowler-cloud/prowler/pull/11835))
  - @johannes-engler-mw — STACKIT `iaas_server_public_ip_attached` check ([#11549](https://github.com/prowler-cloud/prowler/pull/11549))
  - @janderik — Trailing newlines added to compliance, region, and fixture data files for POSIX compliance ([#11765](https://github.com/prowler-cloud/prowler/pull/11765))
  - @Deep070203 — AWS `amplify_app_no_secrets_in_environment` check ([#11825](https://github.com/prowler-cloud/prowler/pull/11825))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.34.0) for the complete list of changes.
</Update>

<Update label="v5.33.0" description="July 7, 2026">
  ### 🤖 Lighthouse AI — The Agentic Cloud Defender

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Lighthouse AI is now a full agentic assistant wired to the Prowler Cloud backend. Ask it about your findings, your compliance posture, or your riskiest resources, and watch it work: the agent discovers and runs the Prowler tools it needs to answer, with every tool call visible in the new agentic view. It reads your security data through read-only tools, so it can never touch secrets or modify your tenant.

  

  The chat experience is rebuilt around **persistent sessions**: conversations stream in real time, stay in your session history, can be archived, and a **sidebar chat mode** lets you ask questions from any page in the app without losing your place.

  

  You control the brain behind it. Configure one or more LLM providers — **OpenAI**, **Amazon Bedrock**, or any **OpenAI-compatible** endpoint (OpenRouter, Ollama) — with connection testing built into the setup and per-provider model selection. Add a shared **business context** (your security goals, compliance needs, organizational priorities) and every session uses it to give answers that fit your environment.

  

  Read more in the [Lighthouse AI documentation](/getting-started/products/prowler-cloud-lighthouse) and the [multiple LLM providers guide](/user-guide/tutorials/prowler-cloud-lighthouse-multi-llm).

  ### 📄 Compliance PDF Reports Without Credentials

  Compliance PDF reports no longer require the provider's credentials to be present. Findings are now enriched from the provider metadata stored in the database, so a report still generates even after the provider secret has been deleted or its credentials have become invalid.

  Read more in the [compliance documentation](/user-guide/compliance/tutorials/compliance).

  ### ⏳ Scan Queueing

  Overlapping scans for the same provider now queue behind the active one instead of dispatching concurrent scan workers. Launch a manual scan while a scheduled one is running and it waits its turn. No more duplicated work or racing scans.

  ### 🔐 Security

  The Kubernetes provider credentials now reject kubeconfigs using `exec` authentication in Prowler Cloud, at the API and in the credential form, preventing user-supplied commands from running on Cloud workers.

  Read more in the [Kubernetes provider authentication documentation](/user-guide/providers/kubernetes/getting-started-k8s#step-2-configure-kubernetes-authentication).

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @kratos0718 — Azure `postgresql_flexible_server_log_retention_days_greater_3` Flexible Server log retention fix ([#11761](https://github.com/prowler-cloud/prowler/pull/11761))
  - @Sanjays2402 — `KeyError: 'MANUAL'` crash fix in the compliance summary table, shipped early in v5.32.1 ([#11823](https://github.com/prowler-cloud/prowler/pull/11823))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.33.0) for the complete list of changes.
</Update>

<Update label="v5.32.0" description="July 2, 2026">
  ### 🔎 Findings Triage

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Triage findings straight from the Findings view. Each finding gets a triage status you can move through its lifecycle:

  **Open → Under Review → Remediating → Risk Accepted → False Positive → Resolved**

  Add a triage note to record the decision, mute a finding, all from the row's actions menu. The current status shows inline on every finding row, so you keep track of what has been reviewed and stop re-checking the same issues scan after scan.

  

  The status also follows the finding automatically across scans: when a finding flips from `FAIL` to `PASS` on the next scan it moves to **Resolved**, and when it flips from `PASS` back to `FAIL` it moves to **Reopened**. You always know whether an issue is genuinely fixed or has regressed, without touching it by hand.

  

  Read more in the [Findings Triage documentation](/user-guide/tutorials/prowler-app-findings-triage).

  ### ⚙️ Scan Configuration

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Create named, reusable scan configurations from a dedicated **Scans / Configuration** page. Each configuration is YAML that follows the structure of [`prowler/config/config.yaml`](https://github.com/prowler-cloud/prowler/blob/master/prowler/config/config.yaml), so you only include the keys you want to override; the rest fall back to the built-in defaults. Values are validated on save against a per-provider, type-safe configuration schema that range-checks each field and rejects unknown keys, so a malformed config is caught before it ever reaches a scan. Attach a configuration to one or more providers so it applies on their next scan, or save it now and attach providers later.

  

  From the Providers view you can pick which configuration a provider uses (`Default` or any of your saved ones) without leaving the page. No more passing config files around by hand.

  

  Read more in the [Scan Configuration documentation](/user-guide/tutorials/prowler-app-scan-configuration).

  ### ✅ Per-Requirement Configuration Validation

  <Note>
  This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
  </Note>

  Compliance frameworks can now declare `ConfigRequirements` on a requirement, so it's reported as **FAIL** when its mapped checks ran under a configuration too loose to satisfy it. Even if every individual finding PASSed. This applies across all compliance outputs: CSV, OCSF, and console tables, and is the engine behind Scan Configuration's "marked as FAIL" behavior described above.

  

  Read more in the [Configuration File documentation](/user-guide/cli/tutorials/configuration_file).

  ### ⏱️ Okta — Request Throttling & Retries

  Prowler now proactively throttles Okta API requests to stay under rate limits, with reactive retries on HTTP 429 as a safety net. Both are set in the scan configuration (or their equivalent CLI flags):

  - `okta_requests_per_second` (config file) / `--okta-requests-per-second` (CLI) — cap the request rate. Default: 4 req/s.
  - `okta_max_retries` (config file) / `--okta-retries-max-attempts` (CLI) — bound retry attempts. Default: 5.

  This makes large Okta scans more reliable and less likely to be rate-limited.

  Read more in the [Okta rate limit documentation](/user-guide/providers/okta/retry-configuration#request-throttling-requests-per-second).

  ### 📉 AWS — Cap Resources Scanned per Service

  Large AWS accounts can now cap how many resources Prowler analyzes for the highest-volume services, keeping scan time and cost under control. Set a global limit with `max_scanned_resources_per_service`, or override it per service:

  - EBS snapshots (`max_ebs_snapshots`)
  - Backup recovery points (`max_backup_recovery_points`)
  - CloudWatch log groups (`max_cloudwatch_log_groups`)
  - Lambda functions (`max_lambda_functions`)
  - ECS task definitions (`max_ecs_task_definitions`)
  - CodeArtifact packages (`max_codeartifact_packages`)

  Limits are **disabled by default** (`0` = unlimited); only positive values cap the analyzed resources.

  <Warning>
  When a positive limit is set, compliance results reflect only the sampled resources, not every matching resource in the account.
  </Warning>

  Read more in the [configuration file documentation](/user-guide/cli/tutorials/configuration_file#supported-aws-resource-limits).

  ### 🏷️ Azure — Filter by Resource Group

  Azure scans can now be scoped to one or more resource groups with the new `--azure-resource-group` / `--azure-resource-groups` option. This lets you run focused assessments against specific environments, teams, or workloads instead of scanning every accessible resource in the subscription. Thanks to @Legin-ML for contributing this feature!

  ```bash
  # Single resource group
  prowler azure --az-cli-auth --azure-resource-group rg-prod

  # Multiple resource groups
  prowler azure --az-cli-auth --azure-resource-group rg-prod1 rg-prod2
  ```

  Read more in the [Azure Resource Groups documentation](/user-guide/providers/azure/resource-groups).

  ### 🧭 Provider Group Filter

  Filter the **Overview, Findings, Resources, Scans, and Providers** views by provider group. Scope the whole app to a team, an environment, or a business unit in one click instead of filtering provider by provider.

  

  Read more about managing provider groups in the [RBAC documentation](/user-guide/tutorials/prowler-app-rbac).

  ### 🔬 API — Timestamp Precision in Findings Filters

  The `/api/v1/findings` endpoint now accepts full timestamps on the `inserted_at` and `updated_at` filters (`filter[inserted_at__gte]`, `filter[inserted_at__lte]`, and the `updated_at` variants), so you can query narrow time windows instead of whole days. Date-only filtering keeps working, so existing integrations are unaffected.

  ```bash
  # Findings inserted within a precise timestamp window
  curl --globoff \
    'http://localhost:8080/api/v1/findings?filter[inserted_at__gte]=2026-07-01T06:12:18Z&filter[inserted_at__lte]=2026-07-02T19:25:55Z' \
    -H 'Authorization: Bearer <YOUR_TOKEN>' \
    -H 'Accept: application/vnd.api+json'
  ```

  ### 🕸️ Attack Paths — Neptune as a persistent sink

  Attack Paths can now persist its graph in **AWS Neptune** in addition to Neo4j, selectable via `ATTACK_PATHS_SINK_DATABASE=neptune` (default `neo4j`). Cartography's per-scan ingest database stays on Neo4j. The scan task preflights the ingest database and the configured sink before ingestion, and provider graph cleanup now deletes relationships in directed batches before deleting nodes.

  This is the groundwork for scale: a managed graph database lets Attack Paths hold much larger graphs, extend coverage to more providers, and link resources across them so an attack path can cross provider boundaries instead of stopping at one cloud's edge.

  Read more in the [Attack Paths documentation](/user-guide/tutorials/prowler-app-attack-paths).

  ### 🔐 New Secret-Scanning Engine — Kingfisher

  Prowler's secret-scanning checks now run on [Kingfisher](https://github.com/mongodb/kingfisher) instead of `detect-secrets`. Scans run **fully offline by default**, and obvious placeholder values (e.g. `password123`, `changeme`) are no longer reported, cutting down false positives.

  Opt in to **live validation** with the new `--scan-secrets-validate` flag (or the `aws.secrets_validate` config option): Prowler checks discovered secrets against the provider APIs, and any secret confirmed to be **live is reported as critical**, so you can prioritize the credentials that actually work.

  <Note>
  The `detect_secrets_plugins` configuration option has been removed, as it is no longer used by the new engine.
  </Note>

  Read more in the [secret detection documentation](/user-guide/cli/tutorials/pentesting#detect-secrets).

  ### 🔍 Checks

  #### AWS

  - `stepfunctions_statemachine_encrypted_with_cmk` — Step Functions state machines use a customer-managed KMS key for encryption at rest instead of the default AWS-owned key. Thanks to @Sid-0602!
  - `waf_regional_webacl_logging_enabled` — AWS WAF Classic Regional Web ACLs have logging enabled to a Kinesis Data Firehose stream. Thanks to @Sid-0602!
  - **IAM privilege escalation** — the privesc checks now cover **AWS Bedrock AgentCore** paths across Runtime, Harness, Code Interpreter, and Custom Browser. Thanks to @MrCloudSec!
  - `apigateway_restapi_no_secrets_in_stage_variables` — scans API Gateway REST API stage variables for hardcoded passwords, API keys, and tokens. Thanks to @chirag1206!
  - `awslambda_function_no_secrets_in_code` — this check now supports a `secrets_ignore_files` audit-config option to skip files inside the deployment package by glob pattern (e.g. `*.deps.json`), suppressing .NET dependency-manifest false positives without masking real secrets.
  - `s3_bucket_object_public` — spot-checks a configurable sample of object ACLs in each bucket and flags objects granted to the `AllUsers` or `AuthenticatedUsers` groups. Disabled by default; opt in via the `s3_bucket_object_public_enabled` configuration option. Thanks to @Synchx00!

  #### Microsoft 365

  New **Conditional Access** hardening checks:

  - `entra_conditional_access_policy_explicitly_targets_azure_devops` — at least one enabled policy explicitly includes the Azure DevOps cloud application, rather than relying on a broad "All cloud apps" policy. Thanks to @mzl2233!
  - `entra_conditional_access_policy_no_exclusion_gaps` — every user, group, role, or application excluded from an enabled policy stays in scope of another enabled policy. Thanks to @UTKARSH698 with @arieleli01212 as co-author!
  - `entra_conditional_access_policy_groups_management_restricted` — every security group referenced by an enabled or report-only policy is management-restricted or role-assignable. Thanks to @SAMurai-16!
  - `exchange_application_access_policy_restricts_mailbox_apps` — every service principal with Microsoft Graph application-level Exchange mailbox permissions is restricted by an Exchange Online Application Access Policy. Thanks to @VasistAcharya!

  ### 📚 Compliance

  #### CIS Benchmark Refresh — Six New Versions

  Prowler ships a coordinated refresh of the CIS Benchmarks across six providers:

  - **AWS** — CIS Amazon Web Services Foundations Benchmark v7.0.0, adding the new Organizations section (2.1.1-2.1.6), resource policy (2.21), web front-end access logging (4.10), and VPC Endpoints (6.8) recommendations.
  - **Azure** — CIS Microsoft Azure Foundations Benchmark v6.0.0.
  - **GCP** — CIS Google Cloud Platform Foundation Benchmark v5.0.0.
  - **Kubernetes** — CIS Kubernetes Benchmark v2.0.1.
  - **GitHub** — CIS GitHub Benchmark v1.2.0.
  - **Microsoft 365** — CIS Microsoft 365 Foundations Benchmark v7.0.0.

  #### CIS Controls v8.1 — Universal Framework

  A new **universal** (cross-provider) compliance framework mapping existing checks across 18 providers — AWS, Azure, GCP, Kubernetes, M365, GitHub, AlibabaCloud, OracleCloud, GoogleWorkspace, Okta, Cloudflare, Vercel, MongoDB Atlas, OpenStack, Linode, StackIT, NHN, and Scaleway — to the 18 CIS Critical Security Controls and their Safeguards. Ships with a dedicated detail view and report mapping in the UI.

  Read more in the [compliance documentation](/user-guide/compliance/tutorials/compliance). Explore the full compliance catalog at [Prowler Hub](https://hub.prowler.com/compliance).

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @chirag1206 — `apigateway_restapi_no_secrets_in_stage_variables` check ([#11188](https://github.com/prowler-cloud/prowler/pull/11188))
  - @MrCloudSec — AWS Bedrock AgentCore privilege escalation paths in the IAM privesc checks ([#11726](https://github.com/prowler-cloud/prowler/pull/11726))
  - @Sid-0602 — `stepfunctions_statemachine_encrypted_with_cmk` ([#11538](https://github.com/prowler-cloud/prowler/pull/11538)) and `waf_regional_webacl_logging_enabled` ([#11539](https://github.com/prowler-cloud/prowler/pull/11539)) checks
  - @mzl2233 — `entra_conditional_access_policy_explicitly_targets_azure_devops` check ([#11182](https://github.com/prowler-cloud/prowler/pull/11182))
  - @UTKARSH698 with @arieleli01212 as co-author — `entra_conditional_access_policy_no_exclusion_gaps` check ([#11577](https://github.com/prowler-cloud/prowler/pull/11577))
  - @SAMurai-16 — `entra_conditional_access_policy_groups_management_restricted` check ([#11342](https://github.com/prowler-cloud/prowler/pull/11342))
  - @vahidg — Azure PostgreSQL flexible server collection resilience fix ([#11595](https://github.com/prowler-cloud/prowler/pull/11595))
  - @davletd — Azure `keyvault_logging_enabled` `AuditEvent` category fix ([#11660](https://github.com/prowler-cloud/prowler/pull/11660))
  - @VasistAcharya — `exchange_application_access_policy_restricts_mailbox_apps` ([#11247](https://github.com/prowler-cloud/prowler/pull/11247))
  - @Legin-ML — Filter scans at Resource Group level ([#10657](https://github.com/prowler-cloud/prowler/pull/10657))
  - @Synchx00 — `s3_bucket_object_public` check ([#9517](https://github.com/prowler-cloud/prowler/pull/9517))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.32.0) for the complete list of changes.
</Update>

<Update label="v5.31.0" description="June 23, 2026">
  ### 🗓️ Flexible Scan Scheduling

  <Note>
  Available exclusively in **Prowler Cloud**. Prowler Local Server supports daily scans only.
  </Note>

  

  You can now set a per-provider scan schedule from the Providers page. Pick a **scan time** and a **repeat cadence**: Daily, Every 48 hours, Weekly (with a day-of-week selector), or Monthly. Schedules can be edited or removed at any time, and a new scan never interrupts access to existing data.

  

  All schedules are listed in one place under the **Scheduled** tab in **Scan Jobs**, showing each provider's cadence, next scan, and last scan at a glance.

  

  Read more in the [scan scheduling documentation](/user-guide/tutorials/prowler-scan-scheduling).

  ### 📚 DORA — Expanded Provider Coverage

  Prowler extends [**DORA**](https://www.eiopa.europa.eu/digital-operational-resilience-act-dora_en) (Digital Operational Resilience Act, Regulation (EU) 2022/2554) coverage to **Azure**, **GCP**, **Cloudflare**, and **Alibaba Cloud**, mapping each provider's existing checks across the five DORA pillars.

  

  <Note>
  The framework follows the `<name>_<version>` naming convention as `DORA_2022_2554`.
  </Note>

  Read more in the [compliance documentation](/user-guide/compliance/tutorials/compliance).

  ### 🚀 Guided Onboarding

  <Note>
  Available exclusively in **Prowler Cloud**.
  </Note>

  New accounts now get a guided first-run experience. The Overview greets you with an **"Add your first provider"** prompt: connect a provider so Prowler has something to scan and assess, then get started in one click (or skip for now).

  

  From there, contextual empty states across the product point you to the next action rather than leaving you stuck. Attack Paths, for example, explains that you need a completed scan before it can build a graph and links straight to **Scan Jobs**, with a **"See how it works"** affordance for first-timers.

  

  ### 🔐 Optional SAML SSO `userType`

  The SAML `userType` attribute is now optional. If your IdP does not send it, or sends it blank, Prowler keeps the user's existing roles unchanged instead of replacing them with a fallback role.

  When `userType` is provided, Prowler still maps the user to the matching role. If that role does not exist yet, Prowler creates it with read-only access: visibility over all providers, with no management permissions.

  Read more in the [SAML SSO documentation](/user-guide/tutorials/prowler-app-sso).

  ### 🏢 New Provider — Linode

  Prowler now scans [**Linode**](https://www.linode.com/) (Akamai Cloud), covering its administration, compute, and networking services. Thanks to @varunmamillapalli for their 1st provider in Prowler!

  <Note>
  Linode is not officially supported. For more information, [contact us](https://prowler.com/contact).
  </Note>

  Read more in the [Linode documentation](/user-guide/providers/linode/getting-started-linode). Explore all Linode checks at [Prowler Hub](https://hub.prowler.com/check?provider=linode).

  ### 🔍 Checks

  #### AWS

  **Post-Quantum Cryptography readiness** — get ahead of the migration to quantum-resistant cryptography:

  - `cloudfront_distributions_pqc_tls_enabled` — CloudFront distributions enforce a post-quantum TLS 1.3 security policy.
  - `apigateway_domain_name_pqc_tls_enabled` — API Gateway custom domain names use a post-quantum TLS security policy.
  - `transfer_server_pqc_ssh_kex_enabled` — Transfer Family servers use a post-quantum hybrid SSH key exchange.
  - `acmpca_certificate_authority_pqc_key_algorithm` — Private CA authorities use a post-quantum (ML-DSA) key algorithm (new `acmpca` service).
  - `rolesanywhere_trust_anchor_pqc_pki` — IAM Roles Anywhere trust anchors are backed by a post-quantum (ML-DSA) PKI (new `rolesanywhere` service).

  **Organization-wide governance:**

  - `securityhub_delegated_admin_enabled_all_regions` — Security Hub has a delegated administrator, active in all opted-in regions, with organization auto-enable on. Thanks to @ernestprovo23!
  - `config_delegated_admin_and_org_aggregator_all_regions` — AWS Config has a delegated administrator and an organization aggregator covering all regions. Thanks to @ernestprovo23!

  **Machine learning:**

  - `sagemaker_clarify_exists` — verifies at least one SageMaker Clarify processing job exists per scanned region, so bias-detection and model-explainability controls are in place. Thanks to @AlexanderSanin!

  #### Azure

  A large batch of new Azure checks spanning data, compute, identity, and networking:

  - **Cosmos DB** — automatic failover, continuous backup policy, minimum TLS 1.2, and public network access disabled.
  - **MySQL & PostgreSQL Flexible Servers** — geo-redundant backup and high availability.
  - **AKS** — auto-upgrade, Azure Monitor (Container Insights), local accounts disabled, and Microsoft Defender enabled.
  - **Databricks** — public network access disabled and secure cluster connectivity (no public IP).
  - **Defender** — CSPM on the Standard tier.
  - **Networking** — NSG association on subnets and DDoS Network Protection on VNets.
  - **Entra ID** — app registration credential expiry, users with recent sign-in and strong authentication enforcement.
  - **Recovery Services** — vaults with at least one protected backup item and vaults with adequate backup policy.

  Thanks to @s1ns3nz0 for all these contributions!

  #### GCP

  New coverage for high availability and public-exposure detection:

  - `cloudsql_instance_high_availability_enabled` — Cloud SQL primary instances use `REGIONAL` availability for automatic zone failover.
  - `cloudfunction_function_inside_vpc` — Cloud Functions use a Serverless VPC Access connector for private egress.
  - `cloudfunction_function_not_publicly_accessible` — detects `allUsers` / `allAuthenticatedUsers` IAM invocation bindings.
  - `secretmanager_secret_not_publicly_accessible` — detects Secret Manager secrets with public IAM bindings.
  - `secretmanager_secret_rotation_enabled` — verifies Secret Manager secrets have automatic rotation configured with a period of 90 days or less and no missed rotation.

  Thanks to @s1ns3nz0 for all these contributions!

  #### Kubernetes

  New core checks for container resource governance and reliability: CPU limits, CPU requests, memory limits, memory requests, fixed image tags, liveness probes, and readiness probes. Thanks to @Nikhilkumar2311 for all these contributions!

  #### Microsoft 365

  - `entra_directory_sync_object_takeover_blocked` — hybrid Entra tenants block cloud object takeover through soft-match and hard-match directory synchronization. Thanks to @PrettyFox0 and @omobolajiadeyan!
  - `entra_conditional_access_policy_no_deleted_object_references` — flags Conditional Access policies that reference user, group, or role objects that no longer resolve in the directory. Thanks to @ernestprovo23!

  #### Oracle Cloud Infrastructure

  - `identity_storage_service_level_admins_scoped` — CIS 3.1 control 1.15, ensuring storage service-level administrators exclude delete permissions.

  Explore all checks at [Prowler Hub](https://hub.prowler.com/check).

  ### 🐍 Python 3.13 Support

  The Prowler SDK now supports **Python 3.13**. Thanks to @branchv!

  ### 🔐 Security Updates

  - **SDK** — `pytest` 8.3.5 → 9.0.3, `black` 25.1.0 → 26.3.1, `microsoft-kiota-*` → 1.9.9, and `aiohttp` → 3.14.0, patching known CVEs.
  - **API** — `aiohttp` → 3.14.0 and `idna` → 3.15, patching known CVEs.
  - **UI** — bumped vulnerable `Next.js`, React, AI SDK, `postcss`, `hono`, `qs`, `esbuild`, and Alpine OpenSSL packages; `dompurify` 3.4.2 → 3.4.10, patching XSS sanitization bypass advisories.
  - **Containers** — base image bumped to `python:3.12.13-slim-bookworm` (patches `libgnutls30` CVE-2026-33845 and CVE-2026-42010) and `trivy` to 0.71.0 (patches embedded `golang.org/x/crypto` and Go stdlib CVEs).

  ### 🙌 External Contributors

  Thank you to our community contributors for this release!

  - @varunmamillapalli — New Linode provider: administration, compute, and networking services ([#11633](https://github.com/prowler-cloud/prowler/pull/11633))
  - @s1ns3nz0 — 20+ Azure & GCP checks across Cosmos DB, AKS, Databricks, Flexible Servers, Entra, networking, and GCP public-exposure
  - @Nikhilkumar2311 — Kubernetes resource limits, requests, image tag, and probe checks ([#11373](https://github.com/prowler-cloud/prowler/pull/11373))
  - @ernestprovo23 — AWS Security Hub/Config org-wide delegated admin checks ([#11259](https://github.com/prowler-cloud/prowler/pull/11259)) and M365 conditional access check ([#11236](https://github.com/prowler-cloud/prowler/pull/11236))
  - @AlexanderSanin — `sagemaker_clarify_exists` check ([#11211](https://github.com/prowler-cloud/prowler/pull/11211))
  - @PrettyFox0 with @omobolajiadeyan as co-author — M365 directory sync object takeover check ([#11098](https://github.com/prowler-cloud/prowler/pull/11098))
  - @branchv — Python 3.13 support ([#9293](https://github.com/prowler-cloud/prowler/pull/9293))
  - @alinealfa — GCP audit-filtered aggregated sinks fix ([#11575](https://github.com/prowler-cloud/prowler/pull/11575))
  - @b-abderrahmane — Configurable Celery worker concurrency ([#11075](https://github.com/prowler-cloud/prowler/pull/11075))

  See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.31.0) for the complete list of changes.
</Update>

<Update label="Earlier releases">
  Release notes for v5.30.0 and earlier, along with every patch release, are on [GitHub Releases](https://github.com/prowler-cloud/prowler/releases).
</Update>

---

### Introduction (`docs/introduction.mdx`)

# What is Prowler?

**Prowler** is the world’s most widely used open-source cloud security platform that **automates security and compliance** across any cloud environment. With thousands of ready-to-use security checks, remediation guidance, and compliance frameworks, Prowler delivers AI-driven, customizable, and easy-to-use monitoring and integrations, making cloud security simple, scalable, and cost-effective for organizations of any size.



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

<Columns cols={2}>
  <Card title="Prowler Cloud" icon="cloud" href="/getting-started/products/prowler-cloud">
    Managed cloud security platform operated by the Prowler team.
  </Card>
  <Card title="Prowler Hub" icon="map" href="/getting-started/products/prowler-hub">
    Free public library of versioned checks, cloud service artifacts, and compliance frameworks.
  </Card>
  <Card title="Prowler MCP" icon="plug" href="/getting-started/products/prowler-mcp">
    MCP server that connects AI assistants and agents to Prowler.
  </Card>
  <Card title="All Prowler Products" icon="list" href="/getting-started/products">
    Prowler Private Cloud, Prowler Lighthouse AI, and more.
  </Card>
</Columns>

### Open Source

<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.
  </Card>
  <Card title="Prowler Local Server" icon="server" href="/getting-started/products/prowler-app">
    Self-hosted web application and API.
  </Card>
</Columns>

## Supported Providers

Prowler supports a wide range of providers organized by category:

### Cloud Service Providers (Infrastructure)

| Provider                                                                         | Support    | Audit Scope/Entities     | Interface    |
| -------------------------------------------------------------------------------- | ---------- | ------------------------ | ------------ |
| [Alibaba Cloud](/user-guide/providers/alibabacloud/getting-started-alibabacloud) | Official   | Accounts                 | UI, API, CLI |
| [AWS](/user-guide/providers/aws/getting-started-aws)                             | Official   | Accounts                 | UI, API, CLI |
| [Azure](/user-guide/providers/azure/getting-started-azure)                       | Official   | Subscriptions            | UI, API, CLI |
| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare)        | Official   | Accounts                 | UI, API, CLI |
| [E2E Networks](/user-guide/providers/e2enetworks/getting-started-e2enetworks)                        | [Contact us](https://prowler.com/contact) | Projects                 | CLI          |
| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp)                    | Official   | Projects                 | UI, API, CLI |
| [Huawei Cloud](/user-guide/providers/huaweicloud/getting-started-huaweicloud)    | [Contact us](https://prowler.com/contact) | Accounts                 | CLI          |
| [Linode](/user-guide/providers/linode/getting-started-linode)                    | [Contact us](https://prowler.com/contact) | Accounts                 | CLI          |
| **NHN**                                                                          | [Contact us](https://prowler.com/contact) | Tenants                  | CLI          |
| [OpenStack](/user-guide/providers/openstack/getting-started-openstack)           | Official   | Projects                 | UI, API, CLI |
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci)                    | Official   | Tenancies / Compartments | UI, API, CLI |
| [Scaleway](/user-guide/providers/scaleway/getting-started-scaleway)              | [Contact us](https://prowler.com/contact) | Organizations            | CLI          |
| [StackIT](/user-guide/providers/stackit/getting-started-stackit)                 | [Contact us](https://prowler.com/contact) | Projects                 | CLI          |

### Infrastructure as Code Providers

| Provider                                                              | Support  | Audit Scope/Entities | Interface    |
| --------------------------------------------------------------------- | -------- | -------------------- | ------------ |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac)        | Official | Repositories         | UI, API, CLI |

### Software as a Service (SaaS) Providers

| Provider                                                                                  | Support  | Audit Scope/Entities         | Interface    |
| ----------------------------------------------------------------------------------------- | -------- | ---------------------------- | ------------ |
| [GitHub](/user-guide/providers/github/getting-started-github)                             | Official | Organizations / Repositories | UI, API, CLI |
| [Google Workspace](/user-guide/providers/googleworkspace/getting-started-googleworkspace)  | Official | Domains                      | UI, API, CLI |
| [LLM](/user-guide/providers/llm/getting-started-llm)                                     | Official | Models                       | CLI          |
| [M365](/user-guide/providers/microsoft365/getting-started-m365)                           | Official | Tenants                      | UI, API, CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas)           | Official | Organizations                | UI, API, CLI |
| [Okta](/user-guide/providers/okta/getting-started-okta)                                   | Official | Organizations                | UI, API, CLI |
| [Vercel](/user-guide/providers/vercel/getting-started-vercel)                             | Official | Teams / Projects             | UI, API, CLI |

### Kubernetes

| Provider                                                                   | Support  | Audit Scope/Entities | Interface    |
| -------------------------------------------------------------------------- | -------- | -------------------- | ------------ |
| [Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s)         | Official | Clusters             | UI, API, CLI |

### Containers

| Provider                                                            | Support  | Audit Scope/Entities | Interface |
| ------------------------------------------------------------------- | -------- | -------------------- | --------- |
| [Image](/user-guide/providers/image/getting-started-image)          | Official | Container Images / Registries | UI, API, CLI |

### Custom Providers (Prowler Private Cloud Only)

| Provider             | Support  | Audit Scope/Entities | Interface |
| -------------------- | -------- | -------------------- | --------- |
| VMware/Broadcom VCF  | Official | Infrastructure       | CLI       |

For more information about the checks and compliance of each provider, visit [Prowler Hub](https://hub.prowler.com).

## Where to Go Next?

<Columns cols={2}>
  <Card title="User Guide" icon="terminal" href="/user-guide/tutorials/prowler-app">
    Detailed instructions on how to use Prowler.
  </Card>
  <Card title="Development Guide" icon="pen-to-square" href="/developer-guide/introduction">
    Interested in contributing to Prowler?
  </Card>
</Columns>

---

### Support (`docs/support.mdx`)

---
title: 'Support'
description: 'Get help with Prowler'
---

import { SubscriptionBanner } from "/snippets/subscription-banner.mdx"

## Prowler Lighthouse AI

Prowler Lighthouse AI is your 24/7 cloud security analyst chatbot, the agentic cloud defender. Powered by [Prowler MCP](/getting-started/products/prowler-mcp), it can:

- **Query your security data**: Findings, compliance status, resources, and remediation guidance
- **Search Prowler Hub**: Over 2,000 security checks and 70+ compliance frameworks
- **Access documentation**: Search and retrieve Prowler docs contextually

Available in Prowler Cloud, Prowler Private Cloud, and Prowler Local Server.

[Learn more about Prowler Lighthouse AI](/getting-started/products/prowler-cloud-lighthouse)

## Support Desk

<SubscriptionBanner label="service" />

Submit support requests through our support desk. We'll route your request to the right team and respond via email.

<Card title="Submit a request" icon="ticket" href="https://support.prowler.com">
  Contact our support team
</Card>

## GitHub Discussions

Prowler is Open Source. If you have a question, it's likely someone else has it too. We'd love to answer in the open on GitHub whenever possible.

<CardGroup cols={3}>
  <Card title="Ask a question" icon="circle-question" href="https://github.com/prowler-cloud/prowler/discussions">
    Get help from the community
  </Card>
  <Card title="Report a bug" icon="bug" href="https://github.com/prowler-cloud/prowler/issues/new?template=bug_report.yml">
    Found something wrong? Let us know
  </Card>
  <Card title="Suggest a feature" icon="lightbulb" href="https://github.com/prowler-cloud/prowler/issues/new?template=feature-request.yml">
    Share your ideas for improvements
  </Card>
</CardGroup>

## Community Slack

Join our Slack workspace to connect with the Prowler community, ask questions, and get help from other users and the Prowler team.

<Card title="Join Prowler Slack" icon="slack" href="https://goto.prowler.com/slack">
  Connect with the community
</Card>

## Office Hours

Join our open calls to discuss what you're building, ask questions, and connect with the Prowler team and community.

Office Hours sessions are announced on [LinkedIn](https://www.linkedin.com/company/prowler-security/). Recordings of previous sessions are available on [YouTube](https://www.youtube.com/playlist?list=PLIwvjRXuMGkE-BDYXmUR2TXYQ7agxtuB1).

## Security

To report a vulnerability or for security-related inquiries, contact [security@prowler.com](mailto:security@prowler.com).

See also: [Responsible Disclosure](https://prowler.com/.well-known/security.txt)

---

### Troubleshooting (`docs/troubleshooting.mdx`)

---
title: 'Troubleshooting'
---

import { VersionBadge } from "/snippets/version-badge.mdx"

## Prowler CLI

### Running `prowler` I get `[File: utils.py:15] [Module: utils] CRITICAL: path/redacted: OSError[13]`

That is an error related to file descriptors or opened files allowed by your operating system.

In macOS Ventura, the default value for the `file descriptors` is `256`. With the following command `ulimit -n 1000` you'll increase that value and solve the issue.

If you have a different OS and you are experiencing the same, please increase the value of your `file descriptors`. You can check it running `ulimit -a | grep "file descriptors"`.

This error is also related with a lack of system requirements. To improve performance, Prowler stores information in memory so it may need to be run in a system with more than 1GB of memory.

See section [Logging](/user-guide/cli/tutorials/logging) for further information or [contact us](/contact).

## Prowler Local Server

Common issues with the Docker Compose installation of Prowler Local Server.

### Problem Adding AWS Provider Using "Connect assuming IAM Role" in Docker

See [GitHub Issue #7745](https://github.com/prowler-cloud/prowler/issues/7745) for more details.

When running Prowler Local Server via Docker, you may encounter errors such as `Provider not set`, `AWS assume role error - Unable to locate credentials`, or `Provider has no secret` when trying to add an AWS Provider using the "Connect assuming IAM Role" option. This typically happens because the container does not have access to the necessary AWS credentials or profiles.

**Workaround:**

- Ensure your AWS credentials and configuration are available to the Docker container. You can do this by mounting your local `.aws` directory into the container. For example, in your `docker-compose.yaml`, add the following volume to the relevant services:

```yaml
volumes:
  - "${HOME}/.aws:/home/prowler/.aws:ro"
```

This should be added to the `api`, `worker`, and `worker-beat` services.

- Create or update your `~/.aws/config` and `~/.aws/credentials` files with the appropriate profiles and roles. For example:

```ini
[profile prowler-profile]
role_arn = arn:aws:iam::<account-id>:role/ProwlerScan
source_profile = default
```

And set the environment variable in your `.env` file:

```env
AWS_PROFILE=prowler-profile
```

- If you are scanning multiple AWS accounts, you may need to add multiple profiles to your AWS config. Note that this workaround is mainly for local testing; for production or multi-account setups, follow the [CloudFormation Template guide](https://github.com/prowler-cloud/prowler/issues/7745) and ensure the correct IAM roles and permissions are set up in each account.

### Scans Complete but Reports Are Missing or Compliance Data Is Empty (`Too many open files` Error)

When running Prowler Local Server via Docker Compose, scans may complete successfully but reports are not available for download, compliance data shows as empty, or 404 errors appear when trying to access scan reports. Checking the `worker` container logs may reveal errors like `[Errno 24] Too many open files`.

This issue occurs because the default file descriptor limits in Docker containers are too low for Prowler's operations. The default `docker-compose.yml` already includes `ulimits` configuration with `nofile` set to `65536` for the `worker` and `worker-beat` services to prevent this issue.

If a custom `docker-compose.yml` is being used or the default configuration has been modified, ensure the `ulimits` configuration is present in both the `worker` and `worker-beat` services:

```yaml
services:
  worker:
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
    # ... rest of service configuration

  worker-beat:
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
    # ... rest of service configuration
```

After making these changes, restart the Docker Compose stack:

```bash
docker compose down
docker compose up -d
```

### Worker Uses Too Much Memory on Hosts with Many CPUs

<VersionBadge version="5.31.0" />

When Prowler Local Server runs on a machine or Kubernetes node with many CPUs,
the Celery worker may create one prefork process per detected CPU if concurrency
is not configured explicitly. Each process loads the SDK runtime and cloud
provider clients, so idle memory can be high and worker containers can be
terminated by their memory limit.

Set `DJANGO_CELERY_WORKER_CONCURRENCY` in the worker runtime environment to cap
the number of prefork processes:

```yaml
services:
  worker:
    environment:
      DJANGO_CELERY_WORKER_CONCURRENCY: "4"
```

For Kubernetes deployments, set the same variable on the worker Deployment:

```yaml
env:
  - name: DJANGO_CELERY_WORKER_CONCURRENCY
    value: "4"
```

Lower values reduce idle memory and the number of tasks a worker can run in
parallel. Increase the value only when the worker has enough memory for the
expected scan workload. Leaving the variable unset preserves Celery's default
CPU-based concurrency.

### API Container Fails to Start with JWT Key Permission Error

See [GitHub Issue #8897](https://github.com/prowler-cloud/prowler/issues/8897) for more details.

When deploying Prowler via Docker Compose on a fresh installation, the API container may fail to start with permission errors related to JWT RSA key file generation. This issue is commonly observed on Linux systems (Ubuntu, Debian, cloud VMs) and Windows with Docker Desktop, but not typically on macOS.

**Error Message:**

Checking the API container logs reveals:

```bash
PermissionError: [Errno 13] Permission denied: '/home/prowler/.config/prowler-api/jwt_private.pem'
```

Or:

```bash
Token generation failed due to invalid key configuration. Provide valid DJANGO_TOKEN_SIGNING_KEY and DJANGO_TOKEN_VERIFYING_KEY in the environment.
```

**Root Cause:**

This permission mismatch occurs due to UID (User ID) mapping between the host system and Docker containers:

* The API container runs as user `prowler` with UID/GID 1000
* In environments like WSL2, the host user may have a different UID than the container user
* Docker creates the mounted volume directory `./_data/api` on the host, often with the host user's UID or root ownership (UID 0)
* When the application attempts to write JWT key files (`jwt_private.pem` and `jwt_public.pem`), the operation fails because the container's UID 1000 does not have write permissions to the host-owned directory

**Solutions:**

There are two approaches to resolve this issue:

**Option 1: Fix Volume Ownership (Resolve UID Mapping)**

Change the ownership of the volume directory to match the container user's UID (1000):

```bash
# The container user 'prowler' has UID 1000
# This command changes the directory ownership to UID 1000
sudo chown -R 1000:1000 ./_data/api
```

Then start Docker Compose:

```bash
docker compose up -d
```

This solution directly addresses the UID mapping mismatch by ensuring the volume directory is owned by the same UID that the container process uses.

**Option 2: Use Environment Variables (Skip File Storage)**

Generate JWT RSA keys manually and provide them via environment variables to bypass file-based key storage entirely:

```bash
# Generate RSA keys
openssl genrsa -out jwt_private.pem 4096
openssl rsa -in jwt_private.pem -pubout -out jwt_public.pem

# Extract key content (removes headers/footers and newlines)
PRIVATE_KEY=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwt_private.pem)
PUBLIC_KEY=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwt_public.pem)
```

Add the following to the `.env` file:

```env
DJANGO_TOKEN_SIGNING_KEY=<content of jwt_private.pem>
DJANGO_TOKEN_VERIFYING_KEY=<content of jwt_public.pem>
```

When these environment variables are set, the API will use them directly instead of attempting to write key files to the mounted volume.

<Note>
A fix addressing this permission issue is being evaluated in [PR #9953](https://github.com/prowler-cloud/prowler/pull/9953).
</Note>

### Scan Stuck in Executing State After Worker Crash

When running Prowler Local Server via Docker Compose, a scan may remain indefinitely in the `executing` state if the worker process crashes (for example, due to an Out of Memory condition) before it can update the scan status. Since it is not currently possible to cancel a scan in `executing` state through the UI, the workaround is to manually update the scan record in the database.

**Root Cause:**

The Celery worker process terminates unexpectedly (OOM, node failure, etc.) before transitioning the scan state to `completed` or `failed`. The scan record remains in `executing` with no active process to advance it.

**Solution:**

Connect to the database using the `prowler_admin` user. Due to Row-Level Security (RLS), the default database user cannot see scan records — you must use `prowler_admin`:

```bash
psql -U prowler_admin -d prowler_db
```

Identify the stuck scan by filtering for scans in `executing` state:

```sql
SELECT id, name, state, started_at FROM scans WHERE state = 'executing';
```

Update the scan state to `failed` using the scan ID:

```sql
UPDATE scans SET state = 'failed' WHERE id = '<scan-id>';
```

After this change, the scan will appear as failed in the UI and you can launch a new scan.

<Note>
A feature to cancel executing scans directly from the UI is being tracked in [GitHub Issue #6893](https://github.com/prowler-cloud/prowler/issues/6893).
</Note>

### SAML/OAuth ACS URL Incorrect When Running Behind a Proxy or Load Balancer

See [GitHub Issue #9724](https://github.com/prowler-cloud/prowler/issues/9724) for more details.

When running Prowler behind a reverse proxy (nginx, Traefik, etc.) or load balancer, the SAML ACS (Assertion Consumer Service) URL or OAuth callback URLs may be incorrectly generated using the internal container hostname (e.g., `http://prowler-api:8080/...`) instead of your external domain URL (e.g., `https://prowler.example.com/...`).

**Root Cause:**

The API base and docs URLs are resolved from the container environment **at runtime**. A single pre-built Docker image (`prowlercloud/prowler-ui:stable`) therefore serves any environment: point the URLs at your external domain and restart the container — no rebuild is required.

**Solution:**

Set the runtime environment variables to your external URL and restart the UI container:

```yaml
services:
  ui:
    image: prowlercloud/prowler-ui:stable
    environment:
      UI_API_BASE_URL: https://prowler.example.com/api/v1
      UI_API_DOCS_URL: https://prowler.example.com/api/v1/docs
    # ... rest of configuration
```

The same values can be supplied through your `.env` file:

```bash
UI_API_BASE_URL=https://prowler.example.com/api/v1
UI_API_DOCS_URL=https://prowler.example.com/api/v1/docs
```

<Note>
Earlier releases inlined these values into the JavaScript bundle at build time (via the `NEXT_PUBLIC_` prefix) and required a rebuild with `--build-arg`. That is no longer necessary: `UI_API_BASE_URL` and `UI_API_DOCS_URL` are read at container start, so updating them and restarting is sufficient.
</Note>

---

### Api Docs Orphan Task Recovery (`api/docs/orphan-task-recovery.md`)

# Orphan Celery task recovery

When a worker is terminated mid-task (a deploy, an OOM kill, a node eviction), the
task it was running can be left non-terminal forever: the `TaskResult` stays
`STARTED` and nothing re-runs it. This page describes the mechanisms that detect and
recover allowlisted idempotent orphans so pending-task alerts do not fire. Scan tasks
are not auto-recovered (re-running a scan is not safe to do automatically); the
watchdog covers the summary/aggregation and deletion tasks.

## How recovery works

1. **Durable delivery.** The broker is configured so a task message is acknowledged
   only after the task finishes (`task_acks_late`), one task is reserved at a time
   (`worker_prefetch_multiplier = 1`), and an abruptly-lost worker re-queues its task
   (`task_reject_on_worker_lost`). On `SIGTERM` the worker is given a soft-shutdown
   window (`worker_soft_shutdown_timeout`) to finish or re-queue in-flight work
   before it is force-killed. `scan-perform`, `scan-perform-scheduled` and
   `integration-jira` opt out of redelivery with `acks_late=False`, so a crash drops
   them rather than re-running and duplicating findings or Jira issues. Other
   non-recovered side-effect tasks keep `acks_late=True`, so the broker can still
   re-deliver them after a worker loss: the S3 upload rebuilds from worker-local files
   that did not survive the crash and so no-ops, but Security Hub re-reads findings from
   the DB and re-sends them to AWS.

2. **Periodic watchdog.** A Beat task, `reconcile-orphan-tasks`, runs every couple of
   minutes (a `django_celery_beat` periodic task created by migration). For each
   in-flight task result with an allowlisted idempotent task name, it pings the
   worker recorded on the task's `TaskResult`:
   - worker responds -> the task is still running, leave it alone;
   - worker is gone (and the task started before a short grace window) -> it is a
     real orphan: the stale task is revoked and marked terminal (clearing the
     pending/started alert), and the task is re-enqueued from its stored name and
     kwargs.

   The re-run is safe because only tasks with proven idempotency are allowlisted: the
   summary/aggregation tasks clear and re-write their own rows, and deletions are
   idempotent. Scan tasks and external side effects are excluded: re-running a scan is
   not safe to do automatically, Jira sends would create duplicate issues, the S3
   upload rebuilds from worker-local files that do not survive a crash, and
   report/Security Hub recovery is out of scope.

3. **Recovery cap.** A per-task Valkey counter limits how often the same task is
   re-enqueued. After `--max-attempts` recoveries (default 3) the orphan is marked
   terminal instead of re-enqueued, so a task that repeatedly kills its worker cannot
   loop forever.

A Postgres advisory lock ensures that, even with multiple API/worker replicas, only
one reconciliation runs at a time; the others no-op.

## On-demand command

The same logic is available as a management command, useful right after a deploy or
for manual intervention:

```bash
python manage.py reconcile_orphan_tasks            # recover now
python manage.py reconcile_orphan_tasks --dry-run  # report orphans, change nothing
python manage.py reconcile_orphan_tasks --grace-minutes 5 --max-attempts 3
```

## Configuration

All settings have safe defaults; override via environment variables.

| Env var | Default | Purpose |
| --- | --- | --- |
| `DJANGO_CELERY_WORKER_PREFETCH_MULTIPLIER` | `1` | Tasks reserved per worker process. |
| `DJANGO_CELERY_WORKER_CONCURRENCY` | unset | Optional Celery prefork pool size. When unset, Celery uses its CPU-based default. Set this on worker containers to bound idle memory on hosts with many CPUs. |
| `DJANGO_CELERY_WORKER_SOFT_SHUTDOWN_TIMEOUT` | `60` | Seconds the worker drains/re-queues on `SIGTERM` before force-kill. |
| `DJANGO_CELERY_TASK_TIME_LIMIT` | `21600` (6h) | Hard limit for most tasks; connection checks are capped at 120s. |
| `DJANGO_CELERY_TASK_SOFT_TIME_LIMIT` | hard - 600 | Soft limit; raises `SoftTimeLimitExceeded` for cleanup. |
| `DJANGO_CELERY_LONG_TASK_TIME_LIMIT` | `172800` (48h) | Hard limit for scans and provider/tenant deletions, which can legitimately run for more than a day. |
| `DJANGO_CELERY_LONG_TASK_SOFT_TIME_LIMIT` | long hard - 600 | Soft limit for the long-running tasks above. |
| `DJANGO_TASK_RECOVERY_ENABLED` | `false` | Master switch for orphan-task recovery, disabled by default (opt-in); set to `true` to enable. When off, no orphan is detected, marked terminal, or re-enqueued (attack-paths stale cleanup still runs). |
| `DJANGO_TASK_RECOVERY_SUMMARIES_ENABLED` | `true` | Auto re-enqueue orphaned scan summary/aggregation tasks. |
| `DJANGO_TASK_RECOVERY_DELETIONS_ENABLED` | `true` | Auto re-enqueue orphaned provider/tenant deletion tasks. |

Recovery is opt-in: with the master flag off (the default) the sweep does nothing.
Once enabled, the per-group flags default to on, so every group recovers unless you
turn one off; a task whose group flag is off is marked terminal instead of
re-enqueued.

Turning recovery off only disables this watchdog sweep; it does not change Celery's
broker-level redelivery (`task_acks_late`/`task_reject_on_worker_lost`), which still
re-delivers tasks that keep `acks_late=True` on worker loss, independently of this flag.

`task_acks_late` and `task_reject_on_worker_lost` are enabled in `config/celery.py`.

## Deployment requirement

Two conditions must both hold for the soft shutdown to actually drain work:

1. **The worker must receive `SIGTERM`.** The container entrypoint `exec`s the
   Celery process so it runs as PID 1; otherwise `SIGTERM` from `docker stop`/ECS
   hits the entrypoint shell, never reaches Celery, and the worker is hard-killed
   (SIGKILL) at the grace deadline without draining. Custom entrypoints must
   preserve the `exec`.
2. **The orchestrator must give the worker enough time** before force-killing it.
   Set the stop grace period to exceed `DJANGO_CELERY_WORKER_SOFT_SHUTDOWN_TIMEOUT`
   plus a margin:
   - **docker-compose:** `stop_grace_period` on the worker services (set to `120s`).
   - **AWS ECS:** the worker container `stopTimeout` (configured in the deployment
     repository).

If either condition is missing, long tasks are still recovered by the watchdog,
but they are cut mid-run on every deploy instead of draining.

---

### Api Docs Partitions (`api/docs/partitions.md`)

# Partitions

## Overview

Partitions are used to split the data in a table into smaller chunks, allowing for more efficient querying and storage.

The Prowler API uses partitions to store findings. The partitions are created based on the UUIDv7 `id` field.

You can use the Prowler API without ever creating additional partitions. This documentation is only relevant if you want to manage partitions to gain additional query performance.

### Required Postgres Configuration

There are 3 configuration options that need to be set in the `postgres.conf` file to get the most performance out of the partitioning:

- `enable_partition_pruning = on` (default is on)
- `enable_partitionwise_join = on` (default is off)
- `enable_partitionwise_aggregate = on` (default is off)

For more information on these options, see the [Postgres documentation](https://www.postgresql.org/docs/current/runtime-config-query.html).

## Partitioning Strategy

The partitioning strategy is defined in the `api.partitions` module. The strategy is responsible for creating and deleting partitions based on the provided configuration.

## Managing Partitions

The application will run without any extra work on your part. If you want to add or delete partitions, you can use the following commands:

To manage the partitions, run `python manage.py pgpartition --using admin`

This command will generate a list of partitions to create and delete based on the provided configuration.

By default, the command will prompt you to accept the changes before applying them.

```shell
Finding:
   + 2024_nov
      name: 2024_nov
      from_values: 0192e505-9000-72c8-a47c-cce719d8fb93
      to_values: 01937f84-5418-7eb8-b2a6-e3be749e839d
      size_unit: months
      size_value: 1
   + 2024_dec
      name: 2024_dec
      from_values: 01937f84-5800-7b55-879c-9cdb46f023f6
      to_values: 01941f29-7818-7f9f-b4be-20b05bb2f574
      size_unit: months
      size_value: 1

0 partitions will be deleted
2 partitions will be created
```

If you choose to apply the partitions, tables will be generated with the following format: `<table_name>_<year>_<month>`.

For more info on the partitioning manager, see https://github.com/SectorLabs/django-postgres-extra

### Changing the Partitioning Parameters

There are 4 environment variables that can be used to change the partitioning parameters:

- `DJANGO_MANAGE_DB_PARTITIONS`: Allow Django to manage database partitons. By default is set to `False`.
- `FINDINGS_TABLE_PARTITION_MONTHS`: Set the months for each partition. Setting the partition monts to 1 will create partitions with a size of 1 natural month.
- `FINDINGS_TABLE_PARTITION_COUNT`: Set the number of partitions to create
- `FINDINGS_TABLE_PARTITION_MAX_AGE_MONTHS`: Set the number of months to keep partitions before deleting them. Setting this to `None` will keep partitions indefinitely.

---

### Developer Guide Ai Skills (`docs/developer-guide/ai-skills.mdx`)

---
title: 'AI Skills System'
---

This guide explains the AI Skills system that provides on-demand context and patterns to AI agents working with the Prowler codebase.

<Info>
**What are AI Skills?** Skills are structured instructions that help AI agents (Claude Code, Cursor, Copilot, etc.) understand Prowler's conventions, patterns, and best practices.
</Info>

Skills live in the [`skills/`](https://github.com/prowler-cloud/prowler/tree/master/skills) directory of the Prowler OSS repository. Each skill is a folder containing a `SKILL.md` file with its patterns and metadata.

## Installation

To enable skills for the supported AI coding assistants, run the setup script from the repository root:

```bash
./skills/setup.sh
```

The script creates symlinks so each tool finds the skills in its expected location:

| Tool | Created by setup |
|------|------------------|
| Claude Code | `.claude/skills/` symlink and `CLAUDE.md` |
| Gemini CLI | `.gemini/skills/` symlink and `GEMINI.md` |
| Codex (OpenAI) | `.codex/skills/` symlink (uses `AGENTS.md` natively) |
| GitHub Copilot | `.github/copilot-instructions.md` symlink to `AGENTS.md` |

After running the setup, restart the AI coding assistant to load the skills.

## Using Skills

AI agents discover skills automatically and load them when a request matches a skill trigger. To load a skill manually during a session, point the agent to the skill's `SKILL.md` file:

```text
Read skills/{skill-name}/SKILL.md
```

For the full list of available skills, their triggers, and the Auto-invoke mappings, see the [`skills/README.md`](https://github.com/prowler-cloud/prowler/blob/master/skills/README.md) and [`AGENTS.md`](https://github.com/prowler-cloud/prowler/blob/master/AGENTS.md) in the repository.

## Available Skills

| Type | Skills |
|------|--------|
| **Generic** | typescript, react-19, nextjs-16, tailwind-4, pytest, playwright, django-drf, zod-4, zustand-5, ai-sdk-5, vitest, tdd |
| **Prowler** | prowler, prowler-sdk-check, prowler-api, prowler-ui, prowler-mcp, prowler-provider, prowler-compliance, prowler-compliance-review, prowler-docs, prowler-pr, prowler-ci, prowler-attack-paths-query |
| **Testing** | prowler-test-sdk, prowler-test-api, prowler-test-ui |
| **Meta** | skill-creator, skill-sync |

<Note>
This table is a snapshot. The repository is the source of truth: see [`skills/README.md`](https://github.com/prowler-cloud/prowler/blob/master/skills/README.md) for the current, complete list.
</Note>

## Skill Structure

Each skill follows the [Agent Skills spec](https://agentskills.io):

```text
skills/{skill-name}/
├── SKILL.md          # Patterns, rules, decision trees
├── assets/           # Code templates, schemas
└── references/       # Links to local docs (single source of truth)
```

## Key Design Decisions

1. **Self-contained skills** - Critical patterns inline for fast loading
2. **Local doc references** - No web URLs, points to `docs/developer-guide/*.mdx`
3. **Single source of truth** - Skills reference docs, no duplication
4. **On-demand loading** - AI loads only what's needed for the task

## Creating New Skills

Use the `skill-creator` meta-skill to create new skills that follow the Agent Skills spec. See [`AGENTS.md`](https://github.com/prowler-cloud/prowler/blob/master/AGENTS.md) for the full list of available skills and their triggers.

## How Skills Work

The diagrams below explain the internals of the skill system. They are useful for understanding the design, but are not required to install or use skills.

### Architecture Overview

```mermaid
graph LR
    subgraph FLOW["AI Skills Architecture"]
        A["AI Agent"] -->|"1. matches trigger"| B["AGENTS.md"]
        B -->|"2. loads"| C["Skill"]
        C -->|"3. provides"| D["Patterns<br/>Templates<br/>Commands"]
        C -->|"4. references"| E["Local Docs"]
        D --> F["Correct Output"]
        E --> F
    end

    style A fill:#1e3a5f,stroke:#4a9eff,color:#fff
    style B fill:#5c4d1a,stroke:#ffd700,color:#fff
    style C fill:#1a4d1a,stroke:#4caf50,color:#fff
    style E fill:#4a1a4d,stroke:#ba68c8,color:#fff
    style F fill:#1a4d2e,stroke:#66bb6a,color:#fff
```

### Request Lifecycle

```mermaid
sequenceDiagram
    participant U as User
    participant A as AI Agent
    participant R as AGENTS.md
    participant S as Skill
    participant AS as assets/
    participant RF as references/
    participant D as Local Docs

    U->>A: "Create an AWS security check"

    Note over A: Analyze request context

    A->>R: Find matching skill trigger
    R-->>A: prowler-sdk-check matches

    A->>S: Load SKILL.md
    S-->>A: Patterns, rules, templates, commands

    Note over A: Need code template?

    A->>AS: Read assets/aws_check.py
    AS-->>A: Check implementation template

    Note over A: Need more details?

    A->>RF: Read references/metadata-docs.md
    RF-->>A: Points to local docs

    A->>D: Read docs/developer-guide/checks.mdx
    D-->>A: Full documentation

    Note over A: Execute with full context

    A->>U: Creates check with correct patterns
```

### With and Without Skills

```mermaid
graph TD
    subgraph COMPARISON["BEFORE vs AFTER"]
        direction LR

        subgraph BEFORE["Without Skills"]
            B1["AI guesses conventions"]
            B2["Wrong structure"]
            B3["Multiple iterations"]
            B4["Web searches for docs"]
            B5["Inconsistent patterns"]
        end

        subgraph AFTER["With Skills"]
            A1["AI loads exact patterns"]
            A2["Correct structure"]
            A3["First-time right"]
            A4["Local docs referenced"]
            A5["Consistent patterns"]
        end
    end

    style BEFORE fill:#5c1a1a,stroke:#ef5350,color:#fff
    style AFTER fill:#1a4d1a,stroke:#66bb6a,color:#fff
```

### Full Component Map

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Developer Guide Alibabacloud Details (`docs/developer-guide/alibabacloud-details.mdx`)

---
title: 'Alibaba Cloud Provider'
---

This page details the [Alibaba Cloud](https://www.alibabacloud.com/) provider implementation in Prowler.

By default, Prowler will audit all the Alibaba Cloud regions that are available. To configure it, follow the [Alibaba Cloud getting started guide](/user-guide/providers/alibabacloud/getting-started-alibabacloud).

## Alibaba Cloud Provider Classes Architecture

The Alibaba Cloud provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the Alibaba Cloud-specific implementation, highlighting how the generic provider concepts are realized for Alibaba Cloud in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).

### Main Class

- **Location:** [`prowler/providers/alibabacloud/alibabacloud_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/alibabacloud_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for Alibaba Cloud-specific logic, session management, credential validation, and configuration.
- **Key Alibaba Cloud Responsibilities:**
    - Initializes and manages Alibaba Cloud sessions (supports Access Keys, STS Temporary Credentials, RAM Role Assumption, ECS RAM Role, OIDC Authentication, and Credentials URI).
    - Validates credentials using STS GetCallerIdentity.
    - Loads and manages configuration, mutelist, and fixer settings.
    - Discovers and manages Alibaba Cloud regions.
    - Provides properties and methods for downstream Alibaba Cloud service classes to access session, identity, and configuration data.

### Data Models

- **Location:** [`prowler/providers/alibabacloud/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/models.py)
- **Purpose:** Define structured data for Alibaba Cloud identity, session, credentials, and region info.
- **Key Alibaba Cloud Models:**
    - `AlibabaCloudCallerIdentity`: Stores caller identity information from STS GetCallerIdentity (account_id, principal_id, arn, identity_type).
    - `AlibabaCloudIdentityInfo`: Holds Alibaba Cloud identity metadata including account ID, user info, profile, and audited regions.
    - `AlibabaCloudCredentials`: Stores credentials (access_key_id, access_key_secret, security_token).
    - `AlibabaCloudRegion`: Represents an Alibaba Cloud region with region_id and region_name.
    - `AlibabaCloudSession`: Manages the session and provides methods to create service clients.

### `AlibabaCloudService` (Service Base Class)

- **Location:** [`prowler/providers/alibabacloud/lib/service/service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/lib/service/service.py)
- **Purpose:** Abstract base class that all Alibaba Cloud service-specific classes inherit from. This implements the generic service pattern (described in [service page](/developer-guide/services#service-base-class)) specifically for Alibaba Cloud.
- **Key Alibaba Cloud Responsibilities:**
    - Receives an `AlibabacloudProvider` instance to access session, identity, and configuration.
    - Manages regional clients for services that are region-specific.
    - Provides `__threading_call__` method to make API calls in parallel by region or resource.
    - Exposes common audit context (`audited_account`, `audited_account_name`, `audit_resources`, `audit_config`) to subclasses.

### Exception Handling

- **Location:** [`prowler/providers/alibabacloud/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for Alibaba Cloud-specific error handling.
- **Key Alibaba Cloud Exceptions:**
    - `AlibabaCloudClientError`: General client errors
    - `AlibabaCloudNoCredentialsError`: No credentials found
    - `AlibabaCloudInvalidCredentialsError`: Invalid credentials provided
    - `AlibabaCloudSetUpSessionError`: Session setup failures
    - `AlibabaCloudAssumeRoleError`: RAM role assumption failures
    - `AlibabaCloudInvalidRegionError`: Invalid region specified
    - `AlibabaCloudHTTPError`: HTTP/API errors

### Session and Utility Helpers

- **Location:** [`prowler/providers/alibabacloud/lib/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/alibabacloud/lib/)
- **Purpose:** Helpers for argument parsing, mutelist management, and other cross-cutting concerns.

## Specific Patterns in Alibaba Cloud Services

The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:

- Directly in the code, in location [`prowler/providers/alibabacloud/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/alibabacloud/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used across all Alibaba Cloud services.

### Alibaba Cloud Service Common Patterns

- Services communicate with Alibaba Cloud using the official Alibaba Cloud Python SDKs. Documentation for individual services can be found in the [Alibaba Cloud SDK documentation](https://www.alibabacloud.com/help/en/sdk).
- Every Alibaba Cloud service class inherits from `AlibabaCloudService`, ensuring access to session, identity, configuration, and client utilities.
- The constructor (`__init__`) always calls `super().__init__` with the service name, provider, and optionally `global_service=True` for services that are not regional (e.g., RAM).
- Resource containers **must** be initialized in the constructor. For regional services, resources are typically stored in dictionaries keyed by region and resource ID.
- All Alibaba Cloud resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
- Alibaba Cloud SDK functions are wrapped in try/except blocks, with specific handling for errors, always logging errors.
- Regional services use `self.regional_clients` to maintain clients for each audited region.
- The `__threading_call__` method is used for parallel execution across regions or resources.

### Example Service Implementation

```python
from prowler.lib.logger import logger
from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService


class MyService(AlibabaCloudService):
    def __init__(self, provider):
        # Initialize parent class with service name
        super().__init__("myservice", provider)

        # Initialize resource containers
        self.resources = {}

        # Discover resources using threading
        self.__threading_call__(self._describe_resources)

    def _describe_resources(self, regional_client):
        try:
            region = regional_client.region
            response = regional_client.describe_resources()

            for resource in response.body.resources:
                self.resources[resource.id] = MyResource(
                    id=resource.id,
                    name=resource.name,
                    region=region,
                    # ... other attributes
                )
        except Exception as error:
            logger.error(
                f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
            )
```

## Specific Patterns in Alibaba Cloud Checks

The Alibaba Cloud checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks:

- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new check is following the [check implementation documentation](/developer-guide/checks#creating-a-check) and taking other similar checks as reference.

### Check Report Class

The `CheckReportAlibabaCloud` class models a single finding for an Alibaba Cloud resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.

#### Purpose

`CheckReportAlibabaCloud` extends the base report structure with Alibaba Cloud-specific fields, enabling detailed tracking of the resource, resource ID, ARN, and region associated with each finding.

#### Constructor and Attribute Population

When you instantiate `CheckReportAlibabaCloud`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its Alibaba Cloud-specific attributes from the resource, using the following logic:

- **`resource_id`**:
    - Uses `resource.id` if present.
    - Otherwise, uses `resource.name` if present.
    - Defaults to an empty string if not available.

- **`resource_arn`**:
    - Uses `resource.arn` if present.
    - Defaults to an empty string if not available.

- **`region`**:
    - Uses `resource.region` if present.
    - Defaults to an empty string if not available.

If the resource object does not contain the required attributes, you must set them manually in the check logic.

Other attributes are inherited from the `Check_Report` class, from which you **always** have to set the `status` and `status_extended` attributes in the check logic.

#### Example Usage

```python
from prowler.lib.check.models import Check, CheckReportAlibabaCloud
from prowler.providers.alibabacloud.services.myservice.myservice_client import myservice_client


class myservice_example_check(Check):
    def execute(self) -> list[CheckReportAlibabaCloud]:
        findings = []

        for resource in myservice_client.resources.values():
            report = CheckReportAlibabaCloud(
                metadata=self.metadata(),
                resource=resource
            )
            report.region = resource.region
            report.resource_id = resource.id
            report.resource_arn = f"acs:myservice::{myservice_client.audited_account}:resource/{resource.id}"

            if resource.is_compliant:
                report.status = "PASS"
                report.status_extended = f"Resource {resource.name} is compliant."
            else:
                report.status = "FAIL"
                report.status_extended = f"Resource {resource.name} is not compliant."

            findings.append(report)

        return findings
```

## Authentication Methods

The Alibaba Cloud provider supports multiple authentication methods, prioritized in the following order:

1. **Credentials URI** - Retrieve credentials from an external URI endpoint
2. **OIDC Role Authentication** - For applications running in ACK with RRSA enabled
3. **ECS RAM Role** - For ECS instances with attached RAM roles
4. **RAM Role Assumption** - Cross-account access with role assumption
5. **STS Temporary Credentials** - Pre-obtained temporary credentials
6. **Permanent Access Keys** - Static access key credentials
7. **Default Credential Chain** - Automatic credential discovery

For detailed authentication configuration, see the [Authentication documentation](/user-guide/providers/alibabacloud/authentication).

## Regions

Alibaba Cloud has multiple regions across the globe. By default, Prowler audits all available regions. You can specify specific regions using the `--region` CLI argument:

```bash
prowler alibabacloud --region cn-hangzhou cn-shanghai
```

The list of supported regions is maintained in [`prowler/providers/alibabacloud/config.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/config.py).

---

### Developer Guide Attack Paths Queries (`docs/developer-guide/attack-paths-queries.mdx`)

---
title: "Attack Paths Queries"
---

This guide explains how to write and maintain Prowler Attack Paths queries: the read-only openCypher queries that traverse the Cartography-ingested cloud graph to detect privilege escalation chains, network exposure, and other graph-shaped security risks.

<Info>
**New to Attack Paths?** Start with the user documentation:
- [Attack Paths](/user-guide/tutorials/prowler-app-attack-paths) - What Attack Paths detects, how to run built-in queries, and how to explore the resulting graph.
- [Writing Custom openCypher Queries](/user-guide/tutorials/prowler-app-attack-paths#writing-custom-opencypher-queries) - Run ad-hoc read-only queries from the Prowler App.
</Info>

## Introduction

Attack Paths queries run against a property graph populated by [Cartography](https://github.com/cartography-cncf/cartography), an open-source graph ingestion framework, and enriched with Prowler findings. Every query is read-only openCypher (Version 9) so it runs on both the Neo4j and Amazon Neptune sinks.

Two categories of query exist, each with a different isolation model:

|                    | Predefined queries                                          | Custom queries                                                        |
| ------------------ | ----------------------------------------------------------- | --------------------------------------------------------------------- |
| Where they live    | `api/src/backend/api/attack_paths/queries/{provider}.py`    | User-supplied through the custom query API endpoint                   |
| Provider isolation | `AWSAccount {id: $provider_uid}` anchor plus path connectivity | Automatic `_Provider_{uuid}` label injection by `cypher_sanitizer.py` |
| What to write      | Chain every `MATCH` from the `aws` variable                 | Plain Cypher, no isolation boilerplate                                |
| Internal labels    | Never use                                                   | Never use (system-injected)                                           |

For **predefined queries**, every node must be reachable from the `AWSAccount` root through graph traversal. That reachability is the isolation boundary.

For **custom queries**, the runner injects a `_Provider_{uuid}` label into every node pattern, and a post-query filter handles edge cases, so query authors write natural Cypher without isolation boilerplate.

The rest of this guide focuses on predefined queries, though the graph model, list-property handling, and compatibility rules apply to both.

## The Graph Model

### Cartography Schema

Node labels, relationship types, and properties follow the upstream Cartography schema for each provider. Do not guess them, fetch the schema for the pinned Cartography version:

```bash
grep cartography api/pyproject.toml
```

Then read the schema for that exact tag:

```text
# Git pin (prowler-cloud/cartography@<TAG>):
https://raw.githubusercontent.com/prowler-cloud/cartography/refs/tags/<TAG>/docs/root/modules/{provider}/schema.md

# PyPI pin (cartography==<TAG>):
https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/<TAG>/docs/root/modules/{provider}/schema.md
```

The public schema reference for AWS is available at [Cartography AWS Schema](https://cartography-cncf.github.io/cartography/modules/aws/schema.html).

### Prowler-Specific Additions

The Prowler sync task enriches the Cartography graph with the following labels and relationships. These are not part of the upstream schema:

| Label / Relationship   | Description                                                 |
| ---------------------- | ----------------------------------------------------------- |
| `ProwlerFinding`       | Finding node (`status`, `severity`, `check_id`)             |
| `Internet`             | Internet sentinel node used to model public exposure        |
| `CAN_ACCESS`           | `(Internet)-[:CAN_ACCESS]->(resource)` exposure edge        |
| `HAS_FINDING`          | `(resource)-[:HAS_FINDING]->(:ProwlerFinding)` finding link |
| `TRUSTS_AWS_PRINCIPAL` | Role trust relationship                                     |
| `STS_ASSUMEROLE_ALLOW` | Principal can assume a role                                 |

### Internal Isolation Labels

The sync layer also adds internal labels used only for tenant and provider isolation: `_ProviderResource`, `_AWSResource`, `_Tenant_*`, and `_Provider_*`. These must never appear in query text, predefined or custom. The runner applies isolation automatically.

## Query Structure

### Provider Scoping Parameter

| Parameter       | Property | Used on      | Purpose                                |
| --------------- | -------- | ------------ | -------------------------------------- |
| `$provider_uid` | `id`     | `AWSAccount` | Scopes the query to a specific account |

The runner binds `$provider_uid` automatically. Every other node is isolated by path connectivity from the `AWSAccount` anchor.

### Imports

```python
from api.attack_paths.queries.types import (
    AttackPathsQueryAttribution,
    AttackPathsQueryDefinition,
    AttackPathsQueryParameterDefinition,
)
from tasks.jobs.attack_paths.config import PROWLER_FINDING_LABEL
```

Always reference `PROWLER_FINDING_LABEL` through f-string interpolation, never hardcode `"ProwlerFinding"`.

### Definition Fields

- **id**: kebab-case `{provider}-{category}-{description}`, e.g. `aws-ec2-privesc-passrole-iam`.
- **name**: short, human-friendly label. Sourced queries append the reference ID: `"EC2 Instance Launch with Privileged Role (EC2-001)"`.
- **short_description**: one sentence, no technical permissions.
- **description**: full technical explanation, plain text.
- **provider**: `aws`, `azure`, `gcp`, `kubernetes`, or `github`.
- **cypher**: f-string Cypher body. Literal `{` and `}` are escaped as `{{` and `}}`.
- **parameters**: `parameters=[]` when the query takes no input.
- **attribution**: optional `AttackPathsQueryAttribution(text, link)` for sourced queries. The `link` uses the lowercase ID.

Append the constant to the `{PROVIDER}_QUERIES` list at the bottom of the provider file.

## The Predefined Query Template

The canonical shape combines a principal walk, an optional target walk, deduplicated nodes, and a typed finding overlay:

```
/* Detailed source-code truncated for AI context efficiency. */
```

Key points:

- The principal walk types the `POLICY` and `STATEMENT` hops. Both are low-fan-out (each principal has a handful of policies; each policy a handful of statements), so the typed edge lets the planner cost a cheap inline filter.
- The `(aws)--` hub hops stay anonymous. `AWSAccount` is a high-degree node that fans out to every principal, role, policy, and resource in the account; typing those edges forces the planner to enumerate from the hub and collapses performance on multi-tenant Neptune.
- Other relationship types appear only where the file's existing queries already use one (`TRUSTS_AWS_PRINCIPAL`, `STS_ASSUMEROLE_ALLOW`, `MEMBER_AWS_GROUP`, `HAS_EXECUTION_ROLE`).
- The finding probe is typed `:HAS_FINDING` and left undirected. The type lets Neptune apply an inline edge filter; the missing direction matches the convention of the rest of the file.
- Collapse duplicate rows after each permission gate with `WITH DISTINCT`, carrying only the variables needed by later clauses.
- The `RETURN` shape `paths, dpf, dpfr` is the contract the serializer and visualizer depend on. Do not change it.

## Avoiding Cartesian Products

The most common performance defect in Attack Paths queries is a Cartesian product between a target set and a policy statement's resource items. When the two are written as independent `MATCH` clauses, the planner pairs every target with every resource item before any filter runs. On accounts with many IAM principals, that multiplies into hundreds of thousands of rows and the query errors or times out.

### The Pattern That Causes It

```cypher
// One row per (target_role x resource_item): a Cartesian product
MATCH path_target = (aws)--(target_role:AWSRole)
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WHERE res.value = '*'
   OR res.value CONTAINS target_role.name
   OR target_role.arn CONTAINS res.value
```

The two `MATCH` clauses share no relationship, so the engine enumerates all targets multiplied by all resource items, applies a non-indexable `CONTAINS` to each pair, then expands the finding overlay for every surviving row. Cost grows with `targets × resources`, and a second constrained statement (`stmt2`) multiplies it again.

### The Pattern That Avoids It

Collect the statement's resource values into a list once, then match each target a single time against that in-memory list:

```cypher
// Pre-aggregate the statement's resource values into a list
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard

// Match each target once; bind name/arn to locals so the predicate reads them once
MATCH path_target = (aws)--(target_role:AWSRole)
WITH path_principal, path_target, res_values, res_wildcard,
     target_role.name AS rname, target_role.arn AS rarn
WHERE res_wildcard
   OR size([rv IN res_values WHERE rv CONTAINS rname OR rarn CONTAINS rv]) > 0
```

Cost now grows with `targets + resources` (linear) rather than `targets × resources`. The rewrite is a pure algebraic identity: the result set is unchanged.

Guidelines:

- **Aggregate resources before matching targets, not after.** `collect(DISTINCT res.value)` reduces the resource items to a single list per statement.
- **Short-circuit the wildcard grant** with `('*' IN res_values)`. When a statement grants `*`, every target matches, so the list scan is skipped entirely.
- **Bind `target.name` and `target.arn` to local variables** in a `WITH` before the predicate. The list comprehension then reads each once per target instead of re-reading the property store once per resource value.
- **Use `size([... ]) > 0`, not `any(...)`.** The `any()`, `all()`, and `none()` predicate functions are not part of the openCypher specification and fail on Amazon Neptune. See [openCypher Compatibility](#opencypher-compatibility).
- **For two-statement queries**, aggregate each statement's resources into its own list (`res_values`, `res2_values`) and combine the two `size([... ]) > 0` checks with `AND`.

Every IAM privilege escalation query in `aws.py` uses this pattern. The lateral-movement variants that constrain the target with a relationship (`STS_ASSUMEROLE_ALLOW`, `TRUSTS_AWS_PRINCIPAL`) already limit the target set before the resource filter, which keeps them efficient without further aggregation.

## Privilege Escalation Sub-Patterns

Four `path_target` shapes cover the common escalation types. Each shares the canonical template's `path_principal`, the resource pre-aggregation, the deduplication tail, and the `RETURN`; only the `path_target` `MATCH` and its resource predicate differ.

| Sub-pattern         | Target                   | `path_target` shape                                                                                     | Example |
| ------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------- | ------- |
| Self-escalation     | Principal's own policies | `(aws)--(target_policy:AWSPolicy)--(principal)`                                                         | IAM-001 |
| Lateral to user     | Other IAM users          | `(aws)--(target_user:AWSUser)`                                                                          | IAM-002 |
| Assume-role lateral | Assumable roles          | `(aws)--(target_role:AWSRole)-[:STS_ASSUMEROLE_ALLOW]-(principal)`                                      | IAM-014 |
| PassRole plus service  | Service-trusting roles   | `(aws)--(target_role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]-(:AWSPrincipal {arn: '{service}.amazonaws.com'})` | EC2-001 |

**Multi-permission queries** (for example PassRole plus a service-create action) add permission gates before `path_target`. Reuse the per-query counter for new variables (`act2`, `policy2`, `stmt2`) and collapse rows after each gate:

```cypher
MATCH (principal)-[:POLICY]->(policy2:AWSPolicy)-[:STATEMENT]->(stmt2:AWSPolicyStatement {effect: 'Allow'})
MATCH (stmt2)-[:HAS_ACTION]->(act2:AWSPolicyStatementActionItem)
WHERE toLower(act2.value) IN ['service:*', 'service:createsomething']
   OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
```

When a permission is an existence-only gate whose statement resource is not checked later, keep the policy and statement anonymous and carry only the variables still needed:

```cypher
MATCH (principal)-[:POLICY]->(:AWSPolicy)-[:STATEMENT]->(:AWSPolicyStatement {effect: 'Allow'})-[:HAS_ACTION]->(act3:AWSPolicyStatementActionItem)
WHERE toLower(act3.value) IN ['service:*', 'service:othersomething']
   OR act3.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
```

## Network Exposure Pattern

The Internet node is reached through `CAN_ACCESS` from an already-scoped resource, never as a standalone lookup:

```python
cypher=f"""
    // Resource scoped through the account anchor
    MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(resource:EC2Instance)
    WHERE resource.exposed_internet = true

    // Internet node reached through path connectivity from the resource
    OPTIONAL MATCH (internet:Internet)-[can_access:CAN_ACCESS]->(resource)

    WITH collect(path) AS paths, head(collect(internet)) AS internet, collect(can_access) AS can_access
    UNWIND paths AS p
    UNWIND nodes(p) AS n

    WITH paths, internet, can_access, collect(DISTINCT n) AS unique_nodes
    UNWIND unique_nodes AS n
    OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})

    RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr,
        internet, can_access
"""
```

The `CAN_ACCESS` edge stays typed and directed (`-[:CAN_ACCESS]->`); that is its canonical sync-time orientation. Network-exposure queries extend the `RETURN` contract with `internet, can_access`.

## Working with List-Typed Properties

Some Cartography node properties carry a list of values: `AWSPolicyStatement.action`, `AWSPolicyStatement.resource`, `AWSPolicyStatement.notaction`, `AWSPolicyStatement.notresource`, `KMSKey.encryption_algorithms`, `CloudFrontDistribution.aliases`, the container-definition lists on `ECSContainerDefinition`, and many others. The graph models each such property as a set of child item nodes connected to the parent by a typed edge. Queries reach the values by traversing the edge; the parent does not carry the list as a single field.

### Naming Convention

For a list-typed parent property the sink stores:

- **Child label**: `<ParentLabel><PropertyPascal>Item`. Example: `AWSPolicyStatement.resource` becomes `AWSPolicyStatementResourceItem`.
- **Edge type**: `HAS_<PROPERTY_UPPER>`. Example: `resource` becomes `HAS_RESOURCE`.
- **Child property**: `value`, a single scalar string per list element. For list-of-dict properties (rare; for example `SecretsManagerSecretVersion.tags`) the child carries the original dict keys as named fields per the catalog's `field_map`.

### Variable Naming for Child-Item Matches

`aws.py` uses a per-query counter for each `HAS_*` traversal so chained matches stay unambiguous. The counter resets at the top of every query.

| Edge              | First  | Second  | Third   |
| ----------------- | ------ | ------- | ------- |
| `HAS_ACTION`      | `act`  | `act2`  | `act3`  |
| `HAS_RESOURCE`    | `res`  | `res2`  | `res3`  |
| `HAS_NOTACTION`   | `nact` | `nact2` | `nact3` |
| `HAS_NOTRESOURCE` | `nres` | `nres2` | `nres3` |

### Matching an Action

To find statements that grant `iam:PassRole`, `iam:*`, or `*`, traverse the `HAS_ACTION` edge in its own `MATCH` clause and apply the predicate in the attached `WHERE`:

```cypher
MATCH (stmt:AWSPolicyStatement {effect: 'Allow'})
MATCH (stmt)-[:HAS_ACTION]->(act:AWSPolicyStatementActionItem)
WHERE toLower(act.value) IN ['iam:passrole', 'iam:*']
   OR act.value = '*'
```

The literal-action list is case-folded with `toLower(act.value)` because IAM authors mix case (`iam:PassRole`, `iam:passrole`); the `*` wildcard never lower-cases.

### Matching a Resource Against a Target

To find statements whose resource can target a specific node, pre-aggregate the resource values and test the target against the list once (see [Avoiding Cartesian Products](#avoiding-cartesian-products)):

```cypher
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard

MATCH path_target = (aws)--(target_role:AWSRole)
WITH path_principal, path_target, res_values, res_wildcard,
     target_role.name AS rname, target_role.arn AS rarn
WHERE res_wildcard
   OR size([rv IN res_values WHERE rv CONTAINS rname OR rarn CONTAINS rv]) > 0
```

Three predicates cover the resource cases: full wildcard (`*`), a pattern containing the target name (`arn:aws:iam::*:role/admin*`), and a pattern that is a prefix or component of the actual ARN.

### Every-Item and Any-Item Predicates on a Custom Query

Custom queries can express list predicates directly with pattern comprehensions. To check whether *every* item satisfies a predicate, count the counter-examples and require zero, together with a guard that ensures at least one item is attached:

```cypher
MATCH (stmt:AWSPolicyStatement)
WHERE size([
    (stmt)-[:HAS_ACTION]->(a:AWSPolicyStatementActionItem)
    WHERE NOT toLower(a.value) STARTS WITH 's3:'
    | a
  ]) = 0
  AND size([(stmt)-[:HAS_ACTION]->(a:AWSPolicyStatementActionItem) | a]) > 0
RETURN stmt
LIMIT 25
```

To return the list of values directly, collect them from the child items:

```cypher
MATCH (stmt:AWSPolicyStatement {effect: 'Allow'})
OPTIONAL MATCH (stmt)-[:HAS_ACTION]->(a:AWSPolicyStatementActionItem)
RETURN stmt, collect(a.value) AS actions
LIMIT 25
```

### Catalog of List Properties

The provider catalog lives in `api/src/backend/tasks/jobs/attack_paths/provider_config.py` (`AWS_NORMALIZED_LISTS`). Beyond policy statements it includes KMS algorithms, ECS container-definition lists (`entry_point`, `command`, `links`, `dns_servers`, and others), CloudFront aliases, Inspector finding URL and vulnerability lists, and RDS event-subscription categories. To query a list property that is not in the catalog, add an entry there first so the sync layer materializes it. Properties absent from the catalog are serialized to a comma-delimited string and emit a one-time warning during sync.

## Working with JSON-Encoded Properties

Some Cartography properties represent nested objects, most notably `condition` on `AWSPolicyStatement` and `S3PolicyStatement` nodes. To keep the schema portable across graph backends, object-typed properties are stored as JSON-encoded strings:

```
'{"StringEquals":{"aws:SourceAccount":"123456789012"}}'
```

No JSON parser is available at query time, so use `CONTAINS` for substring checks against keys or known values:

```cypher
MATCH (stmt:AWSPolicyStatement)
WHERE stmt.effect = 'Allow'
  AND stmt.condition CONTAINS '"aws:SourceAccount"'
RETURN stmt
LIMIT 25
```

When a query needs to inspect the structured members of a condition (for example, to evaluate every operator and key), fetch the rows first and parse the JSON in application code. Cypher cannot navigate JSON object keys or values.

## openCypher Compatibility

Queries must run on both Neo4j and Amazon Neptune. Neptune implements a subset of Cypher, so several convenient constructs are unavailable. Avoid the following:

| Feature                                 | Use instead                                                                                                                                 |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| APOC procedures (`apoc.*`)              | Real nodes and relationships in the graph                                                                                                   |
| Neptune extensions                      | Standard openCypher                                                                                                                         |
| `any(x IN list ...)`                    | `size([x IN list WHERE pred]) > 0`                                                                                                          |
| `all(x IN list ...)`                    | `size([x IN list WHERE pred]) = size(list)`                                                                                                 |
| `none(x IN list ...)`                   | `size([x IN list WHERE pred]) = 0`                                                                                                          |
| `reduce()`                              | `UNWIND` plus `collect()`                                                                                                                    |
| `FOREACH`                               | `WITH` plus `UNWIND` plus `SET`                                                                                                              |
| Regex `=~`                              | `toLower()` plus exact match, or `STARTS WITH` / `CONTAINS`                                                                                 |
| `CALL () { UNION }`                     | Multi-label `OR` in `WHERE`                                                                                                                  |
| Carried value plus aggregate expression | Project the aggregate first (`WITH principal_paths, collect(...) AS target_paths`), then combine lists in the next `WITH`                    |
| `EXISTS { MATCH (pattern) WHERE pred }` | Standalone `MATCH (pattern)` plus `WHERE pred`; precede the downstream `collect(path...)` with `WITH DISTINCT <path-vars>` to dedupe the joins |

The carried-value-plus-aggregate rule is worth calling out because it is easy to hit. Neo4j 5.x rejects an expression that concatenates a carried list variable with an aggregate in the same projection:

```cypher
// Rejected: "Aggregation column contains implicit grouping expressions"
WITH principal_paths + collect(DISTINCT path_target) AS paths
```

Split it into two `WITH` clauses so the aggregation resolves before the concatenation:

```cypher
WITH principal_paths, collect(DISTINCT path_target) AS target_paths
WITH principal_paths + target_paths AS paths
```

For list-typed properties in the catalog (action, resource, and so on), traverse the `HAS_*` edges to the child item nodes rather than reading a single field; `split(...)` and comma-string predicates do not apply.

## Best Practices

1. **Chain every MATCH from the account anchor.** An unanchored `MATCH (role:AWSRole)` returns roles from every provider in the graph; `MATCH (aws)--(role:AWSRole)` is scoped. A second-permission `MATCH` such as `MATCH (principal)--(policy2:AWSPolicy)--(stmt2:AWSPolicyStatement)` is safe because `principal` is already bound to the account subgraph.
2. **Pre-aggregate resource lists before matching targets** to avoid Cartesian products (see [Avoiding Cartesian Products](#avoiding-cartesian-products)).
3. **Type the finding probe.** Always `OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})`. The type lets Neptune apply an inline edge filter; an untyped probe scans every incident edge of high-degree nodes.
4. **Comment each MATCH.** One inline `// ...` line per clause explaining its role.
5. **Never use internal labels.** `_ProviderResource`, `_AWSResource`, `_Tenant_*`, and `_Provider_*` are system isolation labels and must not appear in query text.
6. **Reach the Internet node through path connectivity** with `(internet:Internet)-[:CAN_ACCESS]->(resource)`, never as a standalone match.
7. **Preserve the RETURN contract.** `paths, dpf, dpfr` for the standard shape; add `internet, can_access` for network-exposure queries. The serializer and visualizer depend on these names.

## Naming Conventions

- **ID**: kebab-case `{provider}-{category}-{description}`, e.g. `aws-ec2-privesc-passrole-iam`.
- **Constant**: `UPPER_SNAKE_CASE` `{PROVIDER}_{CATEGORY}_{DESCRIPTION}`, e.g. `AWS_EC2_PRIVESC_PASSROLE_IAM`.

## Creating a New Query

New queries come from one of two input sources: a [pathfinding.cloud](https://github.com/DataDog/pathfinding.cloud) research ID (for example `ECS-001`, `GLUE-001`) or a natural-language description from the requester. The aggregated `paths.json` is too large to fetch whole; query a single path by ID:

```bash
# Fetch a single path by ID
curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \
  | jq '.[] | select(.id == "ecs-002")'

# List all path IDs and names
curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \
  | jq -r '.[] | "\(.id): \(.name)"'
```

Then follow these steps:

1. **Read the queries module first** to match the existing style:

   ```text
   api/src/backend/api/attack_paths/queries/
   ├── __init__.py
   ├── types.py         # dataclass definitions
   ├── registry.py
   └── {provider}.py
   ```

2. **Fetch the Cartography schema for the pinned version.** Do not guess labels, properties, or relationships. See [The Graph Model](#the-graph-model).

3. **Build the query** from the canonical template plus the appropriate sub-pattern (privilege escalation or network exposure). Pre-aggregate resource lists, traverse `HAS_*` edges for list-typed properties, and keep the `RETURN` contract.

4. **Register** the constant in the `{PROVIDER}_QUERIES` list at the bottom of the provider file.

5. **Verify compatibility** against the [openCypher Compatibility](#opencypher-compatibility) rules, and confirm the query parses and runs on Neo4j before it reaches Neptune.

<Note>
AI assistants connected through Prowler MCP Server can fetch the exact Cartography schema for the active scan with the `prowler_get_attack_paths_cartography_schema` tool, which guarantees that generated queries match the schema version pinned by the running Prowler release.
</Note>

## Reference

- **pathfinding.cloud**: [github.com/DataDog/pathfinding.cloud](https://github.com/DataDog/pathfinding.cloud) (use `curl | jq`; the aggregated `paths.json` is too large for a single fetch).
- **Cartography AWS schema**: [cartography-cncf.github.io/cartography/modules/aws/schema.html](https://cartography-cncf.github.io/cartography/modules/aws/schema.html).
- **Neptune openCypher compliance**: [docs.aws.amazon.com/neptune/latest/userguide/feature-opencypher-compliance.html](https://docs.aws.amazon.com/neptune/latest/userguide/feature-opencypher-compliance.html).
- **Neptune openCypher rewrites**: [docs.aws.amazon.com/neptune/latest/userguide/migration-opencypher-rewrites.html](https://docs.aws.amazon.com/neptune/latest/userguide/migration-opencypher-rewrites.html).
- **openCypher specification**: [github.com/opencypher/openCypher](https://github.com/opencypher/openCypher).

---

### Developer Guide Aws Details (`docs/developer-guide/aws-details.mdx`)

---
title: 'AWS Provider'
---

In this page you can find all the details about [Amazon Web Services (AWS)](https://aws.amazon.com/) provider implementation in Prowler.

By default, Prowler will audit just one account and organization settings per scan. To configure it, follow the [AWS getting started guide](/user-guide/providers/aws/getting-started-aws).

## AWS Provider Classes Architecture

The AWS provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the AWS-specific implementation, highlighting how the generic provider concepts are realized for AWS in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider). In next subsection you can find a list of the main classes of the AWS provider.

### `AwsProvider` (Main Class)

- **Location:** [`prowler/providers/aws/aws_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/aws_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for AWS-specific logic, session management, credential validation, role assumption, region and organization discovery, and configuration.
- **Key AWS Responsibilities:**
    - Initializes and manages AWS sessions (with or without role assumption, MFA, etc.).
    - Validates credentials and sets up the AWS identity context.
    - Loads and manages configuration, mutelist, and fixer settings.
    - Discovers enabled AWS regions and organization metadata.
    - Provides properties and methods for downstream AWS service classes to access session, identity, and configuration data.

### Data Models

- **Location:** [`prowler/providers/aws/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/models.py)
- **Purpose:** Define structured data for AWS identity, session, credentials, organization info, and more.
- **Key AWS Models:**
    - `AWSOrganizationsInfo`: Holds AWS Organizations metadata, to be used by the checks.
    - `AWSCredentials`, `AWSAssumeRoleInfo`, `AWSAssumeRoleConfiguration`: Used for role assumption and session management.
    - `AWSIdentityInfo`: Stores account, user, partition, and region context for the scan.
    - `AWSSession`: Wraps the current and original [boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) sessions and config.

### `AWSService` (Service Base Class)

- **Location:** [`prowler/providers/aws/lib/service/service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/lib/service/service.py)
- **Purpose:** Abstract base class that all AWS service-specific classes inherit from. This implements the generic service pattern (described in [service page](/developer-guide/services#service-base-class)) specifically for AWS.
- **Key AWS Responsibilities:**
    - Receives an `AwsProvider` instance to access session, identity, and configuration.
    - Manages clients for all services by regions.
    - Provides `__threading_call__` method to make boto3 calls in parallel. By default, these calls are made by region, but it can be overridden with the first parameter of the method and use by resource.
    - Exposes common audit context (`audited_account`, `audited_account_arn`, `audited_partition`, `audited_resources`) to subclasses.

### Exception Handling

- **Location:** [`prowler/providers/aws/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for AWS-specific error handling, such as credential and role errors.

### Session and Utility Helpers

- **Location:** [`prowler/providers/aws/lib/`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/lib/)
- **Purpose:** Helpers for session setup, ARN parsing, mutelist management, and other cross-cutting concerns.

## Specific Patterns in AWS Services

The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the right now implemented services in the following locations:

- Directly in the code, in location [`prowler/providers/aws/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/aws/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used across all AWS services.

### AWS Service Common Patterns

- Services communicate with AWS using boto3, you can find the documentation with all the services [here](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/index.html).
- Every AWS service class inherits from `AWSService`, ensuring access to session, identity, configuration, and threading utilities.
- The constructor (`__init__`) always calls `super().__init__` with the service name and provider (e.g. `super().__init__(__class__.__name__, provider))`). Ensure that the service name in boto3 is the same that you use in the constructor. Usually is used the `__class__.__name__` to get the service name because it is the same as the class name.
- Resource containers **must** be initialized in the constructor. They should be dictionaries, with the key being the resource ARN or equivalent unique identifier and the value being the resource object.
- Resource discovery and attribute collection are parallelized using `self.__threading_call__`, typically by region or resource, for performance. The first parameter of the method is the iterator, if not provided, it will be the region; but if present indicate an array of the resources to be processed.
- Resource filtering is consistently enforced using `self.audit_resources` attribute and `is_resource_filtered` function, it is used to see if user has provided some resource that is not in the audit scope, so we can skip it in the service logic. Normally it is used before storing the resource in the service container as follows: `if not self.audit_resources or (is_resource_filtered(resource["arn"], self.audit_resources)):`.
- All AWS resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
- AWS API calls are wrapped in try/except blocks, with specific handling for `ClientError` and generic exceptions, always logging errors.
- If ARN is not present for some resource, it can be constructed using string interpolation, always including partition, service, region, account, and resource ID.
- Tags and additional attributes that cannot be retrieved from the default call, should be collected and stored for each resource using dedicated methods and threading using the resource object list as iterator.
- When accessing dictionary values from AWS API responses, always use `.get()` with a default value instead of direct dictionary access (e.g., `response.get("Policies", {})` instead of `response["Policies"]`). AWS API responses may not always include all keys, and direct access can cause `KeyError` exceptions that break the entire scan for that service.

### Extending an Existing Service with New Attributes

When adding a new check that requires data not yet collected by an existing service, you need to extend the service by adding new attributes to its resource models and updating the data collection methods. This is a common contributor task that follows a consistent pattern:

1. **Identify the missing data**: Determine which AWS API call provides the data you need and whether it's already being called by the service.

2. **Add new attributes to the resource model**: Extend the Pydantic `BaseModel` class for the resource with the new fields. Use `Optional` types with `None` as the default value to maintain backward compatibility with existing checks.

3. **Update the data collection method**: Modify the existing method that fetches resource details to also extract and store the new attributes. If no existing method fetches the data, add a new method and call it in the constructor using `self.__threading_call__` if possible.

4. **Use safe dictionary access**: When extracting values from API responses, always use `.get()` with appropriate defaults to prevent `KeyError` exceptions when the API doesn't return certain fields.

#### Example: Adding DKIM Status to SES Identities

```python
# Step 1 & 2: Add new fields to the resource model
class Identity(BaseModel):
    name: str
    arn: str
    region: str
    type: Optional[str]
    policy: Optional[dict] = None
    tags: Optional[list] = []
    # New attributes for DKIM check
    dkim_status: Optional[str] = None
    dkim_signing_attributes_origin: Optional[str] = None

# Step 3: Update the data collection method
def _get_email_identities(self, identity):
    try:
        regional_client = self.regional_clients[identity.region]
        identity_attributes = regional_client.get_email_identity(
            EmailIdentity=identity.name
        )
        # Step 4: Use .get() for safe dictionary access
        for content_key, content_value in identity_attributes.get("Policies", {}).items():
            identity.policy = loads(content)
        identity.tags = identity_attributes.get("Tags", [])
        # Extract new DKIM attributes
        identity.dkim_status = identity_attributes.get("DkimStatus")
        identity.dkim_signing_attributes_origin = (
            identity_attributes.get("DkimSigningAttributesOrigin")
        )
    except Exception as error:
        logger.error(
            f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
        )
```

5. **Update the service tests**: Add the new attributes to the test mock data and assertions to verify correct data extraction.

## Specific Patterns in AWS Checks

The AWS checks pattern is described in [checks page](/developer-guide/checks). You can find all the right now implemented checks:

- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/aws/services/s3/s3_bucket_acl_prohibited/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/aws/services/s3/s3_bucket_acl_prohibited))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new check is following the [check creation documentation](/developer-guide/checks#creating-a-check) and taking other similar checks as reference.

### Check Report Class

The `Check_Report_AWS` class models a single finding for an AWS resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.

#### Purpose

`Check_Report_AWS` extends the base report structure with AWS-specific fields, enabling detailed tracking of the resource, ARN, and region associated with each finding.

#### Constructor and Attribute Population

When you instantiate `Check_Report_AWS`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its AWS-specific attributes from the resource, using the following logic (in order of precedence):

- **`resource_id`**:
    - Uses `resource.id` if present.
    - Otherwise, uses `resource.name` if present.
    - Defaults to an empty string if none are available.

- **`resource_arn`**:
    - Uses `resource.arn` if present.
    - Defaults to an empty string if ARN is not present in the resource object.

- **`region`**:
    - Uses `resource.region` if present.
    - Defaults to an empty string if region is not present in the resource object.

If the resource object does not contain the required attributes, you must set them manually in the check logic.

Other attributes are inherited from the `Check_Report` class, from those you **always** have to set the `status` and `status_extended` attributes in the check logic.

#### Example Usage

```python
report = Check_Report_AWS(
    metadata=check_metadata,
    resource=resource_object
)
report.status = "PASS"
report.status_extended = "Resource is compliant."
```

---

### Developer Guide Azure Details (`docs/developer-guide/azure-details.mdx`)

---
title: 'Azure Provider'
---

In this page you can find all the details about [Microsoft Azure](https://azure.microsoft.com/) provider implementation in Prowler.

By default, Prowler will audit all the subscriptions that it is able to list in the Microsoft Entra tenant, and tenant Entra ID service. To configure it, follow the [Azure getting started guide](/user-guide/providers/azure/getting-started-azure).

## Azure Provider Classes Architecture

The Azure provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the Azure-specific implementation, highlighting how the generic provider concepts are realized for Azure in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider). In next subsection you can find a list of the main classes of the Azure provider.

### `AzureProvider` (Main Class)

- **Location:** [`prowler/providers/azure/azure_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/azure_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for Azure-specific logic, session management, credential validation, and configuration.
- **Key Azure Responsibilities:**
    - Initializes and manages Azure sessions (supports Service Principal, CLI, Browser, and Managed Identity authentication).
    - Validates credentials and sets up the Azure identity context.
    - Loads and manages configuration, mutelist, and fixer settings.
    - Retrieves subscription(s) metadata.
    - Provides properties and methods for downstream Azure service classes to access session, identity, and configuration data.

### Data Models

- **Location:** [`prowler/providers/azure/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/models.py)
- **Purpose:** Define structured data for Azure identity, session, region configuration, and subscription info.
- **Key Azure Models:**
    - `AzureIdentityInfo`: Holds Azure identity metadata, including tenant ID, domain, subscription names and IDs, and locations.
    - `AzureRegionConfig`: Stores the specific region that will be audited. That can be: Global, US Government or China.
    - `AzureSubscription`: Represents a subscription with ID, display name, and state.

### `AzureService` (Service Base Class)

- **Location:** [`prowler/providers/azure/lib/service/service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/lib/service/service.py)
- **Purpose:** Abstract base class that all Azure service-specific classes inherit from. This implements the generic service pattern (described in [service page](/developer-guide/services#service-base-class)) specifically for Azure.
- **Key Azure Responsibilities:**
    - Receives an `AzureProvider` instance to access session, identity, and configuration.
    - Manages clients for all services by subscription.
    - Exposes common audit context (`subscriptions`, `locations`, `audit_config`, `fixer_config`) to subclasses.

### Exception Handling

- **Location:** [`prowler/providers/azure/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for Azure-specific error handling, such as credential, region, and session errors.

### Session and Utility Helpers

- **Location:** [`prowler/providers/azure/lib/`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/lib/)
- **Purpose:** Helpers for argument parsing, region setup, mutelist management, and other cross-cutting concerns.

## Specific Patterns in Azure Services

The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:

- Directly in the code, in location [`prowler/providers/azure/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/azure/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used across all Azure services.

### Azure Service Common Patterns

- Services communicate with Azure using the Azure Python SDK, mainly using the Azure Management Client (except for the Microsoft Entra ID service, that is using the Microsoft Graph API), you can find the documentation with all the management services [here](https://learn.microsoft.com/en-us/python/api/overview/azure/?view=azure-python).
- Every Azure service class inherits from `AzureService`, ensuring access to session, identity, configuration, and client utilities.
- The constructor (`__init__`) always calls `super().__init__` with the service Azure Management Client and Prowler provider object (e.g `super().__init__(WebSiteManagementClient, provider)`).
- Resource containers **must** be initialized in the constructor, and they should be dictionaries, with the key being the subscription ID, the value being a dictionary with the resource ID as key and the resource object as value.
- All Azure resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes. Some are represented as dataclasses due to legacy reasons, but new resources should be represented as Pydantic `BaseModel` classes.
- Azure SDK functions are wrapped in try/except blocks, with specific handling for errors, always logging errors. It is a best practice to create a custom function for every Azure SDK call, in that way we can handle the errors in a more specific way.

## Specific Patterns in Azure Checks

The Azure checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks:

- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/azure/services/storage/storage_blob_public_access_level_is_disabled/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/azure/services/storage/storage_blob_public_access_level_is_disabled))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new check is the [Azure check implementation documentation](/developer-guide/checks#creating-a-check) and taking other similar checks as reference.

### Check Report Class

The `Check_Report_Azure` class models a single finding for an Azure resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.

#### Purpose

`Check_Report_Azure` extends the base report structure with Azure-specific fields, enabling detailed tracking of the resource, resource ID, name, subscription, and location associated with each finding.

#### Constructor and Attribute Population

When you instantiate `Check_Report_Azure`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its Azure-specific attributes from the resource, using the following logic (in order of precedence):

- **`resource_id`**:
    - Uses `resource.id` if present.
    - Otherwise, uses `resource.resource_id` if present.
    - Defaults to an empty string if not available.

- **`resource_name`**:
    - Uses `resource.name` if present.
    - Otherwise, uses `resource.resource_name` if present.
    - Defaults to an empty string if not available.

- **`subscription`**:
    - Defaults to an empty string, it **must** be set in the check logic.

- **`location`**:
    - Uses `resource.location` if present.
    - Defaults to an empty string if not available.

If the resource object does not contain the required attributes, you must set them manually in the check logic.

Other attributes are inherited from the `Check_Report` class, from which you **always** have to set the `status` and `status_extended` attributes in the check logic.

#### Example Usage

```python
report = Check_Report_Azure(
    metadata=check_metadata,
    resource=resource_object
)
report.subscription = subscription_id
report.status = "PASS"
report.status_extended = "Resource is compliant."
```

---

### Developer Guide Check Metadata Guidelines (`docs/developer-guide/check-metadata-guidelines.mdx`)

---
title: 'Check Metadata Guidelines'
---

## Introduction

This guide provides comprehensive guidelines for creating check metadata in Prowler. For basic information on check metadata structure, refer to the [check metadata](/developer-guide/checks#metadata-structure-for-prowler-checks) section.

## Check Title Guidelines

### Writing Guidelines

1. **Determine Resource Finding Scope (Singular vs. Plural)**:
   When determining whether to use singular or plural in the check title, examine the code for certain patterns. If the code contains a loop that generates an individual report for each resource, use the singular form. If the code produces a single report that covers all resources collectively, use the plural form. For organization- or account-wide checks, select the scope that best matches the breadth of the evaluation. Additionally, review the `status_extended` field messages in the code, as they often provide clues about whether the check is scoped to individual resources or to groups of resources.
   Analyze the detection code to determine if the check reports on individual resources or aggregated resources:
    - **Singular**: Use when the check creates one report per resource (e.g., "EC2 instance has IMDSv2 enforced", "S3 bucket does not allow public write access").
    - **Plural**: Use when the check creates one report for all resources together (e.g., "All EC2 instances have IMDSv2 enforced", "S3 buckets do not allow public write access").
2. **Describe the Compliant (*PASS*) State**:
   Always write the title to describe the **desired, compliant state** of the resources. The title should reflect what it looks like when the audited resource is following the check's requirements.
3. **Be Specific and Factual**:
   Include the exact secure configuration being verified. Avoid vague or generic terms like "properly configured".
4. **Avoid Redundant or Action Words**:
   Do not include verbs like "Check", "Verify", "Ensure", or "Monitor". The title is a declarative statement of the secure condition.
5. **Length Limit**:
   Keep the title under 150 characters.

### Common Mistakes to Avoid

- Starting with verbs like "Check", "Verify", "Ensure", "Make sure". Always start with the affected resource instead.
- Being too vague or generic (e.g., "Ensure security groups are properly configured", what does it mean? "properly configured" is not a clear description of the compliant state).
- Focusing on the non-compliant state instead of the compliant state.
- Using unclear scope and resource identification.

## Check Type Guidelines (AWS Only)

### AWS Security Hub Type Format

AWS Security Hub uses a three-part type taxonomy:

- **Namespace**: The top-level security domain.
- **Category**: The security control family or area.
- **Classifier**: The specific security concern (optional).

A partial path may be defined (e.g., `TTPs` or `TTPs/Defense Evasion` are valid).

### Selection Guidelines

1. **Be Specific**: Use the most specific classifier that accurately describes the check.
2. **Standard Compliance**: Consider if the check relates to specific compliance standards.
3. **Multiple Types**: You can specify multiple types if the check addresses multiple concerns.

## Description Guidelines

### Writing Guidelines

1. **Focus on the Finding**: All fields should address how the finding affects the security posture, rather than the control itself.
2. **Use Natural Language**: Write in simple, clear paragraphs with complete, grammatically correct sentences.
3. **Use Markdown Formatting**: Enhance readability with:
   - Use **bold** for emphasis on key security concepts.
   - Use *italic* for a secondary emphasis. Use it for clarifications, conditions, or optional notes. But don't abuse it.
   - Use `code` formatting for specific configuration values, or technical details. Don't use it for service names or common technical terms.
   - Use one or two line breaks (`\n` or `\n\n`) to separate distinct ideas.
   - Use bullet points (`-`) for listing multiple concepts or actions.
   - Use numbers for listing steps or sequential actions.
4. **Be Concise**: Maximum 400 characters (spaces count). Every word should add value.
5. **Explain What the Finding Means**: Focus on what the security control evaluates and what it means when it passes or fails, but without explicitly stating the pass or fail state.
6. **Be Technical but Clear**: Use appropriate technical terminology while remaining understandable.
7. **Avoid Risk Descriptions**: Do not describe potential risks, threats, or consequences.
8. **CheckTitle and Description can be the same**: If the check is very simple and the title is already clear, you can use the same text for the description.

### Common Mistakes to Avoid

- **Technical Implementation Details**: "The control loops through all instances and calls the describe_instances API...".
- **Vague Descriptions**: "This control verifies proper configuration of resources". What does it mean? "proper configuration" is not a clear description of the compliant state.
- **Risk Descriptions**: "This could lead to data breaches" or "This poses a security threat".
- **Starting with Verbs**: "Check if...", "Verify...", "Ensure...". Always start with the affected resource instead.
- **References to Pass/Fail States**: Avoid using words like "pass" or "fail".

## Risk Guidelines

### Writing Guidelines

1. **Explain the Cybersecurity Impact**: Focus on how the finding affects confidentiality, integrity, or availability (CIA triad). If the CIA triad does not apply, explain the risk in terms of the organization's business objectives.
2. **Be Specific About Threats**: Clearly state what could happen if this security control is not in place. What attacks or incidents become possible?
3. **Focus on Risk Context**: Explain the specific security implications of the finding, not just generic security risks.
4. **Use Markdown Formatting**: Enhance readability with markdown formatting:
   - Use **bold** for emphasis on key security concepts.
   - Use *italic* for a secondary emphasis. Use it for clarifications, conditions, or optional notes. But don't abuse it.
   - Use `code` formatting for specific configuration values, or technical details. Don't use it for service names or common technical terms.
   - Use one or two line breaks (`\n` or `\n\n`) to separate distinct ideas.
   - Use bullet points (`-`) for listing multiple concepts or actions.
   - Use numbers for listing steps or sequential actions.
5. **Be Concise**: Maximum 400 characters. Make every word count.

### Common Mistakes to Avoid

- **Generic Risks**: "This could lead to security issues" or "Regulatory compliance violations".
- **Technical Implementation Focus**: "The API call might fail and return incorrect results...".
- **Overly Broad Statements**: "This is a serious security risk that could impact everything".
- **Vague Threats**: "This could be exploited by threat actors" without explaining how.

## Recommendation Guidelines

### Writing Guidelines

1. **Provide Actionable Best Practice Guidance**: Explain what should be done to maintain security posture. Focus on preventive measures and proactive security practices.
2. **Be Principle-Based**: Reference established security principles (least privilege, defense in depth, zero trust, separation of duties) where applicable.
3. **Focus on Prevention**: Explain best practices that prevent the security issue from occurring, not just detection or remediation.
4. **Use Markdown Formatting**: Enhance readability with markdown formatting:
   - Use **bold** for emphasis on key security concepts.
   - Use *italic* for a secondary emphasis. Use it for clarifications, conditions, or optional notes. But don't abuse it.
   - Use `code` formatting for specific configuration values, or technical details. Don't use it for service names or common technical terms.
   - Use one or two line breaks (`\n` or `\n\n`) to separate distinct ideas.
   - Use bullet points (`-`) for listing multiple concepts or actions.
   - Use numbers for listing steps or sequential actions.
5. **Be Concise**: Maximum 400 characters.

### Common Mistakes to Avoid

- **Specific Remediation Steps**: "1. Go to the console\n2. Click on settings..." - Focus on principles, not click-by-click instructions.
- **Implementation Details**: "Configure the JSON policy with the following IAM actions..." - Explain what to achieve, not how.
- **Vague Guidance**: "Follow security best practices..." without explaining what those practices are.
- **Resource-Specific Recommendations**: "Enable MFA on user john.doe@example.com" - Keep it general.
- **Missing Context**: Not explaining why the best practice is important for security.

### Good Examples

- *"Avoid exposing sensitive resources directly to the Internet; configure access controls to limit exposure."*
- *"Apply the principle of least privilege when assigning permissions to users and services."*
- *"Regularly review and update your security configurations to align with current best practices."*

## Remediation Code Guidelines

### Critical Requirement

The **fundamental principle** is to focus on the **specific change** that converts the finding from non-compliant to compliant.

Also is important to keep all code examples as short as possible, including the essential code to fix the issue. Remove any extra configuration, optional parameters, or nice-to-have settings and add comments to explain the code when possible.

### Common Guidelines for All Code Fields

1. **Be Minimal**: Keep code blocks as short as possible - only include what is absolutely necessary.
2. **Focus on the Fix**: Remove any extra configuration, optional parameters, or nice-to-have settings.
3. **Be Accurate**: Ensure all commands and code are syntactically correct.
4. **Use Markdown Formatting**: Format code properly using code blocks and appropriate syntax highlighting.
5. **Follow Best Practices**: Use the most secure and recommended approaches for each platform.

### CLI Guidelines

- Only provide a single command that directly changes the finding from fail to pass.
- The command must be executable as-is and resolve the security issue completely.
- Use proper command syntax for the provider (AWS CLI, Azure CLI, gcloud, kubectl, etc.).
- Do not use markdown formatting or code blocks - just the raw command.
- Do not include multiple commands, comments, or explanations.
- If the issue cannot be resolved with a single command, leave this field empty.

### Native IaC Guidelines

- **Keep It Minimal**: Only include the specific resource/configuration that fixes the security issue.
- Format as markdown code blocks with proper syntax highlighting.
- Include only the required properties to fix the issue.
- Add comments indicating the critical line(s) that remediate the check.
- Use `example_resource` as the generic name for all resources and IDs.

### Terraform Guidelines

- **Keep It Minimal**: Only include the specific resource/configuration that fixes the security issue.
- Provide valid HCL (HashiCorp Configuration Language) code with an example of a compliant configuration.
- Use the latest Terraform syntax and provider versions.
- Include only the required arguments to fix the issue - skip optional parameters.
- Format as markdown code blocks with `hcl` syntax highlighting.
- Add comments indicating the critical line(s) that remediate the check.
- Use `example_resource` as the generic name for all resources and IDs.
- Skip provider requirements unless critical for the fix.

### Other (Manual Steps) Guidelines

- **Keep It Minimal**: Only include the exact steps needed to fix the security issue.
- Provide step-by-step instructions for manual remediation through web interfaces.
- Use numbered lists for sequential steps.
- Be specific about menu locations, button names, and settings.
- Skip optional configurations or nice-to-have settings.
- Format using markdown for better readability.

## Categories Guidelines

### Selection Guidelines

1. **Be Specific**: Only select categories that directly relate to what the automated control evaluates.
2. **Primary Focus**: Consider the primary security concern the automated control addresses.
3. **Avoid Over-Categorization**: Do not select categories just because they are tangentially related.

### Available Categories

| Category                | Definition                                                                                                                                                                                                                                 |
|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| encryption              | Ensures data is encrypted in transit and/or at rest, including key management practices                                                                                                   |
| internet-exposed        | Checks that limit or flag public access to services, APIs, or assets from the Internet                                                                                                              |
| logging                 | Ensures appropriate logging of events, activities, and system interactions for traceability                                                                                                       |
| secrets                 | Manages and protects credentials, API keys, tokens, and other sensitive information                                                                                                               |
| resilience              | Ensures systems can maintain availability and recover from disruptions, failures, or degradation. Includes redundancy, fault-tolerance, auto-scaling, backup, disaster recovery, and failover strategies |
| threat-detection        | Identifies suspicious activity or behaviors using IDS, malware scanning, or anomaly detection                                                                                                      |
| trust-boundaries        | Enforces isolation or segmentation between different trust levels (e.g., VPCs, tenants, network zones)                                                                                            |
| vulnerabilities         | Detects or remediates known software, infrastructure, or config vulnerabilities (e.g., CVEs)                                                                                                      |
| cluster-security        | Secures Kubernetes cluster components such as API server, etcd, and role-based access                                                                                                             |
| container-security      | Ensures container images and runtimes follow security best practices                                                                                        |
| node-security           | Secures nodes running containers or services                                                                                                        |
| gen-ai                  | Checks related to safe and secure use of generative AI services or models                                                                                                                        |
| ci-cd                   | Ensures secure configurations in CI/CD pipelines                                                                                                         |
| identity-access         | Governs user and service identities, including least privilege, MFA, and permission boundaries                                                                                                    |
| email-security          | Ensures detection and protection against phishing, spam, spoofing, etc.                                                                                                                            |
| forensics-ready         | Ensures systems are instrumented to support post-incident investigations. Any digital trace or evidence (logs, volume snapshots, memory dumps, network captures, etc.) preserved immutably and accompanied by integrity guarantees, which can be used in a forensic analysis |
| software-supply-chain   | Detects or prevents tampering, unauthorized packages, or third-party risks in software supply chain                                                                                               |
| e3                      | M365 and Azure Entra checks enabled by or dependent on an E3 license (e.g., baseline security policies, conditional access)                                                                            |
| e5                      | M365 and Azure Entra checks enabled by or dependent on an E5 license (e.g., advanced threat protection, audit, DLP, and eDiscovery)                                                                    |
| privilege-escalation    | Detects IAM policies or permissions that allow identities to elevate their privileges beyond their intended scope, potentially gaining administrator or higher-level access through specific action combinations |
| ec2-imdsv1              | Identifies EC2 instances using Instance Metadata Service version 1 (IMDSv1), which is vulnerable to SSRF attacks and should be replaced with IMDSv2 for enhanced security                        |
| vercel-hobby-plan       | Vercel checks whose audited feature is available on the Hobby plan (and therefore also on Pro and Enterprise plans)                                                                                   |
| vercel-pro-plan         | Vercel checks whose audited feature requires a Pro plan or higher, including features also available on Enterprise or via supported paid add-ons for Pro plans                                        |
| vercel-enterprise-plan  | Vercel checks whose audited feature requires the Enterprise plan                                                                                                                                    |

---

### Developer Guide Checks (`docs/developer-guide/checks.mdx`)

---
title: 'Prowler Checks'
---

This guide explains how to create new checks in Prowler.

## Introduction

Checks are the core component of Prowler. A check is a piece of code designed to validate whether a configuration aligns with cybersecurity best practices. Execution of a check yields a finding, which includes the result and contextual metadata (e.g., outcome, risks, remediation).

### Creating a Check

The most common high level steps to create a new check are:

1. Prerequisites:
    - Verify the check does not already exist by searching [Prowler Hub](https://hub.prowler.com) or checking `prowler/providers/<provider>/services/<service>/<check_name_want_to_implement>/`.
    - Ensure required provider and service exist. If not, follow the [Provider](/developer-guide/provider) and [Service](/developer-guide/services) documentation to create them.
    - Confirm the service has implemented all required methods and attributes for the check (in most cases, you will need to add or modify some methods in the service to get the data you need for the check).
2. Navigate to the service directory. The path should be as follows: `prowler/providers/<provider>/services/<service>`.
3. Create a check-specific folder. The path should follow this pattern: `prowler/providers/<provider>/services/<service>/<check_name_want_to_implement>`. Adhere to the [Naming Format for Checks](#naming-format-for-checks).
4. Populate the folder with files as specified in [File Creation](#file-creation).
5. Run the check locally to ensure it works as expected. For checking you can use the CLI in the next way:
    - To ensure the check has been detected by Prowler: `uv run python prowler-cli.py <provider> --list-checks | grep <check_name>`.
    - To run the check, to find possible issues: `uv run python prowler-cli.py <provider> --log-level ERROR --verbose --check <check_name>`.
6. Create comprehensive tests for the check that cover multiple scenarios including both PASS (compliant) and FAIL (non-compliant) cases. For detailed information about test structure and implementation guidelines, refer to the [Testing](/developer-guide/unit-testing) documentation.
7. If the check and its corresponding tests are working as expected, you can submit a PR to Prowler.

### Naming Format for Checks

If you already know the check name when creating a request or implementing a check, use a descriptive identifier with lowercase letters and underscores only.

Recommended patterns:

- `<service>_<resource>_<best_practice>`

The name components are:

- `service` – The main service or product area being audited (e.g., ec2, entra, iam, bedrock).
- `resource` – The resource, feature, or configuration being evaluated. It can be a single word or a compound phrase joined with underscores (e.g., instance, policy, guardrail, sensitive_information_filter).
- `best_practice` – The expected secure state or best practice being checked (e.g., enabled, encrypted, restricted, configured, not_publicly_accessible).

Additional guidance:

- Use underscores only. Do not use hyphens.
- Keep the name specific enough to describe the behavior of the check.
- The first segment should match the service or product area whenever possible.

Examples:

- `s3_bucket_versioning_enabled`
- `bedrock_guardrail_sensitive_information_filter_enabled`

### File Creation

Each check in Prowler follows a straightforward structure. Within the newly created folder, three files must be added to implement the check logic:

- `__init__.py` (empty file) – Ensures Python treats the check folder as a package.
- `<check_name>.py` (code file) – Contains the check logic, following the prescribed format. Please refer to the [prowler's check code structure](/developer-guide/checks#prowlers-check-code-structure) for more information.
- `<check_name>.metadata.json` (metadata file) – Defines the check's metadata for contextual information. Please refer to the [check metadata](/developer-guide/checks#metadata-structure-for-prowler-checks) for more information.

## Prowler's Check Code Structure

Prowler's check structure is designed for clarity and maintainability. It follows a dynamic loading approach based on predefined paths, ensuring seamless integration of new checks into a provider's service without additional manual steps.

Below the code for a generic check is presented. It is strongly recommended to consult other checks from the same provider and service to understand provider-specific details and patterns. This will help ensure consistency and proper implementation of provider-specific requirements.

Report fields are the most dependent on the provider, consult the `CheckReport<Provider>` class for more information on what can be included in the report [here](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py).

<Note>
Legacy providers (AWS, Azure, GCP, Kubernetes) follow the `Check_Report_<Provider>` naming convention. This is not recommended for current instances. Newer providers adopt the `CheckReport<Provider>` naming convention. Learn more at [Prowler Code](https://github.com/prowler-cloud/prowler/tree/master/prowler/lib/check/models.py).

</Note>
```
/* Detailed source-code truncated for AI context efficiency. */
```

### Data Requirements for Checks in Prowler

One of the most important aspects when creating a new check is ensuring that all required data is available from the service client. Often, default API calls are insufficient. Extending the service class with new methods or resource attributes may be required to fetch and store requisite data.

### Statuses for Checks in Prowler

Required Fields: status and status\_extended

Each check **must** populate the `report.status` and `report.status_extended` fields according to the following criteria:

- Status field: `report.status`
    - `PASS` – Assigned when the check confirms compliance with the configured value.
    - `FAIL` – Assigned when the check detects non-compliance with the configured value.
    - `MANUAL` – This status must not be used unless manual verification is necessary to determine whether the status (`report.status`) passes (`PASS`) or fails (`FAIL`). This includes the case where Prowler could not retrieve the data needed to evaluate the resource (see below).

- Status extended field: `report.status_extended`
    - It **must** end with a period (`.`).
    - It **must** include the audited service, the resource, and a concise explanation of the check result, for instance: `EC2 AMI ami-0123456789 is not public.`.

### Permission and Data-Availability Errors Are Not Findings

A `FAIL` must only be emitted when an insecure condition has actually been detected. A check **must never** report `FAIL` because the underlying API call failed: missing permissions or scopes on the scanning identity, an API that is not enabled, a feature that is not licensed, or data that could not be retrieved are scan-configuration problems, not security issues. Reporting them as `FAIL` surfaces a misleading (and often high-severity) finding to the user and skews compliance scores.

When the service layer cannot obtain the data a check depends on, the check must:

1. Emit a single `MANUAL` finding scoped to the widest affected resource (the tenant, account, project or subscription), not one finding per resource. For example, if user registration details cannot be read, emit one tenant-level `MANUAL` instead of one per user.
2. Explain in `status_extended` that the check could not be evaluated and what to fix, naming the permission, scope, API or license required, for instance: `Cannot evaluate credential exposure for privileged users: unable to query Microsoft Defender XDR Advanced Hunting. Verify that the ThreatHunting.Read.All permission is granted to the scanning application.`
3. Leave the check's severity untouched. Do not override `report.check_metadata.Severity` to hide the problem.

The service layer must make the distinction possible: log the error and expose it to checks in a way that cannot be confused with a legitimate empty result. Common patterns already used in Prowler are:

- Defaulting the attribute to `None` (data could not be read) instead of `[]`/`{}` (data was read and is empty), e.g. the `metric_filters is not None` guard in `prowler/providers/aws/services/cloudwatch/lib/metric_filters.py`.
- Keeping an availability flag raised on any denied listing, e.g. `logs_client.metric_filters_unavailable` consumed by the AWS CloudWatch metric filter checks.
- Keeping an error flag or message next to the data, e.g. `entra_client.user_registration_details_error` in M365 or `*_scan_errors` in AWS Bedrock.
- Keeping a set of resources whose lookup failed, e.g. `accessapproval_client.settings_lookup_failed` in GCP.

Make sure the error branch only captures real access errors. A `404`/not-found response frequently means the feature is simply not configured, which **is** a legitimate `FAIL`; a `403` or an unexpected exception is not. An "API not enabled" error is usually a scan-configuration problem too — **except** when the API's activation is itself the control being audited (e.g. GCP Access Approval: with `accessapproval.googleapis.com` disabled the feature provably cannot be enabled, so a definitive API-disabled state is a legitimate `FAIL`, while an undetermined state stays `MANUAL`).

```python
if <service>_client.<data> is None:
    report = CheckReport<Provider>(metadata=self.metadata(), resource={})
    report.resource_name = "<Tenant/Account-level resource>"
    report.resource_id = "<stable-id>"
    report.status = "MANUAL"
    report.status_extended = "Cannot evaluate <requirement>: <data> could not be retrieved. Verify that <permission/API/license> is granted to the scanning identity."
    findings.append(report)
    return findings
```

### Prowler's Check Severity Levels

The severity of each check is defined in the metadata file using the `Severity` field. Severity values are always lowercase and must be one of the predefined categories below.

- `critical` – Highest potential impact with broad exposure that could affect core security boundaries or business operations.
- `high` – Substantial potential impact with significant exposure that could affect important security controls or resources.
- `medium` – Moderate potential impact with limited exposure that weakens defense layers but has contained scope.
- `low` – Minimal potential impact with negligible exposure that represents minor gaps in security posture.
- `informational` – Provides valuable information but does not affect the security posture.

If the check involves multiple scenarios that may alter its severity, adjustments can be made dynamically within the check's logic using the severity `report.check_metadata.Severity` attribute:

```python
if <generic_condition_1>:
    report.status = "PASS"
    report.check_metadata.Severity = "informational"
    report.status_extended = f"<Resource> is compliant with <requirement>."
elif <generic_condition_2>:
    report.status = "FAIL"
    report.check_metadata.Severity = "low"
    report.status_extended = f"<Resource> is not compliant with <requirement>: <reason>."
elif <generic_condition_3>:
    report.status = "FAIL"
    report.check_metadata.Severity = "medium"
    report.status_extended = f"<Resource> is not compliant with <requirement>: <reason>."
elif <generic_condition_4>:
    report.status = "FAIL"
    report.check_metadata.Severity = "high"
    report.status_extended = f"<Resource> is not compliant with <requirement>: <reason>."
else:
    report.status = "FAIL"
    report.check_metadata.Severity = "critical"
    report.status_extended = f"<Resource> is not compliant with <requirement>: <critical reason>."
```

### Resource Identification in Prowler

Each check **must** populate the report with a unique identifier for the audited resource. This identifier or identifiers are going to depend on the provider and the resource that is being audited. Here are the criteria for each provider:

- AWS
    - Amazon Resource ID — `report.resource_id`.
        - The resource identifier. This is the name of the resource, the ID of the resource, or a resource path. Some resource identifiers include a parent resource (sub-resource-type/parent-resource/sub-resource) or a qualifier such as a version (resource-type:resource-name:qualifier).
        - If the resource ID cannot be retrieved directly from the audited resource, it can be extracted from the ARN. It is the last part of the ARN after the last slash (`/`) or colon (`:`).
        - If no actual resource to audit exists, this format can be used: `<resource_type>/unknown`
    - Amazon Resource Name — `report.resource_arn`.
        - The [Amazon Resource Name (ARN)](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html) of the audited entity.
        - If the ARN cannot be retrieved directly from the audited resource, construct a valid ARN using the `resource_id` component as the audited entity. Examples:
            - Bedrock — `arn:<partition>:bedrock:<region>:<account-id>:model-invocation-logging`.
            - DirectConnect — `arn:<partition>:directconnect:<region>:<account-id>:dxcon`.
        - If no actual resource to audit exists, this format can be used: `arn:<partition>:<service>:<region>:<account-id>:<resource_type>/unknown`.
            - Examples:
                - AWS Security Hub — `arn:<partition>:security-hub:<region>:<account-id>:hub/unknown`.
                - Access Analyzer — `arn:<partition>:access-analyzer:<region>:<account-id>:analyzer/unknown`.
                - GuardDuty — `arn:<partition>:guardduty:<region>:<account-id>:detector/unknown`.
- GCP
    - Resource ID — `report.resource_id`.
        - Resource ID represents the full, [unambiguous path to a resource](https://google.aip.dev/122#full-resource-names), known as the full resource name. Typically, it follows the format: `//{api_service/resource_path}`.
        - If the resource ID cannot be retrieved directly from the audited resource, by default the resource name is used.
    - Resource Name — `report.resource_name`.
        - Resource Name usually refers to the name of a resource within its service.
- Azure
    - Resource ID — `report.resource_id`.
        - Resource ID represents the full Azure Resource Manager path to a resource, which follows the format: `/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}`.
    - Resource Name — `report.resource_name`.
        - Resource Name usually refers to the name of a resource within its service.
        - If the [resource name](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules) cannot be retrieved directly from the audited resource, the last part of the resource ID can be used.
- Kubernetes
    - Resource ID — `report.resource_id`.
        - The UID of the Kubernetes object. This is a system-generated string that uniquely identifies the object within the cluster for its entire lifetime. See [Kubernetes Object Names and IDs - UIDs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids).
    - Resource Name — `report.resource_name`.
        - The name of the Kubernetes object. This is a client-provided string that must be unique for the resource type within a namespace (for namespaced resources) or cluster (for cluster-scoped resources). Names typically follow DNS subdomain or label conventions. See [Kubernetes Object Names and IDs - Names](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).
- M365
    - Resource ID — `report.resource_id`.
        - If the audited resource has a globally unique identifier such as a `guid`, use it as the `resource_id`.
        - If no `guid` exists, use another unique and relevant identifier for the resource, such as the tenant domain, the internal policy ID, or a representative string following the format `<resource_type>/<name_or_id>`.
    - Resource Name — `report.resource_name`.
        - Use the visible or descriptive name of the audited resource. If no explicit name is available, use a clear description of the resource or configuration being evaluated.
    - Examples:
        - For an organization:
            - `resource_id`: Organization GUID
            - `resource_name`: Organization name
        - For a policy:
            - `resource_id`: Unique policy ID
            - `resource_name`: Policy display name
        - For global configurations:
            - `resource_id`: Tenant domain or representative string (e.g., "userSettings")
            - `resource_name`: Description of the configuration (e.g., "SharePoint Settings")
- GitHub
    - Resource ID — `report.resource_id`.
        - The ID of the GitHub resource. This is a system-generated integer that uniquely identifies the resource within the GitHub platform.
    - Resource Name — `report.resource_name`.
        - The name of the GitHub resource. In the case of a repository, this is just the repository name. For full repository names use the resource `full_name`.

### Configurable Checks in Prowler

See [Configurable Checks](/developer-guide/configurable-checks) for detailed information on making checks configurable using the `audit_config` object and configuration file.

## Metadata Structure for Prowler Checks

Each Prowler check must include a metadata file named `<check_name>.metadata.json` that must be located in its directory. This file supplies crucial information for execution, reporting, and context.

### Example Metadata File

Below is a generic example of a check metadata file. **Do not include comments in actual JSON files.**

```json
{
  "Provider": "aws",
  "CheckID": "service_resource_security_setting",
  "CheckTitle": "Service resource has security setting enabled",
  "CheckType": [],
  "ServiceName": "service",
  "SubServiceName": "",
  "ResourceIdTemplate": "",
  "Severity": "medium",
  "ResourceType": "Other",
  "ResourceGroup": "security",
  "Description": "This check verifies that the service resource has the required **security setting** enabled to protect against potential vulnerabilities.\n\nIt ensures that the resource follows security best practices and maintains proper access controls. The check evaluates whether the security configuration is properly implemented and active.",
  "Risk": "Without proper security settings, the resource may be vulnerable to:\n\n- **Unauthorized access** - Malicious actors could gain entry\n- **Data breaches** - Sensitive information could be compromised\n- **Security threats** - Various attack vectors could be exploited\n\nThis could result in compliance violations and potential financial or reputational damage.",
  "RelatedUrl": "",
  "AdditionalURLs": ["https://example.com/security-documentation", "https://example.com/best-practices"],
  "Remediation": {
    "Code": {
      "CLI": "provider-cli service enable-security-setting --resource-id resource-123",
      "NativeIaC": "```yaml\nType: Provider::Service::Resource\nProperties:\n  SecuritySetting: enabled\n  ResourceId: resource-123\n```",
      "Other": "1. Open the provider management console\n2. Navigate to the service section\n3. Select the resource\n4. Enable the security setting\n5. Save the configuration",
      "Terraform": "```hcl\nresource \"provider_service_resource\" \"example\" {\n  resource_id      = \"resource-123\"\n  security_setting = true\n}\n```"
    },
    "Recommendation": {
      "Text": "Enable security settings on all service resources to ensure proper protection. Regularly review and update security configurations to align with current best practices.",
      "Url": "https://hub.prowler.com/check/service_resource_security_setting"
    }
  },
  "Categories": ["internet-exposed", "secrets"],
  "DependsOn": [],
  "RelatedTo": ["service_resource_security_setting", "service_resource_security_setting_2"],
  "Notes": "This is a generic example check that should be customized for specific provider and service requirements."
}
```

### Metadata Fields and Their Purpose

#### Provider

The Prowler provider related to the check. The name **must** be lowercase and match the provider folder name. For supported providers refer to [Prowler Hub](https://hub.prowler.com/check) or directly to [Prowler Code](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers).

#### CheckID

The unique identifier for the check inside the provider. This field **must** match the check's folder, Python file, and JSON metadata file name. For more information about naming, refer to the [Naming Format for Checks](#naming-format-for-checks) section.

#### CheckTitle

The `CheckTitle` field must be plain text, clearly and succinctly define **the best practice being evaluated and which resource(s) each finding applies to**. The title should be specific, concise (no more than 150 characters), and reference the relevant resource(s) involved.

**Always write the `CheckTitle` to describe the *PASS* case**, the desired secure or compliant state of the resource(s). This helps ensure that findings are easy to interpret and that the title always reflects the best practice being met.

For detailed guidelines on writing effective check titles, including how to determine singular vs. plural scope and common mistakes to avoid, see [Check Title Guidelines](/developer-guide/check-metadata-guidelines#check-title-guidelines).

#### CheckType

<Warning>
This field is only applicable to the AWS provider.

</Warning>
It follows the [AWS Security Hub Types](https://docs.aws.amazon.com/securityhub/latest/userguide/asff-required-attributes.html#Types) format using the pattern `namespace/category/classifier`.

For the complete AWS Security Hub selection guidelines, see [Check Type Guidelines](/developer-guide/check-metadata-guidelines#check-type-guidelines-aws-only).

#### ServiceName

The name of the provider service being audited. Must be lowercase and match the service folder name. For supported services refer to [Prowler Hub](https://hub.prowler.com/check) or the [Prowler Code](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers).

#### SubServiceName

This field is in the process of being deprecated and should be **left empty**.

#### ResourceIdTemplate

This field is in the process of being deprecated and should be **left empty**.

#### Severity

Severity level if the check fails. Must be one of: `critical`, `high`, `medium`, `low`, or `informational`, and written in lowercase. See [Prowler's Check Severity Levels](#prowlers-check-severity-levels) for details.

#### ResourceType

The type of resource being audited. This field helps categorize and organize findings by resource type for better analysis and reporting. For each provider:

- **AWS**: Use [Security Hub resource types](https://docs.aws.amazon.com/securityhub/latest/userguide/asff-resources.html) or PascalCase CloudFormation types removing the `::` separator used in CloudFormation templates (e.g., in CloudFormation template the type of an EC2 instance is `AWS::EC2::Instance` but in the check it should be `AwsEc2Instance`). Use `Other` if none apply.
- **Azure**: Use types from [Azure Resource Graph](https://learn.microsoft.com/en-us/azure/governance/resource-graph/reference/supported-tables-resources), for example: `Microsoft.Storage/storageAccounts`.
- **Google Cloud**: Use [Cloud Asset Inventory asset types](https://cloud.google.com/asset-inventory/docs/asset-types), for example: `compute.googleapis.com/Instance`.
- **Kubernetes**: Use types shown under `KIND` from `kubectl api-resources`.
- **Oracle Cloud Infrastructure**: Use types from [Oracle Cloud Infrastructure documentation](https://docs.public.oneportal.content.oci.oraclecloud.com/en-us/iaas/Content/Search/Tasks/queryingresources_topic-Listing_Supported_Resource_Types.htm).
- **OpenStack**: Use types from [OpenStack Heat resource types](https://docs.openstack.org/heat/latest/template_guide/openstack.html).
- **Alibaba Cloud**: Use types from [Alibaba Cloud ROS resource types](https://www.alibabacloud.com/help/en/ros/developer-reference/list-of-resource-types-by-service).
- **Any other provider**: Use `NotDefined` due to lack of standardized resource types in their SDK or documentation.

#### ResourceGroup

A high-level classification that groups checks by the type of cloud resource they audit. This field enables filtering and organizing findings by resource category across all providers. The value must be one of the following predefined groups:

| Group | Description |
|-------|-------------|
| `compute` | Virtual machines, instances, auto-scaling groups, workspaces, streaming |
| `container` | Container orchestration, Kubernetes, registries, pods |
| `serverless` | Functions, step functions, event-driven compute |
| `database` | Relational, NoSQL, caches, search engines, data warehouses, graph databases |
| `storage` | Object storage, block storage, file systems, backups, archives |
| `network` | VPCs, subnets, load balancers, DNS, VPN, firewalls, CDN |
| `IAM` | IAM users, roles, policies, access keys, service accounts, directories |
| `messaging` | Queues, topics, event buses, streaming, email services |
| `security` | WAF, secrets, KMS, certificates, security tools, defenders, DDoS protection |
| `monitoring` | Logs, metrics, alerts, audit trails, observability, config tracking |
| `api_gateway` | API management, REST APIs, GraphQL endpoints |
| `ai_ml` | Machine learning, AI services, notebooks, training, LLM |
| `governance` | Accounts, organizations, projects, policies, settings, compliance tools |
| `collaboration` | Productivity SaaS apps (Exchange, Teams, SharePoint) |
| `devops` | CI/CD, infrastructure as code, automation, code repositories, version control |
| `analytics` | Data warehouses, query engines, ETL pipelines, BI tools, data lakes |

The group is determined by the resource type being audited, not the service. For example, an EC2 security group check would use `network` (not `compute`), while an EC2 instance check would use `compute`.

#### Description

A concise, natural language explanation that **clearly describes what the finding means**, focusing on clarity and context rather than technical implementation details. Use simple paragraphs with line breaks if needed, but avoid sections, code blocks, or complex formatting. This field is limited to maximum 400 characters.

For detailed writing guidelines and common mistakes to avoid, see [Description Guidelines](/developer-guide/check-metadata-guidelines#description-guidelines).

#### Risk

A clear, natural language explanation of **why this finding poses a cybersecurity risk**. Focus on how it may impact confidentiality, integrity, or availability. If those do not apply, describe any relevant operational or financial risks. Use simple paragraphs with line breaks if needed, but avoid sections, code blocks, or complex formatting. Limit your explanation to 400 characters.

For detailed writing guidelines and common mistakes to avoid, see [Risk Guidelines](/developer-guide/check-metadata-guidelines#risk-guidelines).

#### RelatedUrl

*Deprecated*. Use `AdditionalURLs` for adding your URLs references.

#### AdditionalURLs

<Warning>
URLs must be valid and not repeated.

</Warning>
A list of official documentation URLs for further reading. These should be authoritative sources that provide additional context, best practices, or detailed information about the security control being checked. Prefer official provider documentation, security standards, or well-established security resources. Avoid third-party blogs or unofficial sources unless they are highly reputable and directly relevant.

#### Remediation

Provides both code examples and best practice recommendations for addressing the security issue.

- **Code**: Contains remediation examples in different formats:
    - **CLI**: Command-line interface commands to make the finding compliant in runtime.
    - **NativeIaC**: Native Infrastructure as Code templates with an example of a compliant configuration. For now it applies to:
        - **AWS**: CloudFormation YAML formatted code (do not use JSON format).
        - **Azure**: Bicep formatted code (do not use ARM templates).
    - **Terraform**: HashiCorp Configuration Language (HCL) code with an example of a compliant configuration.
    - **Other**: Manual steps through web interfaces or other tools to make the finding compliant.

    For detailed guidelines on writing remediation code, see [Remediation Code Guidelines](/developer-guide/check-metadata-guidelines#remediation-code-guidelines).

- **Recommendation**
    - **Text**: Generic best practice guidance in natural language using Markdown format (maximum 400 characters). For writing guidelines, see [Recommendation Guidelines](/developer-guide/check-metadata-guidelines#recommendation-guidelines).
    - **Url**: [Prowler Hub URL](https://hub.prowler.com/) of the check. This URL is always composed by `https://hub.prowler.com/check/<check_id>`.

#### Categories

One or more functional groupings used for execution filtering (e.g., `internet-exposed`). Categories must match the predefined values enforced by `CheckMetadata`; adding a new category requires updating the validator and the metadata documentation.

For the complete list of available categories, see [Categories Guidelines](/developer-guide/check-metadata-guidelines#categories-guidelines).

#### DependsOn

List of check IDs of checks that if are compliant, this check will be a compliant too or it is not going to give any finding.

#### RelatedTo

List of check IDs of checks that are conceptually related, even if they do not share a technical dependency.

#### Notes

Any additional information not covered in the above fields.

### Python Model Reference

The metadata structure is enforced in code using a Pydantic model. For reference, see the [`CheckMetadata`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py).

## Generic Check Patterns and Best Practices

### Common Patterns

- Every check is implemented as a class inheriting from `Check` (from `prowler.lib.check.models`).
- The main logic is implemented in the `execute()` method (**only method that must be implemented**), which always returns a list of provider-specific report objects (e.g., `CheckReport<Provider>`)—one per finding/resource. If there are no findings/resources, return an empty list.
- **Never** use the provider's client directly; instead, use the service client (e.g., `<service>_client`) and iterate over its resources.
- For each resource, create a provider-specific report object, populate it with metadata, resource details, status (`PASS`, `FAIL`, etc.), and a human-readable `status_extended` message.
- Use the `metadata()` method to attach check metadata to each report.
- Checks are designed to be idempotent and stateless: they do not modify resources, only report on their state.

### Best Practices

- Use clear, actionable, and user-friendly language in `status_extended` to explain the result. Always provide information to identify the resource.
- Use helper functions/utilities for repeated logic to avoid code duplication. Save them in the `lib` folder of the service.
- Handle exceptions gracefully: catch errors per resource, log them, and continue processing other resources.
- Never report `FAIL` because data could not be retrieved (missing permissions, API not enabled, feature not licensed). Emit a single `MANUAL` finding explaining what is required instead; see [Permission and Data-Availability Errors Are Not Findings](#permission-and-data-availability-errors-are-not-findings).
- Document the check with a class and function level docstring explaining what it does, what it checks, and any caveats or provider-specific behaviors.
- Use type hints for the `execute()` method (e.g., `-> list[CheckReport<Provider>]`) for clarity and static analysis.
- Ensure checks are efficient; avoid excessive nested loops. If the complexity is high, consider refactoring the check.
- Keep the check logic focused: one check = one control/requirement. Avoid combining unrelated logic in a single check.

## Specific Check Patterns

Details for specific providers can be found in documentation pages named using the pattern `<provider_name>-details`.

Checks that scan resources for plaintext secrets follow a dedicated batched structure. Refer to [Secret-Scanning Checks](/developer-guide/secret-scanning-checks) before creating or updating one.

---

### Developer Guide Configurable Checks (`docs/developer-guide/configurable-checks.mdx`)

---
title: 'Configurable Checks in Prowler'
---

Prowler empowers users to extend and adapt cloud security coverage by making checks configurable through the use of the `audit_config` object. This approach enables customization of checks to meet specific requirements through a configuration file.

## Understanding the `audit_config` Object

The `audit_config` object is a dictionary attached to each provider's service client (for example, `<service_name>_client.audit_config`). This object loads configuration values from the main configuration file (`prowler/config/config.yaml`). Use `audit_config` to make checks flexible and user-configurable.

## Using `audit_config` to Configure Checks

Retrieve configuration values in a check by using the `.get()` method on the `audit_config` object. For example, to get the minimum number of Availability Zones for Lambda from the configuration file, use the following code. If the value is not set in the configuration, the check defaults to 2:

```python
LAMBDA_MIN_AZS = awslambda_client.audit_config.get("lambda_min_azs", 2)
```

Always provide a default value in `.get()` to ensure the check works even if the configuration is missing the variable.

### Example: Security Group Rule Limit

```python title="ec2_securitygroup_with_many_ingress_egress_rules.py"
class ec2_securitygroup_with_many_ingress_egress_rules(Check):
    def execute(self):
        findings = []
        max_security_group_rules = ec2_client.audit_config.get(
            "max_security_group_rules", 50
        )
        for security_group_arn, security_group in ec2_client.security_groups.items():
            # ... check logic ...
```

## Required File Updates for Configurable Variables

When adding a new configurable check to Prowler, update the following files:

- **Configuration File:** Add the new variable under the relevant provider or service section in `prowler/config/config.yaml`.
  ```yaml
  # aws.awslambda_function_vpc_multi_az
  lambda_min_azs: 2
  ```
- **Provider Schema:** Add the typed field to the provider's Pydantic schema in `prowler/config/schema/<provider>.py`. This is required: the loader validates user configs against these schemas and the shipped `config.yaml` must round-trip with zero warnings. See [Adding a Parameter to the Provider Schema](#adding-a-parameter-to-the-provider-schema) below.
- **Test Fixtures:** If tests depend on this configuration, add the variable to `tests/config/fixtures/config.yaml`.
- **Documentation:** Document the new variable in the list of configurable checks in [Configuration File](/user-guide/cli/tutorials/configuration_file) (`docs/user-guide/cli/tutorials/configuration_file.mdx`).

For a complete list of checks that already support configuration, see the [Configuration File Tutorial](/user-guide/cli/tutorials/configuration_file).

<Note>
Because a configurable check's verdict depends on the `audit_config` value it reads, a compliance requirement can lose meaning if the scan ran with a looser threshold than the control demands. Compliance frameworks can guard against this with **configuration guardrails**: a requirement declares the strictest configuration it tolerates and is forced to FAIL when the scan's config falls short. See [Configuration Guardrails for Requirements](/developer-guide/security-compliance-framework#configuration-guardrails-for-requirements).
</Note>

## Adding a Parameter to the Provider Schema

Most providers have a typed Pydantic schema in `prowler/config/schema/`, registered in `prowler/config/schema/registry.py`. When a config is loaded and the provider has a registered schema, `validate_provider_config` checks each user-supplied key against it, logs a warning, and drops any field that fails validation. The consumer's `.get(key, default)` then falls back to the built-in default. Providers without a registered schema are passed through unchanged.

This catches typos in a value (for example, `0.2` typed as `20`, or `"medium"` for an enum that expects `"MEDIUM"`). It does NOT catch typos in a key name: `disalowed_regions` (one `l` missing) is treated as an unknown key and passes through untouched, because third-party check plugins legitimately rely on unknown keys being preserved. Reviewers should still check that any new key the YAML adds is named exactly the same as the field on the schema.

### Where to Add the Field

1. Open `prowler/config/schema/<provider>.py` (for example, `aws.py`).
2. Add a field on the provider's schema class. Always make it `Optional[...] = None` so the absence of the key is valid.
3. Apply the tightest type the value allows. Examples below.

If you are introducing an entirely new provider rather than a new parameter, also add an entry mapping the provider name to its schema class in `prowler/config/schema/registry.py`. The loader uses that registry to find the schema for the provider it is loading.

### Choosing the Right Type

| Value kind | Field declaration |
|---|---|
| Boolean toggle | `Optional[bool] = None` |
| Strictly positive integer (days, counts) | `Optional[int] = Field(default=None, gt=0)` |
| Fraction in 0..1 (threshold) | `Optional[float] = Field(default=None, ge=0.0, le=1.0)` |
| Closed set of strings | `Optional[Literal["A", "B", "C"]] = None` |
| Free-form string | `Optional[str] = None` |
| List of strings or ints | `Optional[list[str]] = None` |

Prefer `Literal[...]` over `str` whenever the value is one of a known set. Prefer `Field(gt=0)` over `int` whenever zero or negative would be nonsensical. The point of the schema is to catch real-world mistakes that previously passed silently.

### Custom Validators (Only When Needed)

If the value has structural rules beyond type and range, add a `field_validator`. Examples already in `aws.py`:

- `_validate_port_range` rejects ports outside `0..65535`.
- `_validate_account_ids` rejects anything that isn't a 12-digit AWS account ID.
- `_validate_trusted_ips` rejects entries that aren't a valid IP or CIDR.

Raise `ValueError` from the validator. The framework converts the error into a warning and drops the offending key.

### Example: Adding a New Parameter

Say a new check needs `max_iam_role_session_hours`, a strictly positive integer that defaults to 12 in code.

1. **Schema** (`prowler/config/schema/aws.py`):
    ```python
    # IAM
    max_iam_role_session_hours: Optional[int] = Field(default=None, gt=0)
    ```
2. **Shipped config** (`prowler/config/config.yaml`):
    ```yaml
    # aws.iam_role_session_duration_within_limit
    max_iam_role_session_hours: 12
    ```
3. **Consumer** (the check):
    ```python
    max_hours = iam_client.audit_config.get("max_iam_role_session_hours", 12)
    ```
4. **Tests** in `tests/config/schema/aws_schema_test.py`:
    - one test for a valid value that round-trips,
    - one test for an invalid value (zero, negative, wrong type) that is dropped.

### What the Loader Guarantees

- **Unknown keys pass through.** Third-party check plugins can introduce arbitrary keys without schema edits; they will not be filtered.
- **Invalid values never crash the run.** They produce a single warning per field and the key is dropped.
- **Coerced values are normalized.** A YAML-quoted `"180"` for an `int` field arrives downstream as the integer `180`.
- **The shipped `config.yaml` must round-trip cleanly.** The integration test `test_shipped_default_config_loads_without_warnings` will fail if a key is added to the YAML without a matching schema field, so the two stay in sync.

## Configuration Value Limits

Configurable thresholds enforce hard limits. A value outside the documented range is **dropped with a warning** and the check falls back to its built-in default (the same as if the key were absent). These bounds are intentionally conservative: they are not the absolute service maxima but the range that still produces a meaningful security check.

Use this section as the reference when upgrading an existing config: if a value you set is being rejected, it is outside the range below.

Only fields with a numeric range, a fixed value set, or a length cap are listed. Fields typed as free-form strings or lists (for example `disallowed_regions`, `secrets_ignore_patterns`, `trusted_account_ids`) have no range limit — they are validated for shape only (a 12-digit account ID, a valid IP/CIDR, a dotted version string), not for magnitude.

### AWS

| Key | Allowed range | Notes |
|---|---|---|
| `max_unused_access_keys_days` | `30..180` days | CIS AWS 1.13 recommends 45; NIST IA-5 ≤90 |
| `max_console_access_days` | `30..180` days | CIS AWS 1.12 recommends 45 |
| `max_unused_sagemaker_access_days` | `7..180` days | |
| `max_security_group_rules` | `1..1000` | AWS hard limit is 1000 rules per security group |
| `max_ec2_instance_age_in_days` | `1..1095` days | 3 years |
| `max_ec2_instance_stopped_days` | `1..1095` days | 3 years |
| `ec2_high_risk_ports` | each port `1..65535` | port 0 is reserved |
| `max_idle_disconnect_timeout_in_seconds` | `60..1800` s | NIST AC-12: cap at 30 min |
| `max_disconnect_timeout_in_seconds` | `60..3600` s | |
| `max_session_duration_seconds` | `600..86400` s | 10 min .. 24 h (AppStream per-session hard limit) |
| `lambda_min_azs` | `1..6` | |
| `recommended_cdk_bootstrap_version` | `1..100` | |
| `log_group_retention_days` | one of `1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653` | only the CloudWatch Logs API-accepted retention values |
| `threat_detection_privilege_escalation_threshold` | `0.0..1.0` | fraction of suspicious actions |
| `threat_detection_privilege_escalation_minutes` | `5..43200` min | under 5 min the signal is mostly false positives |
| `threat_detection_enumeration_threshold` | `0.0..1.0` | |
| `threat_detection_enumeration_minutes` | `5..43200` min | |
| `threat_detection_llm_jacking_threshold` | `0.0..1.0` | |
| `threat_detection_llm_jacking_minutes` | `5..43200` min | |
| `days_to_expire_threshold` (ACM) | `7..365` days | PCI-DSS 4.2.1.1: alert ≥30 days before expiry |
| `elb_min_azs` | `1..6` | |
| `elbv2_min_azs` | `1..6` | |
| `minimum_snapshot_retention_period` | `1..35` days | ElastiCache service hard limit |
| `max_days_secret_unused` | `7..365` days | |
| `max_days_secret_unrotated` | `1..180` days | NIST IA-5: rotate quarterly; CIS ≤90 |
| `min_kinesis_stream_retention_hours` | `24..8760` h | 1 day .. 1 year |
| `inspector2_max_days_since_last_scan` | `1..90` days | |
| `inspector2_active_finding_max_age_days` | `1..365` days | Default `192` matches the FedRAMP 20x rule that marks vulnerabilities still open after 192 days as accepted |
| `shodan_api_key` | ≤512 chars | |

### Azure

| Key | Allowed range | Notes |
|---|---|---|
| `vm_backup_min_daily_retention_days` | `7..9999` days | Azure Backup hard limit; under 7 days defeats DR/ransomware recovery |
| `apim_threat_detection_llm_jacking_threshold` | `0.0..1.0` | fraction of suspicious actions |
| `apim_threat_detection_llm_jacking_minutes` | `5..43200` min | under 5 min the signal is mostly false positives |
| `shodan_api_key` | ≤512 chars | |

### GCP

| Key | Allowed range | Notes |
|---|---|---|
| `mig_min_zones` | `1..5` | |
| `max_snapshot_age_days` | `1..1095` days | 3 years |
| `max_unused_account_days` | `30..365` days | |
| `storage_min_retention_days` | `1..3650` days | |
| `shodan_api_key` | ≤512 chars | |

### Kubernetes

| Key | Allowed range | Notes |
|---|---|---|
| `audit_log_maxbackup` | `2..1000` | CIS Kubernetes 1.2.18 recommends ≥10 |
| `audit_log_maxsize` | `10..10000` MB | CIS Kubernetes 1.2.19 recommends ≥100 MB |
| `audit_log_maxage` | `7..3650` days | CIS Kubernetes 1.2.17 recommends ≥30 days |

### M365

| Key | Allowed range | Notes |
|---|---|---|
| `sign_in_frequency` | `1..168` h | 1 h .. 7 days; Conditional Access baseline for admins ≤24 h |
| `recommended_mailtips_large_audience_threshold` | `5..10000` | Microsoft default 25 |
| `audit_log_age` | `30..3650` days | M365 E3 default 90 days; SEC/FINRA require ≥7 years |

### GitHub

| Key | Allowed range | Notes |
|---|---|---|
| `inactive_not_archived_days_threshold` | `30..3650` days | CIS GitHub recommends 180 |

### Cloudflare

| Key | Allowed range | Notes |
|---|---|---|
| `max_retries` | `0..10` | 0 disables retries |

### MongoDB Atlas

| Key | Allowed range | Notes |
|---|---|---|
| `max_service_account_secret_validity_hours` | `1..720` h | 1 h .. 30 days |

### Vercel

| Key | Allowed range | Notes |
|---|---|---|
| `days_to_expire_threshold` | `7..365` days | PCI-DSS 4.2.1.1: alert ≥30 days before expiry |
| `stale_token_threshold_days` | `30..3650` days | NIST AC-2(3) typical window 30..90 days |
| `stale_invitation_threshold_days` | `7..365` days | |
| `max_owner_percentage` | `1..50` % | guidance recommends ≤25% |
| `max_owners` | `1..1000` | absolute cap, overrides percentage for large teams |

These bounds live in the provider schemas under `prowler/config/schema/`; each field's `Field(ge=..., le=...)` (or `field_validator`) is the source of truth and the descriptions there carry the full rationale.

This approach ensures that checks are easily configurable, making Prowler highly adaptable to different environments and requirements.

---

### Developer Guide Debugging (`docs/developer-guide/debugging.mdx`)

---
title: 'Debugging in Prowler'
---

Debugging in Prowler simplifies the development process, allowing developers to efficiently inspect and resolve unexpected issues during execution.

## Debugging with Visual Studio Code

Visual Studio Code (also referred to as VSCode) provides an integrated debugger for executing and analyzing Prowler code. Refer to the official VSCode debugger [documentation](https://code.visualstudio.com/docs/editor/debugging) for detailed instructions.

### Debugging Configuration Example

The following file is an example of a [debugging configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) file for [Visual Studio Code](https://code.visualstudio.com/).

This file must be placed inside the *.vscode* directory and named *launch.json*:

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Developer Guide Documentation (`docs/developer-guide/documentation.mdx`)

---
title: 'Contributing to Documentation'
---

Prowler documentation is built using [Mintlify](https://www.mintlify.com/docs), allowing contributors to easily add or enhance documentation.

## Documentation Structure

The Prowler documentation is organized into several sections. The main ones are:

- **Getting Started**: Provides an overview of the Prowler platform and its two product families, Prowler Products (Prowler Cloud, Prowler Hub, Prowler MCP, Prowler Lighthouse AI) and Open Source (Prowler CLI, Prowler Local Server). This section helps new users understand which Prowler solution best fits their needs and includes product comparisons.

- **Guides**: Contains practical tutorials and how-to guides organized by product (Prowler Cloud, CLI) and provider (AWS, Azure, GCP, Kubernetes, Microsoft 365, GitHub, etc.). This section covers authentication, integrations, compliance, and advanced usage scenarios.

- **Developer Guide**: Documentation for contributors looking to extend Prowler functionality. This includes guides on creating providers, services, checks, output formats, integrations, and compliance frameworks. Provider-specific implementation details and testing strategies are also covered here.

- **Troubleshooting**: Common issues, error messages, and their solutions. This section helps users resolve problems encountered during installation, configuration, or execution.


## AI-Driven Documentation

As mentioned in the [Introduction](/developer-guide/introduction#ai-driven-contributions), we have specialized resources to enhance AI-driven development.

This includes the [AGENTS.md](https://github.com/prowler-cloud/prowler/blob/master/docs/AGENTS.md) file that contains the guidelines and style guide for the AI agents in the Prowler documentation.

## Local Development

<Steps>
  <Step title="Install Mintlify CLI">
    ```bash
    npm install mint@4.2.560
    ```
    For detailed instructions, check the [Mintlify documentation](https://www.mintlify.com/docs/installation).
  </Step>

  <Step title="Preview Documentation Locally">
    Start the local development server to preview changes in real-time.

    ```bash
    mint dev
    ```

    A local preview of your documentation will be available at http://localhost:3000
  </Step>

  <Step title="Make Documentation Changes">
    Edit existing Markdown (.mdx) files inside the `docs` directory or add new documents.

    For reference about formatting, check the [Mintlify documentation](https://www.mintlify.com/docs/create/text).

    To add new sections or files, update the [`docs/docs.json`](https://github.com/prowler-cloud/prowler/blob/master/docs/docs.json) file to include them in the navigation.
  </Step>

  <Step title="Submit Changes">
    Once documentation updates are complete, [submit a pull request for review](/developer-guide/introduction#sending-the-pull-request).

    The Prowler team will assess and merge contributions.
  </Step>
</Steps>

Your efforts help improve Prowler documentation. Thank you for contributing! 🤘

---

### Developer Guide End2end Testing (`docs/developer-guide/end2end-testing.mdx`)

---
title: 'End-2-End Tests for Prowler Local Server'
---

End-to-end (E2E) tests validate complete user flows in Prowler Local Server (UI + API). These tests are implemented with [Playwright](https://playwright.dev/) under the `ui/tests` folder and are designed to run against a Prowler Local Server environment.

## General Recommendations

When adding or maintaining E2E tests for Prowler Local Server, follow these guidelines:

1. **Test real user journeys**
   Focus on full workflows (for example, sign-up → login → add provider → launch scan) instead of low-level UI details already covered by unit or integration tests.

2. **Group tests by entity or feature area**
   - Organize E2E tests by entity or feature area (for example, `providers.spec.ts`, `scans.spec.ts`, `invitations.spec.ts`, `sign-up.spec.ts`).
   - Each entity should have its own test file and corresponding page model class (for example, `ProvidersPage`, `ScansPage`, `InvitationsPage`).
   - Related tests for the same entity should be grouped together in the same test file to improve maintainability and make it easier to find and update tests for a specific feature.

3. **Use a Page Model (Page Object Model)**
   - Encapsulate selectors and common actions in page classes instead of repeating them in each test.
   - Leverage and extend the existing Playwright page models in `ui/tests`—such as `ProvidersPage`, `ScansPage`, and others—which are all based on the shared `BasePage`.
   - Page models for Prowler Local Server pages should be placed in their respective entity folders (for example, `ui/tests/providers/providers-page.ts`).
   - Page models for external pages (not part of Prowler Local Server) should be grouped in the `external` folder (for example, `ui/tests/external/github-page.ts`).
   - This approach improves readability, reduces duplication, and makes refactors safer.

4. **Reuse authentication states (StorageState)**
   - Multiple authentication setup projects are available that generate pre-authenticated state files stored in `playwright/.auth/`. Each project requires specific environment variables:
     - `admin.auth.setup` – Admin users with full system permissions (requires `E2E_ADMIN_USER` / `E2E_ADMIN_PASSWORD`)
     - `manage-scans.auth.setup` – Users with scan management permissions (requires `E2E_MANAGE_SCANS_USER` / `E2E_MANAGE_SCANS_PASSWORD`)
     - `manage-integrations.auth.setup` – Users with integration management permissions (requires `E2E_MANAGE_INTEGRATIONS_USER` / `E2E_MANAGE_INTEGRATIONS_PASSWORD`)
     - `manage-account.auth.setup` – Users with account management permissions (requires `E2E_MANAGE_ACCOUNT_USER` / `E2E_MANAGE_ACCOUNT_PASSWORD`)
     - `manage-cloud-providers.auth.setup` – Users with cloud provider management permissions (requires `E2E_MANAGE_CLOUD_PROVIDERS_USER` / `E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD`)
     - `unlimited-visibility.auth.setup` – Users with unlimited visibility permissions (requires `E2E_UNLIMITED_VISIBILITY_USER` / `E2E_UNLIMITED_VISIBILITY_PASSWORD`)
     - `invite-and-manage-users.auth.setup` – Users with user invitation and management permissions (requires `E2E_INVITE_AND_MANAGE_USERS_USER` / `E2E_INVITE_AND_MANAGE_USERS_PASSWORD`)
   <Note>
   If fixtures have been applied (fixtures are used to populate the database with initial development data), you can use the user `e2e@prowler.com` with password `Thisisapassword123@` to configure the Admin credentials by setting `E2E_ADMIN_USER=e2e@prowler.com` and `E2E_ADMIN_PASSWORD=Thisisapassword123@`.
   </Note>

   - Within test files, use `test.use({ storageState: "playwright/.auth/admin_user.json" })` to load the pre-authenticated state, avoiding redundant authentication steps in each test. This must be placed at the test level (not inside the test function) to apply the authentication state to all tests in that scope. This approach is preferred over declaring dependencies in `playwright.config.ts` because it provides more control over which authentication states are used in specific tests.

     **Example:**

     ```typescript
     // Use admin authentication state for all tests in this scope
     test.use({ storageState: "playwright/.auth/admin_user.json" });

     test("should perform admin action", async ({ page }) => {
       // Test implementation
     });
     ```

5. **Tag and document scenarios**
   - Follow the existing naming convention for suites and test cases (for example, `SCANS-E2E-001`, `PROVIDER-E2E-003`) and use tags such as `@e2e`, `@serial` and feature tags (for example, `@providers`, `@scans`, `@aws`) to filter and organize tests.

   **Example:**
     ```typescript
     test(
       "should add a new AWS provider with static credentials",
       {
         tag: [
           "@critical",
           "@e2e",
           "@providers",
           "@aws",
           "@serial",
           "@PROVIDER-E2E-001",
         ],
       },
       async ({ page }) => {
         // Test implementation
       }
     );
     ```
   -  Document each one in the Markdown files under `ui/tests`, including **Priority**, **Tags**, **Description**, **Preconditions**, **Flow steps**, **Expected results**, **Key verification points** and **Notes**.

   **Example**
   ```
/* Detailed source-code truncated for AI context efficiency. */
```

6. **Use environment variables for secrets and dynamic data**
   Credentials, provider identifiers, secrets, tokens must come from environment variables (for example, `E2E_AWS_PROVIDER_ACCOUNT_ID`, `E2E_AWS_PROVIDER_ACCESS_KEY`, `E2E_AWS_PROVIDER_SECRET_KEY`, `E2E_GCP_PROJECT_ID`).

   <Warning>
   Never commit real secrets, tokens, or account IDs to the repository.
   </Warning>

7. **Keep tests deterministic and isolated**
   - Use Playwright's `test.beforeEach()` and `test.afterEach()` hooks to manage test state:
     - **`test.beforeEach()`**: Execute cleanup or setup logic before each test runs (for example, delete existing providers with a specific account ID to ensure a clean state).
     - **`test.afterEach()`**: Execute cleanup logic after each test completes (for example, remove test data created during the test execution to prevent interference with subsequent tests).
   - Define tests as serial using `test.describe.serial()` when they share state or resources that could interfere with parallel execution (for example, tests that use the same provider account ID or create dependent resources). This ensures tests within the serial group run sequentially, preventing race conditions and data conflicts.
   - Use unique identifiers (for example, random suffixes for emails or labels) to prevent data collisions.

8. **Use explicit waiting strategies**
   - Avoid using `waitForLoadState('networkidle')` as it is unreliable and can lead to flaky tests or unnecessary delays.
   - Leverage Playwright's auto-waiting capabilities by waiting for specific elements to be actionable (for example, `locator.click()`, `locator.fill()`, `locator.waitFor()`).
   - **Prioritize selector strategies**: Prefer `page.getByRole()` over other approaches like `page.getByText()`. `getByRole()` is more resilient to UI changes, aligns with accessibility best practices, and better reflects how users interact with the application (by role and accessible name rather than implementation details).
   - For dynamic content, wait for specific UI elements that indicate the page is ready (for example, button becoming enabled, a specific text appearing, etc).
   - This approach makes tests more reliable, faster, and aligned with how users actually interact with the application.

   **Common waiting patterns used in Prowler E2E tests:**

   - **Element visibility assertions**: Use `expect(locator).toBeVisible()` or `expect(locator).not.toBeVisible()` to wait for elements to appear or disappear (Playwright automatically waits for these conditions).

   - **URL changes**: Use `expect(page).toHaveURL(url)` or `page.waitForURL(url)` to wait for navigation to complete.

   - **Element states**: Use `locator.waitFor({ state: "visible" })` or `locator.waitFor({ state: "hidden" })` when you need explicit state control.

   - **Text content**: Use `expect(locator).toHaveText(text)` or `expect(locator).toContainText(text)` to wait for specific text to appear.

   - **Element attributes**: Use `expect(locator).toHaveAttribute(name, value)` to wait for attributes like `aria-disabled="false"` indicating a button is enabled.

   - **Custom conditions**: Use `page.waitForFunction(() => condition)` for complex conditions that cannot be expressed with locators (for example, checking DOM element dimensions or computed styles).

   - **Retryable assertions**: Use `expect(async () => { ... }).toPass({ timeout })` for conditions that may take time to stabilize (for example, waiting for table rows to filter after a server request).

   - **Scroll into view**: Use `locator.scrollIntoViewIfNeeded()` before interacting with elements that may be outside the viewport.

   **Example from Prowler tests:**

   ```typescript
   // Wait for page to load by checking main content is visible
   await expect(page.locator("main")).toBeVisible();

   // Wait for URL change after form submission
   await expect(page).toHaveURL("/providers");

   // Wait for button to become enabled
   await expect(submitButton).toHaveAttribute("aria-disabled", "false");

   // Wait for loading spinner to disappear
   await expect(page.getByText("Loading")).not.toBeVisible();

   // Wait for custom condition
   await page.waitForFunction(() => {
     const main = document.querySelector("main");
     return main && main.offsetHeight > 0;
   });

   // Wait for retryable condition (e.g., table filtering)
   await expect(async () => {
     const rowCount = await tableRows.count();
     expect(rowCount).toBeLessThanOrEqual(1);
   }).toPass({ timeout: 20000 });
   ```

## Running Prowler Tests

E2E tests for Prowler Local Server run from the `ui` project using Playwright. The Playwright configuration lives in `ui/playwright.config.ts` and defines:

- `testDir: "./tests"` – location of E2E test files (relative to the `ui` project root, so `ui/tests`).
- `webServer` – how to start the Next.js development server and connect to Prowler API.
- `use.baseURL` – base URL for browser interactions (defaults to `http://localhost:3000` or `AUTH_URL` if set).
- `reporter: [["list"]]` – uses the list reporter to display test results in a concise format in the terminal. Other reporter options are available (for example, `html`, `json`, `junit`, `github`), and multiple reporters can be configured simultaneously. See the [Playwright reporter documentation](https://playwright.dev/docs/test-reporters) for all available options.
- `expect.timeout: 20000` – timeout for assertions (20 seconds). This is the maximum time Playwright will wait for an assertion to pass before considering it failed.
- **Test artifacts** (in `use` configuration): By default, `trace`, `screenshot`, and `video` are set to `"off"` to minimize resource usage. To review test failures or debug issues, these can be enabled in `playwright.config.ts` by changing them to `"on"`, `"on-first-retry"`, or `"retain-on-failure"` depending on your needs.
- `outputDir: "/tmp/playwright-tests"` – directory where Playwright stores test artifacts (screenshots, videos, traces) during test execution.
- **CI-specific configuration**: The configuration uses different settings when running in CI environments (detected via `process.env.CI`):
  - **Retries**: `2` retries in CI (to handle flaky tests), `0` retries locally (for faster feedback during development).
  - **Workers**: `1` worker in CI (sequential execution for stability), `undefined` locally (parallel execution by default for faster test runs).

### Prerequisites

Before running E2E tests:

- **Install root and UI dependencies**
  - Follow the [developer guide introduction](/developer-guide/introduction#getting-the-code-and-installing-all-dependencies) to clone the repository and install core dependencies.
  - From the `ui` directory, install frontend dependencies:

    ```bash
    cd ui
    pnpm install
    pnpm run test:e2e:install  # Install Playwright browsers
    ```

- **Ensure Prowler API is available**
  - By default, Playwright uses `UI_API_BASE_URL=http://localhost:8080/api/v1` (configured in `playwright.config.ts`).
  - Start Prowler API so it is reachable on that URL (for example, via `docker-compose-dev.yml` or the development orchestration used locally).
  - If a different API URL is required, set `UI_API_BASE_URL` accordingly before running the tests.

- **Ensure Prowler Local Server UI is available**
  - Playwright automatically starts the Next.js server through the `webServer` block in `playwright.config.ts` (`pnpm run dev` by default).
  - If the UI is already running on `http://localhost:3000`, Playwright will reuse the existing server when `reuseExistingServer` is `true`.

- **Configure E2E environment variables**
  - Suite-specific variables (for example, provider account IDs, credentials, and E2E user data) must be provided before running tests.
  - They can be defined either:
    - As exported environment variables in the shell before executing the Playwright commands, or
    - In a `.env.local` or `.env` file under `ui/`, and then loaded into the shell before running tests, for example:

      ```bash
      cd ui
      set -a
      source .env.local  # or .env
      set +a
      ```
  - Refer to the Markdown documentation files in `ui/tests` for each E2E suite (for example, the `*.md` files that describe sign-up, providers, scans, invitations, and other flows) to see the exact list of required variables and their meaning.
  - Each E2E test suite explicitly checks that its required environment variables are defined at runtime and will fail with a clear error message if any mandatory variable is missing, making misconfiguration easy to detect.

### Executing Tests

To execute E2E tests for Prowler Local Server:

1. **Run the full E2E suite (headless)**

   From the `ui` directory:

   ```bash
   pnpm run test:e2e
   ```

   This command runs Playwright with the configured projects.

2. **Run E2E tests with the Playwright UI runner**

   ```bash
   pnpm run test:e2e:ui
   ```

   This opens the Playwright test runner UI to inspect, debug, and rerun specific tests or projects.

3. **Debug E2E tests interactively**

   ```bash
   pnpm run test:e2e:debug
   ```

   Use this mode to step through flows, inspect selectors, and adjust timings. It runs tests in headed mode with debugging tools enabled.

4. **Run tests in headed mode without debugger**

   ```bash
   pnpm run test:e2e:headed
   ```

   This is useful to visually confirm flows while still running the full suite.

5. **View previous test reports**

   ```bash
   pnpm run test:e2e:report
   ```

   This opens the latest Playwright HTML report, including traces and screenshots when enabled.

6. **Run specific tests or subsets**

   In addition to the predefined scripts, Playwright allows filtering which tests run. These examples use the Playwright CLI directly through `pnpm`:

   - **By test ID (`@ID` in the test metadata or description)**

     To run a single test case identified by its ID (for example, `@PROVIDER-E2E-001` or `@SCANS-E2E-001`):

     ```bash
     pnpm playwright test --grep @PROVIDER-E2E-001
     ```

   - **By tags**

     To run all tests that share a common tag (for example, all provider E2E tests tagged with `@providers`):

     ```bash
     pnpm playwright test --grep @providers
     ```

     This is useful to focus on a specific feature area such as providers, scans, invitations, or sign-up.

   - **By Playwright project**

     To run only the tests associated with a given project defined in `playwright.config.ts` (for example, `providers` or `scans`):

     ```bash
     pnpm playwright test --project=providers
     ```

     Combining project and grep filters is also supported, enabling very narrow runs (for example, a single test ID within the `providers` project). For additional CLI options and combinations, see the [Playwright command line documentation](https://playwright.dev/docs/test-cli).

<Note>
For detailed flows, preconditions, and environment variable requirements per feature, always refer to the Markdown files in `ui/tests`. Those documents are the single source of truth for business expectations and validation points in each E2E suite.
</Note>

---

### Developer Guide Environment Variables (`docs/developer-guide/environment-variables.mdx`)

---
title: 'Environment Variable Naming Convention'
---

Prowler is a monorepo composed of several runtime components — Prowler Local Server (the web user interface), Prowler API (the backend), Prowler SDK, and Prowler MCP Server (Model Context Protocol) — that frequently share a single `.env` file. To keep that shared configuration unambiguous, each component namespaces its environment variables with a component-specific prefix.

## Component Prefixes

Each component owns a dedicated prefix for the environment variables it reads:

| Component | Prefix | Status |
|-----------|--------|--------|
| Prowler Local Server (web UI) | `UI_` | Adopted |
| Prowler API (backend) | `API_` | Planned |
| Prowler SDK | `SDK_` | Planned |
| Prowler MCP Server | `MCP_` | Planned |

## Why Component Prefixes Matter

Component prefixes solve three concrete problems in a shared configuration file:

- **Collisions in a shared `.env`:** Several components historically read identically named variables. The API base URL, for example, is consumed by more than one component, so a single unprefixed name is ambiguous. A component prefix removes that ambiguity.
- **Explicit ownership:** A prefix states, at a glance, which component consumes a variable.
- **Reduced accidental exposure:** For Prowler Local Server, scoping browser-facing configuration under one intentional prefix prevents server-only values from leaking into the client bundle.

## Prowler Local Server

Prowler Local Server has adopted the `UI_` prefix. Its public configuration is resolved from the container environment at runtime rather than inlined at build time, so a single pre-built image serves any deployment. For the operational details on changing these values without rebuilding the image, see [Troubleshooting](/troubleshooting).

The former build-time variables map to the new runtime variables as follows:

| Former variable | New variable |
|-----------------|--------------|
| `NEXT_PUBLIC_API_BASE_URL` | `UI_API_BASE_URL` |
| `NEXT_PUBLIC_API_DOCS_URL` | `UI_API_DOCS_URL` |
| `NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID` | `UI_GOOGLE_TAG_MANAGER_ID` |
| `NEXT_PUBLIC_SENTRY_DSN`, `SENTRY_DSN` | `UI_SENTRY_DSN` |
| `NEXT_PUBLIC_SENTRY_ENVIRONMENT`, `SENTRY_ENVIRONMENT` | `UI_SENTRY_ENVIRONMENT` |
| `NEXT_PUBLIC_IS_CLOUD_ENV` | `UI_CLOUD_ENABLED` |

`UI_CLOUD_ENABLED` is a plain runtime boolean flag that enables Prowler Cloud behavior when set to the exact string `"true"` and defaults to off; unlike the other renamed variables it has no legacy fallback, so `NEXT_PUBLIC_IS_CLOUD_ENV` is no longer read.

`UI_SELF_REGISTRATION_ENABLED` is a runtime opt-out flag that Prowler Local Server reads only when `UI_CLOUD_ENABLED` is `"true"`. It defaults to on and turns off when set to `"false"`, matched case-insensitively so the same value can be shared with a backend setting written `False`. When it is off, the sign-up page only opens with an invitation token, the sign-in page drops its "Sign up" link, and the profile hides "Create organization"; invited users can still complete their registration. Outside a Prowler Cloud deployment the flag is ignored and account creation stays open.
## Registry UI Rollout and Rollback

`UI_REGISTRY_ENABLED` is an optional runtime flag for Prowler Cloud and Private Cloud. Registry is eligible only when both `UI_CLOUD_ENABLED` and `UI_REGISTRY_ENABLED` are the exact string `"true"` and the current user has the backend-authorized `manage_registry` permission. Unset, `"false"`, or malformed values fail closed. The flag defaults to off and is not a replacement for backend authorization. Registry access is independent of billing; Private Cloud can use it with `CLOUD_BILLING_ENABLED=false`.

Roll out Registry only after the Registry backend dependency is deployed, intended roles have `manage_registry`, and acceptance with real credentials has exercised installation, provider account creation, credentials, connection, and scan launch. Deploy the UI with `UI_REGISTRY_ENABLED` unset or `"false"`; set it to `"true"` only in the prepared process environment, then restart or otherwise apply the environment update required by the platform. A Registry key must belong to the configured Registry environment; a production key does not authenticate against a development Registry.

The Registry links read `PROWLER_REGISTRY_INDEX_URL`, the same base URL the backend installs artifacts from. The Registry page and the credential banner link to it, and its origin is allowed for images. `UI_REGISTRY_MEDIA_URL` adds the Registry media service origin so artifact logos load. When `PROWLER_REGISTRY_INDEX_URL` is unset or invalid, the links are hidden instead of pointing to the public Prowler Registry, which keeps private and air-gapped deployments from sending users to an unreachable host. `UI_REGISTRY_URL` is no longer read.

The catalog displays all artifacts, including built-ins and packages containing only checks or compliance frameworks. Only external provider artifacts support Add. After confirmed installation, open Providers and select the option labeled Registry to configure an account. Creating accounts and running scans also require the corresponding provider and scan permissions. Removing an artifact keeps existing provider accounts, but future connections or scans can fail until the artifact is installed again.

To roll back, set `UI_REGISTRY_ENABLED=false` or remove it and apply the environment update. Proxy, page, and action checks deny on their next request. Navigation refreshes from server-authorized access when the page is requested again. Rollback does not delete Registry credentials, tenant artifact records, or provider accounts.

The build-time-only Sentry variables used for source-map upload — `SENTRY_ORG`, `SENTRY_PROJECT`, `SENTRY_AUTH_TOKEN`, and `SENTRY_RELEASE` — keep their names, as they are not part of Prowler Local Server's runtime configuration.

## Enabling Third-Party Integrations

Prowler Local Server gates each optional third-party integration behind an explicit enable flag. When an integration is configured through its new `UI_*` variables, it loads only when its flag is set to the exact string `"true"`; any other value, including unset, leaves it off. This default-off behavior keeps a deployment free of third-party egress unless it opts in. Deployments still using the deprecated legacy variable names keep loading without the flag, for backward compatibility (see [Deprecated Names](#deprecated-names)).

| Integration | Enable flag | Required configuration when enabled |
|-------------|-------------|-------------------------------------|
| Sentry (error monitoring) | `UI_SENTRY_ENABLED` | `UI_SENTRY_DSN` |
| Google Tag Manager | `UI_GOOGLE_TAG_MANAGER_ENABLED` | `UI_GOOGLE_TAG_MANAGER_ID` |
| PostHog (product analytics) | `UI_POSTHOG_ENABLED` | `UI_POSTHOG_KEY` and `UI_POSTHOG_HOST` |

When an integration is enabled but its required configuration is missing, Prowler Local Server fails fast at server startup with a clear error, so a misconfigured container never starts silently. A new `UI_*` value set while its enable flag is not `"true"` is ignored, and the server logs a one-time startup warning noting that the integration will not load. Legacy names follow the backward-compatible rule described in [Deprecated Names](#deprecated-names).

PostHog support is currently limited to configuration validation: Prowler Local Server reads and validates the PostHog variables but does not yet load a PostHog client.

<Warning>
Configuring an integration through the new `UI_*` variables now requires its enable flag. A deployment that adopted `UI_SENTRY_DSN` or `UI_GOOGLE_TAG_MANAGER_ID` must also set `UI_SENTRY_ENABLED=true` or `UI_GOOGLE_TAG_MANAGER_ENABLED=true` to keep the integration active. Deployments still using the legacy names (`NEXT_PUBLIC_*`, or `POSTHOG_KEY` and `POSTHOG_HOST`) keep working without the flag.
</Warning>

## Upcoming Breaking Change

<Warning>
Adopting the `API_`, `SDK_`, and `MCP_` prefixes for Prowler API, Prowler SDK, and Prowler MCP Server is a planned breaking change in a future release. Migrate environment configuration to the new names when upgrading.
</Warning>

Prowler API, Prowler SDK, and Prowler MCP Server have not yet adopted the convention. In a future release, the variables each of these components reads will be namespaced under `API_`, `SDK_`, and `MCP_` respectively. The per-component mapping from current to prefixed names will be documented when each change is released.

## Deprecated Names

- **Prowler Local Server:** The bare server-side `SENTRY_DSN` and `SENTRY_ENVIRONMENT` are no longer read; the server and edge runtimes now read `UI_SENTRY_DSN` and `UI_SENTRY_ENVIRONMENT`. The former `NEXT_PUBLIC_*` names — and, for PostHog, the unprefixed `POSTHOG_KEY` and `POSTHOG_HOST` — are deprecated but stay backward compatible: they are read at runtime regardless of the enable flag, so an existing deployment keeps its integration active without opting in. The new `UI_*` names, by contrast, load only when the matching enable flag is set to `"true"`. These legacy names will be removed in a future release, so migrate to the `UI_*` runtime variables — and set the enable flag — on the running container.
- **Prowler API, Prowler SDK, and Prowler MCP Server:** The current, unprefixed variable names are deprecated. They continue to work today and will be removed once the prefixed convention is adopted for each component, as described in [Upcoming Breaking Change](#upcoming-breaking-change).

---

### Developer Guide Gcp Details (`docs/developer-guide/gcp-details.mdx`)

---
title: 'Google Cloud Provider'
---

This page details the [Google Cloud Platform (GCP)](https://cloud.google.com/) provider implementation in Prowler.

By default, Prowler will audit all the GCP projects that the authenticated identity can access. To configure it, follow the [GCP getting started guide](/user-guide/providers/gcp/getting-started-gcp).

## GCP Provider Classes Architecture

The GCP provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the GCP-specific implementation, highlighting how the generic provider concepts are realized for GCP in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).

### Main Class

- **Location:** [`prowler/providers/gcp/gcp_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/gcp_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for GCP-specific logic, session management, credential validation, project and organization discovery, and configuration.
- **Key GCP Responsibilities:**
    - Initializes and manages GCP sessions (supports Application Default Credentials, Service Account, OAuth, and impersonation).
    - Validates credentials and sets up the GCP identity context.
    - Loads and manages configuration, mutelist, and fixer settings.
    - Discovers accessible GCP projects and organization metadata.
    - Provides properties and methods for downstream GCP service classes to access session, identity, and configuration data.

### Data Models

- **Location:** [`prowler/providers/gcp/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/models.py)
- **Purpose:** Define structured data for GCP identity, project, and organization info.
- **Key GCP Models:**
    - `GCPIdentityInfo`: Holds GCP identity metadata, such as the profile name.
    - `GCPOrganization`: Represents a GCP organization with ID, name, and display name.
    - `GCPProject`: Represents a GCP project with number, ID, name, organization, labels, and lifecycle state.

### `GCPService` (Service Base Class)

- **Location:** [`prowler/providers/gcp/lib/service/service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/lib/service/service.py)
- **Purpose:** Abstract base class that all GCP service-specific classes inherit from. This implements the generic service pattern (described in [service page](/developer-guide/services#service-base-class)) specifically for GCP.
- **Key GCP Responsibilities:**
    - Receives a `GcpProvider` instance to access session, identity, and configuration.
    - Manages clients for all services by project.
    - Filters projects to only those with the relevant API enabled.
    - Provides `__threading_call__` method to make API calls in parallel by project or resource.
    - Exposes common audit context (`project_ids`, `projects`, `default_project_id`, `audit_config`, `fixer_config`) to subclasses.

### Exception Handling

- **Location:** [`prowler/providers/gcp/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for GCP-specific error handling, such as credential, session, and project access errors.

### Session and Utility Helpers

- **Location:** [`prowler/providers/gcp/lib/`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/lib/)
- **Purpose:** Helpers for argument parsing, mutelist management, and other cross-cutting concerns.

## Retry Configuration

GCP services implement automatic retry functionality for rate limiting errors (HTTP 429). This is configured centrally and must be included in all API calls:

### Required Implementation

```python
from prowler.providers.gcp.config import DEFAULT_RETRY_ATTEMPTS

# In discovery.build()
client = discovery.build(
    service, version, credentials=credentials,
    num_retries=DEFAULT_RETRY_ATTEMPTS
)

# In request.execute()
response = request.execute(num_retries=DEFAULT_RETRY_ATTEMPTS)
```

### Configuration

- **Default Value**: 3 attempts (configurable in `prowler/providers/gcp/config.py`)
- **Command Line Flag**: `--gcp-retries-max-attempts` for runtime configuration
- **Error Types**: HTTP 429 and quota exceeded errors
- **Backoff Strategy**: Exponential backoff with randomization

### Example Service Implementation

```python
def _get_instances(self):
    for project_id in self.project_ids:
        try:
            client = discovery.build(
                "compute", "v1", credentials=self.credentials,
                num_retries=DEFAULT_RETRY_ATTEMPTS
            )
            request = client.instances().list(project=project_id)
            response = request.execute(num_retries=DEFAULT_RETRY_ATTEMPTS)
            # Process response...
        except Exception as error:
            logger.error(f"{error.__class__.__name__}: {error}")
```

## Specific Patterns in GCP Services

The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:

- Directly in the code, in location [`prowler/providers/gcp/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/gcp/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used across all GCP services.

### GCP Service Common Patterns

- Services communicate with GCP using the Google Cloud Python SDK, you can find the documentation with all the services [here](https://cloud.google.com/python/docs/reference).
- Every GCP service class inherits from `GCPService`, ensuring access to session, identity, configuration, and client utilities.
- The constructor (`__init__`) always calls `super().__init__` with the service name, provider, region (default "global"), and API version (default "v1"). Usually, the service name is the class name in lowercase, so it is called like `super().__init__(__class__.__name__, provider)`.
- Resource containers **must** be initialized in the constructor, typically as dictionaries keyed by resource ID and the value is the resource object.
- Only projects with the API enabled are included in the audit scope.
- Resource discovery and attribute collection can be parallelized using `self.__threading_call__`, typically by region/zone or resource.
- All GCP resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
- Each GCP API call is wrapped in try/except blocks, always logging errors.
- **Retry Configuration**: All `request.execute()` calls must include `num_retries=DEFAULT_RETRY_ATTEMPTS` for automatic retry on rate limiting errors (HTTP 429).
- Tags and additional attributes that cannot be retrieved from the default call should be collected and stored for each resource using dedicated methods and threading.

## Specific Patterns in GCP Checks

The GCP checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks:

- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/gcp/services/iam/iam_sa_user_managed_key_unused/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/gcp/services/iam/iam_sa_user_managed_key_unused))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new check is following the [GCP check implementation documentation](/developer-guide/checks#creating-a-check) and taking other similar checks as reference.

### Check Report Class

The `Check_Report_GCP` class models a single finding for a GCP resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.

#### Purpose

`Check_Report_GCP` extends the base report structure with GCP-specific fields, enabling detailed tracking of the resource, project, and location associated with each finding.

#### Constructor and Attribute Population

When you instantiate `Check_Report_GCP`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its GCP-specific attributes from the resource, using the following logic (in order of precedence):

- **`resource_id`**:
    - Uses the explicit `resource_id` argument if provided.
    - Otherwise, uses `resource.id` if present.
    - Otherwise, uses `resource.name` if present.
    - Defaults to an empty string if none are available.

- **`resource_name`**:
    - Uses the explicit `resource_name` argument if provided.
    - Otherwise, uses `resource.name` if present.
    - Defaults to an empty string.

- **`project_id`**:
    - Uses the explicit `project_id` argument if provided.
    - Otherwise, uses `resource.project_id` if present.
    - Defaults to an empty string.

- **`location`**:
    - Uses the explicit `location` argument if provided.
    - Otherwise, uses `resource.location` if present.
    - Otherwise, uses `resource.region` if present.
    - Defaults to "global" if none are available.

All these attributes can be overridden by passing the corresponding argument to the constructor. If the resource object does not contain the required attributes, you must set them manually.
Other attributes are inherited from the `Check_Report` class, from those you **always** have to set the `status` and `status_extended` attributes in the check logic.

#### Example Usage

```python
report = Check_Report_GCP(
    metadata=check_metadata,
    resource=resource_object,
    resource_id="custom-id",  # Optional override
    resource_name="custom-name",  # Optional override
    project_id="my-gcp-project",  # Optional override
    location="us-central1"  # Optional override
)
report.status = "PASS"
report.status_extended = "Resource is compliant."
```

---

### Developer Guide Github Details (`docs/developer-guide/github-details.mdx`)

---
title: 'GitHub Provider'
---

This page details the [GitHub](https://github.com/) provider implementation in Prowler.

By default, Prowler will audit the GitHub account - scanning all repositories, organizations, and applications that your configured credentials can access. To configure it, follow the [GitHub getting started guide](/user-guide/providers/github/getting-started-github).

## GitHub Provider Classes Architecture

The GitHub provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the GitHub-specific implementation, highlighting how the generic provider concepts are realized for GitHub in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).

### `GithubProvider` (Main Class)

- **Location:** [`prowler/providers/github/github_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/github_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for GitHub-specific logic, session management, credential validation, and configuration.
- **Key GitHub Responsibilities:**
    - Initializes and manages GitHub sessions (supports Personal Access Token, OAuth App, and GitHub App authentication).
    - Validates credentials and sets up the GitHub identity context.
    - Loads and manages configuration, mutelist, and fixer settings.
    - Provides properties and methods for downstream GitHub service classes to access session, identity, and configuration data.

### Data Models

- **Location:** [`prowler/providers/github/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/models.py)
- **Purpose:** Define structured data for GitHub identity, session, and output options.
- **Key GitHub Models:**
    - `GithubSession`: Holds authentication tokens and keys for the session.
    - `GithubIdentityInfo`, `GithubAppIdentityInfo`: Store account or app identity metadata.

### `GithubService` (Service Base Class)

- **Location:** [`prowler/providers/github/lib/service/service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/lib/service/service.py)
- **Purpose:** Abstract base class for all GitHub service-specific classes.
- **Key GitHub Responsibilities:**
    - Receives a `GithubProvider` instance to access session, identity, and configuration.
    - Manages GitHub API clients for the authenticated user or app.
    - Exposes common audit context (`audit_config`, `fixer_config`) to subclasses.

### Exception Handling

- **Location:** [`prowler/providers/github/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for GitHub-specific error handling, such as credential and session errors.

### Session and Utility Helpers

- **Location:** [`prowler/providers/github/lib/`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/lib/)
- **Purpose:** Helpers for argument parsing, mutelist management, and other cross-cutting concerns.

## Specific Patterns in GitHub Services

The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:

- Directly in the code, in location [`prowler/providers/github/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/github/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and by taking other already implemented services as reference.

### GitHub Service Common Patterns

- Services communicate with GitHub using the PyGithub Python SDK. See the [official documentation](https://pygithub.readthedocs.io/).
- Every GitHub service class inherits from `GithubService`, ensuring access to session, identity, configuration, and client utilities.
- The constructor (`__init__`) always calls `super().__init__` with the service name and provider (e.g. `super().__init__(__class__.__name__, provider))`). Ensure that the service name in PyGithub is the same that you use in the constructor. Usually is used the `__class__.__name__` to get the service name because it is the same as the class name.
- Resource containers **must** be initialized in the constructor, typically as dictionaries keyed by resource ID or name.
- All GitHub resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
- GitHub API calls are wrapped in try/except blocks, always logging errors.

## Specific Patterns in GitHub Checks

The GitHub checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks in:

- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/github/services/repository/repository_secret_scanning_enabled/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/github/services/repository/repository_secret_scanning_enabled))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new check is the [GitHub check implementation documentation](/developer-guide/checks#creating-a-check) and by taking other checks as reference.

### Check Report Class

The `CheckReportGithub` class models a single finding for a GitHub resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.

#### Purpose

`CheckReportGithub` extends the base report structure with GitHub-specific fields, enabling detailed tracking of the resource, name, and owner associated with each finding.

#### Constructor and Attribute Population

When you instantiate `CheckReportGithub`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its GitHub-specific attributes from the resource, using the following logic (in order of precedence):

- **`resource_id`**:
    - Uses the explicit `resource_id` argument if provided.
    - Otherwise, uses `resource.id` if present.
    - Defaults to an empty string if not available.

- **`resource_name`**:
    - Uses the explicit `resource_name` argument if provided.
    - Otherwise, uses `resource.name` if present.
    - Defaults to an empty string if not available.

- **`owner`**:
    - Uses the explicit `owner` argument if provided.
    - Otherwise, uses `resource.owner` for repositories and `resource.name` for organizations.
    - Defaults to an empty string if not available.

If the resource object does not contain the required attributes, you must set them manually in the check logic.

Other attributes are inherited from the `Check_Report` class, from which you **always** have to set the `status` and `status_extended` attributes in the check logic.

#### Example Usage

```python
report = CheckReportGithub(
    metadata=check_metadata,
    resource=resource_object
)
report.status = "PASS"
report.status_extended = "Resource is compliant."
```

---

### Developer Guide Integrations (`docs/developer-guide/integrations.mdx`)

---
title: 'Creating a New Integration'
---

## Introduction

Integrating Prowler with external tools enhances its functionality and enables seamless workflow automation. Prowler supports a variety of integrations to optimize security assessments and reporting.

### Supported Integration Targets

- Messaging Platforms – Example: Slack

- Project Management Tools – Example: Jira

- Cloud Services – Example: AWS Security Hub

### Integration Guidelines
To integrate Prowler with a specific product:

Refer to the [Prowler Developer Guide](https://docs.prowler.com/projects/prowler-open-source/en/latest/) to understand its architecture and integration mechanisms.

* Identify the most suitable integration method for the intended platform.

## Steps to Create an Integration

### Defining the Integration Purpose

* Before implementing an integration, clearly define its objective. Common purposes include:

    * Sending Prowler findings to a platform for alerting, tracking, or further analysis.
    * For inspiration and implementation examples, please review the existing integrations in the [`prowler/lib/outputs`](https://github.com/prowler-cloud/prowler/tree/master/prowler/lib/outputs) folder.

### Developing the Integration

* Script Development:

    * Write a script to process Prowler’s output and interact with the target platform’s API.
    * If the goal is to send findings, parse Prowler’s results and use the platform’s API to create entries or notifications.

* Configuration:

    * Ensure the script supports environment-specific settings, such as:

        - API endpoints

        - Authentication tokens

        - Any necessary configurable parameters.

### Fundamental Structure

* Integration Class:

    * To implement an integration, create a class that encapsulates the required attributes and methods for interacting with the target platform. Example: Jira Integration

    ```
/* Detailed source-code truncated for AI context efficiency. */
```

* Test Connection Method:

    * Validating Credentials or Tokens

        To ensure a successful connection to the target platform, implement a method that validates authentication credentials or tokens.

    #### Method Implementation

    The following example demonstrates the `test_connection` method for the `Jira` class:

    ```
/* Detailed source-code truncated for AI context efficiency. */
```

* Send Findings Method:

    * Add a method to send Prowler findings to the target platform, adhering to its API specifications.

    #### Method Implementation

    The following example demonstrates the `send_findings` method for the `Jira` class:

    ```
/* Detailed source-code truncated for AI context efficiency. */
```

### Testing the Integration

* Conduct integration testing in a controlled environment to validate expected behavior. Ensure the following:

    * Transmission Accuracy – Verify that Prowler findings are correctly sent and processed by the target platform.
    * Error Handling – Simulate edge cases to assess robustness and failure recovery mechanisms.

### Documentation

* Ensure the following elements are included:

    * Setup Instructions – List all necessary dependencies and installation steps.
    * Configuration Details – Specify required environment variables, authentication steps, etc.
    * Example Use Cases – Provide practical scenarios demonstrating functionality.
    * Troubleshooting Guide – Document common issues and resolution steps.
    * Comprehensive and clear documentation improves maintainability and simplifies onboarding.

---

### Developer Guide Introduction (`docs/developer-guide/introduction.mdx`)

---
title: 'Introduction to developing in Prowler'
---

Thanks for your interest in contributing to Prowler!

Prowler can be extended in various ways. This guide provides the different ways to contribute and how to get started.

<Warning>
Maintainers will assess whether a change fits the project roadmap and scope before merging.
</Warning>

## Contributing to Prowler

### Review Current Issues
Check out our [GitHub Issues](https://github.com/prowler-cloud/prowler/issues) page for ideas to contribute.
<Columns cols={2}>
  <Card title="Good First Issue" icon="github" href="https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22">
    We tag issues as `good first issue` for new contributors. These are typically well-defined and manageable in scope.
  </Card>
  <Card title="Help Wanted" icon="github" href="https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3A%22help%20wanted%22">
    We tag issues as `help wanted` for other issues that require more time to complete.
  </Card>
</Columns>

### Expand Prowler's Capabilities
Prowler is constantly evolving. Contributions to checks, services, or integrations help improve the tool for everyone. Here is how to get involved:

<Columns cols={2}>
  <Card title="Adding New Checks" icon="shield" href="/developer-guide/checks">
    Want to improve Prowler's detection capabilities for your favorite cloud provider? You can contribute by writing new checks.
  </Card>
  <Card title="Adding New Services" icon="server" href="/developer-guide/services">
    One key service for your favorite cloud provider is missing? Add it to Prowler! Do not forget to include relevant checks to validate functionality.
  </Card>
  <Card title="Adding New Providers" icon="cloud" href="/developer-guide/provider">
    If you would like to extend Prowler to work with a new cloud provider, this typically involves setting up new services and checks to ensure compatibility.
  </Card>
  <Card title="Adding New Compliance Frameworks" icon="scale-balanced" href="/developer-guide/security-compliance-framework">
    Need to ensure Prowler supports a specific compliance framework? Add new security compliance frameworks to map checks against regulatory or industry standards.
  </Card>
  <Card title="Adding New Output Formats" icon="file" href="/developer-guide/outputs">
    Want to tailor how results are displayed or exported? You can add custom output formats.
  </Card>
  <Card title="Adding New Integrations" icon="link" href="/developer-guide/integrations">
    Prowler can work with other tools and platforms through integrations.
  </Card>
  <Card title="Adding New Attack Paths Queries" icon="diagram-project" href="/developer-guide/attack-paths-queries">
    Want to detect new privilege escalation or exposure patterns? Contribute read-only openCypher queries that traverse the cloud graph.
  </Card>
  <Card title="Proposing or Implementing Features" icon="lightbulb" href="https://github.com/prowler-cloud/prowler/issues/new?template=feature-request.yml">
    Propose brand-new features or enhancements to existing ones, or help implement community-requested improvements.
  </Card>
</Columns>

### Improve Documentation
Help make Prowler more accessible by enhancing our documentation, fixing typos, or adding examples/tutorials.

<Columns cols={2}>
  <Card title="Documentation Guide" icon="book" href="/developer-guide/documentation">
    Enhance our documentation, fix typos, or add examples/tutorials.
  </Card>
</Columns>

### Bug Fixes
If you find any issues or bugs, you can report them in the [GitHub Issues](https://github.com/prowler-cloud/prowler/issues) page and if you want you can also fix them.

<Columns cols={2}>
  <Card title="Report a Bug" icon="bug" href="https://github.com/prowler-cloud/prowler/issues/new?template=bug_report.yml">
    Report or fix issues or bugs.
  </Card>
</Columns>

Remember, our community is here to help! If you need guidance, do not hesitate to ask questions in the issues or join our [<Icon icon="slack" /> Slack workspace](https://goto.prowler.com/slack).



## Setting Up Your Development Environment

### Prerequisites

Before proceeding, ensure the following:

- Git is installed.
- Python 3.10 or higher is installed.
- `uv` is installed to manage dependencies.

### Forking the Prowler Repository

Fork the Prowler GitHub repository to contribute to Prowler. This allows proposing changes, submitting new features, and fixing bugs. For guidance on forking, refer to the [official GitHub documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo?tool=webui#forking-a-repository).

### Cloning Your Forked Repository

Once your fork is created, clone it using the following commands (replace `<your-github-user>` with your GitHub username):

```
git clone https://github.com/<your-github-user>/prowler
cd prowler
```

### Dependency Management and Environment Isolation

To prevent conflicts between environments, we recommend using [`uv`](https://docs.astral.sh/uv/), a fast Python package and project manager. Install it by following the [official instructions](https://docs.astral.sh/uv/getting-started/installation/).

### Installing Dependencies

To install all required dependencies, including those needed for development, run:

```
uv sync
source .venv/bin/activate
```

### Running the Local API Development Stack

For API development, Prowler provides a Makefile-based local stack in addition to the manual and Docker Compose workflows documented in the API README. PostgreSQL, Valkey, and Neo4j run with Docker Compose, while Django and the Celery worker run natively through `uv`.

Before using this method, ensure `docker compose`, `tmux`, and `uv` are installed.

This workflow is designed for macOS and should also work on Linux when Docker, `tmux`, and `uv` are available. Windows requires script changes before it can be supported.

To start the local API stack, run:

```shell
make dev
```

This command starts the required services, creates a `tmux` session with panes for the API, worker, and PostgreSQL logs, waits until the API responds, and prints the API URL and log file paths. The API is available at:

```text
http://localhost:8080/api/v1
```

Use these commands to manage the stack:

```shell
make dev-setup   # Bootstrap dependencies, migrations, and fixtures
make dev-attach  # Attach to the tmux session
make dev-launch  # Start the stack on fixed ports and attach
make dev-stop    # Stop the tmux session and containers
make dev-clean   # Remove stopped development containers
make dev-wipe    # Stop everything and delete local development data
make dev-status  # Show development container status
```

The UI is not started by this workflow. Start it separately by following the UI development instructions in the `ui/` directory.

### Pre-Commit Hooks

This repository uses Git pre-commit hooks managed by the [prek](https://prek.j178.dev/) tool, it is installed with `uv sync`. Next, run the following command in the root of this repository:

```shell
prek install
```

Successful installation should produce the following output:

```shell
prek installed at `.git/hooks/pre-commit`
```

<Warning>
If pre-commit hooks were previously installed, run `prek install --overwrite` to replace the existing hook. Otherwise, both tools will run on each commit.
</Warning>

#### Enable TruffleHog as a Pre-Push Hook

By default, only `pre-commit` hooks are installed. To enable [`TruffleHog`](https://github.com/trufflesecurity/trufflehog) secret scanning on every push, install the `pre-push` hook type explicitly:

```shell
prek install --hook-type pre-push
```

Successful installation should produce the following output:

```shell
prek installed at `.git/hooks/pre-push`
```

Once installed, TruffleHog runs before each push and blocks the operation when verified secrets are detected.

### Code Quality and Security Checks

Before merging pull requests, several automated checks and utilities ensure code security and updated dependencies:

<Note>
These should have been already installed if `uv sync` was already run.

</Note>
- [`bandit`](https://pypi.org/project/bandit/) for code security review.
- [`osv-scanner`](https://github.com/google/osv-scanner) and [`dependabot`](https://github.com/features/security) for dependencies.
- [`hadolint`](https://github.com/hadolint/hadolint) and [`dockle`](https://github.com/goodwithtech/dockle) for container security.
- [`Snyk`](https://docs.snyk.io/integrations/snyk-container-integrations/container-security-with-docker-hub-integration) for container security in Docker Hub.
- [`clair`](https://github.com/quay/clair) for container security in Amazon ECR.
- [`vulture`](https://pypi.org/project/vulture/), [`flake8`](https://pypi.org/project/flake8/), [`black`](https://pypi.org/project/black/), and [`pylint`](https://pypi.org/project/pylint/) for formatting and best practices.

Additionally, ensure the latest version of [`TruffleHog`](https://github.com/trufflesecurity/trufflehog) is installed to scan for sensitive data in the code. Follow the official [installation guide](https://github.com/trufflesecurity/trufflehog?tab=readme-ov-file#floppy_disk-installation) for setup.

### AI-Driven Contributions

If you are using AI assistants to help with your contributions, Prowler provides specialized resources to enhance AI-driven development:

- **Prowler MCP Server**: The [Prowler MCP Server](/getting-started/products/prowler-mcp) provides AI assistants with access to the entire Prowler ecosystem, including security checks, compliance frameworks, documentation, and more. This enables AI tools to better understand Prowler's architecture and help you create contributions that align with project standards.

- **AGENTS.md Files**: Each component of the Prowler monorepo includes an `AGENTS.md` file that contains specific guidelines for AI agents working on that component. These files provide context about project structure, coding standards, and best practices. When working on a specific component, refer to the relevant `AGENTS.md` file (e.g., `prowler/AGENTS.md`, `ui/AGENTS.md`, `api/AGENTS.md`) to ensure your AI assistant follows the appropriate guidelines.

- **AI Skills System**: The [AI Skills system](/developer-guide/ai-skills) provides on-demand patterns, templates, and best practices for AI agents. Skills help AI assistants understand Prowler's conventions and generate code that aligns with project standards. The skills are located in the `skills/` directory and are registered in the `AGENTS.md` files.

These resources help ensure that AI-assisted contributions maintain consistency with Prowler's codebase and development practices.

### Dependency Management

All dependencies are listed in the `pyproject.toml` file.

The SDK keeps direct dependencies pinned to exact versions, while `uv.lock` records the full resolved dependency tree and the artifact hashes for every package. Use `uv sync` from the lock file instead of ad-hoc `pip` installs when you need a reproducible environment.

For proper code documentation, refer to the following and follow the code documentation practices presented there: [Google Python Style Guide - Comments and Docstrings](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings).

<Note>
If you encounter issues when committing to the Prowler repository, use the `--no-verify` flag with the `git commit` command.

</Note>
### Repository Folder Structure

The Prowler codebase layout helps quickly locate where to add new features, checks, or integrations. The following is a high-level overview from the root of the repository:

```
prowler/
├── prowler/           # Main source code for Prowler SDK (CLI, providers, services, checks, compliances, config, etc.)
├── api/               # API server and related code
├── dashboard/         # Local Dashboard extracted from the CLI output
├── ui/                # Web UI components
├── util/              # Utility scripts and helpers
├── tests/             # Prowler SDK test suite
├── docs/              # Documentation, including this guide
├── examples/          # Example output formats for providers and scripts
├── permissions/       # Permission-related files and policies
├── contrib/           # Community-contributed scripts or modules
├── kubernetes/        # Kubernetes deployment files
├── .github/           # GitHub-related files (workflows, issue templates, etc.)
├── pyproject.toml     # Python project configuration (uv)
├── uv.lock            # uv lock file
├── README.md          # Project overview and getting started
├── Makefile           # Common development commands
├── Dockerfile         # SDK Docker container
├── docker-compose.yml # Prowler Local Server Docker compose
└── ...                # Other supporting files
```

## Sending the Pull Request

When creating or reviewing a pull request in <Icon icon="github" /> [Prowler](https://github.com/prowler-cloud/prowler), follow [this template](https://github.com/prowler-cloud/prowler/blob/master/.github/pull_request_template.md) and fill it with the relevant information:

- **Context** and **Description** of the change: This will help the reviewers to understand the change and the purpose of the pull request.
- **Steps to review**: A detailed description of how to review the change.
- **Checklist**: A mandatory checklist of the things that should be reviewed before merging the pull request.

## Contribution Appreciation

If you enjoy swag, we'd love to thank you for your contribution with laptop stickers or other Prowler merchandise!

To request swag: Share your pull request details in our [Slack workspace](https://goto.prowler.com/slack).

You can also reach out to Toni de la Fuente on [Twitter](https://twitter.com/ToniBlyx)—his DMs are open!

## Testing a Pull Request from a Specific Branch

To test Prowler from a specific branch (for example, to try out changes from a pull request before it is merged), you can use `pipx` to install directly from GitHub:

```sh
pipx install "git+https://github.com/prowler-cloud/prowler.git@branch-name"
```

Replace `branch-name` with the name of the branch you want to test. This will install Prowler in an isolated environment, allowing you to try out the changes safely.

For more details on testing go to the [Testing section](/developer-guide/unit-testing) of this documentation.

---

### Developer Guide Kubernetes Details (`docs/developer-guide/kubernetes-details.mdx`)

---
title: 'Kubernetes Provider'
---

This page details the [Kubernetes](https://kubernetes.io/) provider implementation in Prowler.

By default, Prowler will audit all namespaces in the Kubernetes cluster accessible by the configured context. To configure it, see the [In-Cluster Execution](/user-guide/providers/kubernetes/getting-started-k8s#in-cluster-execution) or [Non In-Cluster Execution](/user-guide/providers/kubernetes/getting-started-k8s#non-in-cluster-execution) guides.

## Kubernetes Provider Classes Architecture

The Kubernetes provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the Kubernetes-specific implementation, highlighting how the generic provider concepts are realized for Kubernetes in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).

### `KubernetesProvider` (Main Class)

- **Location:** [`prowler/providers/kubernetes/kubernetes_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/kubernetes_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for Kubernetes-specific logic, session management, context and namespace discovery, credential validation, and configuration.
- **Key Kubernetes Responsibilities:**
    - Initializes and manages Kubernetes sessions (supports kubeconfig file or content, context selection, and namespace scoping).
    - Validates credentials and sets up the Kubernetes identity context.
    - Loads and manages configuration, mutelist, and fixer settings.
    - Discovers accessible namespaces and cluster metadata.
    - Provides properties and methods for downstream Kubernetes service classes to access session, identity, and configuration data.

### Data Models

- **Location:** [`prowler/providers/kubernetes/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/models.py)
- **Purpose:** Define structured data for Kubernetes identity and session info.
- **Key Kubernetes Models:**
    - `KubernetesIdentityInfo`: Holds Kubernetes identity metadata, such as context, cluster, and user.
    - `KubernetesSession`: Stores the Kubernetes API client and context information.

### `KubernetesService` (Service Base Class)

- **Location:** [`prowler/providers/kubernetes/lib/service/service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/lib/service/service.py)
- **Purpose:** Abstract base class that all Kubernetes service-specific classes inherit from. This implements the generic service pattern (described in [service page](/developer-guide/services#service-base-class)) specifically for Kubernetes.
- **Key Kubernetes Responsibilities:**
    - Receives a `KubernetesProvider` instance to access session, identity, and configuration.
    - Manages the Kubernetes API client and context.
    - Provides a `__threading_call__` method to make API calls in parallel by resource.
    - Exposes common audit context (`context`, `api_client`, `audit_config`, `fixer_config`) to subclasses.

### Exception Handling

- **Location:** [`prowler/providers/kubernetes/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for Kubernetes-specific error handling, such as session, API, and configuration errors.

### Session and Utility Helpers

- **Location:** [`prowler/providers/kubernetes/lib/`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/lib/)
- **Purpose:** Helpers for argument parsing, mutelist management, and other cross-cutting concerns.

## Specific Patterns in Kubernetes Services

The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:

- Directly in the code, in location [`prowler/providers/kubernetes/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/kubernetes/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other already implemented services as reference.

### Kubernetes Service Common Patterns

- Services communicate with Kubernetes using the Kubernetes Python SDK. See the [official documentation](https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md/).
- Every Kubernetes service class inherits from `KubernetesService`, ensuring access to session, identity, configuration, and client utilities.
- The constructor (`__init__`) always calls `super().__init__` with the provider object, and initializes resource containers (typically as dictionaries keyed by resource UID or name).
- Resource discovery and attribute collection can be parallelized using `self.__threading_call__`.
- All Kubernetes resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
- Kubernetes API calls are wrapped in try/except blocks, always logging errors.
- Additional attributes that cannot be retrieved from the default call should be collected and stored for each resource using dedicated methods and threading.

## Specific Patterns in Kubernetes Checks

The Kubernetes checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks in:

- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/kubernetes/services/rbac/rbac_minimize_wildcard_use_roles/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/kubernetes/services/rbac/rbac_minimize_wildcard_use_roles))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new check is following the [Kubernetes check implementation documentation](/developer-guide/checks#creating-a-check) and taking other checks as reference.

### Check Report Class

The `Check_Report_Kubernetes` class models a single finding for a Kubernetes resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.

#### Purpose

`Check_Report_Kubernetes` extends the base report structure with Kubernetes-specific fields, enabling detailed tracking of the resource, name, and namespace associated with each finding.

#### Constructor and Attribute Population

When you instantiate `Check_Report_Kubernetes`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its Kubernetes-specific attributes from the resource, using the following logic (in order of precedence):

- **`resource_id`**:
    - Uses `resource.uid` if present.
    - Otherwise, uses `resource.name` if present.
    - Defaults to an empty string if none are available.

- **`resource_name`**:
    - Uses `resource.name` if present.
    - Defaults to an empty string if not available.

- **`namespace`**:
    - Uses `resource.namespace` if present.
    - Defaults to "cluster-wide" for cluster-scoped resources.

If the resource object does not contain the required attributes, you must set them manually in the check logic.

Other attributes are inherited from the `Check_Report` class, from which you **always** have to set the `status` and `status_extended` attributes in the check logic.

#### Example Usage

```python
report = Check_Report_Kubernetes(
    metadata=check_metadata,
    resource=resource_object
)
report.status = "PASS"
report.status_extended = "Resource is compliant."
```

---

### Developer Guide Lighthouse Architecture (`docs/developer-guide/lighthouse-architecture.mdx`)

---
title: 'Lighthouse AI Architecture'
---

This document describes the internal architecture of Prowler Lighthouse AI, enabling developers to understand how components interact and where to add new functionality.

<Info>
**Looking for user documentation?** See:
- [Lighthouse AI Overview](/getting-started/products/prowler-lighthouse-ai) - Capabilities and FAQs
- [How Lighthouse AI Works](/user-guide/tutorials/prowler-app-lighthouse) - Configuration and usage
- [Multi-LLM Provider Setup](/user-guide/tutorials/prowler-app-lighthouse-multi-llm) - Provider configuration
</Info>

## Architecture Overview

Lighthouse AI operates as a Langchain-based agent that connects Large Language Models (LLMs) with Prowler security data through the Model Context Protocol (MCP).



### Three-Tier Architecture

The system follows a three-tier architecture:

1. **Frontend (Next.js)**: Chat interface, message rendering, model selection
2. **API Route**: Request handling, authentication, stream transformation
3. **Langchain Agent**: LLM orchestration, tool calling through MCP

### Request Flow

When a user sends a message through the Lighthouse chat interface, the system processes it through several stages:

1. **User Submits a Message**.
   The chat component (`ui/components/lighthouse/chat.tsx`) captures the user's question (e.g., "What are my critical findings in AWS?") and sends it as an HTTP POST request to the backend API route.

2. **Authentication and Context Assembly**.
   The API route (`ui/app/api/lighthouse/analyst/route.ts`) validates the user's session, extracts the JWT token (stored via `auth-context.ts`), and gathers context including the tenant's business context and current security posture data (assembled in `data.ts`).

3. **Agent Initialization**.
   The workflow orchestrator (`ui/lib/lighthouse/workflow.ts`) creates a Langchain agent configured with:
   - The selected LLM, instantiated through the factory (`llm-factory.ts`)
   - A system prompt containing available tools and instructions (`system-prompt.ts`)
   - Two meta-tools (`describe_tool` and `execute_tool`) for accessing Prowler data

4. **LLM Reasoning and Tool Calling**.
   The agent sends the conversation to the LLM, which decides whether to respond directly or call tools to fetch data. When tools are needed, the meta-tools in `ui/lib/lighthouse/tools/meta-tool.ts` interact with the MCP client (`mcp-client.ts`) to:
   - First call `describe_tool` to understand the tool's parameters
   - Then call `execute_tool` to retrieve data from the MCP Server
   - Continue reasoning with the returned data

5. **Streaming Response**.
   As the LLM generates its response, the stream handler (`ui/lib/lighthouse/analyst-stream.ts`) transforms Langchain events into UI-compatible messages and streams tokens back to the browser in real-time using Server-Sent Events. The stream includes both text tokens and tool execution events (displayed as "chain of thought").

6. **Message Rendering**.
   The frontend receives the stream and renders it through `message-item.tsx` with markdown formatting. Any tool calls that occurred during reasoning are displayed via `chain-of-thought-display.tsx`.

## Frontend Components

Frontend components reside in `ui/components/lighthouse/` and handle the chat interface and configuration workflows.

### Core Components

| Component | Location | Purpose |
|-----------|----------|---------|
| `chat.tsx` | `ui/components/lighthouse/` | Main chat interface managing message history and input handling |
| `message-item.tsx` | `ui/components/lighthouse/` | Individual message rendering with markdown support |
| `select-model.tsx` | `ui/components/lighthouse/` | Model and provider selection dropdown |
| `chain-of-thought-display.tsx` | `ui/components/lighthouse/` | Displays tool calls and reasoning steps during execution |

### Configuration Components

| Component | Location | Purpose |
|-----------|----------|---------|
| `lighthouse-settings.tsx` | `ui/components/lighthouse/` | Settings panel for business context and preferences |
| `connect-llm-provider.tsx` | `ui/components/lighthouse/` | Provider connection workflow |
| `llm-providers-table.tsx` | `ui/components/lighthouse/` | Provider management table |
| `forms/delete-llm-provider-form.tsx` | `ui/components/lighthouse/forms/` | Provider deletion confirmation dialog |

### Supporting Components

| Component | Location | Purpose |
|-----------|----------|---------|
| `banner.tsx` / `banner-client.tsx` | `ui/components/lighthouse/` | Status banners and notifications |
| `workflow/` | `ui/components/lighthouse/workflow/` | Multi-step configuration workflows |
| `ai-elements/` | `ui/components/lighthouse/ai-elements/` | Custom UI primitives for chat interface (input, select, dropdown, tooltip) |

## Library Code

Core library code resides in `ui/lib/lighthouse/` and handles agent orchestration, MCP communication, and stream processing.

### Workflow Orchestrator

**Location:** `ui/lib/lighthouse/workflow.ts`

The workflow module serves as the core orchestrator, responsible for:

- Initializing the Langchain agent with system prompt and tools
- Loading tenant configuration (default provider, model, business context)
- Creating the LLM instance through the factory
- Generating dynamic tool listings from available MCP tools

```typescript
// Simplified workflow initialization
export async function initLighthouseWorkflow(runtimeConfig?: RuntimeConfig) {
  await initializeMCPClient();

  const toolListing = generateToolListing();
  const systemPrompt = LIGHTHOUSE_SYSTEM_PROMPT_TEMPLATE.replace(
    "{{TOOL_LISTING}}",
    toolListing,
  );

  const llm = createLLM({
    provider: providerType,
    model: modelId,
    credentials,
    // ...
  });

  return createAgent({
    model: llm,
    tools: [describeTool, executeTool],
    systemPrompt,
  });
}
```

### MCP Client Manager

**Location:** `ui/lib/lighthouse/mcp-client.ts`

The MCP client manages connections to the Prowler MCP Server using a singleton pattern:

- **Connection Management**: Retry logic with configurable attempts and delays
- **Tool Discovery**: Fetches available tools from MCP server on initialization
- **Authentication Injection**: Automatically adds JWT tokens to `prowler_*` tool calls
- **Reconnection**: Supports forced reconnection after server restarts

Key constants:
- `MAX_RETRY_ATTEMPTS`: 3 connection attempts
- `RETRY_DELAY_MS`: 2000ms between retries
- `RECONNECT_INTERVAL_MS`: 5 minutes before retry after failure

```typescript
// Authentication injection for core prowler_ tools (Hub/Docs excluded)
private handleBeforeToolCall = ({ name, args }) => {
  // Only inject auth for prowler_* tools (user-specific data).
  // The legacy prowler_app_ prefix is also accepted for a resilient rollout.
  if (
    !name.startsWith("prowler_") &&
    !name.startsWith("prowler_app_")
  ) {
    return { args };
  }

  const accessToken = getAuthContext();
  return {
    args,
    headers: { Authorization: `Bearer ${accessToken}` },
  };
};
```

### Meta-Tools

**Location:** `ui/lib/lighthouse/tools/meta-tool.ts`

Instead of registering all MCP tools directly with the agent, Lighthouse uses two meta-tools for dynamic tool discovery and execution:

| Tool | Purpose |
|------|---------|
| `describe_tool` | Retrieves full schema and parameter details for a specific tool |
| `execute_tool` | Executes a tool with provided parameters |

This pattern reduces the number of tools the LLM must track while maintaining access to all MCP capabilities.

### Additional Library Modules

| Module | Location | Purpose |
|--------|----------|---------|
| `analyst-stream.ts` | `ui/lib/lighthouse/` | Transforms Langchain stream events to UI message format |
| `llm-factory.ts` | `ui/lib/lighthouse/` | Creates LLM instances for OpenAI, Bedrock, and OpenAI-compatible providers |
| `system-prompt.ts` | `ui/lib/lighthouse/` | System prompt template with dynamic tool listing injection |
| `auth-context.ts` | `ui/lib/lighthouse/` | AsyncLocalStorage for JWT token propagation across async boundaries |
| `types.ts` | `ui/lib/lighthouse/` | TypeScript type definitions |
| `constants.ts` | `ui/lib/lighthouse/` | Configuration constants and error messages |
| `utils.ts` | `ui/lib/lighthouse/` | Message conversion and model parameter extraction |
| `validation.ts` | `ui/lib/lighthouse/` | Input validation utilities |
| `data.ts` | `ui/lib/lighthouse/` | Current data section generation for context enrichment |

## API Route

**Location:** `ui/app/api/lighthouse/analyst/route.ts`

The API route handles chat requests and manages the streaming response pipeline:

1. **Request Parsing**: Extracts messages, model, and provider from request body
2. **Authentication**: Validates session and extracts access token
3. **Context Assembly**: Gathers business context and current data
4. **Agent Initialization**: Creates Langchain agent with runtime configuration
5. **Stream Processing**: Transforms agent events to UI-compatible format
6. **Error Handling**: Captures errors with Sentry integration

```typescript
export async function POST(req: Request) {
  const { messages, model, provider } = await req.json();

  const session = await auth();
  if (!session?.accessToken) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  return await authContextStorage.run(accessToken, async () => {
    const app = await initLighthouseWorkflow(runtimeConfig);
    const agentStream = app.streamEvents({ messages }, { version: "v2" });

    // Transform stream events to UI format
    const stream = new ReadableStream({
      async start(controller) {
        for await (const streamEvent of agentStream) {
          // Handle on_chat_model_stream, on_tool_start, on_tool_end, etc.
        }
      },
    });

    return createUIMessageStreamResponse({ stream });
  });
}
```

## Backend Components

Backend components handle LLM provider configuration, model management, and credential storage.

### Database Models

**Location:** `api/src/backend/api/models.py`

| Model | Purpose |
|-------|---------|
| `LighthouseProviderConfiguration` | Per-tenant LLM provider credentials (encrypted with Fernet) |
| `LighthouseTenantConfiguration` | Tenant-level settings including business context and default provider/model |
| `LighthouseProviderModels` | Available models per provider configuration |

All models implement Row-Level Security (RLS) for tenant isolation.

#### LighthouseProviderConfiguration

Stores provider-specific credentials for each tenant:

- **provider_type**: `openai`, `bedrock`, or `openai_compatible`
- **credentials**: Encrypted JSON containing API keys or AWS credentials
- **base_url**: Custom endpoint for OpenAI-compatible providers
- **is_active**: Connection validation status

#### LighthouseTenantConfiguration

Stores tenant-wide Lighthouse settings:

- **business_context**: Optional context for personalized responses
- **default_provider**: Default LLM provider type
- **default_models**: JSON mapping provider types to default model IDs

#### LighthouseProviderModels

Catalogs available models for each provider:

- **model_id**: Provider-specific model identifier
- **model_name**: Human-readable display name
- **default_parameters**: Optional model-specific parameters

### Background Jobs

**Location:** `api/src/backend/tasks/jobs/lighthouse_providers.py`

#### check_lighthouse_provider_connection

Validates provider credentials by making a test API call:

- OpenAI: Lists models via `client.models.list()`
- Bedrock: Lists foundation models via `bedrock_client.list_foundation_models()`
- OpenAI-compatible: Lists models via custom base URL

Updates `is_active` status based on connection result.

#### refresh_lighthouse_provider_models

Synchronizes available models from provider APIs:

- Fetches current model catalog from provider
- Filters out non-chat models (DALL-E, Whisper, TTS, embeddings)
- Upserts model records in `LighthouseProviderModels`
- Removes stale models no longer available

**Excluded OpenAI model prefixes:**
```python
EXCLUDED_OPENAI_MODEL_PREFIXES = (
    "dall-e", "whisper", "tts-", "sora",
    "text-embedding", "text-moderation",
    # Legacy models
    "text-davinci", "davinci", "curie", "babbage", "ada",
)
```

## MCP Server Integration

Lighthouse AI communicates with the Prowler MCP Server to access security data. For detailed MCP Server architecture, see [Extending the MCP Server](/developer-guide/mcp-server).

### Tool Namespacing

MCP tools are organized into three namespaces based on authentication requirements:

| Namespace | Auth Required | Description |
|-----------|---------------|-------------|
| `prowler_*` | Yes (JWT) | Prowler Cloud, Prowler Private Cloud, and Prowler Local Server tools for findings, providers, scans, resources |
| `prowler_hub_*` | No | Security checks catalog, compliance frameworks |
| `prowler_docs_*` | No | Documentation search and retrieval |

### Authentication Flow

1. User authenticates with Prowler Local Server, receiving a JWT token
2. Token is stored in session and propagated via `authContextStorage`
3. MCP client injects `Authorization: Bearer <token>` header for `prowler_*` calls
4. MCP Server validates token and applies RLS filtering

### Tool Execution Pattern

The agent uses meta-tools rather than direct tool registration:

```
Agent needs data → describe_tool("prowler_search_findings")
    → Returns parameter schema → execute_tool with parameters
    → MCP client adds auth header → MCP Server executes
    → Results returned to agent → Agent continues reasoning
```

## Extension Points

### Adding New LLM Providers

To add a new LLM provider:

1. **Frontend**: Update `ui/lib/lighthouse/llm-factory.ts` with provider-specific initialization
2. **Backend**: Add provider type to `LighthouseProviderConfiguration.LLMProviderChoices`
3. **Jobs**: Add credential extraction and model fetching in `lighthouse_providers.py`
4. **UI**: Add connection workflow in `ui/components/lighthouse/workflow/`

### Modifying System Prompt

The system prompt template lives in `ui/lib/lighthouse/system-prompt.ts`. The `{{TOOL_LISTING}}` placeholder is dynamically replaced with available MCP tools during agent initialization.

### Adding Stream Events

To handle new Langchain stream events, modify `ui/lib/lighthouse/analyst-stream.ts`. Current handlers include:

- `on_chat_model_stream`: Token-by-token text streaming
- `on_chat_model_end`: Model completion with tool call detection
- `on_tool_start`: Tool execution started
- `on_tool_end`: Tool execution completed

### Adding MCP Tools

See [Extending the MCP Server](/developer-guide/mcp-server) for detailed instructions on adding new tools to the Prowler MCP Server.

## Configuration

### Environment Variables

| Variable | Description |
|----------|-------------|
| `PROWLER_MCP_SERVER_URL` | MCP server endpoint (e.g., `https://mcp.prowler.com/mcp`) |

### Database Configuration

Provider credentials are stored encrypted in `LighthouseProviderConfiguration`:

- **OpenAI**: `{"api_key": "sk-..."}`
- **Bedrock**: `{"access_key_id": "...", "secret_access_key": "...", "region": "us-east-1"}` or `{"api_key": "...", "region": "us-east-1"}`
- **OpenAI-compatible**: `{"api_key": "..."}` with `base_url` field

### Tenant Configuration

Business context and default settings are stored in `LighthouseTenantConfiguration`:

```python
{
    "business_context": "Optional organization context for personalized responses",
    "default_provider": "openai",
    "default_models": {
        "openai": "gpt-4o",
        "bedrock": "anthropic.claude-3-5-sonnet-20240620-v1:0"
    }
}
```

## Related Documentation

<CardGroup cols={2}>
  <Card title="MCP Server Extension" icon="wrench" href="/developer-guide/mcp-server">
    Adding new tools to the Prowler MCP Server
  </Card>
  <Card title="Lighthouse AI Overview" icon="robot" href="/getting-started/products/prowler-lighthouse-ai">
    Capabilities, FAQs, and limitations
  </Card>
  <Card title="Multi-LLM Setup" icon="sliders" href="/user-guide/tutorials/prowler-app-lighthouse-multi-llm">
    Configuring multiple LLM providers
  </Card>
  <Card title="How Lighthouse Works" icon="gear" href="/user-guide/tutorials/prowler-app-lighthouse">
    User-facing architecture and setup guide
  </Card>
</CardGroup>

---

### Developer Guide Llm Details (`docs/developer-guide/llm-details.mdx`)

---
title: 'LLM Provider'
---

This page details the [Large Language Model (LLM)](https://en.wikipedia.org/wiki/Large_language_model) provider implementation in Prowler.

The LLM provider enables security testing of language models using red team techniques. By default, Prowler uses the built-in LLM configuration that targets OpenAI models with comprehensive security test suites. To configure it, follow the [LLM getting started guide](/user-guide/providers/llm/getting-started-llm).

## LLM Provider Classes Architecture

The LLM provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the LLM-specific implementation, highlighting how the generic provider concepts are realized for LLM security testing in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).

### Main Class

- **Location:** [`prowler/providers/llm/llm_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/llm/llm_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for LLM-specific logic, configuration management, and integration with promptfoo for red team testing.
- **Key LLM Responsibilities:**
    - Initializes and manages LLM configuration using promptfoo.
    - Validates configuration and sets up the LLM testing context.
    - Loads and manages red team test configuration, plugins, and target models.
    - Provides properties and methods for downstream LLM security testing.
    - Integrates with promptfoo for comprehensive LLM security evaluation.

### Data Models

- **Location:** [`prowler/providers/llm/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/llm/models.py)
- **Purpose:** Define structured data for LLM output options and configuration.
- **Key LLM Models:**
    - `LLMOutputOptions`: Customizes output filename logic for LLM-specific reporting.

### LLM Security Testing Integration

- **Location:** [`prowler/providers/llm/llm_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/llm/llm_provider.py)
- **Purpose:** Integrates with promptfoo for comprehensive LLM security testing.
- **Key LLM Responsibilities:**
    - Executes promptfoo red team evaluations against target LLMs.
    - Processes security test results and converts them to Prowler reports.
    - Manages test concurrency and progress tracking.
    - Handles real-time streaming of test results.

### Configuration Management

The LLM provider uses promptfoo configuration files to define:

- **Target Models**: The LLM models to test (e.g., OpenAI GPT, Anthropic Claude)
- **Red Team Plugins**: Security test suites (OWASP, MITRE, NIST, EU AI Act)
- **Test Parameters**: Concurrency, test counts, and evaluation criteria

### Default Configuration

Prowler includes a comprehensive default LLM configuration that:

- Targets OpenAI models by default
- Includes multiple security test frameworks (OWASP, MITRE, NIST, EU AI Act)
- Provides extensive test coverage for LLM security vulnerabilities
- Supports custom configuration for specific testing needs

## Specific Patterns in LLM Security Testing

The LLM provider implements security testing through integration with promptfoo, following these patterns:

### Red Team Testing Framework

- **Plugin-based Architecture**: Uses promptfoo plugins for different security test categories
- **Comprehensive Coverage**: Includes OWASP LLM Top 10, MITRE ATLAS, NIST AI Risk Management, and EU AI Act compliance
- **Real-Time Evaluation**: Streams test results as they are generated
- **Progress Tracking**: Provides detailed progress information during test execution

### Test Execution Flow

1. **Configuration Loading**: Loads promptfoo configuration with target models and test plugins
2. **Test Generation**: Generates security test cases based on configured plugins
3. **Concurrent Execution**: Runs tests with configurable concurrency limits
4. **Result Processing**: Converts promptfoo results to Prowler security reports
5. **Progress Monitoring**: Tracks and displays test execution progress

### Security Test Categories

The LLM provider supports comprehensive security testing across multiple frameworks:

- **OWASP LLM Top 10**: Covers prompt injection, data leakage, and model security
- **MITRE ATLAS**: Adversarial threat landscape for AI systems
- **NIST AI Risk Management**: AI system risk assessment and mitigation
- **EU AI Act**: European Union AI regulation compliance
- **Custom Tests**: Support for organization-specific security requirements

## Error Handling and Validation

The LLM provider includes comprehensive error handling for:

- **Configuration Validation**: Ensures valid promptfoo configuration files
- **Model Access**: Handles authentication and access issues with target LLMs
- **Test Execution**: Manages test failures and timeout scenarios
- **Result Processing**: Handles malformed or incomplete test results

## Integration with Prowler Ecosystem

The LLM provider seamlessly integrates with Prowler's existing infrastructure:

- **Output Formats**: Supports all Prowler output formats (JSON, CSV, HTML, etc.)
- **Compliance Frameworks**: Integrates with Prowler's compliance reporting
- **Fixer Integration**: Supports automated remediation recommendations
- **Dashboard Integration**: Compatible with Prowler Cloud and Prowler Local Server for centralized management

---

### Developer Guide M365 Details (`docs/developer-guide/m365-details.mdx`)

---
title: 'Microsoft 365 (M365) Provider'
---

This page details the [Microsoft 365 (M365)](https://www.microsoft.com/en-us/microsoft-365) provider implementation in Prowler.

By default, Prowler will audit the Microsoft Entra ID tenant and its supported services. To configure it, follow the [M365 getting started guide](/user-guide/providers/microsoft365/getting-started-m365).

---

## PowerShell Requirements for M365 Checks

> **Most Microsoft 365 checks in Prowler require PowerShell, not just the Microsoft Graph API.**

- **PowerShell is essential** for retrieving data from Exchange Online, Teams, Defender, Purview, and other M365 services. Many checks cannot be performed using only the Graph API.
- **PowerShell 7.4 or higher is required** (7.5 recommended). PowerShell 5.1 and earlier versions are not supported for M365 checks.
- **Required modules:**
    - [ExchangeOnlineManagement](https://www.powershellgallery.com/packages/ExchangeOnlineManagement/3.6.0) (≥ 3.6.0)
    - [MicrosoftTeams](https://www.powershellgallery.com/packages/MicrosoftTeams/6.6.0) (≥ 6.6.0)
- If you use Prowler Cloud or the official containers, PowerShell is pre-installed. For local or pip installations, you must install PowerShell and the modules yourself. See [Authentication: Supported PowerShell Versions](/user-guide/providers/microsoft365/authentication#supported-powershell-versions) and [Needed PowerShell Modules](/user-guide/providers/microsoft365/authentication#required-powershell-modules).
- For more details and troubleshooting, see [Use of PowerShell in M365](/user-guide/providers/microsoft365/use-of-powershell).

---

## M365 Provider Classes Architecture

The M365 provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the M365-specific implementation, highlighting how the generic provider concepts are realized for M365 in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).

### `M365Provider` (Main Class)

- **Location:** [`prowler/providers/m365/m365_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/m365_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for M365-specific logic, session management, credential validation, region/authority configuration, and identity context.
- **Key M365 Responsibilities:**
    - Initializes and manages M365 sessions (supports Service Principal, environment variables, Azure CLI, browser, and user/password authentication).
    - Validates credentials and sets up the M365 identity context.
    - Manages the Microsoft Graph API client and the PowerShell client.
    - Loads and manages configuration, mutelist, and fixer settings.
    - Provides properties and methods for downstream M365 service classes to access session, identity, and configuration data.

### Data Models

- **Location:** [`prowler/providers/m365/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/models.py)
- **Purpose:** Define structured data for M365 identity, session, region configuration, and credentials.
- **Key M365 Models:**
    - `M365IdentityInfo`: Holds M365 identity metadata, including tenant ID, domain(s), user, and location.
    - `M365RegionConfig`: Stores the specific region/authority and API base URL for the tenant.
    - `M365Credentials`: Represents credentials for authentication (user, password, client ID, client secret, tenant ID, etc.).

### `M365Service` (Service Base Class)

- **Location:** [`prowler/providers/m365/lib/service/service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/lib/service/service.py)
- **Purpose:** Abstract base class for all M365 service-specific classes.
- **Key M365 Responsibilities:**
    - Receives an `M365Provider` instance to access session, identity, and configuration.
    - Manages the Microsoft Graph API client for the service.
    - Initializes a PowerShell client for most services if credentials and identity are available.
    - Exposes common audit context (`audit_config`, `fixer_config`) to subclasses.

### Exception Handling

- **Location:** [`prowler/providers/m365/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for M365-specific error handling, such as credential, session, region, and argument errors.

### Session and Utility Helpers

- **Location:** [`prowler/providers/m365/lib/`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/lib/)
- **Purpose:** Helpers for argument parsing, region/authority setup, mutelist management, PowerShell integration, and other cross-cutting concerns.

  > **Key File: [`m365_powershell.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/lib/powershell/m365_powershell.py)**
  >
  > This is the core module for Microsoft 365 PowerShell integration. It manages authentication, session handling, and provides a comprehensive set of methods for interacting with Microsoft Teams, Exchange Online, and Defender policies via PowerShell.
  >
  > This module provides secure credential management and authentication using MSAL and PowerShell. It handles automated installation and initialization of required PowerShell modules. The module offers a rich set of methods for retrieving and managing Teams, Exchange, and Defender configurations. It serves as the central component for all M365 provider operations that require PowerShell automation.

## Specific Patterns in M365 Services

The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:

- Directly in the code, in location [`prowler/providers/m365/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/m365/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new service is by following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and by taking other already implemented services as reference.

### M365 Service Common Patterns

- Services communicate with Microsoft 365 using the Microsoft Graph API **and/or PowerShell**. See the [official documentation](https://learn.microsoft.com/en-us/graph/api/overview) and [PowerShell reference](https://learn.microsoft.com/en-us/powershell/).
- Every M365 service class inherits from `M365Service`, ensuring access to session, identity, configuration, and client utilities.
- The constructor (`__init__`) always calls `super().__init__` with the provider object, and initializes the Graph client and the PowerShell client.
- Resource containers **must** be initialized in the constructor, typically as objects that represent the different settings of the service.
- All M365 resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
- Microsoft Graph API and PowerShell calls are wrapped in try/except blocks, always logging errors.
- To retrieve some data in the services, it is so common that you have to create a new method also in the `m365_powershell.py` file to later be called in the service.

## Specific Patterns in M365 Checks

The M365 checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks in:

- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/m365/services/entra/entra_users_mfa_enabled/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/m365/services/entra/entra_users_mfa_enabled))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new check is following the [M365 check implementation documentation](/developer-guide/checks#creating-a-check) and by taking other checks as reference.

### Check Report Class

The `CheckReportM365` class models a single finding for a Microsoft 365 resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.

#### Purpose

`CheckReportM365` extends the base report structure with M365-specific fields, enabling detailed tracking of the resource, name, and location associated with each finding.

#### Constructor and Attribute Population

When you instantiate `CheckReportM365`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its M365-specific attributes from the resource, using the following logic (in order of precedence):

- **`resource_id`**: A required field that **must** be explicitly set in the constructor to identify the resource being checked.
- **`resource_name`**: A required field that **must** be explicitly set in the constructor to provide a human-readable name for the resource.
- **`location`**: A required field that can be explicitly set in the constructor to indicate where the resource is located. If not specified, defaults to "global".

If the resource object does not contain the required attributes, you must set them manually in the check logic.

Other attributes are inherited from the `Check_Report` class, from which you **always** have to set the `status` and `status_extended` attributes in the check logic.

#### Example Usage

```python
report = CheckReportM365(
    metadata=check_metadata,
    resource=resource_object
)
report.status = "PASS"
report.status_extended = "Resource is compliant."
```

---

### Developer Guide Mcp Server (`docs/developer-guide/mcp-server.mdx`)

---
title: 'Extending the MCP Server'
---

This guide explains how to extend the Prowler MCP Server with new tools and features.

<Info>
**New to Prowler MCP Server?** Start with the user documentation:
- [Overview](/getting-started/products/prowler-mcp) - Key capabilities, use cases, and deployment options
- [Installation](/getting-started/installation/prowler-mcp) - Install locally or use the managed server
- [Configuration](/getting-started/basic-usage/prowler-mcp) - Configure Claude Desktop, Cursor, and other MCP hosts
- [Tools Reference](/getting-started/basic-usage/prowler-mcp-tools) - Complete list of all available tools
</Info>

## Introduction

The Prowler MCP Server brings the entire Prowler ecosystem to AI assistants through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io). It enables seamless integration with AI tools like Claude Desktop, Cursor, and other MCP clients.

The server follows a modular architecture with three independent sub-servers:

| Sub-Server | Tool Prefix | Auth Required | Description |
|------------|-------------|---------------|-------------|
| Prowler | `prowler_` | Yes | Full access to Prowler Cloud, Prowler Private Cloud, and Prowler Local Server features |
| Prowler Hub | `prowler_hub_` | No | Security checks catalog with **over 2,000 checks**, fixers, and **70+ compliance frameworks** |
| Prowler Documentation | `prowler_docs_` | No | Full-text search and retrieval of official documentation |

<Note>
The core Prowler sub-server is served under the `prowler_` tool prefix, while its source lives in the `prowler_app/` module for historical reasons. Tool names use the prefix; import paths use the module.
</Note>

<Note>
For a complete list of tools and their descriptions, see the [Tools Reference](/getting-started/basic-usage/prowler-mcp-tools).
</Note>

## Architecture Overview

The MCP Server architecture is illustrated in the [Overview documentation](/getting-started/products/prowler-mcp#mcp-server-architecture). AI assistants connect through the MCP protocol to access Prowler's three main components.

### Server Structure

The main server orchestrates three sub-servers with prefixed namespacing:

```
mcp_server/prowler_mcp_server/
├── server.py                 # Main orchestrator
├── main.py                   # CLI entry point
├── prowler_hub/
├── prowler_app/
│   ├── tools/                # Tool implementations
│   ├── models/               # Pydantic models
│   └── utils/                # API client, auth, loader
└── prowler_documentation/
```

### Tool Registration Patterns

The MCP Server uses two patterns for tool registration:

1. **Direct Decorators** (Prowler Hub/Docs): Tools are registered using `@mcp.tool()` decorators
2. **Auto-Discovery** (`prowler_app`): All public methods of `BaseTool` subclasses are auto-registered

## Adding Tools to the `prowler_app` Sub-Server

### Step 1: Create the Tool Class

Create a new file or add to an existing file in `prowler_app/tools/`:

```
/* Detailed source-code truncated for AI context efficiency. */
```

There is no `try`/`except` here on purpose. A failed request raises, and
[Error Handling](#error-handling) explains what turns that raise into a message
the agent can act on.

### Step 2: Create the Models

Create corresponding models in `prowler_app/models/`:

```
/* Detailed source-code truncated for AI context efficiency. */
```

### Step 3: Verify Auto-Discovery

No manual registration is needed. The `tool_loader.py` automatically discovers and registers all `BaseTool` subclasses. Verify your tool is loaded by checking the server logs:

```
INFO - Auto-registered 2 tools from NewFeatureTools
INFO - Loaded and registered: NewFeatureTools
```

## Adding Tools to Prowler Hub/Docs

For Prowler Hub or Documentation tools, use the `@mcp.tool()` decorator directly:

```python
# prowler_hub/server.py
from fastmcp import FastMCP

hub_mcp_server = FastMCP("prowler-hub")

@hub_mcp_server.tool()
async def get_new_artifact(
    artifact_id: str,
) -> dict:
    """Fetch a specific artifact from Prowler Hub.

    Args:
        artifact_id: The unique identifier of the artifact

    Returns:
        Dictionary containing artifact details
    """
    response = prowler_hub_client.get(f"/artifact/{artifact_id}")
    response.raise_for_status()
    return response.json()
```

## Model Design Patterns

### MinimalSerializerMixin

All models should use `MinimalSerializerMixin` to optimize responses for LLM consumption:

```python
from prowler_mcp_server.prowler_app.models.base import MinimalSerializerMixin

class MyModel(MinimalSerializerMixin):
    """Model that excludes empty values from serialization."""
    required_field: str
    optional_field: str | None = None  # Excluded if None
    empty_list: list = []              # Excluded if empty
```

This mixin automatically excludes:
- `None` values
- Empty strings
- Empty lists
- Empty dictionaries

### Two-Tier Model Pattern

Use two-tier models for efficient responses:

- **Simplified**: Lightweight models for list operations
- **Detailed**: Extended models for single-item retrieval

```python
class SimplifiedItem(MinimalSerializerMixin):
    """Use for list operations - minimal fields."""
    id: str
    name: str
    status: str

class DetailedItem(SimplifiedItem):
    """Use for get operations - extends simplified with details."""
    description: str | None = None
    configuration: dict | None = None
    created_at: str
    updated_at: str
```

### Factory Method Pattern

Always implement `from_api_response()` for API transformation:

```python
@classmethod
def from_api_response(cls, data: dict[str, Any]) -> "MyModel":
    """Transform API response to model.

    This method handles the JSON:API format used by Prowler API,
    extracting attributes and relationships as needed.
    """
    attributes = data.get("attributes", {})
    return cls(
        id=data["id"],
        name=attributes["name"],
        # ... map other fields
    )
```

## API Client Usage

The `ProwlerAPIClient` is a singleton that handles authentication and HTTP requests:

```python
class MyTools(BaseTool):
    async def my_tool(self) -> dict:
        # GET request
        response = await self.api_client.get("/api/v1/endpoint", params={"key": "value"})

        # POST request
        response = await self.api_client.post(
            "/api/v1/endpoint",
            json_data={"data": {"type": "items", "attributes": {...}}}
        )

        # PATCH request
        response = await self.api_client.patch(
            f"/api/v1/endpoint/{id}",
            json_data={"data": {"attributes": {...}}}
        )

        # DELETE request
        response = await self.api_client.delete(f"/api/v1/endpoint/{id}")
```

### Helper Methods

The API client provides useful helper methods:

```python
# Validate page size (1-1000)
self.api_client.validate_page_size(page_size)

# Normalize date range with max days limit
date_range = self.api_client.normalize_date_range(date_from, date_to, max_days=2)

# Build filter parameters (handles type conversion)
clean_params = self.api_client.build_filter_params({
    "filter[status]": "active",
    "filter[severity__in]": ["high", "critical"],  # Converts to comma-separated
    "filter[muted]": True,  # Converts to "true"
})

# Poll async task until completion
result = await self.api_client.poll_task_until_complete(
    task_id=task_id,
    timeout=60,
    poll_interval=1.0
)
```

## Best Practices

### Tool Docstrings

Tool docstrings become the description that is going to be read by the LLM. Provide clear usage instructions and common workflows:

```python
async def search_items(self, status: str = Field(...)) -> dict:
    """Search items with advanced filtering.

    Returns a lightweight list optimized for LLM consumption.
    Use get_item for complete details about a specific item.

    Common workflows:
    - Find critical items: status="critical"
    - Find recent items: Use date_from parameter
    """
```

### Error Handling

**Raise, never return.** A returned `{"error": ...}` dict is reported to the
client as `isError: false` -- a *successful* tool call whose payload happens to
mention a failure. Clients and models read that as success. A raised exception
becomes a spec-correct tool execution error instead.

The common case therefore needs no handler at all:

```python
async def get_item(self, item_id: str) -> dict:
    response = await self.api_client.get(f"/api/v1/items/{item_id}")
    return DetailedItem.from_api_response(response["data"]).model_dump()
```

`prowler_mcp_server/lib/errors.py` classifies the failures every tool shares --
a rejected credential, a missing permission, a rate limit, an outage, an
unreachable API, a bad argument -- and gives each one a message that says what
went wrong and what to do about it. Anything it does not recognise is masked,
because `mask_error_details=True` is set on every sub-server and upstream
response bodies must never be replayed into a model's context.

Three ways to raise, in the order to reach for them:

```python
from fastmcp.exceptions import ToolError

from prowler_mcp_server.lib.errors import InvalidArgument

# 1. An argument this server rejected before any request went out. The message
#    is repeated to the agent verbatim, so write it for one to read.
if not 1 <= page_size <= 1000:
    raise InvalidArgument("page_size must be between 1 and 1000.")

# 2. A request the API answered or never answered: let it propagate untouched.
#    `ProwlerAPIError` and `ProwlerAPIUnreachable` are what the classifier keys
#    on, and the second one is what stops a retry from duplicating a write.
response = await self.api_client.get(f"/api/v1/items/{item_id}")
data = response["data"]

# 3. A sentence the classifier cannot know -- a resource name, a precondition,
#    the next tool to call. NOTE the absent `from` clause: it is what marks the
#    message as already final. With `from e` the classifier would replace it.
if not data:
    raise ToolError(
        f"No item with the ID {item_id!r} exists. Use prowler_list_items to "
        "find a valid one."
    )
```

The one thing that still *returns* rather than raises is a write whose outcome is
genuinely unknown. `prowler_send_findings_to_jira` is the worked example: work
items are created one at a time and Prowler cannot delete them, so a dispatch
that stopped halfway answers with a result object carrying
`safe_to_retry: false`. "This may have been applied" is a fact about the world,
not an error, and squashing it into one loses the only thing that stops a retry
from duplicating the write.

### Parameter Descriptions

Use Pydantic `Field()` with clear descriptions. This also helps LLMs understand
the purpose of each parameter, so be as descriptive as possible:

```python
async def list_items(
    self,
    severity: list[str] = Field(
        default=[],
        description="Filter by severity levels (critical, high, medium, low)"
    ),
    status: str | None = Field(
        default=None,
        description="Filter by status (PASS, FAIL, MANUAL)"
    ),
    page_size: int = Field(
        default=50,
        description="Results per page"
    ),
) -> dict:
```

## Development Commands

```bash
# Navigate to MCP server directory
cd mcp_server

# Run in STDIO mode (default)
uv run prowler-mcp

# Run in HTTP mode
uv run prowler-mcp --transport http --host 0.0.0.0 --port 8000

# Run with environment variables
PROWLER_API_KEY="pk_xxx" uv run prowler-mcp
```

For complete installation and deployment options, see:
- [Installation Guide](/getting-started/installation/prowler-mcp#from-source-development) - Development setup instructions
- [Configuration Guide](/getting-started/basic-usage/prowler-mcp) - MCP client configuration

For development I recommend to use the [Model Context Protocol Inspector](https://github.com/modelcontextprotocol/inspector) as MCP client to test and debug your tools.

## Testing

Tests live in `mcp_server/tests/`, mirroring the source tree, and use the `test_*.py`
prefix (the same convention as the API, not the SDK's `*_test.py` suffix).

From `mcp_server/`:

```bash
cd mcp_server

uv run pytest                              # Whole suite
uv run pytest tests/prowler_app/models     # One area
uv run pytest --cov=./prowler_mcp_server   # With coverage
```

From the repository root:

```bash
make test-mcp   # Runs the MCP suite exactly as CI does
```

Async tests need no marker — `asyncio_mode` is set to `auto`.

### Reading the Coverage Numbers

<Warning>
Coverage here has a high floor that means nothing. `coverage.py` measures
*statements*, and in a Pydantic model module nearly every statement is a class-body
field declaration that runs at **import** time. `prowler_app/server.py` imports
every tool module — and therefore every model module — when it is first imported,
so all of those declarations execute and count as covered before a single test runs.

Importing the package and executing no tests at all already reports **36% overall**,
with individual model modules between 54% and 84%. A model module sitting at ~68%
with no tests written for it has **none** of its behaviour covered: the covered lines
are its imports, `class` statements and `Field(...)` declarations, and the missing
ranges are its `from_api_response()` bodies.

Judge a module against that import-only floor, not against zero, and do not set a
Codecov target from the raw total.
</Warning>

### Shared Fixtures

All fixtures live in `mcp_server/tests/conftest.py`. Three are autouse and apply to
every test: the environment is pinned to deterministic values, real socket
connections are blocked, and the API client singleton registry is snapshotted and
restored.

| Fixture | What it gives you |
|---------|-------------------|
| `mock_api_client` | The API client singleton with its transport mocked. The workhorse. |
| `mock_router` | Route registry and request recorder |
| `mcp_root_server` | The mounted root server, for in-memory client tests |
| `health_client` | Starlette `TestClient` for the `/health` route |
| `http_request_headers` | Injects request headers for HTTP-transport auth tests |
| `hub_router` / `docs_router` | Mock the Hub and Docs sub-servers' sync HTTP clients |
| `api_client` / `isolated_api_client` | The live singleton / a freshly-constructed one |

Helpers live in `mcp_server/tests/helpers/`: JSON:API document builders
(`jsonapi.py`), the `MockRouter` (`http.py`), tool-contract assertions
(`assertions.py`) and fake credentials (`tokens.py`).

### Writing a Tool Test

Drive tools through an in-memory MCP client, and open the client inside the test —
FastMCP warns that holding a client in a fixture causes event-loop problems.

```python
from fastmcp import Client

from tests.helpers.jsonapi import jsonapi_collection, jsonapi_resource

FINDING_ATTRIBUTES = {
    "uid": "prowler-aws-s3_bucket_public_access-123456789012-us-east-1-my-bucket",
    "status": "FAIL",
    "severity": "high",
    "status_extended": "S3 bucket my-bucket is publicly accessible.",
    "delta": "new",
    "muted": False,
    "muted_reason": None,
    "check_metadata": {"checkid": "s3_bucket_public_access"},
}


async def test_search_without_dates_queries_the_latest_scan_endpoint(
    mcp_root_server, mock_api_client, mock_router
):
    """With no date range the tool targets the cheaper `/findings/latest`."""
    mock_router.add(
        "GET",
        "/api/v1/findings/latest",
        json=jsonapi_collection(
            [jsonapi_resource("findings", "f1", FINDING_ATTRIBUTES)]
        ),
    )

    async with Client(mcp_root_server) as client:
        result = await client.call_tool("prowler_search_security_findings", {})

    assert result.data["findings"][0]["check_id"] == "s3_bucket_public_access"
    assert mock_router.paths() == ["GET /api/v1/findings/latest"]
```

The exemplar suite covers `findings` end to end — `tests/prowler_app/models/test_findings.py`
and `tests/prowler_app/tools/test_findings.py`. It is deliberately one feature
across both layers rather than a scattering of unrelated samples, and `findings`
is the feature that exercises the whole foundation: two-tier models, nested
sub-models, both relationship shapes, endpoint switching on a date range,
list-to-CSV filter encoding, and a tool that returns prose instead of a model.

Note the two files share a name. That is why `__init__.py` is required in every
`tests/` subdirectory here — without it they would collide on import.

<Warning>
Tool parameters are declared with pydantic `Field(default=...)`, and only FastMCP's
tool wrapper resolves those defaults. Calling a tool method directly with an
argument omitted leaves it as a raw `FieldInfo` object, which is truthy — so a
filter such as `if email:` silently builds a query out of the `FieldInfo` repr.
Call tools through the client, or pass every argument explicitly.
</Warning>

### Why the API Key Is Pinned, Not Stripped

`prowler_app/server.py` builds every tool at import time. Constructing a tool
reaches `ProwlerAppAuth`, which raises when `PROWLER_API_KEY` is missing, and
`load_all_tools` swallows that error per tool class. The result is that the whole
`prowler_*` namespace registers **zero** tools while the server still logs
"Successfully mounted Prowler tools server".

The suite therefore pins a fake key in `[tool.pytest_env]`, which is applied before
any test module is imported, and `tests/test_server.py` asserts each namespace is
non-empty so this failure can never return silently.

<Note>
`ProwlerAppAuth` resolves `PROWLER_MCP_TRANSPORT_MODE` and `API_BASE_URL` in its
default arguments, which Python evaluates once at module import. `monkeypatch.setenv`
cannot change them — pass `mode=` and `base_url=` explicitly in auth tests.
</Note>

For the full set of rules and templates, see the
[`prowler-test-mcp` skill](https://github.com/prowler-cloud/prowler/blob/master/skills/prowler-test-mcp/SKILL.md)
and the [official FastMCP testing guide](https://gofastmcp.com/development/tests).

## Related Documentation

<CardGroup cols={2}>
  <Card title="MCP Server Overview" icon="circle-info" href="/getting-started/products/prowler-mcp">
    Key capabilities, use cases, and deployment options
  </Card>
  <Card title="Tools Reference" icon="wrench" href="/getting-started/basic-usage/prowler-mcp-tools">
    Complete reference of all available tools
  </Card>
  <Card title="Prowler Hub" icon="database" href="/getting-started/products/prowler-hub">
    Security checks and compliance frameworks catalog
  </Card>
  <Card title="Lighthouse AI" icon="robot" href="/getting-started/products/prowler-lighthouse-ai">
    AI-powered security analyst
  </Card>
</CardGroup>

## Additional Resources

- [MCP Protocol Specification](https://modelcontextprotocol.io) - Model Context Protocol details
- [Prowler API Documentation](https://api.prowler.com/api/v1/docs) - API reference
- [Prowler Hub API](https://hub.prowler.com/api/docs) - Hub API reference
- [GitHub Repository](https://github.com/prowler-cloud/prowler) - Source code

---

### Developer Guide Outputs (`docs/developer-guide/outputs.mdx`)

---
title: 'Create a Custom Output Format'
---

## Introduction

Prowler supports multiple output formats, allowing users to tailor findings presentation to their needs. Custom output formats are valuable when integrating Prowler with third-party tools, generating specialized reports, or adapting data for specific workflows. By defining a custom output format, users can refine how findings are structured, extracting and displaying only the most relevant information.

- Output Organization in Prowler

    Prowler outputs are managed within the `/lib/outputs` directory. Each format—such as JSON, CSV, HTML, SARIF—is implemented as a Python class.

- Outputs are generated based on scan findings, which are stored as structured dictionaries containing details such as:

    - Resource IDs

    - Severities

    - Descriptions

    - Other relevant metadata

- Creation Guidelines

    Refer to the [Prowler Developer Guide](https://docs.prowler.com/projects/prowler-open-source/en/latest/) for insights into Prowler’s architecture and best practices for creating custom outputs.

- Identify the most suitable integration method for the output being targeted.

## Steps to Create a Custom Output Format

### Schema

- Output Class:

    - The class must inherit from `Output`. Review the [Output Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/outputs/output.py).

    - Create a class that encapsulates the required attributes and methods for interacting with the target platform. Below the code for the `CSV` class is presented:

    ```python title="CSV Class"
    class CSV(Output):
        def transform(self, findings: List[Finding]) -> None:
            """Transforms the findings into the CSV format.

            Args:
                findings (list[Finding]): a list of Finding objects

            """
        ...
    ```


    - Transform Method:

        - This method will transform the findings provided by Prowler to a specific format.

        #### Method Implementation

        The following example demonstrates the `transform` method for the `CSV` class:

        ```
/* Detailed source-code truncated for AI context efficiency. */
```

    - Batch Write Data To File Method:

        - This method will write the modeled object to a file.

        #### Method Implementation

        The following example demonstrates the `batch_write_data_to_file` method for the `CSV` class:

        ```python title="Batch Write Data To File"
        def batch_write_data_to_file(self) -> None:
            """Writes the findings to a file using the CSV format using the `Output._file_descriptor`."""
            try:
                if (
                    getattr(self, "_file_descriptor", None)
                    and not self._file_descriptor.closed
                    and self._data
                ):
                    csv_writer = DictWriter(
                        self._file_descriptor,
                        fieldnames=self._data[0].keys(),
                        delimiter=";",
                    )
                    csv_writer.writeheader()
                    for finding in self._data:
                        csv_writer.writerow(finding)
                    self._file_descriptor.close()
            except Exception as error:
                logger.error(
                    f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
                )
        ```

### Integrating the Custom Output Format into Prowler

Once the custom output format is created, it must be integrated into Prowler to ensure compatibility with the existing architecture.

#### Reviewing Current Supported Outputs

Before implementing the new output format, examine the usage of currently supported formats to understand their structure and integration approach. Example: CSV Output Creation in Prowler

Below is an example of how Prowler generates and processes CSV output within its [codebase](https://github.com/prowler-cloud/prowler/blob/master/prowler/__main__.py):

```python title="CSV creation"
if mode == "csv":
    csv_output = CSV(
        findings=finding_outputs,
        create_file_descriptor=True,
        file_path=f"{filename}{csv_file_suffix}",
    )
    generated_outputs["regular"].append(csv_output)
    # Write CSV Finding Object to file.
    csv_output.batch_write_data_to_file()
```

### Testing

* Verify that Prowler’s findings are accurately typed in the desired output format.

* Error Handling – Simulate edge cases to assess robustness and failure recovery mechanisms.

### Documentation

* Ensure the following elements are included:

    * Setup Instructions – List all necessary dependencies and installation steps.
    * Configuration details.
    * Example Use Cases – Provide practical scenarios demonstrating functionality.
    * Troubleshooting Guide – Document common issues and resolution steps.

* Comprehensive and clear documentation improves maintainability and simplifies onboarding of new users.

---

### Developer Guide Provider (`docs/developer-guide/provider.mdx`)

---
title: 'Prowler Providers'
---

## Introduction

Providers form the backbone of Prowler, enabling security assessments across various cloud environments.

A provider is any platform or service that offers resources, data, or functionality that can be audited for security and compliance. This includes:

- Cloud Infrastructure Providers (like Amazon Web Services, Microsoft Azure, and Google Cloud)
- Software as a Service (SaaS) Platforms (like Microsoft 365)
- Development Platforms (like GitHub)
- Container Orchestration Platforms (like Kubernetes)
- Database-as-a-Service Platforms (like MongoDB Atlas)

For providers supported by Prowler, refer to [Prowler Hub](https://hub.prowler.com/).

<Warning>
    There are some custom providers added by the community, like [NHN Cloud](https://www.nhncloud.com/), that are not maintained by the Prowler team, but can be used in the Prowler CLI. The main purpose of this documentation is to guide you through creating a new provider and integrating it not only in the CLI, but also in the API and UI. Non official providers can be checked directly at the [Prowler GitHub repository](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers).
</Warning>
---

## Provider Types in Prowler

Prowler supports several types of providers, each with its own implementation pattern and use case. Understanding these differences is key to designing your provider correctly.

### Classifying Your Provider

Before implementing a new provider, you need to determine which type it belongs to. This classification will guide your implementation approach and help you choose the right patterns and libraries.

#### Decision Criteria

Once you have decided the provider you want or need to add to Prowler, the next step is to study how to retrieve data from it. Based on that, the provider will fall into one of the following types: SDK, API or Tool/Wrapper (maybe in the future there will be new types but for now these are the only ones).

**Choose SDK Provider if:**

- The target platform/service has an **official Python SDK** available
- The target platform/service has a **non-official Python SDK** available but it's been updated and maintained
- You need to support **multiple authentication methods** (profiles, service principals, IAM roles, etc.)
- The SDK provides **built-in session management**, retry logic, and error handling
- You want to leverage **SDK-specific features** like credential chaining, role assumption, etc
- The platform is a **major cloud provider** (AWS, Azure, GCP, etc.) or has mature SDK support

**Choose API Provider if:**

- The target platform has a **REST API** but **no official Python SDK**
- The target platform has a **non-official Python SDK** available but it's not updated and maintained
- You need to implement **custom authentication flows** (OAuth, token-based, etc.)
- The platform is a **custom or community service** without official SDK support
- You want to use **standard HTTP libraries** like `requests` for API calls
- The platform exposes **well-documented REST endpoints** but lacks SDK tooling

**Choose Tool/Wrapper Provider if:**

- You're integrating a **third-party security tool** or library
- The tool provides **scanning capabilities** that need to be adapted to Prowler's interface
- You don't need **authentication or session management** (the tool handles this)
- You need to **map tool arguments** and **convert outputs** to Prowler's format

**Special Case - Hybrid Providers:**

- Some providers may **combine multiple approaches** (e.g., SDK + Tool wrapper, SDK + API, etc.)
- Example: M365 uses **msgraph SDK** for authentication and some checks, and **PowerShell wrapper** for other checks that the SDK doesn't support
- These require **custom implementation patterns** that blend different provider types

#### Classification Examples

| Provider    | Type   | Reasoning                                                         |
| ----------- | ------ | ----------------------------------------------------------------- |
| AWS         | SDK    | Official boto3 SDK, multiple auth methods, mature ecosystem       |
| Azure       | SDK    | Official azure-identity SDK, service principals, managed identity |
| GCP         | SDK    | Official google-auth SDK, service accounts, ADC support           |
| Kubernetes  | SDK    | Official kubernetes SDK, service accounts, ADC support            |
| NHN Cloud   | API    | Custom REST API, no official SDK, community provider              |
| MongoDB Atlas| API    | Custom REST API, no official SDK                                 |
| IAC         | Tool   | Third-party security tool that uses trivy, no auth needed, output conversion|
| M365        | Hybrid | Combines msgraph SDK for auth + PowerShell wrapper for operations |
| GitHub      | Hybrid | Non-Official PyGithub SDK but it's been updated and maintained + Official graphql API requests|

#### Questions to Ask Yourself

**1. Does the platform have an official Python SDK?**

- Yes → Consider SDK Provider
- No → Continue to question 2

**2. Does the platform have a non-official Python SDK?**

- Yes → Then if the SDK is updated and maintained, consider SDK Provider, otherwise continue to question 3.
- No → Continue to question 3

**3. Is this a third-party security tool or library?**

- Yes → Consider Tool/Wrapper Provider
- No → Continue to question 4

**4. Does the platform expose a REST API?**

- Yes → Consider API Provider
- No → You may need a custom approach



#### Implementation Complexity

- **SDK Providers**: Low complexity. You have mature examples like AWS, Azure, GCP, Kubernetes, etc. that you can leverage to implement your provider.
- **API Providers**: Medium complexity. You need to implement the authentication and session management, and the API calls to the provider. You now have NHN and MongoDB Atlas as example to follow.
- **Tool/Wrapper Providers**: High complexity. You need to implement the argument/output mapping to the provider and handle problems that the tool/wrapper may have. You now have IAC and the PowerShell wrapper as example to follow.
- **Hybrid Providers**: High complexity. You need to "customize" your provider, mixing the other types of providers to achieve the desired result. You have M365 (msgraph SDK + PowerShell wrapper) and GitHub (PyGithub SDK + graphql API requests) as examples.

### Determining Regional vs Non-Regional Architecture

After classifying your provider type, the next critical decision is determining whether your provider operates with **regional concepts** or is **global/non-regional**. This decision fundamentally affects how your provider and services are structured and executed.

#### Regional Providers

Regional providers operate across multiple geographic locations and require region-specific resource discovery and iteration.

**Examples:**

- **AWS**: Has regions like `us-east-1`, `eu-west-1`, `ap-southeast-2`
- **Azure**: Has regions like `East US`, `West Europe`, `Australia East`
- **GCP**: Has regions like `us-central1`, `europe-west1`, `asia-southeast1`

**Implementation Requirements:**

- Must implement region discovery and iteration
- Services must be instantiated per region or handle multi-region data
- Checks must execute across all available/specified regions
- Resource ARNs/IDs must include region information
- Region-specific client initialization

**Execution Pattern:**

```python
# Regional provider execution pattern
for region in provider.get_regions():
    regional_client = service.get_regional_client(region)
    regional_resources = regional_client.discover_resources()
    # Process regional resources
```

#### Non-Regional (Global) Providers

Non-regional providers operate globally without geographic partitioning.

**Examples:**

- **GitHub**: Repositories, organizations are global concepts
- **M365**: Tenants operate globally across Microsoft datacenters
- **Kubernetes**: Clusters are independent units without regional concepts

**Implementation Requirements:**

- Single global client/session
- No region iteration required
- Global resource discovery
- Simpler resource identification (no region in ARNs/IDs)
- Single audit execution

**Execution Pattern:**

```python
# Non-regional provider execution pattern
global_client = service.get_client()
global_resources = global_client.discover_resources()
# Process all resources in single iteration
```

#### Decision Matrix

| Aspect                   | Regional Provider        | Non-Regional Provider  |
| ------------------------ | ------------------------ | ---------------------- |
| **Client Init**          | Per-region clients       | Single global client   |
| **Resource Discovery**   | Iterate through regions  | Single discovery call  |
| **Resource ARN/ID**      | Include region           | Global identifier/None |
| **Audit Execution**      | Multi-region loops       | Single execution       |
| **Service Architecture** | Region-aware services    | Global services        |
| **Performance**          | Parallelizable by region | Linear execution       |

#### Region Discovery

Region discovery is the process of getting the list of regions that are available for the account. This is done by the provider and is stored in the `prowler/providers/<provider_name>/lib/regions/<provider_name>_regions.py` file.

```python
# File: prowler/providers/aws/aws_provider.py
def get_aws_enabled_regions(self, current_session: Session) -> set:
    """get_aws_enabled_regions returns a set of enabled AWS regions"""
    try:
        # EC2 Client to check enabled regions
        service = "ec2"
        default_region = self.get_default_region(service)
        ec2_client = current_session.client(service, region_name=default_region)

        enabled_regions = set()
        # With AllRegions=False we only get the enabled regions for the account
        for region in ec2_client.describe_regions(AllRegions=False).get("Regions", []):
            enabled_regions.add(region.get("RegionName"))

        return enabled_regions
    except Exception as error:
        logger.error(f"{error.__class__.__name__}: {error}")
        return set()
```

The function returns a JSON file containing the list of regions for the provider. It is used to retrieve the provider’s regions and to validate the region specified by the user.

```json
# File: prowler/providers/aws/aws_regions_by_service.json (extract)
{
  "services": {
    "ec2": {
      "regions": {
        "aws": [
          "af-south-1", "ap-east-1", "ap-northeast-1", "ap-northeast-2",
          "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2",
          "ca-central-1", "eu-central-1", "eu-north-1", "eu-south-1",
          "eu-west-1", "eu-west-2", "eu-west-3", "me-south-1",
          "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2"
        ],
        "aws-cn": ["cn-north-1", "cn-northwest-1"],
        "aws-eusc": ["eusc-de-east-1"],
        "aws-us-gov": ["us-gov-east-1", "us-gov-west-1"]
      }
    }
  }
}
```

### Regional Service Implementation

For detailed guidance on implementing services for regional services, including code examples, service architecture, and check execution patterns, see the [Regional Service Implementation](./services#regional-service-implementation) section in the Services documentation.

**Key concepts covered:**

- Threading and parallel processing across regions
- Service implementation patterns for regional providers
- Cross-region resource attribution and ARN handling
- Best practices for performance and error isolation

## Step 1: Create the Provider Backend (CLI Integration)

Once the type of provider and its regional architecture are determined, the next step is to start creating the code of the provider.

### SDK Providers

General aspects to consider when implementing a new SDK provider:

**Definition:**

- Use the official SDK of the provider to interact with its resources and APIs.
- Examples: AWS (boto3), Azure (azure-identity), GCP (google-auth), Kubernetes (kubernetes), M365 (msal/msgraph), GitHub (PyGithub).

**Typical Use Cases:**

- Cloud platforms and services with mature Python SDKs.
- Need to support multiple authentication methods (profiles, service principals, etc).
- Providers that offer comprehensive Python libraries for resource management.

**Key Characteristics:**

- Authentication and session management handled by the SDK.
- Arguments: Depends on the provider, but for example we can have `profile`, `region`, `tenant_id`, `client_id`, `client_secret`, etc.
- Outputs: Standardized via SDK models and responses.
- Session objects that can be reused across multiple API calls.
- Built-in retry logic and error handling.

**Implementation Details:**

- SDK providers typically use credential objects or session objects provided by the official SDK.
- They often support multiple authentication methods (several types of credentials, configuration files, IAM roles, etc.).
- Session management includes token refresh, connection pooling, and retry mechanisms.
- Resource discovery and enumeration is usually straightforward through SDK methods.

---

### Implementation Guide for SDK Providers

Now it's time to start creating the code needed to implement the provider.

#### Step 1: Create the Provider Structure

**Explanation:**
SDK providers require a specific folder structure to organize authentication, configuration, and service management. This structure follows Prowler's conventions and ensures proper integration with the CLI and API.

**Required Structure:**

```
prowler/providers/<provider_name>/
├── __init__.py
├── <provider_name>_provider.py
├── models.py
├── exceptions/
│   ├── __init__.py
│   └── exceptions.py
├── services/
│   ├── service_name1/
│   └── service_name2/
└── lib/
    ├── __init__.py
    ├── arguments/
    │   ├── __init__.py
    │   └── arguments.py
    ├── mutelist/
    │   ├── __init__.py
    │   └── mutelist.py
    ├── regions/
    │   ├── __init__.py
    │   └── <provider_name>_regions.py
    └── service/
        ├── __init__.py
        └── service.py
```

**Key Components:**

- **`<provider_name>_provider.py`**: Main provider class with authentication and session management
- **`models.py`**: Data structures for identity, session, and provider-specific information
- **`exceptions/`**: Custom exception classes for error handling
- **`services/`**: Folder that contains all the provider services, how to make a new service is explained in another section.
- **`lib/arguments/`**: CLI argument validation and parsing
- **`lib/mutelist/`**: Resource exclusion and muting functionality
- **`lib/regions/`**: Region management and validation. If the provider is NOT regional, this folder will not be created.
- **`lib/service/`**: Base service class for provider-specific services

#### Step 2: Implement the Provider Class

**Explanation:**
The provider class is the core component that handles authentication, session management, and identity information. It inherits from Prowler's base Provider class and implements SDK-specific authentication flows. All providers must share, as far as possible, common patterns for session setup, identity management, and credential validation.

Nevertheless, you may encounter changes and must adapt the implementation logic accordingly. A basic example of a common provider implementation is the following:

**File:** `prowler/providers/<provider_name>/<provider_name>_provider.py`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 3: Create Models

**Explanation:**
Models define the data structures used by your provider. They include identity information, session details, and provider-specific configurations. These models ensure type safety and consistent data handling across the provider.

**File:** `prowler/providers/<provider_name>/models.py`

```python
# Import the needed generic libraries for the provider.
from pydantic import BaseModel
from dataclasses import dataclass
from typing import Optional, List

# Import the needed Prowler libraries for the provider.
from prowler.providers.common.models import ProviderOutputOptions
from prowler.config.config import output_file_timestamp

class YourProviderIdentityInfo:
    """
    Identity information for the provider.

    This class holds all the identity-related information retrieved
    from the provider, including account details and user information.
    """
    account_id: str
    account_name: str
    region: str
    user_id: str
    # Add other identity fields as needed

class YourProviderSession:
    """
    Session object that contains the credentials and authentication details for the provider.

    This class holds the actual credentials and authentication information needed
    to establish a connection with the provider's services.
    """
    # Authentication credentials
    access_key: str
    secret_key: str
    # Or for other providers:
    # client_id: str
    # client_secret: str
    # tenant_id: str

    # Connection details
    region: str
```

#### Step 4: Implement Arguments

**Explanation:**
Argument validation ensures that the provider receives valid configuration parameters. This step is crucial for preventing runtime errors and providing clear error messages to users. The validation should check for required parameters and validate their format.

**File:** `prowler/providers/<provider_name>/lib/arguments/arguments.py`

```python
def init_parser(self):
    """Init the <provider_name> Provider CLI parser"""
    <provider_name>_parser = self.subparsers.add_parser(
        "<provider_name>", parents=[self.common_providers_parser], help="<provider_name> Provider"
    )
    # Authentication Modes
    <provider_name>_auth_subparser = <provider_name>_parser.add_argument_group("Authentication Modes")
    <provider_name>_auth_modes_group = <provider_name>_auth_subparser.add_mutually_exclusive_group()
    <provider_name>_auth_modes_group.add_argument(
        "--credentials-file",
        nargs="?",
        metavar="FILE_PATH",
        help="Authenticate using a <provider_name> Service Account Application Credentials JSON file",
    )
    <provider_name>_auth_modes_group.add_argument(
        "--impersonate-service-account",
        nargs="?",
        metavar="SERVICE_ACCOUNT",
        help="Impersonate a <provider_name> Service Account",
    )
    <provider_name>_parser.add_argument(
        "--your-provider-region",
        help="Your Provider Region",
        type=str,
    )
    <provider_name>_parser.add_argument(
        "--env-auth",
        action="store_true",
        help="Use User and Password environment variables authentication to log in against <provider_name>",
    )
    # More arguments for the provider.
```

##### Sensitive CLI Arguments

CLI flags that accept secrets (tokens, passwords, API keys) require special handling to protect credentials from leaking in HTML output and process listings:

1. **Use `nargs="?"` with `default=None`** so the flag works both with and without an inline value. This allows the provider to fall back to an environment variable when no value is passed.
2. **Add a `SENSITIVE_ARGUMENTS` frozenset** at the top of the `arguments.py` file listing every flag that accepts secret values:

    ```python
    SENSITIVE_ARGUMENTS = frozenset({"--your-provider-password", "--your-provider-token"})
    ```

    Prowler automatically discovers these frozensets and uses them to redact values in HTML output and warn users who pass secrets directly on the command line.

3. **Document the environment variable** in the `help` text so users know the recommended alternative:

    ```python
    <provider_name>_parser.add_argument(
        "--your-provider-password",
        nargs="?",
        default=None,
        metavar="PASSWORD",
        help="Password for authentication. We recommend using the YOUR_PROVIDER_PASSWORD environment variable instead.",
    )
    ```

<Warning>
Do not add new arguments that require passing secrets as CLI values without an environment variable fallback. Prowler CLI warns users when sensitive flags receive explicit values on the command line.
</Warning>

#### Step 5: Implement Mutelist

**Explanation:**
The mutelist functionality allows users to exclude specific resources or checks from the audit. This is useful for handling false positives or excluding resources that are intentionally configured differently.

**File:** `prowler/providers/<provider_name>/lib/mutelist/mutelist.py`

```python
from prowler.lib.mutelist.mutelist import Mutelist
from prowler.lib.check.models import CheckReportYourProvider

class YourProviderMutelist(Mutelist):
    """
    Mutelist implementation for YourProvider.

    This class handles the muting functionality for the provider,
    allowing users to exclude specific checks or resources from audits.
    """

    def is_finding_muted(self, finding: CheckReportYourProvider) -> bool:
        """
        Check if a specific finding is muted.

        Args:
            finding: The finding to check
        """
        return self.is_muted(finding.check_id, finding.resource_id)
```

#### Step 6: Implement Regions

**Explanation:**
Region management is essential for cloud providers that operate across multiple geographic locations. This component handles region validation and provides region-specific functionality.

<Note>
Regions are optional, only if the provider has regions, for example GitHub does not have regions, but AWS does.
</Note>

**File:** `prowler/providers/<provider_name>/lib/regions/<provider_name>_regions.py`

```python
from typing import List, Set

def get_regions() -> List[str]:
    """
    Get list of available regions for the provider.

    Returns:
        List[str]: List of available region names
    """
    return [
        "region1",
        "region2",
        "region3",
        # ... other regions
    ]

def validate_region(region: str) -> bool:
    """
    Validate if a region is supported.

    Args:
        region: The region to validate

    Returns:
        bool: True if the region is valid, False otherwise
    """
    return region in get_regions()

def get_default_region() -> str:
    """
    Get the default region for the provider.

    Returns:
        str: The default region name
    """
    return "region1"

def get_global_region() -> str:
    """
    Get the global region for the provider.

    Returns:
        str: The global region name
    """
    return "global"
```

#### Step 7: Create Custom Exceptions

**Explanation:**
Custom exceptions are needed to be able to handle the errors in a more specific way. Prowler uses a structured exception system with error codes, messages, and remediation steps.

**File:** `prowler/providers/<provider_name>/exceptions.py`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 8: Implement Service Base Class

**Explanation:**
The service base class defines a common interface for all services in your provider, since they will inherit from it. It defines the client to make requests to, the audit configuration and the fixer configuration.

**File:** `prowler/providers/<provider_name>/lib/service/service.py`

```python
from prowler.providers.<provider_name>.<provider_name>_provider import <ProviderName>Provider

class YourProviderService(BaseService):
    """
    Base service class for YourProvider services.

    This class provides common functionality for all services
    within the provider, including session management and error handling.
    """

    def __init__(self, provider: <ProviderName>Provider):
        """
        Initialize the service.

        Args:
            provider: The provider instance
        """
        self.client = provider.session.get_client(self.service_name)
        self.audit_config = provider.audit_config
        self.fixer_config = provider.fixer_config
```

#### Step 9: Register in CLI

**Explanation:**
Add your provider to the available providers in the CLI.

**File:** `prowler/lib/cli/parser.py`

```python
class ProwlerArgumentParser:
    # Set the default parser
    def __init__(self):
        # CLI Arguments
        self.parser = argparse.ArgumentParser(
            prog="prowler",
            formatter_class=RawTextHelpFormatter,
            usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,dashboard,iac,your_provider} ...",
            epilog="""
Available Providers:
  {aws,azure,gcp,kubernetes,m365,github,iac,nhn,your_provider}
    aws                 AWS Provider
    azure               Azure Provider
    gcp                 GCP Provider
    kubernetes          Kubernetes Provider
    m365                Microsoft 365 Provider
    github              GitHub Provider
    iac                 IaC Provider (Preview)
    nhn                 NHN Provider (Unofficial)
    your_provider       Your Provider

Available components:
    dashboard           Local dashboard

To see the different available options on a specific component, run:
    prowler {provider|dashboard} -h|--help

Detailed documentation at https://docs.prowler.com
""",
```

#### Step 10: Register in Main

**Explanation:**
Main registration makes your provider discoverable by Prowler's core system. It's needed to add your provider to the output options and to the compliance evaluation.

**File:** `prowler/__main__.py`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 11: Register in the List of Providers

**Explanation:**
This is needed to be able to use the provider in the generic checks. The provider must be registered in the `init_global_provider` method to handle CLI arguments and initialization.

**File:** `prowler/providers/common/provider.py`

```python
elif "your_provider" in provider_class_name.lower():
    provider_class(
        username=arguments.your_provider_username,
        password=arguments.your_provider_password,
        tenant_id=arguments.your_provider_tenant_id,
        config_path=arguments.config_file,
        mutelist_path=arguments.mutelist_file,
        fixer_config=fixer_config,
    )
```

#### Step 12: Add to Config

**Explanation:**
Configuration registration ensures your provider is recognized by Prowler's configuration system. This enables proper handling of provider-specific settings and defaults.

**File:** `prowler/config/config.py`

```python
class Provider(str, Enum):
    AWS = "aws"
    AZURE = "azure"
    GCP = "gcp"
    KUBERNETES = "kubernetes"
    M365 = "m365"
    GITHUB = "github"
    YOUR_PROVIDER = "your_provider"  # Add your provider here
```

In some cases, you may need to create a new configuration file for your provider, for example, the AWS one that is inside `prowler/providers/aws/config.py`.

#### Step 13: Create Compliance Files

**Explanation:**
Compliance files define the security checks and standards that your provider supports. These JSON files map security controls to specific checks and provide remediation guidance. It's needed to create the folder with an init file to ensure the provider will work, however, adding different compliance files is optional.

**Folder:** `prowler/compliance/<provider_name>/`

```json
{
  "Framework": "CIS",
  "Version": "1.0",
  "Provider": "your_provider",
  "Description": "Description of the compliance framework",
  # The requirements depends on the framework, for example, CIS has a requirements section with the checks and attributes.
  "Requirements": [
    {
      "Id": "1.1.1",
      "Description": "Description of the requirement",
      "Checks": ["your_provider_check_1", "your_provider_check_2"],
      "Attributes": []
    }
  ]
}
```

#### Step 14: Add Output Support

**Explanation:**
Output support ensures that your provider's results are properly formatted in Prowler's various output formats (CSV, JSON, HTML, etc.). This step integrates your provider with Prowler's reporting system.

**File:** `prowler/lib/outputs/summary_table.py`

```python
# Add your provider case in the display_summary_table function
elif provider.type == "your_provider":
    entity_type = "Your Entity Type"
    audited_entities = provider.identity.your_entity_field
```

**File:** `prowler/lib/outputs/finding.py`

```python
# Add your provider case in the fill_common_finding_data function
elif provider.type == "your_provider":
    output_data["auth_method"] = f"Your Auth Method: {get_nested_attribute(provider, 'identity.auth_type')}"
    output_data["account_uid"] = get_nested_attribute(provider, "identity.account_id")
    output_data["account_name"] = get_nested_attribute(provider, "identity.account_name")
    output_data["resource_name"] = check_output.resource_name
    output_data["resource_uid"] = check_output.resource_id
    output_data["region"] = check_output.location  # or your location field
```

**File:** `prowler/lib/outputs/outputs.py`

```python
# Add your provider case in the stdout_report function
if finding.check_metadata.Provider == "your_provider":
    details = finding.your_location_field  # e.g., finding.location, finding.namespace, etc.
```

#### Step 15: Generate the HTML Report

**Explanation:**
The HTML file is needed to be able to generate the HTML report. This step involves adding support for your provider in the HTML output generation system to ensure proper display of assessment summaries and findings.

**File:** `prowler/lib/outputs/html/html.py`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 16: Add the Check Report Model

**Explanation:**
Add the provider to the generic models, this is needed to be able to use the provider in the generic checks.

**File:** `prowler/providers/check/models.py`

```python
@dataclass
class CheckReportYourProvider(CheckReport):
    """
    Check report for YourProvider.
    """
    resource_name: str
    resource_id: str

    def _init_(self, metadata: Dict, resource: Any) -> None:
        super()._init_(metadata, resource)
        self.resource_name = resource.name
        self.resource_id = resource.id
```

#### Step 17: Add Dependencies

**Explanation:**
Dependencies ensure that your provider's required libraries are available when Prowler is installed. This step adds the necessary SDK or API client to Prowler's dependency management.

**File:** `pyproject.toml`

```toml
[project]
requires-python = ">=3.10,<3.13"
dependencies = [
  # ... other dependencies
  "your-sdk-library>=1.0.0,<2.0.0",  # Add your SDK dependency
]
```

#### Step 18: Create Tests

**Explanation:**
Testing ensures that your provider works correctly and maintains compatibility as Prowler evolves. Comprehensive tests cover authentication, session management, and provider-specific functionality.

**Folder:** `tests/providers/<provider_name>/`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 19: Update Documentation

**Explanation:**
Documentation updates ensure that users can find information about your provider in Prowler's documentation. This includes examples, configuration guides, and troubleshooting information.

Update the provider documentation to include your new provider in the examples and implementation guidance.

---

### API Providers

**Definition:**

- Interact directly with the provider's REST API using HTTP requests (e.g., via `requests`).
- Examples: NHN Cloud.

**Typical Use Cases:**

- Providers without an official Python SDK.
- Providers with a non-official Python SDK that is not updated and maintained.
- Providers that expose REST APIs and meet above requirements.

**Key Characteristics:**

- Manual management of authentication (tokens, username/password, etc).
- Arguments: Depends on the provider, for example, `username`, `password`, `tenant_id`, etc.
- Outputs: Dicts or custom models based on API responses.
- Custom HTTP session management with headers and authentication.
- Manual handling of pagination, rate limiting, and error responses.

**Implementation Details:**

- API providers require manual HTTP request management using libraries like `requests`.
- Authentication typically involves obtaining tokens via login endpoints or OAuth flows.
- Session management includes setting appropriate headers (Authorization, Content-Type, etc.).
- Resource discovery often requires multiple API calls to different endpoints.
- Error handling and retry logic must be implemented manually.

---

### Implementation Guide for API Providers

#### Step 1: Create the Provider Structure

**Explanation:**
API providers require the same structure as the SDK providers, the main difference would be that due to the lack of an official Python SDK, some methods could be implemented differently or not implemented at all.

**Required Structure:**

```
prowler/providers/<provider_name>/
├── __init__.py
├── <provider_name>_provider.py
├── models.py
├── exceptions/
│   ├── __init__.py
│   └── exceptions.py
├── services/
│   ├── service_name1/
│   └── service_name2/
└── lib/
    ├── __init__.py
    ├── arguments/
    │   ├── __init__.py
    │   └── arguments.py
    ├── mutelist/
    │   ├── __init__.py
    │   └── mutelist.py
    ├── regions/
    │   ├── __init__.py
    │   └── regions.py
    └── service/
        ├── __init__.py
        └── service.py
```

**Key Components:**

- **`<provider_name>_provider.py`**: Main provider class with HTTP session management
- **`models.py`**: Data structures for identity and API responses
- **`exceptions/`**: Custom exception classes for API errors
- **`services/`**: Folder that contains all the provider services
- **`lib/arguments/`**: CLI argument validation and parsing
- **`lib/mutelist/`**: Resource exclusion and muting functionality
- **`lib/regions/`**: Region management and validation. If the provider is NOT regional, this folder will not be created.
- **`lib/service/`**: Base service class for provider-specific services

#### Step 2: Implement the Provider Class

**Explanation:**
The provider class is the core component that handles HTTP session management, authentication, and identity information. It inherits from Prowler's base Provider class and implements API-specific authentication flows using direct HTTP requests.

**File:** `prowler/providers/<provider_name>/<provider_name>_provider.py`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 3: Create Models

**Explanation:**
Models define the data structures used by your API provider. They include identity information and API response structures. These models ensure type safety and consistent data handling across the provider.

**File:** `prowler/providers/<provider_name>/models.py`

This step is common with SDK providers so you can follow the same pattern as [there](#step-3-create-models).

#### Step 4: Implement Arguments

**Explanation:**
Argument validation ensures that the API provider receives valid configuration parameters. This step is crucial for preventing runtime errors and providing clear error messages to users.

**File:** `prowler/providers/<provider_name>/lib/arguments/arguments.py`

Arguments depends on the provider and not the type, so the pattern for this step is the same as the [SDK providers](#step-4-implement-arguments).

#### Step 5: Implement Mutelist

**Explanation:**
The mutelist functionality allows users to exclude specific resources or checks from the audit. This is useful for handling false positives or excluding resources that are intentionally configured differently.

**File:** `prowler/providers/<provider_name>/lib/mutelist/mutelist.py`

The implementation of the mutelist is the same as the [SDK providers](#step-5-implement-mutelist).

#### Step 6: Implement Regions

**Explanation:**
Region management is essential for cloud providers that operate across multiple geographic locations. This component handles region validation and provides region-specific functionality.

<Note>
Regions are optional, only if the provider has regions, for example GitHub does not have regions, but AWS does.
</Note>

**File:** `prowler/providers/<provider_name>/lib/regions/<provider_name>_regions.py`

The implementation of the regions is the same as the [SDK providers](#step-6-implement-regions).

#### Step 7: Create Custom Exceptions

**Explanation:**
Custom exceptions provide specific error handling for API-related issues, making debugging and error reporting more effective. Prowler uses a structured exception system with error codes, messages, and remediation steps.

**File:** `prowler/providers/<provider_name>/exceptions/exceptions.py`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 8: Implement Service Base Class

**Explanation:**
The service base class defines a common interface for all services in your provider, since they will inherit from it. It defines the client to make requests to, the audit configuration and the fixer configuration.

**File:** `prowler/providers/<provider_name>/lib/service/service.py`

```python
from prowler.providers.<provider_name>.<provider_name>_provider import <ProviderName>Provider

class APIProviderService(BaseService):
    """
    Base service class for API Provider services.

    This class provides common functionality for all services
    within the provider, including session management and error handling.
    """

    def __init__(self, provider: <ProviderName>Provider):
        """
        Initialize the service.

        Args:
            provider: The provider instance
        """
        self.client = provider.session.get_client(self.service_name)
        self.audit_config = provider.audit_config
        self.fixer_config = provider.fixer_config
        self.session = provider.session
        self.base_url = provider.session.base_url
        self.auth = HTTPDigestAuth(
            provider.session.public_key,
            provider.session.private_key,
        )
        self.headers = {
            "Authorization": self.auth.encode(),
            "Content-Type": "application/json",
        }
```

#### Step 9: Register in CLI

**Explanation:**
Add your provider to the available providers in the CLI.

**File:** `prowler/lib/cli/parser.py`

This step is the same as the [SDK providers](#step-9-register-in-cli).

#### Step 10: Register in Main

**Explanation:**
Main registration makes your provider discoverable by Prowler's core system. It's needed to add your provider to the output options and to the compliance evaluation.

**File:** `prowler/__main__.py`

This step is the same as the [SDK providers](#step-10-register-in-main).

#### Step 11: Register in the List of Providers

**Explanation:**
This is needed to be able to use the provider in the generic checks. The provider must be registered in the `init_global_provider` method to handle CLI arguments and initialization.

**File:** `prowler/providers/common/provider.py`

This step is the same as the [SDK providers](#step-11-register-in-the-list-of-providers).

#### Step 12: Add to Config

**Explanation:**
Configuration registration ensures your API provider is recognized by Prowler's configuration system. This enables proper handling of provider-specific settings and defaults.

**File:** `prowler/config/config.py`

This step is the same as the [SDK providers](#step-12-add-to-config).

#### Step 13: Create Compliance Files

**Explanation:**
Compliance files define the security checks and standards that your provider supports. These JSON files map security controls to specific checks and provide remediation guidance. It's needed to create the folder with an init file to ensure the provider will work, however, adding different compliance files is optional.

**Folder:** `prowler/compliance/<provider_name>/`

This step is the same as the [SDK providers](#step-13-create-compliance-files).

#### Step 14: Add Output Support

**Explanation:**
Output support ensures that your provider's results are properly formatted in Prowler's various output formats (CSV, JSON, HTML, etc.). This step integrates your provider with Prowler's reporting system.

**File:** `prowler/lib/outputs/summary_table.py`

This step is the same as the [SDK providers](#step-14-add-output-support).

#### Step 15: Generate the HTML Report

**Explanation:**
The HTML file is needed to be able to generate the HTML report. This step involves adding support for your provider in the HTML output generation system to ensure proper display of assessment summaries and findings.

**File:** `prowler/lib/outputs/html/html.py`

This step is the same as the [SDK providers](#step-15-generate-the-html-report).

#### Step 16: Add the Check Report Model

**Explanation:**
Add the provider to the generic models, this is needed to be able to use the provider in the generic checks.

**File:** `prowler/providers/check/models.py`

This step is the same as the [SDK providers](#step-16-add-the-check-report-model).

#### Step 17: Create Tests

**Explanation:**
Testing ensures that your API provider works correctly and maintains compatibility as Prowler evolves. Comprehensive tests cover authentication, session management, and API-specific functionality.

**Folder:** `tests/providers/<provider_name>/`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 18: Update Documentation

**Explanation:**
Documentation updates ensure that users can find information about your API provider in Prowler's documentation. This includes examples, configuration guides, and troubleshooting information.

Update the provider documentation to include your new API provider in the examples and implementation guidance.

---

### Tool/Wrapper Providers

**Definition:**

- Integrate third-party tools as libraries or subprocesses (e.g., Trivy for IaC).
- Examples: IaC (Trivy).

**Typical Use Cases:**

- Providers that require integration with external security tools.
- Tools that need to be executed as subprocesses or imported as libraries.
- Providers that require specific tool configurations and argument mapping.
- Legacy systems or tools that don't have direct API access.

**Key Characteristics:**

- No session/identity management required (tool handles this internally).
- Arguments: specific to the tool, but for example: `scan_path`, `frameworks`, `exclude_path`, `scan_repository_url`, etc.
- Outputs: Tool-specific output formats that need to be parsed and converted.
- Tool execution and output parsing.
- Configuration file mapping and argument translation.

**Implementation Details:**

- Tool providers typically execute external tools as subprocesses (e.g., `pwsh` or `trivy` command).
- They require mapping between Prowler's interface and the tool's arguments.
- Output parsing and conversion to Prowler's standard format is crucial.
- Tool-specific configuration files and validation.
- Repository cloning and temporary file management for remote scans (if needed).

**Note:** This guide provides a general framework for integrating any external tool. The specific implementation details (like repository cloning, authentication tokens, etc.) will depend on your particular tool's requirements. The core pattern is: integrate with Prowler's CLI, execute your tool via subprocess, and parse the output into Prowler's format.

---

### Implementation Guide for Tool/Wrapper Providers

#### Step 1: Create the Provider Structure

**Explanation:**
Tool/Wrapper providers require a specific folder structure to organize tool integration, configuration, and service management. This structure follows Prowler's conventions and ensures proper integration with the CLI and API.

**Required Structure:**

```
prowler/providers/<provider_name>/
├── __init__.py
├── <provider_name>_provider.py
├── models.py
└── lib/
    ├── __init__.py
    └── arguments/
        ├── __init__.py
        └── arguments.py
```

**Key Components:**

- **`<provider_name>_provider.py`**: Main provider class with tool integration
- **`models.py`**: Data structures for tool output and configuration
- **`lib/arguments/`**: CLI argument validation and parsing

#### Step 2: Implement the Provider Class

**Explanation:**
The provider class is the core component that handles tool integration, execution, and output parsing. It inherits from Prowler's base Provider class and implements tool-specific execution flows using subprocesses or library calls.

**File:** `prowler/providers/<provider_name>/<provider_name>_provider.py`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 3: Create Models

**Explanation:**
Models define the data structures used by your tool provider. They include output options and tool-specific configurations. These models ensure type safety and consistent data handling across the provider.

**File:** `prowler/providers/<provider_name>/models.py`

```python
from prowler.config.config import output_file_timestamp
from prowler.providers.common.models import ProviderOutputOptions

class YourToolOutputOptions(ProviderOutputOptions):
    """
    YourToolOutputOptions overrides ProviderOutputOptions for tool-specific output logic.
    For example, generating a filename that includes the tool name.

    Attributes inherited from ProviderOutputOptions:
        - output_filename (str): The base filename used for generated reports.
        - output_directory (str): The directory to store the output files.
        - ... see ProviderOutputOptions for more details.

    Methods:
        - __init__: Customizes the output filename logic for the tool provider.
    """

    def __init__(self, arguments, bulk_checks_metadata):
        super().__init__(arguments, bulk_checks_metadata)

        # If --output-filename is not specified, build a default name.
        if not getattr(arguments, "output_filename", None):
            self.output_filename = f"prowler-output-your_tool-{output_file_timestamp}"
        # If --output-filename was explicitly given, respect that
        else:
            self.output_filename = arguments.output_filename
```

#### Step 4: Implement Arguments

**Explanation:**
Argument validation ensures that the tool provider receives valid configuration parameters. This step is crucial for preventing runtime errors and providing clear error messages to users.

**File:** `prowler/providers/<provider_name>/lib/arguments/arguments.py`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 5: Register in CLI

**Explanation:**
Add your provider to the available providers in the CLI.

**File:** `prowler/lib/cli/parser.py`

This step is the same as the [SDK providers](#step-9-register-in-cli).

#### Step 6: Register in Main

**Explanation:**
Main registration makes your provider discoverable by Prowler's core system. It's needed to add your provider to the output options and to the compliance evaluation.

**File:** `prowler/__main__.py`

This step is the same as the [SDK providers](#step-10-register-in-main).

#### Step 7: Register in the List of Providers

**Explanation:**
This is needed to be able to use the provider in the generic checks. The provider must be registered in the `init_global_provider` method to handle CLI arguments and initialization.

**File:** `prowler/providers/common/provider.py`

This step is the same as the [SDK providers](#step-11-register-in-the-list-of-providers).

#### Step 8: Add to Config

**Explanation:**
Configuration registration ensures your tool provider is recognized by Prowler's configuration system. This enables proper handling of provider-specific settings and defaults.

**File:** `prowler/config/config.py`

This step is the same as the [SDK providers](#step-12-add-to-config).

In some cases, you may need to create a new configuration file for your provider, for example, the AWS one that is inside `prowler/providers/aws/config.py`.

#### Step 9: Create Compliance Files

**Explanation:**
Compliance files define the security checks and standards that your provider supports. These JSON files map security controls to specific checks and provide remediation guidance. It's needed to create the folder with an init file to ensure the provider will work, however, adding different compliance files is optional.

**Folder:** `prowler/compliance/<provider_name>/`

This step is the same as the [SDK providers](#step-13-create-compliance-files).

#### Step 10: Add Output Support

**Explanation:**
Output support ensures that your provider's results are properly formatted in Prowler's various output formats (CSV, JSON, HTML, etc.). This step integrates your provider with Prowler's reporting system.

**File:** `prowler/lib/outputs/summary_table.py`

This step is the same as the [SDK providers](#step-14-add-output-support).

#### Step 11: Generate the HTML Report

**Explanation:**
The HTML file is needed to be able to generate the HTML report. This step involves adding support for your provider in the HTML output generation system to ensure proper display of assessment summaries and findings.

**File:** `prowler/lib/outputs/html/html.py`

This step is the same as the [SDK providers](#step-15-generate-the-html-report).

#### Step 12: Add the Check Report Model

**Explanation:**
Add the provider to the generic models, this is needed to be able to use the provider in the generic checks.

**File:** `prowler/providers/check/models.py`

This step is the same as the [SDK providers](#step-16-add-the-check-report-model).

#### Step 13: Create Tests

**Explanation:**
Testing ensures that your tool provider works correctly and maintains compatibility as Prowler evolves. Comprehensive tests cover tool execution, output parsing, and provider-specific functionality.

**Folder:** `tests/providers/<provider_name>/`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Step 14: Update Documentation

**Explanation:**
Documentation updates ensure that users can find information about your tool provider in Prowler's documentation. This includes examples, configuration guides, and troubleshooting information.

Update the provider documentation to include your new tool provider in the examples and implementation guidance.

---


## Step 2: Integrate the Provider in the API

This step is required only if you want your provider to be available in the API and UI. The API integration involves several components:

### 2.1. Backend API Models

**Location:** `api/src/backend/api/models.py`

Add your provider to the `ProviderChoices` enum and implement UID validation:

```python
class ProviderChoices(models.TextChoices):
    AWS = "aws", "AWS"
    AZURE = "azure", "Azure"
    GCP = "gcp", "GCP"
    KUBERNETES = "kubernetes", "Kubernetes"
    M365 = "m365", "Microsoft 365"
    GITHUB = "github", "GitHub"
    NHN = "nhn", "NHN Cloud"
    IAC = "iac", "Infrastructure as Code"
    YOUR_PROVIDER = "your_provider", "Your Provider"  # Add your provider here

@staticmethod
def validate_your_provider_uid(value):
    """Validate your provider UID format."""
    if not re.match(r"^your-regex-pattern$", value):
        raise ModelValidationError(
            detail="Your provider UID must follow the specified format.",
            code="your-provider-uid",
            pointer="/data/attributes/uid",
        )
```

**Provider Model:**
The `Provider` model already exists and supports all provider types. Ensure your provider type is included in the choices.

### 2.2. Add the Provider to the Provider Choices

Update the `return_prowler_provider` function to include your provider. This function is crucial for the API to instantiate the correct provider class.

**File:** `api/src/backend/api/utils.py`

```python
from prowler.providers.your_provider.your_provider import YourProvider  # Add your import

def return_prowler_provider(
    provider: Provider,
) -> [
    AwsProvider
    | AzureProvider
    | GcpProvider
    | GithubProvider
    | KubernetesProvider
    | M365Provider
    | YourProvider  # Add your provider to the return type annotation
]:
    """Return the Prowler provider class based on the given provider type."""
    match provider.provider:
        case Provider.ProviderChoices.AWS.value:
            prowler_provider = AwsProvider
        case Provider.ProviderChoices.AZURE.value:
            prowler_provider = AzureProvider
        case Provider.ProviderChoices.GCP.value:
            prowler_provider = GcpProvider
        case Provider.ProviderChoices.KUBERNETES.value:
            prowler_provider = KubernetesProvider
        case Provider.ProviderChoices.M365.value:
            prowler_provider = M365Provider
        case Provider.ProviderChoices.GITHUB.value:
            prowler_provider = GithubProvider
        case Provider.ProviderChoices.YOUR_PROVIDER.value:  # Add your provider here
            prowler_provider = YourProvider
        case _:
            raise ValueError(f"Provider type {provider.provider} not supported")
    return prowler_provider
```

**Also update the `initialize_prowler_provider` function:**

```python
def initialize_prowler_provider(
    provider: Provider,
    mutelist_processor: Processor | None = None,
) -> (
    AwsProvider
    | AzureProvider
    | GcpProvider
    | GithubProvider
    | KubernetesProvider
    | M365Provider
    | YourProvider  # Add your provider to the return type annotation
):
    """Initialize a Prowler provider instance based on the given provider type."""
    prowler_provider = return_prowler_provider(provider)
    prowler_provider_kwargs = get_prowler_provider_kwargs(provider, mutelist_processor)
    return prowler_provider(**prowler_provider_kwargs)
```

**Note:** The `match` statement requires Python 3.10+. If you're using an older version, you can use traditional `if-elif` statements instead.

### 2.3. API Serializers

Create or update serializers for your provider. You'll need to add your provider to the validation logic:

**File:** `api/src/backend/api/v1/serializers.py`

```python
def validate_secret_based_on_provider(provider_type, secret):
    """Validate provider-specific secrets."""
    if provider_type == Provider.ProviderChoices.AWS.value:
        serializer = AWSProviderSecret(data=secret)
    elif provider_type == Provider.ProviderChoices.AZURE.value:
        serializer = AzureProviderSecret(data=secret)
    elif provider_type == Provider.ProviderChoices.GCP.value:
        serializer = GCPProviderSecret(data=secret)
    elif provider_type == Provider.ProviderChoices.YOUR_PROVIDER.value:  # Add your provider here
        serializer = YourProviderSecret(data=secret)
    # ... other providers

    if serializer.is_valid():
        return serializer.validated_data
    else:
        raise serializers.ValidationError(serializer.errors)

class YourProviderSecret(serializers.Serializer):
    """Serializer for your provider credentials."""
    your_auth_field = serializers.CharField(required=True)
    your_optional_field = serializers.CharField(required=False)

    class Meta:
        resource_name = "provider-secrets"
```

Also update the providers included in the serializer:

**File:** `api/src/backend/api/v1/serializer_utils/providers.py`

```python
@extend_schema_field(
    {
        "oneOf": [
            # ... existing provider schemas ...
            {
                "type": "object",
                "title": "Your Provider Credentials",
                "properties": {
                    "your_auth_field": {
                        "type": "string",
                        "description": "Your provider authentication field description.",
                    },
                    "your_optional_field": {
                        "type": "string",
                        "description": "Optional field for your provider (if applicable).",
                    },
                    "your_required_field": {
                        "type": "string",
                        "description": "Required field for your provider authentication.",
                    }
                },
                "required": ["your_auth_field", "your_required_field"]
            },
            # ... other existing schemas ...
        ]
    }
)
```

### 2.4. Database Migration

Create a new migration to add your provider to the database. This is crucial for the API to recognize your provider type.

**File:** `api/src/backend/api/migrations/XXXX_your_provider.py`

```python
# Generated by Django X.X.X on YYYY-MM-DD

from django.db import migrations

import api.db_utils


class Migration(migrations.Migration):
    dependencies = [
        ("api", "previous_migration_name"),  # Update this to the latest migration
    ]

    operations = [
        migrations.AlterField(
            model_name="provider",
            name="provider",
            field=api.db_utils.ProviderEnumField(
                choices=[
                    ("aws", "AWS"),
                    ("azure", "Azure"),
                    ("gcp", "GCP"),
                    ("kubernetes", "Kubernetes"),
                    ("m365", "M365"),
                    ("github", "GitHub"),
                    ("your_provider", "Your Provider"),  # Add your provider here
                ],
                default="aws",
            ),
        ),
        migrations.RunSQL(
            "ALTER TYPE provider ADD VALUE IF NOT EXISTS 'your_provider';",
            reverse_sql=migrations.RunSQL.noop,
        ),
    ]
```

**Important Notes:**

- **Migration Number**: Use the next sequential number (e.g., if latest is 0044, use 0045)
- **Dependencies**: Update the `dependencies` list to point to the most recent migration
- **Choices Array**: Add your provider to the `choices` array with proper display name
- **SQL Operation**: The `RunSQL` operation adds your provider to the PostgreSQL enum type
- **Reverse SQL**: Use `migrations.RunSQL.noop` since adding enum values cannot be easily reversed

**Migration Naming Convention:**

- Format: `XXXX_your_provider.py` (e.g., `0045_your_provider.py`)
- Use descriptive names that indicate what the migration does
- Follow the existing pattern in the migrations folder

### 2.5. Update the V1 Yaml

Update the OpenAPI specification (`v1.yaml`) to include your provider in all relevant endpoints and schemas. This is crucial for API documentation and client generation.

**File:** `api/src/backend/api/specs/v1.yaml`

#### 2.5.1. Provider Enum Values

Add your provider to the provider enum in the Provider schema:

```yaml
# Around line 12150 in v1.yaml
Provider:
  type: object
  properties:
    attributes:
      properties:
        provider:
          enum:
          - aws
          - azure
          - gcp
          - kubernetes
          - m365
          - github
          - your_provider  # Add your provider here
          type: string
          description: |-
            * `aws` - AWS
            * `azure` - Azure
            * `gcp` - GCP
            * `kubernetes` - Kubernetes
            * `m365` - M365
            * `github` - GitHub
            * `your_provider` - Your Provider  # Add your provider here
```

#### 2.5.2. Provider Credential Schemas

Add your provider's credential schema to the integration configuration. This defines how your provider's credentials are structured:

```yaml
# Around line 11100 in v1.yaml, in the integration configuration
- type: object
  title: Your Provider Credentials  # Add your provider here
  properties:
    your_auth_field:
      type: string
      description: Your provider authentication field description.
    your_optional_field:
      type: string
      description: Optional field for your provider (if applicable).
    your_required_field:
      type: string
      description: Required field for your provider authentication.
  required:
  - your_auth_field
  - your_required_field
```

#### 2.5.3. Example Provider Schemas

Here are examples of how existing providers are documented:

**AWS Provider:**
```yaml
- type: object
  title: AWS Static Credentials
  properties:
    aws_access_key_id:
      type: string
      description: The AWS access key ID.
    aws_secret_access_key:
      type: string
      description: The AWS secret access key.
  required:
  - aws_access_key_id
  - aws_secret_access_key

- type: object
  title: AWS Assume Role
  properties:
    role_arn:
      type: string
      description: The Amazon Resource Name (ARN) of the role to assume.
    external_id:
      type: string
      description: An identifier to enhance security for role assumption.
  required:
  - role_arn
  - external_id
```

**GitHub Provider:**
```yaml
- type: object
  title: GitHub Personal Access Token
  properties:
    personal_access_token:
      type: string
      description: GitHub personal access token for authentication.
  required:
  - personal_access_token

- type: object
  title: GitHub OAuth App Token
  properties:
    oauth_app_token:
      type: string
      description: GitHub OAuth App token for authentication.
  required:
  - oauth_app_token
```

**M365 Provider:**
```yaml
- type: object
  title: M365 Static Credentials
  properties:
    client_id:
      type: string
      description: The Azure application (client) ID for authentication in Azure AD.
    client_secret:
      type: string
      description: The client secret associated with the application (client) ID.
    tenant_id:
      type: string
      description: The Azure tenant ID, representing the directory where the application is registered.
    user:
      type: email
      description: User microsoft email address.
    password:
      type: string
      description: User password.
  required:
  - client_id
  - client_secret
  - tenant_id
  - user
  - password
```

#### 2.5.4. Important Notes

- **Position**: Add your schema in the `oneOf` array alongside existing providers
- **Structure**: Follow the exact pattern of other providers (title, properties, required fields)
- **Descriptions**: Provide clear, helpful descriptions for each field
- **Required Fields**: Specify which fields are mandatory in the `required` array
- **Field Types**: Use appropriate JSON schema types (`string`, `integer`, `boolean`, `email`, etc.)
- **Validation**: Add any field-specific validation patterns or constraints
- **Documentation**: Ensure your provider appears in the generated API documentation

### 2.6. Testing API Integration

Create tests for your provider:

**Location:** `api/src/backend/api/tests/`

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### 2.6.1. Add Your Mocked Provider to the Tests

If needed, add a named provider fixture or extend the provider factory defaults so tests can request only the provider they need.

**File:** `api/src/backend/conftest.py`

```python
@pytest.fixture
def your_provider(provider_factory):
    return provider_factory(
        provider="your_provider",
        uid="your_uid",
        alias="your_alias",
    )
```

### 2.7. Compliance and Output Support

Add your provider to the compliance export functionality:

**File:** `api/src/backend/tasks/jobs/export.py`

```python
COMPLIANCE_FRAMEWORKS = {
    "aws": [...],
    "azure": [...],
    "gcp": [...],
    "kubernetes": [...],
    "m365": [...],
    "github": [...],
    "your_provider": [  # Add your provider here
        (lambda name: name.startswith("cis_"), YourProviderCIS),
        (lambda name: name.startswith("iso27001_"), YourProviderISO27001),
    ],
}
```

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":
    # ... existing code ...

    # Your provider specific field
    if provider.type == "your_provider":
        finding.your_field = resource.your_field

    # ... rest of the code ...
```

### 2.8. API Endpoints

Your provider will be available through these endpoints:

- `GET /api/v1/providers/` - List all providers
- `POST /api/v1/providers/` - Create a new provider
- `GET /api/v1/providers/{id}/` - Get provider details
- `PUT /api/v1/providers/{id}/` - Update provider
- `DELETE /api/v1/providers/{id}/` - Delete provider
- `POST /api/v1/providers/secrets/` - Add provider credentials

### 2.9. Update the Provider If Needed

Depending on your provider's authentication requirements, you may need to add new authentication methods that are compatible with the API. This involves updating the provider class to support additional credential types beyond the basic ones.

#### 2.9.1. Adding New Authentication Methods

If your provider requires specific authentication methods, you'll need to:

1. **Update the provider constructor** to accept new authentication parameters
2. **Extend the credential handling** to support the new authentication method
3. **Update the API serializers** to include the new credential fields
4. **Modify the OpenAPI specification** to document the new authentication schema

#### 2.9.2. Example: GitHub Provider Authentication Methods

The GitHub provider demonstrates how to implement multiple authentication methods:

```python
# In prowler/providers/github/github_provider.py
def __init__(
    self,
    # Authentication methods
    personal_access_token: str = "",
    oauth_app_token: str = "",
    github_app_key: str = "",
    #Needed for the API integration
    github_app_key_content: str = "",
    github_app_id: int = 0,
    # Provider configuration
    config_path: str = None,
    # ... other parameters
):
    """
    Initialize GitHub provider.

    Args:
        personal_access_token (str): GitHub personal access token.
        oauth_app_token (str): GitHub OAuth App token.
        github_app_key (str): GitHub App key.
        github_app_key_content (str): GitHub App key content.
        github_app_id (int): GitHub App ID.
        config_path (str): Path to the audit configuration file.
        # ... other parameters
    """
    super().__init__(
        personal_access_token,
        oauth_app_token,
        github_app_id,
        github_app_key,
        github_app_key_content,
    )
```

---

## Step 3: Integrate the Provider in the UI

TBD

---

## Provider Implementation Guidance

Use existing providers as templates, this will help you to understand better the structure and the implementation will be easier:

- [AWS (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/aws_provider.py)
- [Azure (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/azure_provider.py)
- [GCP (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/gcp_provider.py)
- [Kubernetes (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/kubernetes_provider.py)
- [M365 (SDK/Wrapper)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/m365_provider.py)
- [GitHub (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/github_provider.py)
- [NHN (API)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/nhn/nhn_provider.py)
- [IAC (Tool)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/iac/iac_provider.py)
- [MongoDB Atlas](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/mongodbatlas/mongodbatlas_provider.py)

---

## Best Practices

- **Code Quality & Documentation**

    - **Comprehensive Docstrings**: Every class, method, and function should have detailed docstrings following Prowler's format
    ```python
    def method_name(self, param: str) -> str:
        """
        Brief description of what the method does.

        Args:
            param: Description of the parameter

        Returns:
            Description of the return value

        Raises:
            ExceptionType: When and why this exception occurs
        """
    ```

    - **Type Hints**: Use type hints for all function parameters and return values
    ```python
    from typing import Optional, List, Dict, Any
    ```

    - **Logging**: Implement proper logging using Prowler's logger
    ```python
    from prowler.lib.logger import logger

    logger.info("Operation completed successfully")
    logger.warning("Something to be aware of")
    logger.error("Something went wrong")
    logger.critical("Critical error that may cause failure")
    ```

- **Error Handling & Validation**

    - **Custom Exceptions**: Create provider-specific exceptions for better error handling
    - **Input Validation**: Validate all inputs and provide clear error messages
    - **Graceful Degradation**: Handle errors gracefully without crashing the entire scan
    - **Raise on Exception**: Use `raise_on_exception` parameter for test methods

- **Testing & Quality Assurance**

    - **Comprehensive Test Coverage**: Aim for >80% test coverage
    - **Test Naming**: Use descriptive test names: `test_method_name_scenario`
    - **Test Organization**: Group related tests in test classes
    - **Mock External Dependencies**: Mock external API calls and services
    - **Test Edge Cases**: Include tests for error conditions and edge cases
    - **End-to-End Testing**: Test the provider on real infrastructure

- **Performance & Security**

    - **Session Management**: Reuse sessions when possible, don't create new ones unnecessarily
    - **Rate Limiting**: Implement rate limiting for API calls to avoid hitting limits
    - **Resource Cleanup**: Ensure proper cleanup of temporary resources
    - **Authentication Security**: Never log sensitive credentials or tokens

- **Code Organization**

    - **Single Responsibility**: Each method should have one clear purpose
    - **Consistent Naming**: Follow Prowler's naming conventions
    - **Modular Design**: Break complex functionality into smaller, testable methods
    - **Configuration Management**: Use configuration files for provider-specific settings

- **Documentation & Maintenance**

    - **README Updates**: Update provider-specific documentation
    - **Changelog**: Document changes and new features with a fragment under `prowler/changelog.d/` (see the [Pull Request Template](https://github.com/prowler-cloud/prowler/blob/master/.github/pull_request_template.md))
    - **Examples**: Provide usage examples and common scenarios
    - **Troubleshooting**: Include common issues and solutions
    - **Documentation**: Update the provider documentation to include your new tool provider in the examples and implementation guidance.

- **Integration Standards**

    - **CLI Consistency**: Follow Prowler's CLI argument patterns
    - **Output Format**: Ensure outputs are compatible with Prowler's reporting system
    - **Compliance Mapping**: Map provider checks to relevant compliance frameworks
    - **Backward Compatibility**: Maintain compatibility when possible

- **AI-Assisted Development**

    - **Use Rules**: Use rules to ensure the code generated by AI is following the way of working in Prowler.

---

## OCSF Field Requirements for Prowler Cloud Integration

When implementing a new provider that supports the `--push-to-cloud` feature, specific OCSF fields must be correctly populated to ensure proper findings ingestion into Prowler Cloud.

### Required OCSF Fields

The following fields in the OCSF output are critical for successful ingestion:

| Field | Requirement | Description |
|-------|-------------|-------------|
| `provider_uid` | Must match the UID used when registering the provider in the API | This identifier links findings to the correct provider in Prowler Cloud |
| `provider` | Must be the provider name | The name of the provider (e.g., `aws`, `azure`, `gcp`, `googleworkspace`) |
| `finding_info.uid` | Must be unique | Each finding must have a unique identifier to avoid duplicates |
| `resources.uid` | Must have a value | The resource UID cannot be empty; it identifies the specific resource being assessed |

### Implementation Reference

These fields are set in the OCSF output generation. See the [OCSF output implementation](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/outputs/ocsf/ocsf.py) for reference.

### Validation Checklist

Before releasing a new provider with `--push-to-cloud` support:

- [ ] Verify `provider_uid` matches the UID used in the API to register the provider
- [ ] Confirm `provider` field contains the correct provider name
- [ ] Ensure all `finding_info.uid` values are unique across findings
- [ ] Validate that `resources.uid` is populated for every finding

<Tip>
  Use `python scripts/validate_ocsf_output.py output/*.ocsf.json` to automate these checks.
</Tip>

## Checklist for New Providers

### CLI Integration Only

**Phase 1: Research & Planning**

- [ ] Soft research completed
- [ ] Spike date scheduled
- [ ] Deeper research completed
- [ ] Action plan created

**Phase 2: Implementation**

- [ ] Folder and files created in `prowler/providers/<name>`
- [ ] Provider class implemented and inherits from `Provider`
- [ ] Authentication/session logic implemented
- [ ] Arguments/flags mapped and documented
- [ ] Outputs and metadata standardized
- [ ] Registered in the CLI
- [ ] Minimal usage example provided

**Phase 3: Delivery**

- [ ] PoC delivered
- [ ] MVP delivered
- [ ] Version 1 completed
- [ ] QA and documentation completed
- [ ] GA release ready

### API Integration

- [ ] All CLI integration items completed
- [ ] Provider added to `ProviderChoices` enum in API models
- [ ] API serializers created/updated for the provider
- [ ] API views support the new provider type
- [ ] Provider credentials model supports the new provider
- [ ] API endpoints tested and working
- [ ] Provider-specific validation implemented
- [ ] API tests created and passing

### UI Integration

- TBD

---

## Next Steps

- [How to add a new Service](./services)
- [How to add new Checks](./checks)
- [How to contribute](./introduction#contributing-to-prowler)

---

### Developer Guide Prowler Studio (`docs/developer-guide/prowler-studio.mdx`)

---
title: 'Prowler Studio'
---

**Prowler Studio is an AI workflow that ensures Claude Code follows Prowler's skills, guardrails, and best practices when creating new security checks.** What lands in the resulting pull request is consistent, tested, and ready for human review — not half-correct boilerplate that needs to be rewritten.

<Info>
**Contributor Tool**: Prowler Studio is a workflow for advanced contributors adding new Prowler security checks. It is not part of Prowler Cloud, Prowler Local Server, or Prowler CLI.
</Info>

<Warning>
**Preview Feature**: Prowler Studio is under active development and breaking changes are expected. Please report issues or share feedback on [GitHub](https://github.com/prowler-cloud/prowler-studio/issues) or in the [Slack community](https://goto.prowler.com/slack).
</Warning>

<Card title="Prowler Studio Repository" icon="github" href="https://github.com/prowler-cloud/prowler-studio" horizontal>
  Clone the source code, install Prowler Studio, and explore the agent workflow in detail.
</Card>

## The Problem

Adding a new check to [Prowler](https://github.com/prowler-cloud/prowler) is more than writing detection logic. A correct check has to:

- Match Prowler's exact service and check folder structure and naming conventions
- Wire up metadata, severity, remediation, tests, and compliance mappings
- Mirror the patterns used by the hundreds of existing checks in the same provider
- Actually load when Prowler scans for available checks — silent structural mistakes are easy to make

Asking a general-purpose AI assistant to do this usually means guessing. It misses conventions, skips tests, or invents structure that looks right but does not load. The result is a half-correct PR that needs to be reviewed line by line or rewritten.

## The Solution

Prowler Studio enforces the workflow end-to-end. Describe the check once — a markdown ticket, a Jira issue, or a GitHub issue — and the workflow:

1. **Loads Prowler-specific skills into every agent.** Every step starts with the same context an experienced Prowler engineer would have in mind. See [AI Skills System](/developer-guide/ai-skills) for how skills are structured.
2. **Runs specialized agents in sequence.** Implementation → testing → compliance mapping → review → PR creation. Each agent has one job and a tight scope.
3. **Verifies as it goes.** The check must load in Prowler. Tests must pass. If something fails, the agent fixes it and re-runs (up to a bounded number of attempts) before moving on.
4. **Produces a complete pull request.** Branch, passing check, tests, compliance mappings, and a pull request waiting for human review.

The result is a consistent starting point, every time, on every supported provider.

## Quick Start

### Install

Prowler Studio requires [`uv`](https://docs.astral.sh/uv/getting-started/installation/) — see the official [installation guide](https://docs.astral.sh/uv/getting-started/installation/).

```bash
git clone https://github.com/prowler-cloud/prowler-studio
cd prowler-studio
uv sync
source .venv/bin/activate
```

### Describe the Check

A ticket is a structured markdown description of the check to create. It is the only input the workflow needs; every agent (implementation, testing, compliance mapping, review, PR creation) uses it as the source of truth, so the more concrete it is, the closer the first PR will land to the desired outcome.

The ticket can be supplied in three ways:

- **Local markdown file** → `--ticket path/to/ticket.md`
- **Jira issue** → `--jira-url https://...` (uses the issue body)
- **GitHub issue** → `--github-url https://...` (uses the issue body)

The content should follow the **New Check Request** template:

- The local copy at [`check_ticket_template.md`](https://github.com/prowler-cloud/prowler-studio/blob/main/check_ticket_template.md) covers `--ticket` and Jira tickets.
- A prefilled GitHub form is also available: [Create a New Check Request issue](https://github.com/prowler-cloud/prowler/issues/new?template=new-check-request.yml).

Sections marked *Optional* can be skipped; everything else helps the agents make the right decisions.

### Run the Workflow

From a local markdown ticket:

```bash
prowler-studio --ticket check_ticket.md
```

From a Jira ticket:

```bash
prowler-studio --jira-url https://mycompany.atlassian.net/browse/PROJ-123
```

From a GitHub issue:

```bash
prowler-studio --github-url https://github.com/owner/repo/issues/123
```

<Note>
Provide exactly one of `--ticket`, `--jira-url`, or `--github-url`.
</Note>

Keep changes local (no push, no pull request):

```bash
prowler-studio -b feat/my-check --ticket check_ticket.md --local
```

### What You Get

After a successful run the working environment contains:

- A new branch on a clean Prowler worktree containing the check, metadata, tests, and compliance mappings
- A pull request opened against Prowler (skipped with `--local`)
- A timestamped log file under `logs/` capturing every step the agents took

## CLI Options

| Option | Short | Description |
|--------|-------|-------------|
| `--branch` | `-b` | Branch name (default: `feat/<ticket>-<check_name>` or `feat/<check_name>`) |
| `--ticket` | `-t` | Path to a markdown check ticket file |
| `--jira-url` | `-j` | Jira ticket URL (e.g., `https://mycompany.atlassian.net/browse/PROJ-123`) |
| `--github-url` | `-g` | GitHub issue URL (e.g., `https://github.com/owner/repo/issues/123`) |
| `--working-dir` | `-w` | Working directory for the Prowler clone (default: `./working`) |
| `--no-worktree` |  | Legacy mode — work directly on the main clone instead of using worktrees |
| `--cleanup-worktree` |  | Remove the worktree after a successful pull request is created |
| `--local` |  | Keep changes local — skip push and pull request creation |

## Configuration

Set these environment variables depending on the input source:

| Variable | When Needed | Purpose |
|----------|-------------|---------|
| `GITHUB_TOKEN` | `--github-url` (recommended) | Higher GitHub API rate limits and access to private issues |
| `JIRA_SITE_URL` | `--jira-url` | Jira site, e.g. `https://mycompany.atlassian.net` |
| `JIRA_EMAIL` | `--jira-url` | Email of the Jira account used to fetch the ticket |
| `JIRA_API_TOKEN` | `--jira-url` | API token for the Jira account |

---

### Developer Guide Renaming Checks (`docs/developer-guide/renaming-checks.mdx`)

---
title: 'Renaming Checks in Prowler'
---

To rename a check in Prowler, follow these steps when aligning with Check ID structure, fixing typos, or updating check logic that requires a new name.

When changing a Check ID, update the following files:

## Update Check Folder Structure

First, rename the check folder with the new check name.

**Path:** `prowler/providers/<provider>/services/<service>/<check_name>`

**Example:**
```
# Before
prowler/providers/aws/services/inspector2/inspector2_findings_exist/

# After
prowler/providers/aws/services/inspector2/inspector2_active_findings_exist/
```

Next, rename the file that contains the check logic. Inside that file, also rename the class name to match the new check name.

**Path:** `prowler/providers/<provider>/services/<service>/<check_name>/<check_name>.py`

**Example:**
```python
# Before
class inspector2_findings_exist(Check):
    def execute(self):
        findings = []
        # ... check logic ...

# After
class inspector2_active_findings_exist(Check):
    def execute(self):
        findings = []
        # ... check logic ...
```

Then, rename the file that contains the check metadata. Inside that file, add the old check name as an alias in the `CheckAliases` field and modify the `CheckID` to the new check name.

**Path:** `prowler/providers/<provider>/services/<service>/<check_name>/<check_name>.metadata.json`

**Example:**
```json
{
  "Provider": "aws",
  "CheckID": "inspector2_active_findings_exist",
  "CheckTitle": "Check if Inspector2 active findings exist",
  "CheckAliases": [
    "inspector2_findings_exist"
  ],
  "CheckType": [],
  "ServiceName": "inspector2",
  "SubServiceName": "",
  "ResourceIdTemplate": "arn:aws:inspector2:region:account-id/detector-id",
  "Severity": "medium",
  "ResourceType": "Other",
  "Description": "This check determines if there are any active findings in your AWS account that have been detected by AWS Inspector2.",
  "Risk": "Without using AWS Inspector, you may not be aware of all the security vulnerabilities in your AWS resources.",
  "RelatedUrl": "https://docs.aws.amazon.com/inspector/latest/user/findings-understanding.html",
  "Remediation": {
    "Code": {
      "CLI": "",
      "NativeIaC": "",
      "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Inspector/amazon-inspector-findings.html",
      "Terraform": ""
    },
    "Recommendation": {
      "Text": "Review the active findings from Inspector2",
      "Url": "https://docs.aws.amazon.com/inspector/latest/user/what-is-inspector.html"
    }
  },
  "Categories": [],
  "DependsOn": [],
  "RelatedTo": [],
  "Notes": ""
}
```

## Update Test Files

Second, rename the tests folder with the new check name.

**Path:** `tests/providers/<provider>/services/<service>/<check_name>`

**Example:**
```
# Before
tests/providers/aws/services/inspector2/inspector2_findings_exist/

# After
tests/providers/aws/services/inspector2/inspector2_active_findings_exist/
```

Next, rename the test file that contains all the unit tests. Inside that file, rename all appearances of the old check name to the new check name.

**Path:** `tests/providers/<provider>/services/<service>/<check_name>/<check_name>_test.py`

**Example:**
```python
# Before
from prowler.providers.aws.services.inspector2.inspector2_findings_exist.inspector2_findings_exist import (
    inspector2_findings_exist,
)

class Test_inspector2_findings_exist:
    def test_inspector2_no_findings(self):
        # ... test logic ...

    def test_inspector2_with_findings(self):
        # ... test logic ...

# After
from prowler.providers.aws.services.inspector2.inspector2_active_findings_exist.inspector2_active_findings_exist import (
    inspector2_active_findings_exist,
)

class Test_inspector2_active_findings_exist:
    def test_inspector2_no_findings(self):
        # ... test logic ...

    def test_inspector2_with_findings(self):
        # ... test logic ...
```

**Important:** Update all references to the old check name in the test file, including:

- Import statements at the top of the file
- Class name in the test class
- Any function calls to the check
- Any string references to the check name
- Mock patches that reference the check

**Complete example of all changes needed in test files:**
```python
# Before
from prowler.providers.aws.services.inspector2.inspector2_findings_exist.inspector2_findings_exist import (
    inspector2_findings_exist,
)

class Test_inspector2_findings_exist:
    def test_inspector2_no_findings(self):
        # Mock setup
        with mock.patch(
            "prowler.providers.aws.services.inspector2.inspector2_findings_exist.inspector2_findings_exist.inspector2_client",
            inspector2_client,
        ):
            check = inspector2_findings_exist()
            result = check.execute()
            assert len(result) == 1
            assert result[0].status == "PASS"
            assert "No active findings found" in result[0].status_extended

# After
from prowler.providers.aws.services.inspector2.inspector2_active_findings_exist.inspector2_active_findings_exist import (
    inspector2_active_findings_exist,
)

class Test_inspector2_active_findings_exist:
    def test_inspector2_no_findings(self):
        # Mock setup
        with mock.patch(
            "prowler.providers.aws.services.inspector2.inspector2_active_findings_exist.inspector2_active_findings_exist.inspector2_client",
            inspector2_client,
        ):
            check = inspector2_active_findings_exist()
            result = check.execute()
            assert len(result) == 1
            assert result[0].status == "PASS"
            assert "No active findings found" in result[0].status_extended
```

## Update Compliance Mappings

Finally, rename all appearances of the old check name to the new check name inside any compliance framework where the check is mapped.

- `prowler/compliance/<service>/<compliance_where_the_check_is_mapped>.json`

**Example:**
```json
{
  "Framework": "CIS",
  "Version": "2.0",
  "Provider": "AWS",
  "Description": "The CIS Amazon Web Services Foundations Benchmark provides prescriptive guidance for configuring security options for a subset of Amazon Web Services.",
  "Requirements": [
    {
      "Id": "4.1",
      "Description": "Ensure a log metric filter and alarm exist for unauthorized API calls",
      "Checks": [
        "inspector2_active_findings_exist"
      ],
      "Attributes": [
        {
          "Section": "4 Logging and Monitoring",
          "Profile": "Level 1",
          "AssessmentStatus": "Automated",
          "Description": "Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms."
        }
      ]
    }
  ]
}
```

The development compliance file may contain examples of the check being renamed. If so, modify this file as well:

- `api/src/backend/api/fixtures/dev/7_dev_compliance.json`

---

### Developer Guide Secret Scanning Checks (`docs/developer-guide/secret-scanning-checks.mdx`)

---
title: 'Secret-Scanning Checks'
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="5.32.0" />

Prowler scans audited resources for plaintext secrets using [Kingfisher](https://github.com/mongodb/kingfisher), an open-source secret-scanning engine that Prowler invokes as a subprocess. This guide explains the structure every secret-scanning check must follow to keep scanning correct and efficient on large accounts.

<Note>
Since Prowler 5.32.0 the secret-scanning checks scan with Kingfisher. Earlier versions used the `detect-secrets` library.
</Note>

## Overview

Secret detection runs through a single helper in `prowler/lib/utils/utils.py`:

- **`detect_secrets_scan_batch(payloads, excluded_secrets=..., validate=...)`** scans many payloads in chunked subprocess invocations and returns a `{key: [findings]}` dictionary. To scan a single payload, pass a one-entry mapping (for example, `{0: data}`).

Every Kingfisher invocation carries a fixed process-startup cost (around 100 ms). Scanning once per resource would spawn thousands of subprocesses on large accounts (for example, thousands of CloudWatch log groups). `detect_secrets_scan_batch` amortizes that cost: it writes each payload to a temporary file as it consumes them, runs one subprocess per chunk (500 payloads by default), and maps the findings back to each payload by key.

## The Batched Structure

Every secret-scanning check follows three phases.

### Phase 1: Collect

Define a generator that yields `(key, payload)` for each scannable unit. The generator builds payload strings only — it does not call Kingfisher. Lazy yielding keeps memory and temporary-disk usage bounded to a single chunk, which matters when an account holds thousands of resources.

### Phase 2: Batch

Call `detect_secrets_scan_batch` once with the generator. The helper consumes it in chunks, runs Kingfisher per chunk, and returns the keys that produced findings mapped to their finding lists.

### Phase 3: Report

Iterate the resources, look up the findings by key, and build one report per resource. Emit a finding for **every** iterated resource — never drop one silently. When a resource's payload cannot be prepared for scanning (for example, user data that fails to base64-decode or decompress), report it as `MANUAL` with a status explaining the scan could not inspect it, rather than omitting it or claiming `PASS`.

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Choosing the Key

The key maps each finding back to its source. Two shapes cover every check:

- **One payload per resource:** use the resource index. This fits checks that serialize a single payload per resource, such as launch configurations, CloudFormation outputs, SSM documents, Step Functions definitions, and OpenStack metadata.
- **Several payloads per resource:** use a `(resource_index, fragment)` tuple, where the fragment identifies the variable, log stream, container, file, or version. Phase 3 groups the per-fragment findings to build the resource report. This fits CloudWatch log streams, ECS containers, CodeBuild variables, Glue arguments, and Lambda code files.

Derive the indices from the same `list(...)` of resources in both Phase 1 and Phase 3 so the order stays stable and the keys align.

## Preserving Per-Payload Results

`detect_secrets_scan_batch` runs Kingfisher with `--no-dedup`, so a secret that appears in more than one payload is reported for each one. This reproduces the result of scanning each payload individually. Build payload strings exactly as a single scan would: serialize the same data and keep line ordering, because messages often map a finding's `line_number` back to a variable name or metadata key.

## Validation and Severity

`detect_secrets_scan_batch` accepts `validate`, read from `secrets_validate` in the provider configuration or the `--scan-secrets-validate` flag. When enabled, Kingfisher confirms whether each secret is live, and confirmed secrets carry `is_verified: True`.

After marking a report as `FAIL`, pass the findings to `annotate_verified_secrets(report, findings)`. When any secret is verified, the helper escalates the finding to critical severity and appends a note that the secret was confirmed live. Validation stays off by default because it sends the discovered secret to the provider API.

## Excluded Secrets

`detect_secrets_scan_batch` applies `secrets_ignore_patterns` — regular expressions from the provider configuration — against each finding's source line and drops the matches, mirroring single-scan behavior.

## Testing

To assert on the verified-secret path, mock `detect_secrets_scan_batch` in the check module and return the keyed dictionary. For a single resource scanned at index `0`:

```python
mock.patch(
    "prowler.providers.aws.services.example.example_resource_no_secrets.example_resource_no_secrets.detect_secrets_scan_batch",
    return_value={
        0: [{"type": "...", "line_number": 1, "is_verified": True}]
    },
)
```

Most tests need no mock at all: they seed resources that contain example secrets and assert on the `FAIL` status and message, which exercises the real batched path. Refer to the [Testing](/developer-guide/unit-testing) documentation for the general structure.

---

### Developer Guide Security Compliance Framework (`docs/developer-guide/security-compliance-framework.mdx`)

---
title: 'Creating a New Security Compliance Framework in Prowler'
---

import { VersionBadge } from "/snippets/version-badge.mdx"

This guide explains how to add a new security compliance framework to Prowler, end to end. It covers directory layout, the two supported JSON schemas (universal and legacy), the Pydantic models that validate each framework, check mapping conventions, output formatting, local validation, testing, and the pull request process.

## Introduction

A compliance framework in Prowler maps a public or custom control catalog (for example CIS, NIST 800-53, PCI DSS, HIPAA, ENS, CCC, DORA) to the security checks that Prowler already runs. Each requirement links to zero, one or more Prowler checks. When a scan executes, findings are aggregated per requirement to produce the compliance report rendered by Prowler CLI and Prowler Cloud.

Prowler ships 85+ compliance frameworks across all providers. The catalog lives under `prowler/compliance/<provider>/` (legacy, per-provider) or `prowler/compliance/` (universal, multi-provider).

<Warning>
A compliance framework must represent the **complete state** of the source catalog. Every requirement defined by the framework has to be present in the JSON file, even when no Prowler check can automate it. In that case, leave the requirement's check list empty, but do not omit the requirement.

Requirement coverage feeds the compliance percentage calculations and the metadata surfaces (dashboards, widgets, exports). Missing requirements skew those metrics and break the report as a faithful snapshot of the framework.
</Warning>

### Two Supported Schemas

| Schema | When to use | File location | Discovered as |
| --- | --- | --- | --- |
| **Universal (recommended for new frameworks)** | Multi-provider frameworks, or single-provider frameworks that benefit from declarative table/PDF rendering | `prowler/compliance/<framework>.json` (top-level) | Available for **every** provider whose key appears in any `requirement.checks` dict |
| **Legacy provider-specific** | Single-provider frameworks with framework-specific attribute classes already declared in the codebase (CIS, ENS, ISO 27001, etc.) | `prowler/compliance/<provider>/<framework>_<version>_<provider>.json` | Available only under that provider |

Auto-discovery happens in `get_bulk_compliance_frameworks_universal(provider)` (`prowler/lib/check/compliance_models.py`), which scans **both** the top-level `prowler/compliance/` directory and every per-provider sub-directory. Legacy frameworks are transparently converted to the universal `ComplianceFramework` model via `adapt_legacy_to_universal()` before being returned, so the rest of Prowler — CLI table rendering, CSV/OCSF outputs, PDF generation — works the same regardless of the source schema.

> The legacy entry-point `Compliance.get_bulk(provider)` (used by older code paths) only scans per-provider sub-directories. Universal top-level files are picked up exclusively via the universal loader; this matters if you are wiring a new code path against the legacy API.

For **new** frameworks, prefer the universal schema: it requires no Python code changes, supports multiple providers in a single file, and table/PDF rendering is driven entirely from declarative configuration inside the JSON.

> All Pydantic models in `compliance_models.py` are imported from `pydantic.v1`. Subclasses you add for the legacy schema must use `from pydantic.v1 import BaseModel`.

### Prerequisites

Before adding a new framework, complete the following checks:

- **Verify the framework is not already supported.** Inspect `prowler/compliance/` and every `prowler/compliance/<provider>/` for an existing JSON file matching the name and version.
- **Confirm the required checks exist.** Every requirement that can be automated must point to one or more existing Prowler checks. For each missing check, implement it first by following the [Prowler Checks](/developer-guide/checks) guide.
- **Review a reference framework.** Use an existing framework with a similar structure as your template:
  - Universal: `prowler/compliance/dora_2022_2554.json`, `prowler/compliance/csa_ccm_4.0.json`.
  - Legacy: `prowler/compliance/aws/cis_2.0_aws.json` (canonical CIS shape), `prowler/compliance/aws/ccc_aws.json`, `prowler/compliance/aws/ens_rd2022_aws.json`, `prowler/compliance/aws/nist_800_53_revision_5_aws.json`.

## Universal Compliance Framework

### Where the File Lives

Place the file at the top level of the compliance directory:

```
prowler/compliance/<framework_name>.json
```

Examples in the repository: `prowler/compliance/csa_ccm_4.0.json`, `prowler/compliance/dora_2022_2554.json`.

The file is auto-discovered — there is **no** need to register it in any `__init__.py`, modify `prowler/lib/outputs/`, or update any other Python module. The framework key Prowler CLI accepts via `--compliance` is the basename of the JSON file without `.json` (`dora_2022_2554.json` → `dora_2022_2554`).

### Top-Level Structure

```json
{
  "framework": "<short identifier, e.g. \"DORA\" or \"CSA-CCM\">",
  "name": "<human-readable full name>",
  "version": "<framework version>",
  "description": "<one-paragraph description shown in --list-compliance and PDF reports>",
  "icon": "<short icon slug, optional>",
  "attributes_metadata": [ /* see below */ ],
  "outputs": { /* see below — optional */ },
  "requirements": [ /* see below */ ]
}
```

A `provider` field at the top level is **optional**. The framework's effective provider list is derived by `ComplianceFramework.get_providers()` (`compliance_models.py`) from the union of all keys appearing in `requirement.checks` across all requirements; the explicit `provider` field is used **only as a fallback** when no requirement carries any `checks` key. This is what enables a single file (e.g. `dora_2022_2554.json`) to cover AWS today and add Azure / GCP / etc. tomorrow without restructuring.

Provider keys inside `requirement.checks` must match the directory names under `prowler/providers/`. The valid keys at present are: `aws`, `azure`, `gcp`, `m365`, `kubernetes`, `iac`, `github`, `googleworkspace`, `alibabacloud`, `cloudflare`, `mongodbatlas`, `nhn`, `openstack`, `oraclecloud`, `llm`. Comparison in `supports_provider()` is case-insensitive, but lowercase is the convention used everywhere in the repository.

### `attributes_metadata`

Declares the shape of the per-requirement `attributes` dict. When this field is present, the root validator `validate_attributes_against_metadata` (`compliance_models.py`) enforces the schema at load time and rejects:

- Missing keys marked `required: true`.
- Keys present in `attributes` but not declared in `attributes_metadata` (typo / drift guard).
- Values that violate a declared `enum`.
- Values whose Python type does not match a declared `int`, `float` or `bool`.

The runtime type check **only** covers `int`, `float` and `bool`. For `str`, `list_str` and `list_dict` the type is documentation-only — non-conforming values won't fail validation. If `attributes_metadata` is omitted, **no per-requirement validation runs at all**.

```json
"attributes_metadata": [
  {
    "key": "Pillar",
    "label": "Pillar",
    "type": "str",
    "required": true,
    "enum": [
      "ICT Risk Management",
      "ICT-Related Incident Reporting",
      "Digital Operational Resilience Testing",
      "ICT Third-Party Risk Management",
      "Information Sharing"
    ],
    "output_formats": { "csv": true, "ocsf": true }
  },
  {
    "key": "Article",
    "label": "Article",
    "type": "str",
    "required": true,
    "output_formats": { "csv": true, "ocsf": true }
  }
]
```

Per attribute:

- `key` (required): attribute name as it will appear in `requirement.attributes`.
- `label`: human-readable label used in CSV headers and PDF.
- `type`: one of `str`, `int`, `float`, `bool`, `list_str`, `list_dict`. Defaults to `str`.
- `enum`: optional list of allowed values; non-conforming values are rejected at load time.
- `required`: if `true`, every requirement must include this key with a non-null value.
- `enum_display` / `enum_order`: optional per-enum-value visual metadata (label, abbreviation, color, icon) and explicit ordering for PDF rendering.
- `output_formats`: `{ "csv": <bool>, "ocsf": <bool> }` — toggles inclusion in each output format. Both default to `true`.

### `outputs`

Optional. Controls how the framework is rendered in the console table and in the generated PDF report. Skipping it falls back to sensible defaults.

```json
"outputs": {
  "table_config": {
    "group_by": "Pillar"
  },
  "pdf_config": {
    "language": "en",
    "primary_color": "#003399",
    "secondary_color": "#0055A5",
    "bg_color": "#F0F4FA",
    "group_by_field": "Pillar",
    "sections": [ "ICT Risk Management", "ICT-Related Incident Reporting", "..." ],
    "section_short_names": { "ICT Risk Management": "ICT Risk Mgmt" },
    "charts": [
      {
        "id": "pillar_compliance",
        "type": "horizontal_bar",
        "group_by": "Pillar",
        "title": "Compliance Score by Pillar",
        "y_label": "Pillar",
        "x_label": "Compliance %",
        "value_source": "compliance_percent",
        "color_mode": "by_value"
      }
    ],
    "filter": { "only_failed": true, "include_manual": false }
  }
}
```

`table_config.group_by` must reference an attribute key declared in `attributes_metadata`. The same applies to `pdf_config.group_by_field` and to every `charts[].group_by`.

For frameworks with weighted scoring (e.g. ThreatScore) declare `pdf_config.scoring` with `risk_field` / `weight_field` / `risk_boost_factor`. For column splitting (e.g. CIS Level 1 vs Level 2) use `table_config.split_by`.

### `requirements`

```json
"requirements": [
  {
    "id": "DORA-Art5",
    "name": "Governance and organisation",
    "description": "Financial entities shall have a sound, comprehensive and well-documented ICT internal governance and control framework. ...",
    "attributes": {
      "Pillar": "ICT Risk Management",
      "Article": "Article 5",
      "ArticleTitle": "Governance and organisation"
    },
    "checks": {
      "aws": [
        "iam_avoid_root_usage",
        "iam_no_root_access_key",
        "iam_root_mfa_enabled"
      ],
      "azure": [],
      "gcp": []
    }
  }
]
```

Per requirement:

- `id` (required): unique identifier within the framework.
- `description` (required): the requirement text as authored by the framework.
- `name`: short title shown alongside the id.
- `attributes`: flat dict; keys must conform to `attributes_metadata`.
- `checks`: dict keyed by provider name (the same lowercase keys listed in the previous section). Each value is a list of Prowler check names that evidence this requirement for that provider. The list **may be empty** and the dict itself defaults to `{}` if omitted; either way the requirement is still loaded and listed by `--list-compliance-requirements`, it just has zero checks to execute. Note: there is **no automatic check-existence validation** at load time — referencing a non-existent check name will silently produce a requirement with no findings. Validate this yourself (see "Validating Your Framework" below).
- `config_requirements`: optional list of configuration guardrails. Each entry asserts that a configurable check referenced by this requirement ran with a configuration strict enough to actually satisfy the requirement; otherwise the requirement is forced to FAIL. See [Configuration Guardrails for Requirements](#configuration-guardrails-for-requirements) for the full schema and semantics. In the universal schema the field name is lowercase (`config_requirements`); legacy files use `ConfigRequirements`.

For MITRE-style frameworks, additional optional fields are available on the requirement: `tactics`, `sub_techniques`, `platforms`, `technique_url` (these are populated automatically when adapting a legacy MITRE JSON to the universal model).

### Multi-Provider Frameworks

A single universal file can cover any number of providers. The framework appears under each provider's `--list-compliance` output as long as **at least one** requirement has that provider key in its `checks` dict.

When extending an existing universal framework with a new provider, the only change required is editing `requirement.checks`:

```diff
 "checks": {
   "aws": ["iam_avoid_root_usage", "iam_no_root_access_key"],
+  "azure": ["entra_policy_ensure_mfa_for_admin_roles"]
 }
```

No code changes, no new file, no registration step.

## Legacy Provider-Specific Compliance Framework

The legacy schema is still fully supported and remains the format used by most frameworks shipped today (CIS, NIST, ISO 27001, FedRAMP, PCI DSS, GDPR, HIPAA, ENS, etc.). It binds a framework to a single provider and validates each requirement against a framework-specific Pydantic attribute class.

The legacy schema spans **four layers** — a complete contribution must touch every layer that applies:

- **Layer 1 — Schema validation:** the Pydantic models in `prowler/lib/check/compliance_models.py` define the canonical schema for each attribute shape.
- **Layer 2 — JSON catalog:** the framework JSON file in `prowler/compliance/<provider>/` lists every requirement and maps it to checks.
- **Layer 3 — Output formatter:** the Python module in `prowler/lib/outputs/compliance/<framework>/` builds the CSV row model, the per-provider transformer, and the CLI summary table.
- **Layer 4 — Output dispatchers:** the dispatchers in `prowler/lib/outputs/compliance/compliance.py` and `prowler/lib/outputs/compliance/compliance_output.py` route findings to the right formatter based on the framework identifier.

The universal schema collapses Layers 3 and 4 into declarative configuration inside the JSON — that is the main reason it is preferred for new contributions.

### Directory Structure and File Naming

Compliance frameworks live at:

```
prowler/compliance/<provider>/<framework>_<version>_<provider>.json
```

The filename conventions are:

- All lowercase, words separated with underscores.
- `<provider>` is a supported provider identifier (same lowercase list as the universal section above).
- `<version>` is optional but recommended. Omit only when the framework has no versioning (e.g. `ccc_aws.json`).
- The file basename (without `.json`) is the framework key that Prowler CLI accepts via `--compliance`.

Examples:

- `prowler/compliance/aws/cis_2.0_aws.json`
- `prowler/compliance/aws/nist_800_53_revision_5_aws.json`
- `prowler/compliance/azure/ens_rd2022_azure.json`
- `prowler/compliance/kubernetes/cis_1.10_kubernetes.json`
- `prowler/compliance/aws/ccc_aws.json`

The output formatter directory mirrors the framework name:

```
prowler/lib/outputs/compliance/<framework>/
├── <framework>.py            # CLI summary-table dispatcher
├── <framework>_<provider>.py # Per-provider transformer class
├── models.py                 # Pydantic CSV row model
└── __init__.py
```

### JSON Schema Reference

Every legacy compliance file is a JSON document with the following top-level keys. `Framework`, `Name` and `Provider` are validated non-empty by the root validator `framework_and_provider_must_not_be_empty` (`compliance_models.py`).

| Field | Type | Required | Description |
|---|---|---|---|
| `Framework` | string | Yes | Canonical framework identifier, for example `CIS`, `NIST-800-53-Revision-5`, `ENS`, `CCC`. |
| `Name` | string | Yes | Human-readable framework name displayed by Prowler Cloud and Prowler Local Server. |
| `Version` | string | Yes (recommended) | Framework version, e.g. `2.0`. See [Version Handling](#version-handling). |
| `Provider` | string | Yes | Upper-cased provider identifier: `AWS`, `AZURE`, `GCP`, `KUBERNETES`, `M365`, `GITHUB`, `GOOGLEWORKSPACE`, and so on. |
| `Description` | string | Yes | Short description of the framework's scope and purpose. |
| `Requirements` | array | Yes | List of [requirement objects](#requirement-object). |

#### Requirement Object

Each entry in `Requirements` describes one control or requirement.

| Field | Type | Required | Description |
|---|---|---|---|
| `Id` | string | Yes | Unique identifier within the framework, for example `1.10` or `CCC.Core.CN01.AR01`. |
| `Name` | string | No | Optional human-readable name (frameworks like NIST distinguish control name from description). |
| `Description` | string | Yes | Verbatim description from the source framework. |
| `Attributes` | array | Yes | List of [attribute objects](#attribute-objects). The shape depends on the framework. |
| `Checks` | array of strings | Yes | Prowler check identifiers that automate the requirement. Leave the list empty when the control cannot be automated. |
| `ConfigRequirements` | array of objects | No | Optional [configuration guardrails](#configuration-guardrails-for-requirements). Each entry asserts that a configurable check ran with a configuration strict enough to satisfy the requirement; when it did not, the requirement is forced to FAIL. |

#### Attribute Objects

`Attributes` is parsed against the union declared in `Compliance_Requirement.Attributes` (`compliance_models.py`). Pydantic v1 tries each member of the union in declaration order and falls back to `Generic_Compliance_Requirement_Attribute` (the last entry) when nothing else matches — so a brand-new shape that doesn't match any existing class will silently be accepted as Generic, losing its specific fields.

As of today, the registered attribute classes are: `CIS_Requirement_Attribute`, `ENS_Requirement_Attribute`, `ASDEssentialEight_Requirement_Attribute`, `ISO27001_2013_Requirement_Attribute`, `AWS_Well_Architected_Requirement_Attribute`, `KISA_ISMSP_Requirement_Attribute`, `Prowler_ThreatScore_Requirement_Attribute`, `CCC_Requirement_Attribute`, `C5Germany_Requirement_Attribute`, `CSA_CCM_Requirement_Attribute`, and `Generic_Compliance_Requirement_Attribute` (fallback). MITRE-style frameworks use the separate `Mitre_Requirement` model with `Tactics` / `SubTechniques` / `Platforms` / `TechniqueURL` at the requirement top level. The most common shapes are summarized below.

##### CIS_Requirement_Attribute

Used by every CIS benchmark.

| Field | Type | Required | Notes |
|---|---|---|---|
| `Section` | string | Yes | Top-level section, e.g. `1 Identity and Access Management`. |
| `SubSection` | string | No | Optional second-level grouping. |
| `Profile` | enum | Yes | One of `Level 1`, `Level 2`, `E3 Level 1`, `E3 Level 2`, `E5 Level 1`, `E5 Level 2`. |
| `AssessmentStatus` | enum | Yes | `Manual` or `Automated`. |
| `Description` | string | Yes | Control description. |
| `RationaleStatement` | string | Yes | Reason the control exists. |
| `ImpactStatement` | string | Yes | Impact of non-compliance. |
| `RemediationProcedure` | string | Yes | Remediation steps. |
| `AuditProcedure` | string | Yes | Audit steps. |
| `AdditionalInformation` | string | Yes | Free-form notes. |
| `DefaultValue` | string | No | Default configuration value, when relevant. |
| `References` | string | Yes | Colon-separated list of reference URLs. |

##### ENS_Requirement_Attribute

Used by the Spanish ENS (Esquema Nacional de Seguridad) frameworks.

| Field | Type | Required | Notes |
|---|---|---|---|
| `IdGrupoControl` | string | Yes | Control group identifier. |
| `Marco` | string | Yes | Framework block (`operacional`, `organizativo`, `proteccion`). |
| `Categoria` | string | Yes | Control category. |
| `DescripcionControl` | string | Yes | Control description in Spanish. |
| `Tipo` | enum | Yes | `refuerzo`, `requisito`, `recomendacion`, `medida`. |
| `Nivel` | enum | Yes | `opcional`, `bajo`, `medio`, `alto`. |
| `Dimensiones` | array of enum | Yes | Subset of `confidencialidad`, `integridad`, `trazabilidad`, `autenticidad`, `disponibilidad`. |
| `ModoEjecucion` | string | Yes | Execution mode (`manual`, `automático`, `híbrido`). |
| `Dependencias` | array of strings | Yes | Ids of prerequisite controls. Empty list when none. |

##### CCC_Requirement_Attribute

Used by the Common Cloud Controls Catalog.

| Field | Type | Required | Notes |
|---|---|---|---|
| `FamilyName` | string | Yes | Control family, e.g. `Data`. |
| `FamilyDescription` | string | Yes | Description of the family. |
| `Section` | string | Yes | Section title. |
| `SubSection` | string | Yes | Subsection title, or empty string. |
| `SubSectionObjective` | string | Yes | Stated objective for the subsection. |
| `Applicability` | array of strings | Yes | Applicability tags such as `tlp-green`, `tlp-amber`, `tlp-red`. |
| `Recommendation` | string | Yes | Implementation recommendation. |
| `SectionThreatMappings` | array of objects | Yes | Each entry has `ReferenceId` and `Identifiers`. |
| `SectionGuidelineMappings` | array of objects | Yes | Each entry has `ReferenceId` and `Identifiers`. |

##### Generic_Compliance_Requirement_Attribute

The fallback attribute model used when no framework-specific schema applies (e.g. NIST 800-53, PCI DSS, GDPR, HIPAA). It is **always the last** element of the `Compliance_Requirement.Attributes` Union; that ordering is load-bearing.

| Field | Type | Required | Notes |
|---|---|---|---|
| `ItemId` | string | No | Item identifier. |
| `Section` | string | No | Section name. |
| `SubSection` | string | No | Subsection name. |
| `SubGroup` | string | No | Subgroup name. |
| `Service` | string | No | Affected service, e.g. `iam`. |
| `Type` | string | No | Control type. |
| `Comment` | string | No | Free-form comment. |

For the remaining attribute classes (`AWS_Well_Architected_Requirement_Attribute`, `ISO27001_2013_Requirement_Attribute`, `Mitre_Requirement_Attribute_<Provider>`, `KISA_ISMSP_Requirement_Attribute`, `Prowler_ThreatScore_Requirement_Attribute`, `C5Germany_Requirement_Attribute`, `CSA_CCM_Requirement_Attribute`) consult `prowler/lib/check/compliance_models.py` for the full field sets.

<Note>
The `Attributes` field is a Pydantic `Union`. The generic attribute model **must** remain the last element of that Union — otherwise Pydantic v1 silently coerces every framework into the generic shape and your specialized fields are dropped. Adding a brand-new attribute shape requires inserting the Pydantic class **before** `Generic_Compliance_Requirement_Attribute`.
</Note>

#### Minimal Working Example

The following snippet is a complete, valid framework file named `my_framework_1.0_aws.json`, saved at `prowler/compliance/aws/my_framework_1.0_aws.json`. It uses the generic attribute shape for simplicity.

```json title="prowler/compliance/aws/my_framework_1.0_aws.json"
{
  "Framework": "My-Framework",
  "Name": "My Framework 1.0 for AWS",
  "Version": "1.0",
  "Provider": "AWS",
  "Description": "Internal baseline for AWS accounts.",
  "Requirements": [
    {
      "Id": "MF-1.1",
      "Description": "Root account must have multi-factor authentication enabled.",
      "Attributes": [
        {
          "ItemId": "MF-1.1",
          "Section": "Identity and Access Management",
          "SubSection": "Root Account",
          "Service": "iam"
        }
      ],
      "Checks": [
        "iam_root_mfa_enabled",
        "iam_root_hardware_mfa_enabled"
      ]
    },
    {
      "Id": "MF-2.1",
      "Description": "S3 buckets must block public access at the account level.",
      "Attributes": [
        {
          "ItemId": "MF-2.1",
          "Section": "Data Protection",
          "Service": "s3"
        }
      ],
      "Checks": [
        "s3_account_level_public_access_blocks"
      ]
    }
  ]
}
```

### Mapping Checks to Requirements

Each requirement links to the Prowler checks that, together, produce a PASS or FAIL verdict for that control.

- **Include every requirement from the source catalog.** The framework file must mirror the full control list, one-to-one. Compliance percentages, dashboards, and exported metadata are computed against the total requirement count.
- List every check by its canonical identifier — the value of `CheckID` inside the check's `.metadata.json` file.
- One requirement can reference multiple checks. The requirement is evaluated as FAIL when any referenced check produces a FAIL finding for a resource in scope.
- Leave `Checks` (legacy) or `checks.<provider>` (universal) as an empty array when the requirement cannot be automated. The requirement still appears in the report and contributes to the total.
- Reuse checks across requirements when the same control applies in multiple places. Do not duplicate check logic to match framework structure.
- Avoid referencing checks from a different provider. A legacy compliance file is bound to one provider, and cross-provider checks will never match findings in the scan.

To discover available checks:

```bash
uv run python prowler-cli.py <provider> --list-checks
```

### Supporting Multiple Providers (Legacy)

The legacy schema binds each file to a single provider. To cover several providers with the same framework, ship one JSON file per provider:

```
prowler/compliance/aws/cis_2.0_aws.json
prowler/compliance/azure/cis_2.0_azure.json
prowler/compliance/gcp/cis_2.0_gcp.json
```

Keep the `Framework` and `Version` values identical across the files so the dispatcher matches them; change only the `Provider`, `Checks`, and provider-specific metadata. The CIS output formatter already supports every provider listed above.

For a brand-new framework that spans several providers, **prefer the universal schema** — it covers every provider from a single file. If you must use the legacy schema, add one transformer per provider in `prowler/lib/outputs/compliance/<framework>/` and extend the summary-table dispatcher accordingly. See [Output Formatter](#output-formatter).

### Output Formatter

Legacy frameworks render in two forms: a detailed CSV report written to disk, and a summary table printed in the CLI. Both are produced by the output formatter package for the framework. Universal frameworks do **not** need a Python output formatter — the `outputs` config inside the JSON drives rendering — so this section applies only to the legacy schema.

For a new legacy framework named `my_framework`, create:

```
prowler/lib/outputs/compliance/my_framework/
├── __init__.py
├── my_framework.py            # CLI summary table dispatcher
├── my_framework_aws.py        # Per-provider transformer
└── models.py                  # CSV row Pydantic model
```

#### Step 1 — Define the CSV Row Model

In `models.py`, declare a Pydantic v1 model with one field per CSV column. Use existing models such as `AWSCISModel` in `prowler/lib/outputs/compliance/cis/models.py` as the reference. Fields typically include `Provider`, `Description`, `AccountId`, `Region`, `AssessmentDate`, `Requirements_Id`, `Requirements_Description`, one `Requirements_Attributes_*` field per attribute key, plus the finding fields `Status`, `StatusExtended`, `ResourceId`, `ResourceName`, `CheckId`, `Muted`, `Framework`, `Name`.

#### Step 2 — Implement the Transformer

In `my_framework_aws.py`, subclass `ComplianceOutput` from `prowler.lib.outputs.compliance.compliance_output` and implement `transform(findings, compliance, compliance_name)`. Iterate over `findings`, match each finding to the requirements it satisfies through `finding.compliance.get(compliance_name, [])`, and append one row per attribute to `self._data`.

#### Step 3 — Add the Summary-Table Dispatcher

In `my_framework.py`, implement `get_my_framework_table(findings, bulk_checks_metadata, compliance_framework, output_filename, output_directory, compliance_overview)` following the pattern in `prowler/lib/outputs/compliance/cis/cis.py`.

#### Step 4 — Register the Framework in the Dispatchers

- Add the dispatcher call in `prowler/lib/outputs/compliance/compliance.py`, inside `display_compliance_table`, with a branch such as `elif "my_framework" in compliance_framework:`.
- Register the CSV model and transformer in `prowler/lib/outputs/compliance/compliance_output.py` so the CSV file is emitted during the scan.

<Note>
For NIST-style catalogs that use `Generic_Compliance_Requirement_Attribute`, no custom formatter is needed. The generic formatter in `prowler/lib/outputs/compliance/generic/` handles them automatically, provided the JSON validates against the generic attribute schema.
</Note>

### Legacy-to-Universal Adapter

At load time, every legacy file is transparently adapted to a `ComplianceFramework` via `adapt_legacy_to_universal()` (`compliance_models.py`), which: (a) flattens the first element of `Attributes` into a flat `attributes` dict, (b) wraps `Checks` as `{provider_lower: [...]}`, (c) infers `attributes_metadata` from the matched Pydantic class via `_infer_attribute_metadata()`. The rest of Prowler (CSV/OCSF/PDF output, CLI table) then treats both formats identically.

Loader-error behaviour differs between the two entry points:

- `load_compliance_framework()` (legacy) is **fail-fast**: it calls `sys.exit(1)` on any `ValidationError` (`compliance_models.py`).
- `load_compliance_framework_universal()` is more lenient — it logs the error and returns `None`, so `get_bulk_compliance_frameworks_universal()` simply skips the broken file and keeps loading the rest.

## Configuration Guardrails for Requirements

<VersionBadge version="5.32.0" />

Some requirements are only truly satisfied when the configurable checks behind them ran with a configuration strict enough to meet the control. A [configurable check](/developer-guide/configurable-checks) reads thresholds from the scan's `audit_config`, so loosening a value can make the check PASS while the requirement it backs is, in fact, not satisfied.

A worked example: CIS AWS 6.0 requirement 2.11 ("credentials unused for 45 days or more are disabled") maps to `iam_user_accesskey_unused`, which is driven by the `max_unused_access_keys_days` config key. If a user raises that value to `120`, the check passes for a key unused for 90 days — yet the requirement explicitly demands a 45-day threshold, so the PASS is misleading.

Configuration guardrails close that gap. A requirement declares the configuration it expects, and when the scan ran with a configuration too loose to honor it, the requirement is forced to **FAIL** in every compliance output, with the reason surfaced in the finding's extended status.

<Note>
Guardrails are an **optional** safety net for configurable checks. A requirement that maps only to non-configurable checks does not need them. When the field is absent, behavior is unchanged.
</Note>

### Where Guardrails Are Declared

The field is attached to each requirement and exists in both schemas:

- **Legacy** (`prowler/compliance/<provider>/...`): `ConfigRequirements`, a list of objects, validated against the `Compliance_Requirement_ConfigConstraint` Pydantic model (`prowler/lib/check/compliance_models.py`).
- **Universal** (`prowler/compliance/...`): `config_requirements`, the same list of objects as plain dicts on `UniversalComplianceRequirement`.

When a legacy file is adapted to the universal model, `adapt_legacy_to_universal()` copies `ConfigRequirements` into `config_requirements` (`compliance_models.py`), so downstream code only ever reads one shape.

### Constraint Schema

Each entry in the list is a single constraint with the following fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `Check` | string | Yes | The configurable check this constraint guards. Should be one of the requirement's `Checks`. Used only to build a human-readable reason. |
| `ConfigKey` | string | Yes | The `audit_config` key the check reads (for example `max_unused_access_keys_days`). |
| `Operator` | enum | Yes | How to compare the applied value against `Value`. One of `lte`, `gte`, `eq`, `in`, `subset`, `superset`. |
| `Value` | bool, int, float, string, or list | Yes | The strictest configuration the requirement tolerates. The accepted Python type depends on the operator (see below). |
| `Provider` | string | No | The provider this constraint applies to (e.g. `aws`). **Required for universal (multi-provider) frameworks**, where the same requirement maps checks across providers — the constraint is only evaluated when the scanned provider matches. Single-provider (legacy) frameworks omit it. |

### Operators

| Operator | Applied value satisfies the guardrail when… | Typical use |
|---|---|---|
| `lte` | `applied <= Value` | Maximum-age / maximum-count thresholds (e.g. `max_unused_access_keys_days <= 45`). |
| `gte` | `applied >= Value` | Minimum-retention / minimum-count thresholds. |
| `eq` | `applied == Value` | Boolean toggles or an exact required value (e.g. `mute_non_default_regions == false`). |
| `in` | `applied` is one of `Value` (a list) | The applied scalar must belong to an allowed set. |
| `subset` | `set(applied) <= set(Value)` | **Allowlist** configs — every applied value must already be permitted. Widening the allowlist with a weaker value (e.g. adding TLS `1.0` to `recommended_minimal_tls_versions`) breaks the guardrail. |
| `superset` | `set(applied) >= set(Value)` | **Denylist** configs — every forbidden value must remain forbidden. Removing an entry from a denylist (e.g. dropping a weak algorithm from `insecure_key_algorithms`) breaks the guardrail. |

<Note>
`subset` / `superset` require both the applied value and `Value` to be lists; any other type is treated as not satisfied. For `eq` against a boolean, declare `Value` as a JSON boolean (`false`, not `0`) — the model keeps booleans distinct from integers.
</Note>

### How Guardrails Are Evaluated

All evaluation lives in one shared module, `prowler/lib/check/compliance_config_eval.py`, consumed by every compliance output (CSV, OCSF, and the CLI tables) and reused by the Prowler API backend so the rule is defined exactly once.

1. The applied configuration is the scan-global `audit_config` (the same mapping for every resource and region), resolved via `get_scan_audit_config()`.
2. For each requirement that declares constraints, `evaluate_config_constraints()` walks the list and returns `(is_compliant, reason)`. The requirement is compliant when **every** explicitly-set key satisfies its constraint.
3. A constraint tagged with a `Provider` that does **not** match the provider being scanned (resolved via `get_scan_provider_type()`) is **skipped**. This scopes a universal framework's constraints to the right provider, so a guardrail authored for an AWS check never affects a GCP or Azure scan of the same requirement. Untagged constraints (legacy single-provider frameworks) always apply.
4. A constraint whose `ConfigKey` is **not present** in `audit_config` is **skipped** — the check's built-in default is assumed to already match what the requirement expects. This is why nothing changes for the default configuration.
5. When a constraint is violated, the finding's status is overridden to `FAIL` and a plain-language explanation is prepended to `status_extended` (via `apply_config_status()`). The message opens with `Configuration not valid for this requirement.` and names the check, the value the scan applied, what the requirement needs and how to fix it. For the table generators, `get_effective_status()` applies the same FAIL roll-up so per-section counts stay consistent.

<Warning>
Guardrails only ever make a result **stricter** (they can turn PASS into FAIL); they never relax a real FAIL into PASS. A requirement with no constraints, or whose keys all use defaults, is reported exactly as before.
</Warning>

### Example: Legacy Framework

From `prowler/compliance/aws/cis_6.0_aws.json`, requirement 2.11 declares two guardrails — one per configurable check it maps to:

```json title="prowler/compliance/aws/cis_6.0_aws.json"
{
  "Id": "2.11",
  "Description": "Ensure credentials unused for 45 days or more are disabled.",
  "Checks": [
    "iam_user_accesskey_unused",
    "iam_user_console_access_unused"
  ],
  "ConfigRequirements": [
    {
      "Check": "iam_user_accesskey_unused",
      "ConfigKey": "max_unused_access_keys_days",
      "Operator": "lte",
      "Value": 45
    },
    {
      "Check": "iam_user_console_access_unused",
      "ConfigKey": "max_console_access_days",
      "Operator": "lte",
      "Value": 45
    }
  ],
  "Attributes": [ /* ... */ ]
}
```

A boolean guardrail from the same file: requirement 2.5 (IAM Access Analyzer) only holds when regions are not muted, so a scan with `mute_non_default_regions: true` cannot be trusted for it:

```json
"ConfigRequirements": [
  {
    "Check": "accessanalyzer_enabled",
    "ConfigKey": "mute_non_default_regions",
    "Operator": "eq",
    "Value": false
  }
]
```

### Example: Universal Framework

The universal schema uses the lowercase `config_requirements` key with the identical object shape:

```json
{
  "id": "MF-2.1",
  "name": "Restrict TLS to modern versions",
  "description": "Endpoints must negotiate only TLS 1.2 or higher.",
  "checks": {
    "aws": ["elbv2_listener_ssl_listeners"]
  },
  "config_requirements": [
    {
      "Check": "elbv2_listener_ssl_listeners",
      "Provider": "aws",
      "ConfigKey": "recommended_minimal_tls_versions",
      "Operator": "subset",
      "Value": ["TLS 1.2", "TLS 1.3"]
    }
  ]
}
```

Each constraint declares the `Provider` it targets so the guardrail is only evaluated on scans of that provider — essential for universal frameworks like CSA CCM and DORA, where one requirement maps checks across `aws`, `azure`, `gcp` and more. Because the operator is `subset`, adding `"TLS 1.0"` to `recommended_minimal_tls_versions` widens the allowlist beyond `["TLS 1.2", "TLS 1.3"]` and the requirement is forced to FAIL.

### What the User Sees

With a loosened config, the affected requirement's findings report:

```text
Status:         FAIL
StatusExtended: Configuration not valid for this requirement. The check
                iam_user_accesskey_unused has max_unused_access_keys_days set
                to 120, but the requirement needs a value of 45 or lower.
                Update it to 45 or lower. <original status_extended>
```

The same `Configuration not valid for this requirement.` message appears identically across the CSV, OCSF, and console-table outputs.

### Authoring Guidelines

- Declare a guardrail only for keys whose value actually changes whether the requirement is met. Most configurable checks do not need one.
- Set `Value` to the **strictest** configuration the control tolerates — the same number the control text cites (CIS 45 days, NIST ≤90, and so on).
- Keep `ConfigKey` spelled exactly as the check reads it from `audit_config`; an unknown key is never present in the config and the constraint is silently skipped.
- In a **universal (multi-provider) framework**, always set `Provider` to the provider that owns `Check` — otherwise the guardrail would leak onto scans of the other providers the requirement maps. Legacy single-provider files omit it.
- Pick the operator from the value's role: a max threshold is `lte`, a min threshold is `gte`, a toggle is `eq`, an allowlist is `subset`, a denylist is `superset`.
- An unrecognized operator does **not** block the requirement — a malformed constraint is treated as satisfied rather than failing the whole framework. Validate your JSON with the tests below.

### Testing Guardrails

The shared evaluator and the per-output integration are covered by:

- `tests/lib/check/compliance_config_eval_test.py` — operator semantics, skipped-key behavior, and the FAIL override.
- `tests/lib/check/compliance_config_constraint_model_test.py` — model validation (types, operator enum, bool-vs-int).
- `tests/lib/check/compliance_config_requirements_data_test.py` — sanity-checks the guardrails shipped in the JSON catalog.
- Per-output tests under `tests/lib/outputs/compliance/` (CIS AWS/Azure, ENS AWS, OCSF, universal table) confirm the override reaches each format.

Run them with:

```bash
uv run pytest -n auto \
  tests/lib/check/compliance_config_eval_test.py \
  tests/lib/check/compliance_config_constraint_model_test.py \
  tests/lib/check/compliance_config_requirements_data_test.py \
  tests/lib/outputs/compliance/
```

## Version Handling

Prowler matches frameworks by concatenating `Framework` and `Version`. A missing or empty `Version` collapses several frameworks to the same key and breaks CLI filtering with `--compliance`.

- Always set `Version` (or `version` for universal frameworks) to a non-empty string, even for frameworks that rename editions rather than version them. Use the edition identifier (for example `RD2022`, `v2025.10`, `4.0`, `2022/2554`).
- When the source catalog has no version, use the first year of adoption or the release date.
- For **legacy** files, make sure the version substring embedded in the filename matches `Version`, because the CLI dispatcher reads `compliance_framework.split("_")[1]` to select the correct version.

## Validating Your Framework

Before opening a PR, validate the JSON loads cleanly against the model and that every referenced check actually exists.

### 1. Schema Validation

For **universal** frameworks, load the file and inspect what was parsed. The framework key inside `bulk` is the **basename of the JSON file** (without `.json`); for `prowler/compliance/dora_2022_2554.json` that key is `dora_2022_2554`, for `prowler/compliance/aws/cis_5.0_aws.json` it is `cis_5.0_aws`.

```python
from prowler.lib.check.compliance_models import (
    load_compliance_framework_universal,
    get_bulk_compliance_frameworks_universal,
)

fw = load_compliance_framework_universal("prowler/compliance/<your_framework>.json")
assert fw is not None, "load returned None — check the logs for the validation error"
print(fw.framework, len(fw.requirements), fw.get_providers())

bulk = get_bulk_compliance_frameworks_universal("aws")
assert "<your_framework_filename_without_json>" in bulk
```

### 2. Check Existence Cross-Check

There is **no automatic check-existence validation** at load time. Cross-check that every check name in your framework maps to a real check directory:

```python
import os
real = set()
for svc in os.listdir("prowler/providers/aws/services"):
    svc_path = f"prowler/providers/aws/services/{svc}"
    if not os.path.isdir(svc_path):
        continue
    for entry in os.listdir(svc_path):
        if os.path.isfile(f"{svc_path}/{entry}/{entry}.metadata.json"):
            real.add(entry)

referenced = {c for r in fw.requirements for c in r.checks.get("aws", [])}
missing = referenced - real
assert not missing, f"checks referenced in framework but not found in repo: {sorted(missing)}"
```

### 3. CLI Smoke Test

```bash
uv run python prowler-cli.py <provider> --list-compliance
```

The framework must appear in the output. A validation error indicates a schema mismatch.

```bash
uv run python prowler-cli.py <provider> \
  --compliance <framework_key> \
  --log-level ERROR
```

Verify that:

- Prowler produces a CSV file under `output/compliance/` with the expected name.
- The CLI summary table lists every section / pillar of the framework.
- Findings roll up under the expected requirements.

### 4. Inspect the CSV Output

Open the generated CSV and confirm:

- All columns defined in `models.py` (legacy) or in `attributes_metadata` (universal) appear.
- Every requirement has at least one row per scanned resource (when there are findings).
- Attribute values such as `Requirements_Attributes_Section` reflect the JSON content.

### 5. Verify the Framework in Prowler Local Server

Launch Prowler Local Server (`docker compose up` from the repository root) and run a scan with the new compliance framework. Confirm the compliance page renders the requirements, sections, and status widgets correctly.

## Testing

Compliance contributions require two layers of tests.

- **Schema tests** exercise the Pydantic models. Extend `tests/lib/check/universal_compliance_models_test.py` with a case that loads the new JSON file and asserts the attribute type matches the expected model.
- **Output tests** (legacy frameworks only) exercise the transformer. Mirror the structure under `tests/lib/outputs/compliance/<framework>/` with fixtures that feed synthetic findings through the transformer and assert the resulting CSV rows.

Run the suite with:

```bash
uv run pytest -n auto tests/lib/check/universal_compliance_models_test.py \
  tests/lib/outputs/compliance/
```

For guidance on writing Prowler SDK tests, refer to [Unit Testing](/developer-guide/unit-testing).

## Running and Listing Your Framework

Once the file is in place, the CLI auto-discovers it:

```sh
prowler <provider> --list-compliance                                            # framework appears in the list
prowler <provider> --compliance <framework_key> --list-checks
prowler <provider> --compliance <framework_key>                                 # full scan + compliance report
prowler <provider> --compliance <framework_key> --list-compliance-requirements <framework_key>
```

For end-user-facing tutorials (recommended for high-profile frameworks), add a dedicated page under `docs/user-guide/compliance/tutorials/` and register it in the `"Compliance"` group of `docs/docs.json`. See `docs/user-guide/compliance/tutorials/threatscore.mdx` as a reference.

## Submitting the Pull Request

Before opening the pull request:

1. Run the complete QA pipeline:
    ```bash
    uv run pre-commit run --all-files
    uv run pytest -n auto
    ```
2. Add a changelog fragment `prowler/changelog.d/<slug>.added.md`, describing the new framework and the providers it covers (no PR link in the text; it is attached automatically at release time).
3. Follow the [Pull Request Template](https://github.com/prowler-cloud/prowler/blob/master/.github/pull_request_template.md) and set the PR title using Conventional Commits, e.g. `feat(compliance): add My Framework 1.0 for AWS`.
4. Request review from the compliance codeowners listed in `.github/CODEOWNERS`.

## Troubleshooting

The following issues are the most common when contributing a compliance framework.

- **`ValidationError: field required` during scan (legacy).** The JSON is missing a required attribute field. Re-check the matching Pydantic model in `prowler/lib/check/compliance_models.py`.
- **All attributes collapse to `Generic_Compliance_Requirement_Attribute` values (legacy).** The Pydantic `Union` is ordered incorrectly, or the JSON matches only the generic shape. Keep the generic model in the last Union position and ensure every required field is present in the JSON.
- **`attributes_metadata validation failed` (universal).** The root validator in `compliance_models.py` rejected the file. The error message lists each offending requirement; common causes are unknown attribute keys (typo or missing entry in `attributes_metadata`), enum violations, or missing required keys.
- **`--compliance` filter does not find the framework.** For legacy: the filename does not match `<framework>_<version>_<provider>.json`, the version is empty, or the file lives outside `prowler/compliance/<provider>/`. For universal: the file is not at the top level of `prowler/compliance/` or it loaded as `None` (check logs for the validation error).
- **CLI summary table is empty but the CSV is populated (legacy).** The dispatcher branch in `prowler/lib/outputs/compliance/compliance.py` is missing or its substring match does not catch the framework key.
- **CSV file is missing after the scan (legacy).** The transformer class is not registered in `prowler/lib/outputs/compliance/compliance_output.py`, or `transform()` raises silently. Run the scan with `--log-level DEBUG`.
- **Findings do not roll up under a requirement.** A check listed in `Checks` either does not exist for that provider or is spelled incorrectly. Run `--list-checks | grep <check_name>` to confirm, or run the check-existence cross-check from "Validating Your Framework".

## Reference Examples

Use the following files as templates when modeling a new contribution.

- `prowler/compliance/dora_2022_2554.json` — universal schema, single-provider populated (AWS), ready to extend with more providers.
- `prowler/compliance/csa_ccm_4.0.json` — universal schema, multi-provider populated (AWS, Azure, GCP, AlibabaCloud, OracleCloud).
- `prowler/compliance/aws/cis_2.0_aws.json` — legacy CIS attribute shape.
- `prowler/compliance/aws/nist_800_53_revision_5_aws.json` — legacy generic attribute shape.
- `prowler/compliance/aws/ccc_aws.json` — legacy CCC attribute shape.
- `prowler/compliance/azure/ens_rd2022_azure.json` — legacy ENS attribute shape.
- `prowler/lib/check/compliance_models.py` — canonical Pydantic schemas for both formats.
- `prowler/lib/outputs/compliance/cis/` — reference implementation of a multi-provider legacy output formatter.
- `prowler/lib/outputs/compliance/generic/` — reference implementation of a legacy generic output formatter.

---

### Developer Guide Server Sent Events (`docs/developer-guide/server-sent-events.mdx`)

---
title: 'Server-Sent Events (SSE)'
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="1.32.0" />

This guide explains how to add a **Server-Sent Events (SSE)** endpoint to the Prowler API. SSE lets the backend push a one-way stream of events to a client over a single long-lived HTTP connection — ideal for live progress, token-by-token LLM output, or any "the server has news for you" use case where the client should not poll.

<Info>
The platform ships the SSE **infrastructure** (`api.sse`) and wiring. No feature endpoint streams over SSE out of the box — this guide shows how to build one on top of the shared base.
</Info>

## When to Use SSE

| Need | Use |
|------|-----|
| Server pushes incremental updates, client only reads | **SSE** |
| Bidirectional, low-latency messaging (chat both ways, games) | WebSocket |
| Client asks, server answers once | Plain REST |

SSE is the right tool when the **client only consumes**: scan progress, long-running job checkpoints, streamed LLM tokens, cross-client resource-sync notifications. It rides on plain HTTP, reconnects automatically in the browser via the native [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) API, and needs no extra protocol.

## How It Works

SSE is wired through [`django-eventstream`](https://github.com/fanout/django_eventstream) and a small platform layer in `api/src/backend/api/sse/`:

| Piece | File | Responsibility |
|-------|------|----------------|
| `BaseSSEViewSet` | `api/sse/base_views.py` | Base DRF viewset a feature subclasses. The feature implements `get_channels`; the base handles auth, the tenant transaction, and delegates streaming to `django-eventstream`. |
| `SSEChannelManager` | `api/sse/channelmanager.py` | Registered in `settings.EVENTSTREAM_CHANNELMANAGER_CLASS`. Reads the channel set off the request and enforces the platform-wide tenant gate. |
| `SSEAuthentication` | `api/authentication.py` | Same JWT/API-key stack as the rest of the API, plus an `?access_token=<jwt>` fallback for browser `EventSource` clients. Lives with the other authentication classes, not in the `sse` package. |
| `make_channel_name` / `tenant_id_from_channel` | `api/sse/utils.py` | Single source of truth for the channel-name format, so publishers and the channel manager agree byte-for-byte. |
| Settings | `config/settings/eventstream.py` | Valkey Pub/Sub backend (dedicated DB), channel manager, allowed headers. |

### Transport: The Server Runs on ASGI

SSE connections are long-lived. Holding one open per synchronous worker would exhaust the worker pool, so the API runs under Gunicorn's native **`asgi` worker** (`config.asgi:application`). Streams are parked on the event loop while ordinary CRUD endpoints keep their synchronous execution (Django runs sync views in a thread-sensitive executor under ASGI). This is configured in `config/guniconf.py` and used by both the dev and production entrypoints — no separate server process is needed.

### The Data Flow

```
publisher (Celery task / view)             subscriber (browser, CLI)
        │                                          │
        │  send_event(channel, "scan.progress", …) │  GET …/event-stream
        ▼                                          ▼
   Valkey Pub/Sub  ◄────────────────────►  BaseSSEViewSet.list
   (EVENTSTREAM_VALKEY_DB)                  → get_channels() (RLS-scoped)
                                            → SSEChannelManager (tenant gate)
                                            → StreamingHttpResponse (text/event-stream)
```

A publisher anywhere in the system (most often a Celery task) calls `send_event(channel, event_type, payload)`. `django-eventstream` fans it out over Valkey Pub/Sub to every connection subscribed to that channel.

## Adding an SSE Endpoint to Your Feature

The example below streams progress for a long-running **scan**. Adapt the resource, prefix, and event names to your feature.

<Steps>

<Step title="Pick a channel prefix">

Channels follow the format `<prefix>:<tenant_id>:<resource_id>`, built only through `make_channel_name`. The prefix is owned by your feature and may contain hyphens but **never colons** (the parser splits on `:`).

```python
CHANNEL_PREFIX = "scan-progress"
```

The tenant id is baked into every channel name. That is what lets the platform enforce cross-tenant isolation without knowing anything about your feature.

</Step>

<Step title="Subclass BaseSSEViewSet">

Create the viewset for the SSE sub-resource. The only required method is `get_channels`; it runs inside the tenant transaction set up by the base class, so any database lookup inside it is automatically RLS-scoped.

```python
# scans/event_streams.py
from api.sse import BaseSSEViewSet, make_channel_name
from django.shortcuts import get_object_or_404
from scans.models import Scan

CHANNEL_PREFIX = "scan-progress"


class ScanEventStreamViewSet(BaseSSEViewSet):
    def get_queryset(self):
        # RLS already scopes to the tenant; narrow further as needed
        # (e.g. only scans the requesting user may see).
        return Scan.objects.filter(tenant_id=self.request.tenant_id)

    def get_channels(self) -> set[str]:
        scan = get_object_or_404(self.get_queryset(), pk=self.kwargs["scan_pk"])
        return {make_channel_name(CHANNEL_PREFIX, scan.tenant_id, scan.id)}
```

<Warning>
`get_channels` **must raise** the relevant DRF exception (`NotFound`, `PermissionDenied`, `NotAuthenticated`) when authorization fails — `get_object_or_404` does this for you. Returning an empty set surfaces as django-eventstream's confusing "No channels specified" error instead of the real cause.
</Warning>

</Step>

<Step title="Wire the URL as a sub-resource">

Mount the endpoint as an `event-stream` sub-resource. Keep it **outside the DRF router**, which would force the URL into a list/detail convention. Route the `get` method to the viewset's `list` action.

```python
# scans/urls.py
path(
    "scans/<uuid:scan_pk>/event-stream",
    ScanEventStreamViewSet.as_view({"get": "list"}),
    name="scan-event-stream",
),
```

</Step>

<Step title="Define your event vocabulary">

A feature owns its event types in `<app>/<domain>/events.py`: one `publish_<event>` function per event type, each body a **single** `send_event` call so the wire-level string lives in exactly one place.

```python
# scans/events.py
from django_eventstream import send_event


def publish_progress(channel: str, checked: int, total: int) -> None:
    send_event(channel, "scan.progress", {"checked": checked, "total": total})


def publish_end(channel: str, scan_id: str) -> None:
    # Terminal event carries the canonical id so reconnecting clients
    # can refetch the persisted resource over REST.
    send_event(channel, "scan.end", {"scan_id": scan_id})


def publish_error(channel: str, code: str, detail: str) -> None:
    send_event(channel, "scan.error", {"code": code, "detail": detail})
```

There is no platform-side enum, registry, or dispatch table — **the naming convention is the contract** (see below).

</Step>

<Step title="Publish from the producer">

Wherever the work happens — usually a Celery task — build the channel the same way and publish:

```python
from api.sse import make_channel_name
from scans.events import publish_progress, publish_end

channel = make_channel_name("scan-progress", scan.tenant_id, scan.id)
publish_progress(channel, checked=42, total=100)
...
publish_end(channel, scan_id=str(scan.id))
```

</Step>

</Steps>

## Event Naming Convention

Every event uses an event type of the form **`<resource>.<verb>`** (lowercased, dot-separated). The verb comes from this platform-wide vocabulary — if you need a verb that is not listed, document the addition in this guide so the catalog stays discoverable.

| Verb | When to use |
|------|-------------|
| `delta` | An incremental piece of a stream the client concatenates (LLM text tokens, audio chunks). Standard term across OpenAI / Anthropic / LiteLLM / Vercel AI SDK. |
| `start` | Begin marker for a compound operation (e.g. a tool call whose execution will be reported by a matching `end`). |
| `end` | Terminal marker. Carries the canonical resource id so reconnecting clients can refetch persisted state via REST. |
| `progress` | Periodic checkpoint with quantifiable completion, e.g. `{"checked": 42, "total": 100}`. |
| `created` / `updated` / `deleted` | Resource-lifecycle events for cross-client sync streams. |
| `error` | Terminal failure. Carries a stable `code` for client switching and a human-readable `detail`. |

<Note>
Payloads are **flat JSON**. The wire-level `event:` field already names the event type, so do **not** wrap the payload in `{"type": ..., "data": ...}`. Include the canonical resource UUID on terminal events so reconnecting clients can reconcile via REST.
</Note>

## Authentication

SSE endpoints use the same authentication stack as the rest of the API. Non-browser clients (CLI, programmatic) send the standard `Authorization` header — JWT or API key.

Browser `EventSource` is the only widely available SSE client API and it **cannot set custom headers**. For that case only, the endpoint accepts a JWT via the `?access_token=<jwt>` query parameter. The header always wins when present — a header is intentional, while a query parameter can leak into referers and logs, so it is consulted only as a fallback.

```javascript
// Browser
const es = new EventSource(
  `/api/v1/scans/${scanId}/event-stream?access_token=${jwt}`
);
```

```bash
# CLI / programmatic — header, exactly like every other endpoint
curl -N -H "Authorization: Bearer $JWT" \
  https://<host>/api/v1/scans/$SCAN_ID/event-stream
```

## Tenant Isolation & Security Model

Authorization is enforced at two layers:

1. **At connect**, `get_channels` runs under the regular DRF stack inside the tenant transaction (`rls_transaction`). Resource lookups are RLS-scoped, so a user cannot even resolve a channel for a resource they cannot see. Narrow the queryset further (e.g. `created_by=request.user`) when a resource is per-user within a tenant.
2. **After connect**, `SSEChannelManager.can_read_channel` re-verifies tenant membership by parsing the tenant id embedded in the channel name. Cross-tenant subscription is rejected even if a URL-level check ever has a bug. A malformed channel name is treated as "not authorized".

Because the tenant id lives inside the channel name, this gate works for any feature without the platform knowing anything about it.

## Reconnect & State Recovery

The platform deliberately ships **without server-side replay** (`is_channel_reliable` returns `False`). When a client reconnects, it does **not** receive missed events. Instead:

- Terminal events (`*.end`) carry the canonical resource **UUID**.
- On reconnect, the client refetches the authoritative state from the normal REST endpoint using that id.

Design your event payloads accordingly: deltas are ephemeral and concatenated in-flight; the durable truth always lives behind a REST resource.

## Local Development

- The dev and production entrypoints both launch Gunicorn with the `asgi` worker (`config.asgi:application`). In dev, `DJANGO_DEBUG=True` enables hot reload; `preload_app` is automatically disabled under debug so edited code is picked up.
- SSE uses a **dedicated Valkey database** (`EVENTSTREAM_VALKEY_DB`, default `2`) kept separate from the Celery broker so a noisy broker cannot crowd out streaming traffic. It reuses the same `VALKEY_*` connection settings as the rest of the platform.

| Env var | Default | Purpose |
|---------|---------|---------|
| `EVENTSTREAM_VALKEY_DB` | `2` | Valkey DB index for the SSE Pub/Sub bus |
| `DJANGO_WORKER_CLASS` | `asgi` | Gunicorn worker class |

Test the stream end to end with `curl -N` (disable buffering) and an auth header:

```bash
curl -N -H "Authorization: Bearer $JWT" \
  http://localhost:8080/api/v1/scans/$SCAN_ID/event-stream
```

## Testing

The platform basis is covered by `api/tests/test_sse.py` (channel parsing, the tenant gate, and auth precedence). For a feature endpoint, test:

- `get_channels` returns the expected channel for an authorized resource and raises `NotFound`/`PermissionDenied` otherwise.
- Each `publish_<event>` helper emits the correct event type and flat payload (mock `send_event`).
- The producer builds the channel with `make_channel_name` using the resource's own `tenant_id`.

---

### Developer Guide Services (`docs/developer-guide/services.mdx`)

---
title: 'Prowler Services'
---

Here you can find how to create a new service, or to complement an existing one, for a [Prowler Provider](/developer-guide/provider).

<Note>
First ensure that the provider you want to add the service is already created. It can be checked [here](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers). If the provider is not present, please refer to the [Provider](./provider.md) documentation to create it from scratch.
</Note>
## Introduction

In Prowler, a **service** represents a specific solution or resource offered by one of the supported [Prowler Providers](/developer-guide/provider), for example, [EC2](https://aws.amazon.com/ec2/) in AWS, or [Microsoft Exchange](https://www.microsoft.com/en-us/microsoft-365/exchange/exchange-online) in M365. Services are the building blocks that allow Prowler to interact directly with the various resources exposed by each provider.

Each service is implemented as a class that encapsulates all the logic, data models, and API interactions required to gather and store information about that service's resources. All of this data is used by the [Prowler checks](/developer-guide/checks) to generate the security findings.

## Adding a New Service

To create a new service, a new folder must be created inside the specific provider following this pattern: `prowler/providers/<provider>/services/<new_service_name>/`.

Within this folder the following files are also to be created:

- `__init__.py` (empty) – Ensures Python recognizes this folder as a package.
- `<new_service_name>_service.py` – Contains all the logic and API calls of the service.
- `<new_service_name>_client.py` – Contains the initialization of the freshly created service's class so that the checks can use it.

Once the files are created, you can check that the service has been created by running the following command: `uv run python prowler-cli.py <provider> --list-services | grep <new_service_name>`.

## Service Structure and Initialisation

The Prowler's service structure is as outlined below. To initialise it, just import the service client in a check.

### Service Base Class

All Prowler provider services should inherit from a common base class to avoid code duplication. This base class handles initialization and storage of functions and objects needed across services. The exact implementation depends on the provider's API requirements, but the following are the most common responsibilities:

- Initialize/store clients to interact with the provider's API.
- Store the audit and fixer configuration.
- Implement threading logic where applicable.

For reference, the base classes for each provider can be checked here:

- [AWS Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/lib/service/service.py)
- [GCP Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/lib/service/service.py)
- [Azure Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/lib/service/service.py)
- [Kubernetes Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/lib/service/service.py)
- [M365 Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/lib/service/service.py)
- [GitHub Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/lib/service/service.py)

### Service Class

Due to the complexity and differences across provider APIs, the following example demonstrates best practices for structuring a service in Prowler.

File `<new_service_name>_service.py`:

```
/* Detailed source-code truncated for AI context efficiency. */
```

<Note>
To prevent false findings, when Prowler fails to retrieve items due to Access Denied or similar errors, the affected item's value is set to `None`.

</Note>
#### Resource Models

Resource models define structured classes used within services to store and process data extracted from API calls. They are defined in the same file as the service class, but outside of the class, usually at the bottom of the file.

Prowler leverages Pydantic's [BaseModel](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel) to enforce data validation.

```python title="Service Model"

# Implementation Approach

# Each service class should include custom model classes using Pydantic's BaseModel for the resources being audited.

class <Item>(BaseModel):
    """<Item> holds a <Service> <Item>"""

    id: str
    """<Items>[].id"""

    name: str
    """<Items>[].name"""

    region: str
    """<Items>[].region"""

    public: bool
    """<Items>[].public"""

    # Optional attributes can be created set to None by default.

    tags: Optional[list]
     """<Items>[].tags"""
```

#### Service Attributes

_Optimized Data Storage with Python Dictionaries_

Each group of resources within a service should be structured as a Python [dictionary](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) to enable efficient lookups. The dictionary lookup operation has [O(1) complexity](https://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions), and lookups are constantly executed.

_Assigning Unique Identifiers_

Each dictionary key must be a unique ID to identify the resource in a univocal way.

Example:

```python
self.virtual_machines = {}
self.virtual_machines["vm-01234567890abcdef"] = VirtualMachine()
```

### Service Client

Each Prowler service requires a service client to use the service in the checks.

The following is the `<new_service_name>_client.py` file, which contains the initialization of the freshly created service's class so that service checks can use it. This file is almost the same for all the services among the providers:

```python
from prowler.providers.common.provider import Provider
from prowler.providers.<provider>.services.<new_service_name>.<new_service_name>_service import <Service>

<new_service_name>_client = <Service>(Provider.get_global_provider())
```

## Provider Permissions in Prowler

Before implementing a new service, verify that Prowler's existing permissions for each provider are sufficient. If additional permissions are required, refer to the relevant documentation and update accordingly.

Provider-Specific Permissions Documentation:

- [AWS](/user-guide/providers/aws/authentication#required-permissions)
- [Azure](/user-guide/providers/azure/authentication#required-permissions)
- [GCP](/user-guide/providers/gcp/authentication#required-permissions)
- [M365](/user-guide/providers/microsoft365/authentication#required-permissions)
- [GitHub](/user-guide/providers/github/authentication)

## Service Architecture and Cross-Service Communication

### Core Principle: Service Isolation with Client Communication

Each service must contain **ONLY** the information unique to that specific service. When a check requires information from multiple services, it must use the **client objects** of other services rather than directly accessing their data structures.

This architecture ensures:

- **Loose coupling** between services
- **Clear separation of concerns**
- **Maintainable and testable code**
- **Consistent data access patterns**

### Cross-Service Communication Pattern

Instead of services directly accessing each other's internal data, checks should import and use client objects:

**❌ INCORRECT - Direct data access:**

```python
# DON'T DO THIS
from prowler.providers.aws.services.cloudtrail.cloudtrail_service import cloudtrail_service
from prowler.providers.aws.services.s3.s3_service import s3_service

class cloudtrail_bucket_requires_mfa_delete(Check):
    def execute(self):
        # WRONG: Directly accessing service data
        for trail in cloudtrail_service.trails.values():
            for bucket in s3_service.buckets.values():
                # Direct access violates separation of concerns
```

**✅ CORRECT - Client-based communication:**

```python
# DO THIS INSTEAD
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client
from prowler.providers.aws.services.s3.s3_client import s3_client

class cloudtrail_bucket_requires_mfa_delete(Check):
    def execute(self):
        # CORRECT: Using client objects for cross-service communication
        for trail in cloudtrail_client.trails.values():
            trail_bucket = trail.s3_bucket
            for bucket in s3_client.buckets.values():
                if trail_bucket == bucket.name:
                    # Use bucket properties through s3_client
                    if bucket.mfa_delete:
                        # Implementation logic
```

### Real-World Example: CloudTrail + S3 Integration

This example demonstrates how CloudTrail checks validate S3 bucket configurations:

```python
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client
from prowler.providers.aws.services.s3.s3_client import s3_client

class cloudtrail_bucket_requires_mfa_delete(Check):
    def execute(self):
        findings = []
        if cloudtrail_client.trails is not None:
            for trail in cloudtrail_client.trails.values():
                if trail.is_logging:
                    trail_bucket_is_in_account = False
                    trail_bucket = trail.s3_bucket

                    # Cross-service communication: CloudTrail check uses S3 client
                    for bucket in s3_client.buckets.values():
                        if trail_bucket == bucket.name:
                            trail_bucket_is_in_account = True
                            if bucket.mfa_delete:
                                report.status = "PASS"
                                report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) has MFA delete enabled."

                    # Handle cross-account scenarios
                    if not trail_bucket_is_in_account:
                        report.status = "MANUAL"
                        report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) is a cross-account bucket or out of Prowler's audit scope, please check it manually."

                    findings.append(report)
        return findings
```

**Key Benefits:**

- **CloudTrail service** only contains CloudTrail-specific data (trails, configurations)
- **S3 service** only contains S3-specific data (buckets, policies, ACLs)
- **Check logic** orchestrates between services using their public client interfaces
- **Cross-account detection** is handled gracefully when resources span accounts

### Service Consolidation Guidelines

**When to combine services in the same file:**

Implement multiple services as **separate classes in the same file** when two services are **practically the same** or one is a **direct extension** of another.

**Example: S3 and S3Control**

S3Control is an extension of S3 that provides account-level controls and access points. Both are implemented in `s3_service.py`:

```python
# File: prowler/providers/aws/services/s3/s3_service.py

class S3(AWSService):
    """Standard S3 service for bucket operations"""
    def __init__(self, provider):
        super().__init__(__class__.__name__, provider)
        self.buckets = {}
        self.regions_with_buckets = []

        # S3-specific initialization
        self._list_buckets(provider)
        self._get_bucket_versioning()
        # ... other S3-specific operations

class S3Control(AWSService):
    """S3Control service for account-level and access point operations"""
    def __init__(self, provider):
        super().__init__(__class__.__name__, provider)
        self.account_public_access_block = None
        self.access_points = {}

        # S3Control-specific initialization
        self._get_public_access_block()
        self._list_access_points()
        # ... other S3Control-specific operations
```

**Separate client files:**

```python
# File: prowler/providers/aws/services/s3/s3_client.py
from prowler.providers.aws.services.s3.s3_service import S3
s3_client = S3(Provider.get_global_provider())

# File: prowler/providers/aws/services/s3/s3control_client.py
from prowler.providers.aws.services.s3.s3_service import S3Control
s3control_client = S3Control(Provider.get_global_provider())
```

**When NOT to consolidate services:**

Keep services separate when they:

- **Operate on different resource types** (EC2 vs RDS)
- **Have different authentication mechanisms** (different API endpoints)
- **Serve different operational domains** (IAM vs CloudTrail)
- **Have different regional behaviors** (global vs regional services)

### Cross-Service Dependencies Guidelines

**1. Always use client imports:**

```python
# Correct pattern
from prowler.providers.aws.services.service_a.service_a_client import service_a_client
from prowler.providers.aws.services.service_b.service_b_client import service_b_client
```

**2. Handle missing resources gracefully:**

```python
# Handle cross-service scenarios
resource_found_in_account = False
for external_resource in other_service_client.resources.values():
    if target_resource_id == external_resource.id:
        resource_found_in_account = True
        # Process found resource
        break

if not resource_found_in_account:
    # Handle cross-account or missing resource scenarios
    report.status = "MANUAL"
    report.status_extended = "Resource is cross-account or out of audit scope"
```

**3. Document cross-service dependencies:**

```python
class check_with_dependencies(Check):
    """
    Check Description

    Dependencies:
    - service_a_client: For primary resource information
    - service_b_client: For related resource validation
    - service_c_client: For policy analysis
    """
```

## Regional Service Implementation

When implementing services for regional providers (like AWS, Azure, GCP), special considerations are needed to handle resource discovery across multiple geographic locations. This section provides a complete guide using AWS as the reference example.

### Regional vs Non-Regional Services

**Regional Services:** Require iteration across multiple geographic locations where resources may exist (e.g., EC2 instances, VPC, RDS databases).

**Non-Regional/Global Services:** Operate at a global or tenant level without regional concepts (e.g., IAM users, Route53 hosted zones).

### AWS Regional Implementation Example

AWS is the perfect example of a regional provider. Here's how Prowler handles AWS's regional architecture:


```python
# File: prowler/providers/aws/services/ec2/ec2_service.py
class EC2(AWSService):
    def __init__(self, provider):
        super().__init__(__class__.__name__, provider)
        self.instances = {}
        self.security_groups = {}

        # Regional resource discovery across all AWS regions
        self.__threading_call__(self._describe_instances)
        self.__threading_call__(self._describe_security_groups)

    def _describe_instances(self, regional_client):
        """Discover EC2 instances in a specific region"""
        try:
            describe_instances_paginator = regional_client.get_paginator("describe_instances")
            for page in describe_instances_paginator.paginate():
                for reservation in page["Reservations"]:
                    for instance in reservation["Instances"]:
                        # Each instance includes its region
                        self.instances[instance["InstanceId"]] = Instance(
                            id=instance["InstanceId"],
                            region=regional_client.region,
                            state=instance["State"]["Name"],
                            # ... other properties
                        )
        except Exception as error:
            logger.error(f"Failed to describe instances in {regional_client.region}: {error}")
```

#### Regional Check Execution

```python
# File: prowler/providers/aws/services/ec2/ec2_instance_public_ip/ec2_instance_public_ip.py
class ec2_instance_public_ip(Check):
    def execute(self):
        findings = []

        # Automatically iterates across ALL AWS regions where instances exist
        for instance in ec2_client.instances.values():
            report = Check_Report_AWS(metadata=self.metadata(), resource=instance)
            report.region = instance.region  # Critical: region attribution
            report.resource_arn = f"arn:aws:ec2:{instance.region}:{instance.account_id}:instance/{instance.id}"

            if instance.public_ip:
                report.status = "FAIL"
                report.status_extended = f"Instance {instance.id} in {instance.region} has public IP {instance.public_ip}"
            else:
                report.status = "PASS"
                report.status_extended = f"Instance {instance.id} in {instance.region} does not have a public IP"

            findings.append(report)

        return findings
```

#### Key AWS Regional Features

**Region-Specific ARNs:**

```
arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0
arn:aws:s3:eu-west-1:123456789012:bucket/my-bucket
arn:aws:rds:ap-southeast-2:123456789012:db:my-database
```

**Parallel Processing:**

- Each region processed independently in separate threads
- Failed regions don't affect other regions
- User can filter specific regions: `-f us-east-1`

**Global vs Regional Services:**

- **Regional**: EC2, RDS, VPC (require region iteration)
- **Global**: IAM, Route53, CloudFront (single `us-east-1` call)

This architecture allows Prowler to efficiently scan AWS accounts with resources spread across multiple regions while maintaining performance and error isolation.

### Regional Service Best Practices

1. **Use Threading for Regional Discovery**: Leverage the `__threading_call__` method to parallelize resource discovery across regions
2. **Store Region Information**: Always include region metadata in resource objects for proper attribution
3. **Handle Regional Failures Gracefully**: Ensure that failures in one region don't affect others
4. **Optimize for Performance**: Use paginated calls and efficient data structures for large-scale resource discovery
5. **Support Region Filtering**: Allow users to limit scans to specific regions for focused audits

## Best Practices

- When available in the provider, use threading or parallelization utilities for all methods that can be parallelized to maximize performance and reduce scan time.
- Define a Pydantic `BaseModel` for every resource you manage, and use these models for all resource data handling.
- Log every major step (start, success, error) in resource discovery and attribute collection for traceability and debugging; include as much context as possible.
- Catch and log all exceptions, providing detailed context (region, subscription, resource, error type, line number) to aid troubleshooting.
- Use consistent naming for resource containers, unique identifiers, and model attributes to improve code readability and maintainability.
- Add docstrings to every method and comments to explain any service-specific logic, especially where provider APIs behave differently or have quirks.
- Collect and store resource tags and additional attributes to support richer checks and reporting.
- Leverage shared utility helpers for session setup, identifier parsing, and other cross-cutting concerns to avoid code duplication. This kind of code is typically stored in a `lib` folder in the service folder.
- Keep code modular, maintainable, and well-documented for ease of extension and troubleshooting.
- **Each service should contain only information unique to that specific service** - use client objects for cross-service communication.
- **Handle cross-account and missing resources gracefully** when checks span multiple services.

---

### Developer Guide Stackit Details (`docs/developer-guide/stackit-details.mdx`)

---
title: 'StackIT Provider'
---

This page details the [StackIT Cloud](https://www.stackit.de/) provider implementation in Prowler.

By default, Prowler audits a single StackIT project per scan. To configure it, provide the project ID and either a service account key file path or inline service account key JSON.

## StackIT Provider Classes Architecture

The StackIT provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the StackIT-specific implementation, highlighting how the generic provider concepts are realized for StackIT in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).

### `StackitProvider` (Main Class)

- **Location:** [`prowler/providers/stackit/stackit_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/stackit/stackit_provider.py)
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
- **Purpose:** Central orchestrator for StackIT-specific logic, API authentication, credential validation, and configuration.
- **Key StackIT Responsibilities:**
    - Initializes StackIT SDK authentication via a service account key file or inline service account key JSON. The SDK mints and refreshes access tokens internally.
    - Validates the service account credentials and project ID (UUID format validation).
    - Loads and manages configuration, mutelist, and fixer settings.
    - Provides properties and methods for downstream StackIT service classes to access credentials, identity, and configuration data.

### Data Models

- **Location:** [`prowler/providers/stackit/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/stackit/models.py)
- **Purpose:** Define structured data for StackIT identity and output configuration.
- **Key StackIT Models:**
    - `StackITIdentityInfo`: Holds StackIT identity metadata, including project ID and project name (fetched automatically from Resource Manager API).
    - `StackITOutputOptions`: Customizes default output filenames so StackIT reports include the audited project ID.
    - IaaS resource models such as `SecurityGroup` and `SecurityGroupRule` are defined in the IaaS service module.

### StackIT Services

- **Location:** [`prowler/providers/stackit/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/services)
- **Purpose:** Implement StackIT service clients and resource collection logic following the generic [service pattern](/developer-guide/services#service-base-class).
- **Current Implementation:** The `IaaSService` collects security groups, rules, and network interface usage across supported StackIT regions.

### Exception Handling

- **Location:** [`prowler/providers/stackit/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/stackit/exceptions/exceptions.py)
- **Purpose:** Custom exception classes for StackIT-specific error handling, such as credential validation, API connection, and configuration errors.
- **Key Exception Classes:**
    - `StackITBaseException`: Base exception for all StackIT provider errors.
    - `StackITCredentialsError`: Raised when credentials are invalid or missing.
    - `StackITInvalidProjectIdError`: Raised when project ID is invalid or not in UUID format.
    - `StackITAPIError`: Raised when StackIT API calls fail.

## Authentication

### Service Account Creation and Key Generation

StackIT uses service account keys for API authentication. Service account keys are RSA key-pair based and provide secure, short-lived access tokens.

### Creating a Service Account Key

#### Method 1: Via StackIT Portal

1. **Navigate to Service Accounts**
   - Go to the [StackIT Portal](https://portal.stackit.cloud/)
   - Select your project
   - Click **Service Accounts** in the left sidebar

2. **Create or Select Service Account**
   - If you don't have a service account, click **Create Service Account**
   - Provide a name and description
   - Assign necessary permissions:
     - For IaaS security checks: `iaas.viewer` or `project.owner`
     - For comprehensive audits: `project.owner`

3. **Generate Service Account Key**
   - Select your service account
   - Navigate to **Service Account Keys**
   - Click **Create key**
   - Choose one of the following options:
     - **STACKIT-generated key pair** (Recommended): Let STACKIT automatically generate an RSA key-pair
     - **User-provided key pair**: Upload your own RSA 2048 public key

4. **Download and Save the Key**
   - Download the generated service account key file (JSON format)
   - **Important**: Save the key securely - it contains your private key and will only be available once
   - Store the key file in a secure location (e.g., `~/.stackit/sa_key.json`)

#### Method 2: Via StackIT CLI

```bash
# Install STACKIT CLI (if not already installed)
# Follow instructions at: https://github.com/stackitcloud/stackit-cli

# Create service account key (STACKIT-generated)
stackit service-account key create --email my-service-account@example.com

# Or create with your own RSA 2048 public key
# First, generate your RSA key pair:
openssl genrsa -out private-key.pem 2048
openssl rsa -in private-key.pem -pubout -out public-key.pem

# Then create the key with your public key:
stackit service-account key create \
  --email my-service-account@example.com \
  --public-key "$(cat public-key.pem)"
```

### Finding Your Project ID

Your StackIT project ID is a UUID that can be found:

1. In the StackIT Portal URL when viewing your project: `https://portal.stackit.cloud/projects/{PROJECT_ID}/...`
2. In the project settings page
3. Using the StackIT CLI: `stackit project list`

### Passing the Service Account Key to Prowler

Prowler accepts the service account credentials in two equivalent forms; both go through the same StackIT SDK flow and refresh access tokens internally.

#### Option 1: Key File Path (key persisted on disk)

```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"
export STACKIT_PROJECT_ID="12345678-1234-1234-1234-123456789abc"

prowler stackit
```

Or as CLI flags:

```bash
prowler stackit \
  --stackit-service-account-key-path ~/.stackit/sa-key.json \
  --stackit-project-id 12345678-1234-1234-1234-123456789abc
```

#### Option 2: Inline Key Content (CI/CD, secret managers)

```bash
export STACKIT_SERVICE_ACCOUNT_KEY="$(vault kv get -field=key stackit/sa)"
export STACKIT_PROJECT_ID="12345678-1234-1234-1234-123456789abc"

prowler stackit
```

Prefer the environment variable over the matching `--stackit-service-account-key` CLI flag; passing the secret on the command line leaks it through process listings and shell history.

### Credential Lookup Order

Prowler resolves credentials in this order:

1. **Command-line arguments**:
   - `--stackit-service-account-key`
   - `--stackit-service-account-key-path`
   - `--stackit-project-id`
2. **Environment variables**:
   - `STACKIT_SERVICE_ACCOUNT_KEY`
   - `STACKIT_SERVICE_ACCOUNT_KEY_PATH`
   - `STACKIT_PROJECT_ID`

When both the inline key and the key file path are set, the inline content takes precedence.

## Configuration

### Command-Line Arguments

StackIT-specific command-line arguments:

| Argument | Description | Required | Default |
|----------|-------------|----------|---------|
| `--stackit-service-account-key-path` | Path to a StackIT service account key JSON file | Yes* | `$STACKIT_SERVICE_ACCOUNT_KEY_PATH` |
| `--stackit-service-account-key` | Inline JSON content of a StackIT service account key (preferred env var: `STACKIT_SERVICE_ACCOUNT_KEY`) | Yes* | `$STACKIT_SERVICE_ACCOUNT_KEY` |
| `--stackit-project-id` | StackIT project ID (UUID format) | Yes* | `$STACKIT_PROJECT_ID` |
| `--stackit-region` | StackIT region(s) to scan | No | All available regions |

\* Required unless provided via environment variables.

### Input Validation

The StackIT provider performs comprehensive input validation:

- **Service Account Credentials**:
  - At least one of `service_account_key_path` (file path) or `service_account_key` (inline JSON) must be supplied; both empty raises `StackITNonExistentTokenError`
  - When both are provided the inline content takes precedence
  - The key file path is logged as-is; the inline content is redacted in the credentials box

- **Project ID**:
  - Must not be empty
  - Must be a valid UUID format (e.g., `12345678-1234-1234-1234-123456789abc`)
  - Validated using Python's UUID constructor

Invalid credentials will result in clear error messages before any API calls are made.

## Available Services

### IaaS (Infrastructure as a Service)

- **Service Class:** `IaaSService`
- **Location:** [`prowler/providers/stackit/services/iaas/iaas_service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/stackit/services/iaas/iaas_service.py)
- **SDK:** Uses the [stackit-iaas](https://pypi.org/project/stackit-iaas/) Python SDK
- **Purpose:** Manages IaaS resources including security groups, servers, and network interfaces.

**Supported Resources:**
- Security Groups and Rules
- Servers (Virtual Machines)
- Network Interfaces (NICs)

**Key Features:**
- Automatic discovery of all security groups in the project
- Security rule parsing with support for unrestricted access detection
- Network interface analysis to determine whether security groups are in use
- By default, reports only security groups attached to at least one NIC; `--scan-unused-services` includes unused security groups too

## Available Checks

The StackIT provider currently implements 4 security checks focused on network security:

### 1. iaas_security_group_ssh_unrestricted

- **Severity:** High
- **Description:** Detects security groups that allow unrestricted SSH access (port 22) from the internet.
- **Risk:** Unrestricted SSH access increases the attack surface and risk of brute-force attacks.
- **Detection Logic:**
  - Checks for ingress rules allowing TCP port 22
  - Flags rules with `ip_range=None` or `ip_range="0.0.0.0/0"` or `ip_range="::/0"`
  - Reports security groups attached to NICs by default, or all security groups when `--scan-unused-services` is enabled

### 2. iaas_security_group_rdp_unrestricted

- **Severity:** High
- **Description:** Detects security groups that allow unrestricted RDP access (port 3389) from the internet.
- **Risk:** Unrestricted RDP access enables potential unauthorized remote desktop access.
- **Detection Logic:**
  - Checks for ingress rules allowing TCP port 3389
  - Flags unrestricted IP ranges (None, 0.0.0.0/0, ::/0)
  - Reports security groups attached to NICs by default, or all security groups when `--scan-unused-services` is enabled

### 3. iaas_security_group_database_unrestricted

- **Severity:** High
- **Description:** Detects security groups that allow unrestricted access to common database ports.
- **Monitored Ports:**
  - MySQL: 3306
  - PostgreSQL: 5432
  - MongoDB: 27017
  - Redis: 6379
  - SQL Server: 1433
  - CouchDB: 5984
- **Risk:** Unrestricted database access can lead to data breaches and unauthorized data access.

### 4. iaas_security_group_all_traffic_unrestricted

- **Severity:** Critical
- **Description:** Detects security groups that allow all traffic from the internet.
- **Detection Logic:**
  - Checks for rules with `port_range=None` (all ports)
  - Checks for rules with port range covering 0-65535 or 1-65535
  - Flags unrestricted IP ranges
  - Critical security misconfiguration requiring immediate remediation

### Important Implementation Notes

**Self-Referencing Security Group Rules:**

Security group rules with `remoteSecurityGroupId` set are automatically filtered out from unrestricted access checks. These rules only allow traffic from instances within the same security group (self-referencing), not from the internet, and are therefore not flagged as security risks.

**Rule Display Names:**

All findings include user-friendly rule descriptions when available. If a security group rule has a description field set (the name shown in the StackIT UI), it will be displayed in the finding message along with the rule ID:
- With description: `'Allow SSH from office' (sgr-abc123)`
- Without description: `'sgr-abc123'`

**Network Interface (NIC) Usage Filtering:**

The IaaS service lists project NICs and records the security group IDs attached to them. Checks use that signal to decide whether a security group is in use:

1. **Default behavior:** Report security groups attached to at least one NIC.
2. **`--scan-unused-services`:** Report every security group, including unused ones.
3. **FAIL logic:** Internet exposure is driven by security group rules that allow unrestricted source ranges, not by the presence of a public IP on the NIC.

**Unrestricted IP Ranges:**

The StackIT API represents "unrestricted" in two ways:
- **`ip_range=null`**: No IP restriction specified (implicit unrestricted)
- **`ip_range="0.0.0.0/0"` or `"::/0"`**: Explicitly configured to allow all IPs

Both are flagged as unrestricted. A `null` value is **more permissive** than an explicit range and applies to all protocols/ports if other fields are also `null`.

## Requirements

### Python Version

- **Minimum:** Python 3.10+
- **Reason:** The StackIT SDK requires Python 3.10 or higher

### Dependencies

The StackIT provider requires the following Python packages (automatically installed with Prowler):

- **stackit-core** (v0.2.0): Core SDK for StackIT API authentication and configuration
- **stackit-iaas** (v1.4.0): IaaS service SDK for managing compute resources
- **stackit-resourcemanager** (v0.8.0): Resource Manager SDK for fetching project metadata (e.g., project names)

These dependencies are defined in `pyproject.toml` and installed automatically with:

```bash
poetry install
```

**Note:** The `stackit-resourcemanager` package enables automatic retrieval of project names for display in reports. If this package is not available, Prowler will still function normally but project names will be empty in the output.

## Region Support

### Supported Regions

- **Available Regions:** `eu01` (Germany South) and `eu02` (Austria West)
- **Default:** All scans use both `eu01` and `eu02` regions by default.

### Multi-Region Scanning

Prowler supports scanning multiple StackIT regions in a single execution. By default, it will scan all regions defined in the `stackit_regions_by_service.json` configuration file.

### CLI Argument

You can specify which regions to scan using the `--stackit-region` argument:

```bash
# Scan only eu01
prowler stackit --stackit-region eu01

# Scan both eu01 and eu02
prowler stackit --stackit-region eu01 eu02
```

### Implementation Details

- **Regional Clients:** Prowler generates a separate API client for each audited region.
- **Service Iteration:** Each service (e.g., IaaS) iterates through the regional clients to fetch and audit resources.
- **Identity Tracking:** The `audited_regions` are stored in the identity model for reporting.

### Future Enhancements

As StackIT adds more regions, they can be easily added to Prowler by updating the `prowler/providers/stackit/stackit_regions_by_service.json` file without requiring code changes.

## Command Examples

### Scan Specific Regions

Scan only the `eu01` region:

```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"

prowler stackit \
  --stackit-project-id "your-project-id" \
  --stackit-region eu01
```

Scan multiple regions:

```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"

prowler stackit \
  --stackit-project-id "your-project-id" \
  --stackit-region eu01 eu02
```

### Scan Specific Checks

Run only SSH unrestricted check:

```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"

prowler stackit \
  --stackit-project-id "your-project-id" \
  --checks iaas_security_group_ssh_unrestricted
```

### Scan All Security Group Checks

```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"

prowler stackit \
  --stackit-project-id "your-project-id" \
  --services iaas
```

### Output Formats

Generate JSON output:

```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"

prowler stackit \
  --stackit-project-id "your-project-id" \
  --output-formats json
```

Generate HTML report:

```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"

prowler stackit \
  --stackit-project-id "your-project-id" \
  --output-formats html
```

## Known Limitations

### Current Limitations

1. **Single Project Scope**: Only one project can be scanned at a time
2. **Service Coverage**: Only the IaaS service is currently implemented
3. **Check Coverage**: Limited to security group network security checks (4 checks total)
4. **No Compliance Frameworks**: Compliance framework mappings are not yet implemented

### Planned Enhancements

- Multi-project scanning capability
- Additional IaaS checks (volume encryption, server public IP exposure, backup status)
- Compliance framework mappings (CIS, custom StackIT best practices)
- StackIT CLI remediation examples in metadata

## Troubleshooting

### Authentication Errors

**Error:** `StackIT service account key was rejected`

**Solutions:**
1. Re-issue the service account key in the StackIT Portal
2. Verify the service account key file or inline JSON content is complete
3. Check that the service account has the necessary permissions (`iaas.viewer` or `project.owner`)
4. Ensure the service account key is provided through `STACKIT_SERVICE_ACCOUNT_KEY_PATH`, `STACKIT_SERVICE_ACCOUNT_KEY`, or the matching CLI arguments

**Error:** `StackIT credentials not found or are invalid`

**Solutions:**
1. Ensure the project ID and one service account credential source are provided
2. Check that credentials are set via environment variables or command-line arguments
3. Verify there are no extra spaces or newlines in the credentials

**Error:** `Invalid StackIT project ID format`

**Solutions:**
1. Verify the project ID is a valid UUID format: `12345678-1234-1234-1234-123456789abc`
2. Copy the project ID directly from the StackIT Portal
3. Ensure there are no extra spaces or quotes around the UUID

### API Connection Errors

**Error:** `Failed to connect to StackIT API`

**Solutions:**
1. Check your internet connection
2. Verify the StackIT API endpoint is accessible from your network
3. Check if there are any firewall rules blocking HTTPS connections
4. Review the full error message for specific API error codes

**Error:** `HTTP 403 Forbidden`

**Solutions:**
1. Verify the service account has the correct permissions
2. Ensure the project ID is correct and you have access to it
3. Check that the service account is enabled (not disabled or expired)
4. Verify the service account key has not been revoked

**Error:** `HTTP 404 Not Found`

**Solutions:**
1. Verify the project ID exists and is correct
2. Check that the IaaS service is enabled in your project
3. Ensure you're using the correct region (eu01)

### Empty Results

**Issue:** No security groups or findings reported

**Solutions:**
1. Verify that security groups exist in your project
2. Check that the IaaS service is properly configured
3. Ensure the service account has `iaas.viewer` permission
4. Check Prowler logs for any API errors (use `--log-level DEBUG`)

### Debug Mode

Enable debug logging for detailed troubleshooting:

```bash
export STACKIT_SERVICE_ACCOUNT_KEY_PATH="$HOME/.stackit/sa-key.json"

prowler stackit \
  --stackit-project-id "your-project-id" \
  --log-level DEBUG
```

This will show:
- API authentication details (with inline service account keys redacted)
- Resource discovery progress
- Security rule parsing details
- Any API errors or warnings

## Specific Patterns in StackIT Services

The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:

- Directly in the code, in location [`prowler/providers/stackit/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/services)
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other StackIT services as reference.

### StackIT Service Common Patterns

- Services communicate with StackIT using the StackIT Python SDK, you can find the documentation [here](https://github.com/stackitcloud/stackit-sdk-python).
- Service constructors receive a `StackitProvider` instance and use it to access credentials, identity, and configuration.
- The provider builds StackIT SDK `Configuration` objects from the service account key path or inline key content.
- Resource containers **must** be initialized in the constructor, typically as lists or dictionaries.
- Do not manipulate `os.environ` for credentials inside services. Use the provider session and SDK configuration helpers.
- All StackIT resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
- StackIT SDK calls are wrapped in try/except blocks, with specific handling for API errors, always logging errors.
- **Centralized Error Handling**: Use `provider.handle_api_error(exception)` for consistent authentication error detection across all services.
- **SDK Warning Suppression**: StackIT SDK prints deprecation warnings to stderr - use the `suppress_stderr()` context manager during SDK initialization and API calls.
- **Unrestricted Access Detection**: In StackIT API, `None` values mean "allow all" (more permissive than explicit 0.0.0.0/0).
  - `protocol=None` → All protocols allowed
  - `ip_range=None` → All source IPs allowed (unrestricted!)
  - `port_range=None` → All ports allowed
  - `remote_security_group_id` set → Only allows traffic from the same security group (not unrestricted!)

### IaaS Service Specific Patterns

**Security Group Discovery:**
```python
# List all security groups
security_groups = client.list_security_groups(
    project_id=self.project_id,
    region=region,
)

# List network interfaces to determine security group usage
nics = client.list_project_nics(
    project_id=self.project_id,
    region=region,
)

# Checks report in-use security groups by default. Use --scan-unused-services
# to include security groups that are not attached to any NIC.
```

**Centralized Authentication Error Handling:**
```python
def _handle_api_call(self, api_function, *args, **kwargs):
    """Wrapper for API calls with centralized error handling."""
    try:
        with suppress_stderr():  # Suppress SDK warnings
            return api_function(*args, **kwargs)
    except Exception as e:
        # Use centralized error handler from provider
        self.provider.handle_api_error(e)  # Detects 401 and raises StackITInvalidTokenError
```

**Unrestricted Access Detection:**
```python
def is_unrestricted(rule):
    """Check if a rule allows unrestricted access."""
    # Filter out self-referencing rules
    if rule.remote_security_group_id is not None:
        return False
    # Check for unrestricted IP ranges
    return rule.ip_range is None or rule.ip_range in ["0.0.0.0/0", "::/0"]

def is_tcp(rule):
    """Check if a rule applies to TCP protocol."""
    # None means all protocols (including TCP)
    return rule.protocol is None or rule.protocol.lower() in ["tcp", "all"]

def includes_port(rule, port):
    """Check if a rule includes a specific port."""
    # None means all ports
    if rule.port_range is None:
        return True
    return rule.port_range.min <= port <= rule.port_range.max
```

## Specific Patterns in StackIT Checks

The StackIT checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks:

- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/stackit/services/iaas/iaas_security_group_ssh_unrestricted/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/services/iaas/iaas_security_group_ssh_unrestricted))
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.

The best reference to understand how to implement a new check is following the [check creation documentation](/developer-guide/checks#creating-a-check) and taking other similar StackIT checks as reference.

### Check Report Class

The `CheckReportStackIT` class models a single finding for a StackIT resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.

#### Purpose

`CheckReportStackIT` extends the base report structure with StackIT-specific fields, enabling detailed tracking of the resource, project, and location associated with each finding.

#### Constructor and Attribute Population

When you instantiate `CheckReportStackIT`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its StackIT-specific attributes from the resource, using the following logic:

- **`resource_id`**:
    - Uses `resource.id` if present.
    - Otherwise, uses `resource.resource_id` if present.
    - Defaults to an empty string if none are available.

- **`resource_name`**:
    - Uses `resource.name` if present.
    - Defaults to an empty string if not available.

- **`project_id`**:
    - Uses `resource.project_id` if present.
    - Defaults to an empty string if not available (should be set in check logic).

- **`location`**:
    - Uses `resource.region` if present.
    - Otherwise, uses `resource.location` if present.
    - Defaults to an empty string if not available.

If the resource object does not contain the required attributes, you must set them manually in the check logic.

Other attributes are inherited from the `Check_Report` class, from which you **always** have to set the `status` and `status_extended` attributes in the check logic.

#### Example Usage

```python
from prowler.lib.check.models import CheckReportStackIT

report = CheckReportStackIT(
    metadata=self.metadata(),
    resource=security_group
)
report.status = "FAIL"
report.status_extended = f"Security group {security_group.name} allows unrestricted SSH access from the internet."
report.resource_id = security_group.id
report.resource_name = security_group.name
report.project_id = security_group.project_id
report.location = security_group.region
```

### Common Check Pattern

```python
from prowler.lib.check.models import Check, CheckReportStackIT
from prowler.providers.stackit.services.iaas.iaas_client import iaas_client

class iaas_security_group_ssh_unrestricted(Check):
    """Check if IaaS security groups allow unrestricted SSH access."""

    def execute(self):
        findings = []

        for security_group in iaas_client.security_groups:
            if not (iaas_client.scan_unused_services or security_group.in_use):
                continue

            report = CheckReportStackIT(
                metadata=self.metadata(),
                resource=security_group
            )
            report.status = "PASS"
            report.status_extended = f"Security group {security_group.name} does not allow unrestricted SSH access."

            # Check each rule
            for rule in security_group.rules:
                if (rule.is_ingress() and
                    rule.is_tcp() and
                    rule.includes_port(22) and
                    rule.is_unrestricted()):
                    report.status = "FAIL"
                    report.status_extended = f"Security group {security_group.name} allows unrestricted SSH access from the internet."
                    break

            findings.append(report)

        return findings
```

## Resources

### Official StackIT Documentation

- **StackIT Portal**: [https://portal.stackit.cloud/](https://portal.stackit.cloud/)
- **StackIT Documentation**: [https://docs.stackit.cloud/](https://docs.stackit.cloud/)
- **StackIT API Documentation**: [https://docs.api.eu01.stackit.cloud/](https://docs.api.eu01.stackit.cloud/)

### Python SDK

- **StackIT Python SDK (GitHub)**: [https://github.com/stackitcloud/stackit-sdk-python](https://github.com/stackitcloud/stackit-sdk-python)
- **stackit-core (PyPI)**: [https://pypi.org/project/stackit-core/](https://pypi.org/project/stackit-core/)
- **stackit-iaas (PyPI)**: [https://pypi.org/project/stackit-iaas/](https://pypi.org/project/stackit-iaas/)
- **IaaS Models**: [https://github.com/stackitcloud/stackit-sdk-python/tree/main/services/iaas/src/stackit/iaas/models](https://github.com/stackitcloud/stackit-sdk-python/tree/main/services/iaas/src/stackit/iaas/models)

### Prowler Resources

- **Provider Implementation**: [`prowler/providers/stackit/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/)
- **IaaS Service**: [`prowler/providers/stackit/services/iaas/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/stackit/services/iaas/)
- **Prowler Hub**: [https://hub.prowler.com/](https://hub.prowler.com/)
- **GitHub Issues**: [https://github.com/prowler-cloud/prowler/issues](https://github.com/prowler-cloud/prowler/issues)

## Contributing

If you'd like to contribute to the StackIT provider:

1. **Add New Checks**: Follow the [check creation guide](/developer-guide/checks#creating-a-check) and use existing StackIT checks as templates
2. **Enhance Services**: Implement additional IaaS resource discovery or add new services
3. **Improve Documentation**: Add metadata enhancements, CLI remediation examples, or Terraform code samples
4. **Report Issues**: Submit bug reports or feature requests on [GitHub](https://github.com/prowler-cloud/prowler/issues)

### Quick Start for Contributors

1. **Install dependencies**: `poetry install` (includes stackit-core and stackit-iaas)
2. **Set credentials**: Export `STACKIT_SERVICE_ACCOUNT_KEY_PATH` and `STACKIT_PROJECT_ID`
3. **Run checks**: `prowler stackit`
4. **View code**: Start in `prowler/providers/stackit/`
5. **Add checks**: Create new check directories under `services/iaas/`
6. **Run tests**: `poetry run pytest tests/providers/stackit/ -v`

### Code Quality Standards

The StackIT provider should follow the same quality expectations as the rest of the Prowler SDK:

- Keep service and check logic covered by unit tests.
- Redact inline service account keys from generated output.
- Keep documentation aligned with the implemented services and checks.
- Follow existing provider, service, and check patterns before adding StackIT-specific abstractions.

---

### Developer Guide Test Impact Analysis (`docs/developer-guide/test-impact-analysis.mdx`)

---
title: 'Test Impact Analysis'
---

Test impact analysis (TIA) determines which tests to run based on the files changed in a pull request. Instead of running the full test suite on every pull request, TIA maps changed files to the specific Prowler SDK, API, and end-to-end (E2E) tests that cover them. This approach reduces continuous integration (CI) time and resource usage while maintaining confidence that relevant code paths are tested.

## Architecture

### Components

| Component | Path | Role |
|-----------|------|------|
| Configuration | `.github/test-impact.yml` | Defines ignored, critical, and module path mappings |
| Analysis engine | `.github/scripts/test-impact.py` | Python script that evaluates changed files against the configuration |
| Reusable workflow | `.github/workflows/test-impact-analysis.yml` | GitHub Actions reusable workflow that orchestrates the analysis |
| E2E consumer | `.github/workflows/ui-e2e-tests-v2.yml` | Consumes TIA outputs to run targeted Playwright tests |

### Flow Diagram

```
PR opened/updated
       |
       v
+-------------------------------+
| tj-actions/changed-files      |  Gets list of changed files from PR
+-------------------------------+
       |
       v
+-------------------------------+
| test-impact.py                |
|                               |
|  1. Filter ignored paths      |  docs/**, *.md, .gitignore, etc.
|  2. Check critical paths      |  prowler/lib/**, ui/lib/**, .github/workflows/**
|  3. Match modules             |  Map remaining files to module definitions
|  4. Categorize tests          |  Split into sdk-tests, api-tests, ui-e2e
+-------------------------------+
       |
       v
+-------------------------------+
| GitHub Actions Outputs        |
|                               |
|  run-all: true/false          |
|  sdk-tests: "tests/providers/aws/**"
|  api-tests: "api/src/backend/api/tests/**"
|  ui-e2e: "ui/tests/providers/**"
|  modules: "sdk-aws,ui-providers"
|  has-tests: true/false        |
|  has-sdk-tests: true/false    |
|  has-api-tests: true/false    |
|  has-ui-e2e: true/false       |
+-------------------------------+
       |
       v
+-------------------------------+
| Consumer Workflows            |
|                               |
|  ui-e2e-tests-v2.yml:         |
|    - Path resolution pipeline |
|    - Playwright execution     |
+-------------------------------+
```

## Configuration Reference

The configuration lives in `.github/test-impact.yml` and contains three sections.

### `ignored` — Paths That Never Trigger Tests

Files matching these patterns are filtered out before any analysis takes place. This section is intended for non-code files.

```yaml
ignored:
  paths:
    - docs/**
    - "*.md"
    - .gitignore
    - skills/**
    - ui/tests/setups/**   # E2E auth setup helpers (not runnable tests)
```

### `critical` — Paths That Trigger All Tests

If any changed file matches a critical path, the system short-circuits and outputs `run-all: true`. All downstream consumers then run their complete test suites.

```yaml
critical:
  paths:
    - prowler/lib/**           # SDK core
    - ui/lib/**                # UI shared utilities
    - ui/playwright.config.ts  # Test infrastructure
    - .github/workflows/**     # CI changes
    - .github/test-impact.yml  # This config itself
```

### `modules` — Path-to-Test Mappings

Each module maps source file patterns to the tests that cover them.

```yaml
- name: ui-providers           # Unique identifier
  match:                       # Source file glob patterns
    - ui/components/providers/**
    - ui/actions/providers/**
    - ui/app/**/providers/**
    - ui/tests/providers/**    # Test file changes also trigger themselves
  tests: []                    # SDK/API unit test patterns (empty for UI modules)
  e2e:                         # Playwright E2E test patterns
    - ui/tests/providers/**
```

#### Module Schema

| Field | Type | Description |
|-------|------|-------------|
| `name` | `string` | Unique module identifier (for example, `sdk-aws`, `ui-providers`, `api-views`) |
| `match` | `list[glob]` | Source file patterns that trigger this module |
| `tests` | `list[glob]` | Prowler SDK (`tests/`) or API (`api/`) unit test patterns to run |
| `e2e` | `list[glob]` | UI E2E test patterns (`ui/tests/`) to run |

#### Module Categories

- **`sdk-*`:** Provider and lib modules. These only produce `tests` output, not `e2e`.
- **`api-*`:** API views, serializers, filters, and role-based access control (RBAC). These produce `tests` and sometimes `e2e` (API changes can affect UI flows).
- **`ui-*`:** UI feature modules. These only produce `e2e` output, not `tests`.

## Path Resolution Pipeline

The E2E consumer workflow (`.github/workflows/ui-e2e-tests-v2.yml`, lines 202–253) transforms the `ui-e2e` output from glob patterns into paths that Playwright can execute. This transformation follows a multi-step shell pipeline.

### Step 1: Check Run Mode

```bash
if [[ "${RUN_ALL_TESTS}" == "true" ]]; then
  pnpm run test:e2e    # Run everything, skip pipeline
fi
```

### Step 2: Strip the `ui/` Prefix and `**` Suffix

```bash
# "ui/tests/providers/**" -> "tests/providers/"
TEST_PATHS=$(echo "$E2E_TEST_PATHS" | sed 's|ui/||g' | sed 's|\*\*||g')
```

### Step 3: Filter Out Setup Paths

```bash
# Remove auth setup helpers (not runnable test suites)
TEST_PATHS=$(echo "$TEST_PATHS" | grep -v '^tests/setups/')
```

### Step 4: Safety Net for Bare `tests/`

If the pattern `ui/tests/**` was present in the output (from a critical path or a broad module like `ui-shadcn`), it resolves to bare `tests/` after stripping. This would cause Playwright to discover setup files in `tests/setups/`, so it gets expanded instead:

```bash
if echo "$TEST_PATHS" | grep -qx 'tests/'; then
  # Expand to specific subdirs, excluding tests/setups/
  for dir in tests/*/; do
    [[ "$dir" == "tests/setups/" ]] && continue
    SPECIFIC_DIRS="${SPECIFIC_DIRS}${dir}"
  done
fi
```

### Step 5: Empty Directory Check

Directories that do not contain any `.spec.ts` or `.test.ts` files are skipped. This handles forward-looking patterns where a module is configured but tests have not been written yet.

```bash
if find "$p" -name '*.spec.ts' -o -name '*.test.ts' | head -1 | grep -q .; then
  VALID_PATHS="${VALID_PATHS}${p}"
else
  echo "Skipping empty test directory: $p"
fi
```

### Step 6: Execute Playwright

```bash
pnpm exec playwright test $TEST_PATHS
# For example: pnpm exec playwright test tests/providers/ tests/scans/
```

## Playwright Project Mapping

Playwright discovers tests by scanning the directories passed to it. The `playwright.config.ts` file defines projects with `testMatch` patterns that control which spec files each project claims:

```
tests/providers/providers.spec.ts  -> "providers" project  -> depends on admin.auth.setup
tests/scans/scans.spec.ts          -> "scans" project      -> depends on admin.auth.setup
tests/sign-in-base/*.spec.ts       -> "sign-in-base"       -> no auth dependency
tests/auth/*.spec.ts               -> "auth"               -> no auth dependency
tests/sign-up/sign-up.spec.ts      -> "sign-up"            -> no auth dependency
tests/invitations/invitations.spec.ts -> "invitations"      -> depends on admin.auth.setup
```

Auth setup projects (`admin.auth.setup`, `manage-scans.auth.setup`, and others) create authenticated browser state files. Projects that declare them as `dependencies` wait for the setup to complete before running.

When TIA runs only `tests/providers/`, Playwright still automatically runs `admin.auth.setup` because the `providers` project declares it as a dependency.

## Edge Cases and Known Considerations

### Forward-Looking Patterns (Empty Test Directories)

A module can reference `ui/tests/attack-paths/**` before any tests exist there. The empty directory check (step 5) gracefully skips it instead of failing.

### Broad Patterns and the Safety Net

Modules like `ui-shadcn` and `api-views` list every E2E test suite explicitly to avoid using `ui/tests/**`. If a broad pattern does produce bare `tests/`, the safety net expands it to specific subdirectories, excluding `tests/setups/`.

### Setup Files and Auth Dependencies

`ui/tests/setups/**` is listed in the `ignored` section and also filtered in the path resolution pipeline. This double protection ensures setup files are never passed as test targets to Playwright. Auth setups run only when declared as project dependencies.

### Critical Path Triggering Run-All

Changes to `.github/workflows/**` or `.github/test-impact.yml` trigger `run-all: true`. This means editing any workflow file (even unrelated ones) runs the full test suite. This behavior is intentional — CI infrastructure changes should be validated broadly.

### Unmatched Files

Files that do not match any ignored, critical, or module pattern produce no test output. The `has-tests` flag is set to `false` and consumer workflows skip entirely via the `skip-e2e` job.

## Adding New Test Modules

To add tests for a new UI feature (for example, `dashboards`):

1. **Add the module to `.github/test-impact.yml`:**

```yaml
- name: ui-dashboards
  match:
    - ui/components/dashboards/**
    - ui/actions/dashboards/**
    - ui/app/**/dashboards/**
    - ui/tests/dashboards/**
  tests: []
  e2e:
    - ui/tests/dashboards/**
```

2. **Create the test directory and spec file:**

```
ui/tests/dashboards/dashboards.spec.ts
```

3. **Add a Playwright project in `ui/playwright.config.ts`:**

```typescript
{
  name: "dashboards",
  testMatch: "dashboards.spec.ts",
  dependencies: ["admin.auth.setup"],  // if tests need auth
},
```

4. **Register E2E paths in shared UI modules (if applicable):**

   If the feature uses shared UI components, add the E2E path to the `ui-shadcn` module so that changes to shared components also trigger dashboard tests:

```yaml
- name: ui-shadcn
  match:
    - ui/components/shadcn/**
    - ui/components/ui/**
  e2e:
    - ui/tests/dashboards/**   # Add here
    # ... existing paths
```

5. **Register E2E paths in API modules (if applicable):**

   If API changes affect this feature, add the E2E path to the relevant `api-*` module (for example, `api-views`).

## Troubleshooting

### Tests Not Running When Expected

1. Check whether the changed file matches an `ignored` pattern. The script logs `[IGNORED]` to stderr.
2. Verify the file matches a module's `match` pattern. To test locally, run:
   ```bash
   python .github/scripts/test-impact.py path/to/changed/file.ts
   ```
3. Confirm the module has non-empty `e2e` (for E2E) or `tests` (for unit tests).
4. Check the `has-ui-e2e` output — the consumer workflow gates on this flag.

### Unexpected Auth Setup Errors

Auth setup projects run automatically when a test project declares them as `dependencies`. If auth failures occur:

- **Verify secrets:** Confirm that the `E2E_ADMIN_USER` and `E2E_ADMIN_PASSWORD` secrets are set.
- **Check setup file existence:** Ensure the auth setup file exists in `ui/tests/setups/`.
- **Validate test match patterns:** Ensure the `testMatch` pattern in `playwright.config.ts` correctly matches the setup file.

### "No Tests Found" Errors

This typically means the path resolution pipeline produced valid directories but Playwright could not match any spec files to a project:

- **Check project configuration:** Verify that `playwright.config.ts` has a project with a `testMatch` pattern for the spec files in that directory.
- **Verify file naming:** Confirm the spec file naming matches the expected pattern (for example, `feature.spec.ts`).

### "No Runnable E2E Test Paths After Filtering Setups"

All resolved paths were under `tests/setups/`. This indicates the module's `e2e` patterns only point to setup files, which is a configuration error. The module should be updated to point to actual test directories.

### Debugging Locally

```bash
# See what the analysis engine produces for specific files
python .github/scripts/test-impact.py ui/components/providers/some-file.tsx

# Output goes to stderr (analysis log) and GITHUB_OUTPUT (structured output)
# Without the GITHUB_OUTPUT env var, results print to stderr only
```

---

### Developer Guide Unit Testing (`docs/developer-guide/unit-testing.mdx`)

---
title: 'Unit Tests for Prowler Checks'
---

Unit tests for Prowler checks vary based on the provider being evaluated.

Below are key resources and insights gained throughout the testing process.

**Python Testing**

- https://docs.python-guide.org/writing/tests/

**Where to Patch**

- https://docs.python.org/3/library/unittest.mock.html#where-to-patch
- https://stackoverflow.com/questions/893333/multiple-variables-in-a-with-statement
- ​https://docs.python.org/3/reference/compound_stmts.html#the-with-statement

**Utilities for Tracing Mocking and Test Execution**

- https://news.ycombinator.com/item?id=36054868
- https://docs.python.org/3/library/sys.html#sys.settrace
- https://github.com/kunalb/panopticon

## General Recommendations

When writing tests for Prowler provider checks, follow these guidelines to maximize coverage across test scenarios:

1. Zero Findings Scenario:
Develop tests where no resources exist. Prowler returns zero findings if the audited service lacks the required resources.

2. Positive and Negative Outcomes:
Create tests that generate both a passing (`PASS`) and a failing (`FAIL`) result.

3. Multi-Resource Evaluations:
Design tests with multiple resources to verify check behavior and ensure the correct number of findings.

## Test File Naming Conventions

Test files follow the pattern `{service}_{check_name}_test.py` for checks and `{service}_service_test.py` for services.

### Duplicate Names Across Providers

When a test file name already exists in another provider, add your provider prefix to avoid conflicts. A GitHub Action will fail if duplicate names are detected.

**Example:** If `kms_service_test.py` already exists in AWS, name your Oracle Cloud test `oraclecloud_kms_service_test.py`.

## Running Prowler Tests

To execute the Prowler test suite, install the necessary dependencies listed in the `pyproject.toml` file.

### Prerequisites

If you have not installed Prowler yet, refer to the [developer guide introduction](/developer-guide/introduction#getting-the-code-and-installing-all-dependencies).

### Executing Tests

Navigate to the project's root directory and execute: `pytest -n auto -vvv -s -x`

Alternatively, use:
`Makefile` with `make test`.

Other Commands for Running Tests

- Running tests for a provider:
`pytest -n auto -vvv -s -x tests/providers/<provider>/services`
- Running tests for a provider service:
`pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>`
- Running tests for a provider check:
`pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>/<check>`

<Note>
Refer to the [pytest documentation](https://docs.pytest.org/en/7.1.x/getting-started.html) for more details.

</Note>

## AWS Service Dependency Table (CI Optimization)

To optimize CI pipeline execution time, the GitHub Actions workflow for AWS tests uses a **service dependency table** that determines which tests to run based on changed files. This ensures that when a service is modified, all dependent services are also tested.

### How It Works

The dependency table is defined in `.github/workflows/sdk-tests.yml` within the "Resolve AWS services under test" step. When files in a specific AWS service are changed:

1. Tests for the changed service are run
2. Tests for all services that **depend on** the changed service are also run

For example, if you modify the `ec2` service, tests will also run for `dlm`, `dms`, `elbv2`, `emr`, `inspector2`, `rds`, `redshift`, `route53`, `shield`, `ssm`, and `workspaces` because these services use the EC2 client.

### Current Dependency Table

The table maps a service (key) to the list of services that depend on it (values):

| Service | Dependent Services |
|---------|-------------------|
| `acm` | `elb` |
| `autoscaling` | `dynamodb` |
| `awslambda` | `ec2`, `inspector2` |
| `backup` | `dynamodb`, `ec2`, `rds` |
| `cloudfront` | `shield` |
| `cloudtrail` | `awslambda`, `cloudwatch` |
| `cloudwatch` | `bedrock` |
| `ec2` | `dlm`, `dms`, `elbv2`, `emr`, `inspector2`, `rds`, `redshift`, `route53`, `shield`, `ssm` |
| `ecr` | `inspector2` |
| `elb` | `shield` |
| `elbv2` | `shield` |
| `globalaccelerator` | `shield` |
| `iam` | `bedrock`, `cloudtrail`, `cloudwatch`, `codebuild` |
| `kafka` | `firehose` |
| `kinesis` | `firehose` |
| `kms` | `kafka` |
| `organizations` | `iam`, `servicecatalog` |
| `route53` | `shield` |
| `s3` | `bedrock`, `cloudfront`, `cloudtrail`, `macie` |
| `ssm` | `ec2` |
| `vpc` | `awslambda`, `ec2`, `efs`, `elasticache`, `neptune`, `networkfirewall`, `rds`, `redshift`, `workspaces` |
| `waf` | `elbv2` |
| `wafv2` | `cognito`, `elbv2` |

### When to Update the Table

You must update the dependency table when:

1. **A new check or service uses another service's client**: If your check imports a client from another service (e.g., `from prowler.providers.aws.services.ec2.ec2_client import ec2_client` in a non-ec2 check), add your service to the dependent services list of that client's service.

2. **A service relationship changes**: If you remove or add a service client dependency in an existing check, update the table accordingly.

### How to Update the Table

1. Open `.github/workflows/sdk-tests.yml`
2. Find the `dependents` dictionary in the "Resolve AWS services under test" step
3. Add or modify entries as needed
4. **Update this documentation page** (`docs/developer-guide/unit-testing.mdx`) to reflect the changes in the [Current Dependency Table](#current-dependency-table) section above

```python
dependents = {
    # ... existing entries ...
    "service_being_used": ["service_that_uses_it"],
}
```

**Example**: If you create a new check in the `newservice` service that imports `ec2_client`, add `newservice` to the `ec2` entry:

```python
"ec2": ["dlm", "dms", "elbv2", "emr", "inspector2", "newservice", "rds", "redshift", "route53", "shield", "ssm"],
```

<Warning>
Failing to update this table when adding cross-service dependencies may result in CI tests passing even when related functionality is broken, as the dependent service tests won't be triggered.
</Warning>

## AWS Testing Approaches

For AWS provider, different testing approaches apply based on API coverage based on several criteria.

<Note>
Prowler leverages and contributes to the [Moto](https://github.com/getmoto/moto) library for mocking AWS infrastructure in tests.

</Note>
- AWS API Calls Covered by [Moto](https://github.com/getmoto/moto):
    - Service Tests: `@mock_aws`
    - Checks Tests: `@mock_aws`

- AWS API Calls Not Covered by Moto:
    - Service Tests: `mock_make_api_call`
    - Checks Tests: [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock)

- AWS API Calls Partially Covered by Moto:
    - Service Tests: `@mock_aws` and `mock_make_api_call`
    - Check Tests: `@mock_aws` and `mock_make_api_call`

#### AWS Check Testing Scenarios

The following section provides examples for each testing scenario. The primary distinction between these scenarios depends on whether the [Moto](https://github.com/getmoto/moto) library covers the AWS API calls made by the service. You can review the supported API calls [here](https://github.com/getmoto/moto/blob/master/IMPLEMENTATION_COVERAGE.md).

### AWS Check Testing Approach

For AWS test examples, we reference tests for the `iam_password_policy_uppercase` check.

This section is categorized based on [Moto](https://github.com/getmoto/moto) API coverage.

#### API Calls Covered by Moto

When the [Moto](https://github.com/getmoto/moto) library supports the API calls required for testing, use the `@mock_aws` decorator. This ensures that all AWS API calls within the decorated function are properly mocked while maintaining state within the test.

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Handling API Calls Not Covered by Moto

If the IAM service required for testing is not supported by the Moto library, use [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock) to inject objects into the service client.

<Warning>
As stated above, direct service instantiation must be avoided to prevent actual AWS API calls.

</Warning>
<Note>
The example below demonstrates the IAM GetAccountPasswordPolicy API, which is covered by Moto, but is used for instructional purposes only.

</Note>
#### Mocking Service Objects Using MagicMock

The following code demonstrates how to use MagicMock to create service objects.

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Ensuring Test Isolation with Mocked/Patched Objects

In all above scenarios, check execution must occur within the context of mocked or patched objects. This guarantees that the test only evaluates objects explicitly created within its scope, preventing interference from shared state or external dependencies.

#### Handling Partially Covered API Calls

When a service requires API calls that are partially covered by the Moto decorator, additional mocking is necessary. In such cases, custom mocked API calls must be implemented alongside Moto to ensure full coverage.

To achieve this, mock the `botocore.client.BaseClient._make_api_call` function—the method responsible for making actual API requests to AWS—using [`mock.patch`](https://docs.python.org/3/library/unittest.mock.html#patch):

```
/* Detailed source-code truncated for AI context efficiency. */
```

<Note>
This example does not use Moto to simplify the setup.
However, if additional `moto` decorators are applied alongside the patch, Moto will automatically intercept the call to `orig(self, operation_name, kwarg)`.

</Note>
<Note>
The source of the above implementation can be found here: [Patch Other Services with Moto](https://docs.getmoto.org/en/latest/docs/services/patching_other_services.html)

</Note>
#### Mocking Several Services

Since the provider is being mocked, multiple attributes can be configured to customize its behavior:

```python
def set_mocked_aws_provider(
    audited_regions: list[str] = [],
    audited_account: str = AWS_ACCOUNT_NUMBER,
    audited_account_arn: str = AWS_ACCOUNT_ARN,
    audited_partition: str = AWS_COMMERCIAL_PARTITION,
    expected_checks: list[str] = [],
    profile_region: str = None,
    audit_config: dict = {},
    fixer_config: dict = {},
    scan_unused_services: bool = True,
    audit_session: session.Session = session.Session(
        profile_name=None,
        botocore_session=None,
    ),
    original_session: session.Session = None,
    enabled_regions: set = None,
    arguments: Namespace = Namespace(),
    create_default_organization: bool = True,
) -> AwsProvider:
```

If a test is designed for a check that interacts with multiple provider services, each service used must be individually mocked. For instance, if the check `cloudtrail_logs_s3_bucket_access_logging_enabled` relies on both the CloudTrail and S3 clients, the test's service mocking section should be structured as follows:

```python
with mock.patch(
    "prowler.providers.common.provider.Provider.get_global_provider",
    return_value=set_mocked_aws_provider(
        [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1]
    ),
), mock.patch(
    "prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_client",
    new=Cloudtrail(
        set_mocked_aws_provider([AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1])
    ),
), mock.patch(
    "prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.s3_client",
    new=S3(
        set_mocked_aws_provider([AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1])
    ),
):
```

As demonstrated in the code above, mocking both the AWS audit information and all used services is mandatory for proper test execution.

#### Patching vs. Importing

Properly understanding patching versus importing is critical for unit testing with Prowler checks. Given the dynamic nature of the check-loading mechanism, the process for importing a service client within a check follows this structured approach:

1. `<check>.py`:

    ```python
    from prowler.providers.<provider>.services.<service>.<service>_client import <service>_client
    ```

2. `<service>_client.py`:

    ```python
    from prowler.providers.common.provider import Provider
    from prowler.providers.<provider>.services.<service>.<service>_service import <SERVICE>

    <service>_client = <SERVICE>(Provider.get_global_provider())
    ```

Due to the import path structure, patching certain objects does not always ensure full isolation. If multiple tests—executed sequentially or in parallel—reuse service clients, some instances may already be initialized by another check. This can lead to unintended shared state, affecting test accuracy:

- `<service>_client` imported at `<check>.py`
- `<service>_client` initialised at `<service>_client.py`
- `<SERVICE>` imported at `<service>_client.py`

#### Additional Resources on Mocking Imports

For a deeper understanding of mocking imports in Python, refer to the following article: https://stackoverflow.com/questions/8658043/how-to-mock-an-import

#### Approaches to Mocking a Service Client

1\. Mocking the Service Client at the Service Client Level

2\. Mocking a Service Client via Below Code Implementation

Once all required attributes are configured for the mocked provider, it can be used as the service client for test execution:

```python title="Mocking the service_client"
with mock.patch(
    "prowler.providers.common.provider.Provider.get_global_provider",
    new=set_mocked_aws_provider([<region>]),
), mock.patch(
    "prowler.providers.<provider>.services.<service>.<check>.<check>.<service>_client",
    new=<SERVICE>(set_mocked_aws_provider([<region>])),
):
```

will cause that the service will be initialised twice:

1. When `<SERVICE>(set_mocked_aws_provider([<region>]))` is mocked out using `mock.patch`, it must be properly prepared before patching to ensure test consistency.

2. At the point of patching, in `<service>_client.py`, and since `mock.patch` needs to access said object and initialise it, `<SERVICE>(set_mocked_aws_provider([<region>]))` will be called again.

Later, when importing `<service>_client.py` at `<check>.py`, Python uses the mocked instance since the patch was applied at the correct reference point.

In the [next section](/developer-guide/unit-testing#mocking-the-service-and-the-service-client-at-the-service-client-level) we will explore an improved approach to mock objects.

##### Mocking the Service and the Service Client at the Service Client Level

##### Mocking a Service Client via Below Code Implementation

```python title="Mocking the service and the service_client"
with mock.patch(
    "prowler.providers.common.provider.Provider.get_global_provider",
    new=set_mocked_aws_provider([<region>]),
), mock.patch(
    "prowler.providers.<provider>.services.<service>.<SERVICE>",
    new=<SERVICE>(set_mocked_aws_provider([<region>])),
) as service_client, mock.patch(
    "prowler.providers.<provider>.services.<service>.<service>_client.<service>_client",
    new=service_client,
):
```

will cause that the service is initialized only once—at the moment of mocking out `set_mocked_aws_provider([<region>])` using `mock.patch`.

Later, when Python attempts to import the client at the check level, the execution continues using `from prowler.providers.<provider>.services.<service>.<service>_client`. As a result of it being already mocked out, the execution will continue using `service_client` without getting into `<service>_client.py`.

### Testing AWS Services

AWS service testing follows the same methodology as AWS checks:
Verify whether the AWS API calls made by the service are covered by Moto.

Execute tests on the service `__init__` to ensure correct information retrieval.

While service tests resemble *Integration Tests*, as they assess how the service interacts with the provider, they ultimately fall under *Unit Tests*, due to the use of Moto or custom mock objects.

For detailed guidance on test creation and existing service tests, check the current [AWS checks implementation](https://github.com/prowler-cloud/prowler/tree/master/tests/providers/aws/services).

## GCP

### GCP Check Testing Approach

Currently the GCP Provider does not have a dedicated library for mocking API calls. To ensure proper test isolation, objects must be manually injected into the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).

Mocking Service Objects Using MagicMock

The following code demonstrates how to use MagicMock to create service objects for a GCP check test. This is a real-world implementation, adapted for instructional clarity.

```
/* Detailed source-code truncated for AI context efficiency. */
```

### Testing GCP Services

The testing of Google Cloud Services follows the same principles as the one of Google Cloud checks. While all API calls must be mocked, attribute setup for API calls in this scenario is defined in the fixtures file, specifically within the [fixtures file](https://github.com/prowler-cloud/prowler/blob/master/tests/providers/gcp/gcp_fixtures.py) in the `mock_api_client` function.

<Warning>
Every method within a service must be tested to ensure full coverage and accurate validation.

</Warning>
The following example presents a real testing class, but includes additional comments for educational purposes, explaining key concepts and implementation details.

```
/* Detailed source-code truncated for AI context efficiency. */
```

Clarifying Value Origins with an Example

Understanding where specific values originate can be challenging, so the following example provides clarity.

- Step 1: Identify the API Call for Dataset Retrieval

    To determine how datasets are obtained, examine the API call used by the service. In this case, the relevant service call is: `self.client.datasets().list(projectId=project_id)`.

- Step 2: Mocking the API Call in the Fixture File

    In the fixture file, mock this call in the `MagicMock` client, in the function `mock_api_client`.

- Step 3: Structuring the Mock Function

    The best approach for mocking is to adhere to the service’s existing format:

Define a dedicated function that modifies the client.

Follow the naming convention: `mock_api_<endpoint>_calls` (*endpoint* refers to the first attribute pointed after *client*).

For BigQuery, the mock function is called `mock_api_dataset_calls`. Within this function, an assignment is made for use in the `_get_datasets` method of the BigQuery class:

```python
# Mocking datasets
dataset1_id = str(uuid4())
dataset2_id = str(uuid4())

client.datasets().list().execute.return_value = {
    "datasets": [
        {
            "datasetReference": {
                "datasetId": "unique_dataset1_name",
                "projectId": GCP_PROJECT_ID,
            },
            "id": dataset1_id,
            "location": "US",
        },
        {
            "datasetReference": {
                "datasetId": "unique_dataset2_name",
                "projectId": GCP_PROJECT_ID,
            },
            "id": dataset2_id,
            "location": "EU",
        },
    ]
}
```

## Azure

### Azure Check Testing Approach

Currently the Azure Provider does not have a dedicated library for mocking API calls. To ensure proper test isolation, objects must be manually injected into the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).

Mocking Service Objects Using MagicMock

The following code demonstrates how to use MagicMock to create service objects for an Azure check test. This is a real-world implementation, adapted for instructional clarity.

```
/* Detailed source-code truncated for AI context efficiency. */
```

### Testing Azure Services

The testing of Azure Services follows the same principles as the one of Google Cloud checks. All API calls are still mocked, but for methods that initialize attributes via an API call, use the [patch](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch) decorator at the beginning of the class to ensure proper mocking.

<Warning>
**Remember**

Every method within a service must be tested to ensure full coverage and accurate validation.

</Warning>
The following example presents a real testing class, but includes additional comments for educational purposes, explaining key concepts and implementation details.

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Security Data Regions (`docs/security/data-regions.mdx`)

---
title: 'Data Regions & Availability'
---

Prowler Cloud runs on AWS with high availability built in.

## Regions

| Region | URL | Location |
|--------|-----|----------|
| **EU** | [cloud.prowler.com](https://cloud.prowler.com) | Ireland (`eu-west-1`) |
| **US** | On-Demand | On-Demand |


## Business Continuity

| Control | Details |
|---------|---------|
| **High Availability** | Multi-AZ databases and load-balanced stateless application layer on AWS |
| **Disaster Recovery** | Encrypted backups, tested regularly |
| **[RPO](https://en.wikipedia.org/wiki/Recovery_point_objective)** | 24 hours |
| **[RTO](https://en.wikipedia.org/wiki/Recovery_time_objective)** | 2 hours |
| **Status** | [status.prowler.com](https://status.prowler.com) — uptime history and incidents |

## Contact

For questions about data regions and availability, visit the [Support page](/support).

---

### Security Encryption (`docs/security/encryption.mdx`)

---
title: 'Encryption'
---

Prowler Cloud uses encryption everywhere possible. All data and communications are encrypted at rest and in transit.

## Encryption at Rest

All data stored in Prowler Cloud is encrypted at rest using AES-256 encryption, including:

- **Database contents:** All scan results, findings, and configuration data.
- **File storage:** Reports, exports, and uploaded files.
- **Backups:** All backup data is encrypted.

## Encryption in Transit

All communications with Prowler Cloud are encrypted in transit using TLS 1.2 or higher, including:

- **API requests:** All REST API communications.
- **Web application traffic:** Browser-to-server connections.
- **Internal service communication:** Service-to-service traffic within the platform.

## Contact

For questions regarding encryption, visit the [Support page](/support).

---

### Security Networking (`docs/security/networking.mdx`)

---
title: 'Networking'
---

## Egress IP Addresses

Prowler Cloud makes outbound API calls to scan cloud provider accounts and connect to integrations. Allowlist these IPs in firewalls or security groups to restrict access to Prowler Cloud only.

| Region | IP Address |
|--------|------------|
| EU (Ireland) | `52.48.254.174` |

Resolve the egress IP via DNS:

```bash
dig egress.prowler.com +short
```

<Note>
The egress IP address is stable, but it is recommended to periodically verify it remains current by querying `egress.prowler.com`.
</Note>

## Use Cases

Allowlisting Prowler Cloud's egress IP address enables:

- **Credential Usage Control**: Restrict where cloud provider credentials can be used from across AWS, Azure, GCP, and other providers
- **Kubernetes Security**: Limit inbound HTTPS traffic to clusters by allowing only Prowler Cloud's IP address
- **Compliance Requirements**: Meet security policies requiring allowlisting of external services

## Contact

For questions about networking, visit the [Support page](/support).

---

### Security Software Security (`docs/security/software-security.mdx`)

---
title: 'Software Security'
---

Prowler applies security-by-design across the development lifecycle. Every change passes automated checks at each stage: pre-commit hooks locally, multiple CI gates on pull requests, branch protection before merge, container scanning before publish, and registry monitoring after release.

All security tooling and configuration lives in the [Prowler GitHub repository](https://github.com/prowler-cloud/prowler): [pre-commit hooks](https://github.com/prowler-cloud/prowler/blob/master/.pre-commit-config.yaml), [CI/CD workflows](https://github.com/prowler-cloud/prowler/tree/master/.github/workflows), and [Dependabot configuration](https://github.com/prowler-cloud/prowler/blob/master/.github/dependabot.yml).

## Coverage

Security controls cover six domains, each detailed below:

| Domain | What It Protects |
|--------|------------------|
| [**CI/CD**](#cicd-security) | GitHub Actions workflows, runners, third-party actions |
| [**SAST**](#static-application-security-testing-sast) | Application source code |
| [**SCA**](#software-composition-analysis-sca) | Third-party dependencies and their known vulnerabilities |
| [**Supply-Chain Pinning**](#supply-chain-pinning) | Reproducible installs across Python, npm, GitHub Actions, container base images |
| [**Containers**](#container-security) | Runtime images for UI, API, SDK, Model Context Protocol (MCP) Server |
| [**Secrets**](#secrets-detection) | Credentials, tokens, API keys in code and git history |

## CI/CD Security

Every GitHub Actions workflow uses runner hardening, pinned action versions, and audited permissions.

### Runner Hardening With StepSecurity

- [**`step-security/harden-runner`**](https://github.com/step-security/harden-runner) runs as the first step in every workflow, pinned by commit SHA.
- Workflows are being migrated to explicit egress controls: some already declare an egress allow-list with `egress-policy: block`, while others still run in `egress-policy: audit` until their allowed endpoints are fully defined.
- **Global Block Policy** (StepSecurity) blocks known-malicious domains and IP addresses across every workflow run. This protection applies even in audit mode, so workflows that have not yet moved to `block` still resist known-bad egress.

### Third-Party Action Pinning

- Every third-party action reference uses a commit SHA with the version as a comment: `uses: org/action@<sha> # v1.2.3`.
- Dependabot tracks the comment and proposes SHA-pinned upgrades on a monthly cadence.

### Workflow Permissions

- Workflows declare `permissions: {}` at the top level and grant the minimum required scopes per job.
- Code review covers permission changes; zizmor enforces the rules (see below).

### Workflow Security Audit With Zizmor

- **[zizmor](https://github.com/zizmorcore/zizmor)** audits every workflow file for known security anti-patterns. Runs on every pull request and push.
- Triggers on every push, every pull request that touches `.github/`, and on a daily schedule.
- Results upload to the GitHub Security tab via Static Analysis Results Interchange Format (SARIF).
- Key [audit rules](https://docs.zizmor.sh/audits/) the build gates on:
  - **[PWN Request](https://docs.zizmor.sh/audits/#dangerous-triggers)** (`dangerous-triggers`): unsafe use of `pull_request_target` with checked-out PR code.
  - **[Script Injection](https://docs.zizmor.sh/audits/#template-injection)** (`template-injection`): unsanitized `${{ github.event.* }}` expressions in `run:` blocks.
  - **[artipacked](https://docs.zizmor.sh/audits/#artipacked)**: credential leakage through artifacts.
  - **[Excessive permissions](https://docs.zizmor.sh/audits/#excessive-permissions)** (`excessive-permissions`): workflows with unneeded `write` scopes.

### Branch Protection

Pull requests to `master` and the active `v5.*` release branches must pass several required workflows before merge. These gates prevent specific classes of supply-chain and pipeline attacks from reaching the main branch:

- **Compromised packages:** the **npm Package Compromised Updates** and **PyPI Package Compromised Updates** checks (StepSecurity) fail any PR that introduces a package version present in the compromised-package feed. Layered on top of osv-scanner.
- **Premature releases:** the **npm Package Cooldown** and **PyPI Package Cooldown** checks (StepSecurity) fail any PR that introduces a package version published within the cooldown window. Layered on top of pnpm's `minimumReleaseAge`.
- **Workflow exploitation:** the **PWN Request** and **Script Injection** checks (StepSecurity) reject the corresponding zizmor-detected anti-patterns at PR time. Layered on top of zizmor's audit.
- **Vulnerable code or dependencies:** CodeQL (UI, API, SDK), osv-scanner (SDK, API, UI), Bandit (SDK, API), and Trivy (container images) must all pass.

## Static Application Security Testing (SAST)

Multiple SAST tools run on every push and pull request to catch vulnerabilities and code-quality issues before merge.

### Cross-Language

- **CodeQL:** semantic code analysis for the UI (JavaScript/TypeScript), API (Python), and SDK (Python). Runs on every push and pull request, plus a daily scheduled scan. Results upload to the GitHub Security tab via SARIF.

### Python (SDK + API)

- **Bandit:** detects common Python security issues (SQL injection, hardcoded credentials, insecure deserialization). Runs in pre-commit and on every pull request and push.
- **Pylint:** analyzes your code without actually running it. It checks for errors, enforces a coding standard, looks for code smells, and can suggest refactors. Runs in pre-commit and on every pull request and push.
- **Vulture:** dead-code detection at `--min-confidence 100`. Unused code can hide incomplete implementations or stale security paths. Runs in pre-commit and on every pull request and push.
- **Flake8:** style and correctness checks for the SDK. Runs in pre-commit and on every pull request and push.

### JavaScript/TypeScript (UI)

- **TypeScript (`tsc`):** strict type checking for the UI. Catches whole classes of null/undefined and type-confusion bugs at build time. Runs on every pull request and push via `pnpm run healthcheck`.
- **ESLint:** UI linting with a capped warning budget (`--max-warnings 40`). Runs on every pull request and push via `pnpm run healthcheck`.
- **Knip:** dead-code and unused-export detection for the UI. The UI analogue to Vulture.

<Note>
Knip runs locally on demand via `pnpm run lint:knip` and is not yet wired into CI.
</Note>

### Shell

- **Shellcheck:** correctness and security checks for shell scripts in `.github/scripts/` and `scripts/`. Runs in pre-commit on staged files.

## Software Composition Analysis (SCA)

Dependencies are scanned against public vulnerability databases on every pull request and push, with results posted directly on the PR.

### Cross-Language

- **osv-scanner:** scans lockfiles against the [OSV.dev](https://osv.dev) vulnerability database for SDK (`uv.lock`), API (`api/uv.lock`), and UI (`ui/pnpm-lock.yaml`). Runs on every pull request and push.
  - The action installs the `osv-scanner` binary and verifies its SHA-256 checksum against the upstream-signed `SHA256SUMS` manifest before running. Any mismatch aborts the scan.
  - Gates the build on `HIGH`, `CRITICAL`, and `UNKNOWN` severity findings.
  - Posts and updates a per-lockfile report as a pull request comment.
  - Per-vulnerability ignores live in [`osv-scanner.toml`](https://github.com/prowler-cloud/prowler/blob/master/osv-scanner.toml) at the repo root, each with a reason and an expiry date.
- **Trivy:** scans container images for OS-package and application-dependency vulnerabilities. Runs on every pull request and push that touches an image or its dependencies. Trivy uploads SARIF to the GitHub Security tab and posts a scan summary on the PR.
- **Dependabot:** [configured](https://github.com/prowler-cloud/prowler/blob/master/.github/dependabot.yml) for monthly updates of the SDK Python dependencies, GitHub Actions, Docker base images, and pre-commit hooks. Dependabot opens pull requests for known security advisories, so critical patches reach the team without delay. A 7-day default cooldown reduces exposure to compromised package releases.
- **Renovate:** [configured](https://github.com/prowler-cloud/prowler/blob/master/.github/renovate.json) dependency update automation is transitioning from Dependabot to **Renovate** to gain finer control over update cadence, grouping, and per-component scope. Both tools currently run in parallel during the migration.

#### Renovate (Primary)

Configuration: [`.github/renovate.json`](https://github.com/prowler-cloud/prowler/blob/master/.github/renovate.json)

- **Coverage:** Python (SDK, API, MCP Server), npm (UI), GitHub Actions, Docker images, and Pre-commit hooks
- **Range Strategy:** Versions are pinned to ensure reproducible builds
- **Vulnerability Alerts:** GitHub Security Advisories generate immediate pull requests that bypass rate limits and scheduled windows, labeled `security` for prioritized triage

#### Dependabot (Legacy)

Configuration: [`.github/dependabot.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/dependabot.yml)

Dependabot remains active for the SDK and shared automation ecosystems until the Renovate migration completes:

- **Python (pip):** Monthly updates for SDK
- **GitHub Actions:** Monthly updates for workflow dependencies
- **Docker:** Monthly updates for base images
- **Pre-commit:** Monthly updates for hook revisions

Dependabot is paused for the API and UI; Renovate now handles those components. Even when paused, Dependabot continues to open pull requests for security vulnerabilities, ensuring critical patches are never delayed.

### JavaScript/TypeScript (UI)

- **pnpm audit:** runs `pnpm audit --audit-level critical` on every UI pull request and push as part of `pnpm run audit`. Cross-checks the npm registry's advisory database in addition to the OSV scan and surfaces npm-specific advisories that may not yet have an OSV identifier.

## Supply-Chain Pinning

Pinning runs across Python, npm, GitHub Actions, and container base images. Every install resolves to the exact set of versions already vetted in CI, and any drift fails loudly instead of slipping in silently.

### Python (uv)

The SDK, API, and MCP Server all use [uv](https://docs.astral.sh/uv/) for dependency management. Each component has its own project manifest and lock file:

| Component | Manifest | Lock File |
|-----------|----------|-----------|
| SDK | `pyproject.toml` | `uv.lock` |
| API | `api/pyproject.toml` | `api/uv.lock` |
| MCP Server | `mcp_server/pyproject.toml` | `mcp_server/uv.lock` |

The controls applied across all three:

- **Direct dependencies pinned to exact versions** (`==`). No version ranges in dependency lists.
- **Transitive dependencies pinned** via `[tool.uv].constraint-dependencies` in the SDK and API manifests. The constraint set mirrors the versions locked in the corresponding `uv.lock`. A future `uv lock` preserves these versions instead of silently picking up newer releases, and the resolver fails when a constraint becomes infeasible, signaling that a deliberate bump is needed.
- **Lock files committed.** CI installs strictly from the lock.
- **uv itself pinned** in the [`setup-python-uv`](https://github.com/prowler-cloud/prowler/tree/master/.github/actions/setup-python-uv) composite action.

<Note>
The MCP Server declares a small constraint set of its own, covering transitive pins that `fastmcp` does not raise on its own. Its lock file remains the source of truth for everything else.
</Note>

### JavaScript/TypeScript (pnpm)

The UI uses [pnpm](https://pnpm.io) with supply-chain controls configured in [`ui/pnpm-workspace.yaml`](https://github.com/prowler-cloud/prowler/blob/master/ui/pnpm-workspace.yaml).

- **Minimum release age** (`minimumReleaseAge: 1440`): packages must publish at least 24 hours before install. This reduces exposure during the window when a compromised release has not yet been detected and yanked.
- **Lifecycle script allow-list** (`strictDepBuilds: true` + `allowBuilds`): only explicitly approved packages may run `install` or `postinstall` scripts (currently `sharp`, `esbuild`, `@sentry/cli`, `unrs-resolver`, `msw`). Any unlisted package with lifecycle scripts fails the install.
- **Trust policy** (`trustPolicy: no-downgrade`): the install fails when a package's trust evidence drops, for example after a new publisher takes over.
- **Block exotic subdeps** (`blockExoticSubdeps: true`): transitive dependencies cannot ship as git URLs or tarballs. Every package in the tree resolves from the configured registry.
- **Transitive overrides** in [`ui/package.json`](https://github.com/prowler-cloud/prowler/blob/master/ui/package.json) force specific versions for transitive packages (`lodash`, `serialize-javascript`, `qs`, `rollup`, `minimatch`, `ajv`, and others).
- **`pnpm-lock.yaml` committed** and CI installs strictly from the lock.
- **pnpm itself pinned** via the `packageManager` field in `package.json` with an integrity hash.

### GitHub Actions

- Every third-party action reference uses a commit SHA, with the version in a trailing comment.
- Dependabot opens monthly PRs to bump pinned SHAs.

### Container Base Images

- Every Dockerfile references base images by digest (`image@sha256:...`).
- Dependabot opens monthly PRs to bump digests.

## Container Security

Container images get scanned twice: once in CI before they push to a registry, and continuously after publish by the registries themselves.

### Pre-Publish (CI)

- **Trivy** scans for OS-package and application-dependency vulnerabilities. Runs on every pull request and push that touches an image or its dependencies. Trivy uploads SARIF to the GitHub Security tab and posts a summary on the PR. Builds fail on any critical finding that is not explicitly accepted. Accepted findings live in [`.trivyignore.yaml`](https://github.com/prowler-cloud/prowler/blob/master/.trivyignore.yaml), each carrying a reason and an expiry date, the same policy `osv-scanner.toml` follows. A local `trivy image` run does not apply these suppressions unless you pass `--ignorefile .trivyignore.yaml`: Trivy auto-loads only the classic `.trivyignore` format, never the YAML one.
- **Hadolint** validates Dockerfile syntax and structure against secure-build best practices. Runs in pre-commit and alongside the image scans above.

### Post-Publish (Registries)

- **Amazon ECR:** ECR continuously scans published images for vulnerabilities. New advisories disclosed after publish surface on the image without requiring a rebuild.
- **Docker Hub:** Docker Hub continuously scans the same images mirrored from ECR.
- The security team reviews findings from both registries for triage and remediation.

### Known Findings

A small number of findings remain in the published images and cannot be resolved by Prowler: the upstream project has released no fix, the package cannot be removed without breaking the image, or the finding comes from a vendored SBOM rather than from a package that is actually installed. Alternative base distributions have been evaluated and none currently satisfies both the vulnerability profile and the runtime requirements of every supported provider.

Each suppression is recorded in [`.trivyignore.yaml`](https://github.com/prowler-cloud/prowler/blob/master/.trivyignore.yaml) with the reason it cannot be fixed, why it is not exploitable in Prowler's runtime, and an expiry date that forces re-review. Nothing is suppressed without that rationale, and a build fails on any critical finding that is not listed there.

To see the current set for any image, scan it directly. This reports everything, including the accepted findings above, because Trivy does not read `.trivyignore.yaml` unless it is named:

```bash
trivy image prowlercloud/prowler:latest
```

To see only what is *not* already accepted, point Trivy at the suppression file:

```bash
trivy image --ignorefile .trivyignore.yaml prowlercloud/prowler:latest
```

## Secrets Detection

- **[TruffleHog](https://github.com/trufflesecurity/trufflehog)** scans the codebase and git history on every push and pull request. Detects high-entropy strings, API keys, tokens, and credentials, and reports verified and unknown findings.
- A pre-commit hook runs the same check locally and blocks secrets before they leave the developer machine.

## Security Monitoring

- **GitHub Security tab:** centralized view of findings from CodeQL, Trivy, zizmor, and any other SARIF-compatible tool.
- **PR comments:** osv-scanner and Trivy post per-PR summaries so issues surface during review, not after merge.
- **Artifact retention:** the build retains security scan reports for post-deployment analysis.

## Contact

For questions about software security, see the [Support page](/support). To report a vulnerability, follow the [responsible disclosure process](https://prowler.com/.well-known/security.txt).

---

### Snippets Applies To (`docs/snippets/applies-to.mdx`)

export const AppliesTo = ({ products = ["Prowler Cloud", "Prowler Private Cloud", "Prowler Local Server"] }) => {
    return (
        <Info>
            This guide applies to{" "}
            {products.map((name, index) => (
                <span key={name}>
                    {index > 0 && (index === products.length - 1 ? (products.length > 2 ? ", and " : " and ") : ", ")}
                    <b>{name}</b>
                </span>
            ))}
            . See <a href="/getting-started/products">Prowler product families</a>.
        </Info>
    );
};

---

### Snippets Provider Cards (`docs/snippets/provider-cards.mdx`)

{/* AUTO-GENERATED by docs/scripts/generate_provider_cards.py — do not edit by hand. */}
{/* Regenerated on pre-commit whenever any provider getting-started page changes. */}

export const ProviderCards = () => (
    <Columns cols={3}>
        <Card title="Alibaba Cloud" icon="cloud" href="/user-guide/providers/alibabacloud/getting-started-alibabacloud" />
        <Card title="AWS" icon="aws" href="/user-guide/providers/aws/getting-started-aws" />
        <Card title="Azure" icon="microsoft" href="/user-guide/providers/azure/getting-started-azure" />
        <Card title="Cloudflare" icon="cloudflare" href="/user-guide/providers/cloudflare/getting-started-cloudflare" />
        <Card title="GCP" icon="google" href="/user-guide/providers/gcp/getting-started-gcp" />
        <Card title="GitHub" icon="github" href="/user-guide/providers/github/getting-started-github" />
        <Card title="Google Workspace" icon="users" href="/user-guide/providers/googleworkspace/getting-started-googleworkspace" />
        <Card title="IaC" icon="code" href="/user-guide/providers/iac/getting-started-iac" />
        <Card title="Image" icon="docker" href="/user-guide/providers/image/getting-started-image" />
        <Card title="Kubernetes" icon="dharmachakra" href="/user-guide/providers/kubernetes/getting-started-k8s" />
        <Card title="Microsoft 365" icon="briefcase" href="/user-guide/providers/microsoft365/getting-started-m365" />
        <Card title="MongoDB Atlas" icon="leaf" href="/user-guide/providers/mongodbatlas/getting-started-mongodbatlas" />
        <Card title="Okta" icon="key" href="/user-guide/providers/okta/getting-started-okta" />
        <Card title="OpenStack" icon="cubes" href="/user-guide/providers/openstack/getting-started-openstack" />
        <Card title="Oracle Cloud Infrastructure (OCI)" icon="database" href="/user-guide/providers/oci/getting-started-oci" />
        <Card title="Vercel" icon="triangle" href="/user-guide/providers/vercel/getting-started-vercel" />
    </Columns>
);

---

### Snippets Subscription Banner (`docs/snippets/subscription-banner.mdx`)

export const SubscriptionBanner = ({ children, label = "feature" }) => {
    return (
        <Note>
            This {label} is available exclusively in <b>Prowler Cloud</b> and <b>Prowler Private Cloud</b> with a <a href="https://prowler.com/pricing">subscription</a>.
            {children}
        </Note>
    );
};

---

### Snippets Version Badge (`docs/snippets/version-badge.mdx`)

export const VersionBadge = ({ version }) => {
    return (
        <a
            href={`https://github.com/prowler-cloud/prowler/releases/tag/${version}`}
            target="_blank"
            rel="noopener noreferrer"
            className="version-badge-link"
        >
            <span className="version-badge-container">
                <span className="version-badge">
                    <span className="version-badge-label">Added in:</span>&nbsp;
                    <span className="version-badge-version">{version}</span>
                </span>
            </span>
        </a>
    );
};

---

### User Guide Organizations (`docs/user-guide/organizations.mdx`)

---
title: 'Organizations Across Cloud Providers'
description: 'Understand organization hierarchies and onboarding across AWS, Google Cloud, and Azure'
---

Cloud providers use organization-level hierarchies to group accounts, projects, or subscriptions and apply access and governance consistently. Prowler uses these hierarchies to discover cloud targets and help configure multi-account or multi-project scanning.

This guide explains the shared lifecycle and the differences between AWS Organizations, Google Cloud organizations, and Azure Management Groups. Use the provider-specific guides for commands, permissions, and limitations.

## Organization Lifecycle

Organization-level onboarding generally follows these steps:

1. **Identify the hierarchy:** Locate the organization, management account, management group, folder, organizational unit, or equivalent parent node in the cloud provider.
2. **Grant access:** Assign the provider permissions required to enumerate the hierarchy and read the resources that Prowler scans.
3. **Discover members:** Use Prowler to retrieve accounts, projects, or subscriptions under the selected hierarchy.
4. **Select scan targets:** Choose the cloud targets to connect or scan. Discovery does not necessarily make every discovered target a Prowler provider.
5. **Test access:** Confirm that Prowler can authenticate to each selected target and read its resources.
6. **Scan and maintain:** Run scans, review findings, and repeat discovery when the provider hierarchy changes.

<Note>
Organization membership changes are not automatically synchronized in every Prowler workflow. Follow the provider-specific guide to learn when manual rediscovery is required.
</Note>

## Capability Matrix

| Capability | AWS Organizations | Google Cloud organization | Azure Management Groups |
| --- | --- | --- | --- |
| Hierarchy members | AWS accounts grouped in organizational units (OUs) | Projects grouped in folders and nested folders | Subscriptions grouped in management groups |
| Organization-level discovery | Supported through AWS Organizations APIs | Supported through the Cloud Asset API | Supported through Azure management-group and subscription APIs |
| Primary scan target | AWS account | Google Cloud project | Azure subscription |
| Common organization-level permission | IAM role in the management or delegated administrator account | Cloud Asset Viewer or Cloud Asset Owner at the organization node | Appropriate Azure role assignment at the management-group or subscription scope |
| Provider-specific onboarding | AWS account discovery and optional StackSet role deployment | Project discovery under an organization ID | Subscription discovery under a management group; role assignments inherit to subscriptions |
| Membership maintenance | Repeat the discovery flow when accounts are added or removed | Re-run organization discovery when projects or folders change | Refresh discovery when subscriptions move between management groups |

## Provider Guides

### AWS Organizations

The [AWS Organizations guide](/user-guide/providers/aws/organizations) covers account details, delegated administration, IAM roles, CloudFormation StackSets, and CLI scanning. For Prowler Cloud onboarding, see [AWS Organizations in Prowler Cloud](/user-guide/tutorials/prowler-cloud-aws-organizations).

### Google Cloud Organization

The [Google Cloud organization guide](/user-guide/providers/gcp/organization) covers scanning projects under an organization ID, organization-level permissions, and Cloud Asset API requirements. For Prowler Cloud onboarding, see [Google Cloud organizations in Prowler Cloud](/user-guide/tutorials/prowler-cloud-gcp-organizations).

### Azure Management Groups

The [Azure Management Groups guide](/user-guide/providers/azure/management-groups) covers hierarchy setup, role assignment, subscription scope, and Azure-specific limitations. For Prowler Cloud onboarding, see [Azure Management Groups in Prowler Cloud](/user-guide/tutorials/prowler-cloud-azure-management-groups).

## Scope Boundaries

The organization concepts in this guide refer only to cloud-provider resource hierarchies:

- **GitHub organizations** group repositories and GitHub resources. They are a separate provider concept and are not part of AWS, Google Cloud, or Azure organization discovery.
- **MongoDB Atlas organizations** group Atlas projects and teams. They use a separate provider API and authentication model.
- **Prowler Cloud organizations** are internal tenants that isolate providers, scans, findings, users, and permissions. They are not the same as a cloud-provider organization and do not replace one.

Choose the guide that matches the hierarchy being configured, then use the relevant Prowler Cloud or CLI workflow for the scan targets.

---

### Contrib Terraform Kickstarter Docs Tf (`contrib/terraform-kickstarter/docs/tf.md`)

## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.54 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 3.55.0 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_cloudwatch_event_rule.prowler_check_scheduler_event](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource |
| [aws_cloudwatch_event_target.run_prowler_scan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource |
| [aws_codebuild_project.prowler_codebuild](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) | resource |
| [aws_iam_policy.prowler_event_trigger_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_policy.prowler_kickstarter_iam_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_policy_attachment.prowler_event_trigger_policy_attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy_attachment) | resource |
| [aws_iam_policy_attachment.prowler_kickstarter_iam_policy_attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy_attachment) | resource |
| [aws_iam_role.prowler_event_trigger_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.prowler_kick_start_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_s3_bucket.prowler_report_storage_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
| [aws_s3_bucket_policy.prowler_report_storage_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
| [aws_s3_bucket_public_access_block.prowler_report_storage_bucket_block_public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
| [aws_securityhub_account.securityhub_resource](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_account) | resource |
| [aws_securityhub_product_subscription.security_hub_enable_prowler_findings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_product_subscription) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy.SecurityAudit](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_codebuild_timeout"></a> [codebuild\_timeout](#input\_codebuild\_timeout) | Codebuild timeout setting | `number` | `300` | no |
| <a name="input_enable_security_hub"></a> [enable\_security\_hub](#input\_enable\_security\_hub) | Enable AWS SecurityHub. | `bool` | `true` | no |
| <a name="input_enable_security_hub_prowler_subscription"></a> [enable\_security\_hub\_prowler\_subscription](#input\_enable\_security\_hub\_prowler\_subscription) | Enable a Prowler Subscription. | `bool` | `true` | no |
| <a name="input_prowler_cli_options"></a> [prowler\_cli\_options](#input\_prowler\_cli\_options) | Run Prowler With The Following Command | `string` | `"_q _M json_asff _S _f us_east_1"` | no |
| <a name="input_prowler_schedule"></a> [prowler\_schedule](#input\_prowler\_schedule) | Run Prowler based on cron schedule | `string` | `"cron(0 0 ? * * *)"` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_account_id"></a> [account\_id](#output\_account\_id) | TODO Move these to outputs file |

---

### Getting Started Basic Usage Prowler App (`docs/getting-started/basic-usage/prowler-app.mdx`)

---
title: 'Basic Usage'
---

## Access Prowler Local Server

After [installation](/getting-started/installation/prowler-app), navigate to [http://localhost:3000](http://localhost:3000) and sign up with email and password.

<img src="/images/sign-up-button.png" alt="Sign Up Button" width="320" />
<img src="/images/sign-up.png" alt="Sign Up" width="285" />

<Note>
**User creation and default tenant behavior**


When creating a new user, the behavior depends on whether an invitation is provided:

- **Without an invitation**:

    - A new tenant is automatically created.
    - The new user is assigned to this tenant.
    - A set of **RBAC admin permissions** is generated and assigned to the user for the newly-created tenant.

- **With an invitation**: The user is added to the specified tenant with the permissions defined in the invitation.

This mechanism ensures that the first user in a newly created tenant has administrative permissions within that tenant.

</Note>
## Log In

Access Prowler Local Server by logging in with **email and password**.

<img src="/images/log-in.png" alt="Log In" width="285" />

## Add Provider

Configure a provider for scanning:

1. Navigate to `Settings > Providers` and click `Add Provider`.
2. Select the cloud provider.
3. Enter the provider's identifier (Optional: Add an alias):
    - **AWS**: Account ID
    - **GCP**: Project ID
    - **Azure**: Subscription ID
    - **Kubernetes**: Cluster ID
    - **M365**: Domain ID
4. Follow the guided instructions to add and authenticate your credentials.

## Start a Scan

Once credentials are successfully added and validated, Prowler initiates a scan of your cloud environment.

Click `Go to Scans` to monitor progress.

## View Results

Review findings during scan execution in the following sections:

- **Overview** – Provides a high-level summary of your scans.
  <img src="/images/products/overview.png" alt="Overview" width="700" />

- **Compliance** – Displays compliance insights based on security frameworks.
  <img src="/images/compliance.png" alt="Compliance" width="700" />

> For detailed usage instructions, refer to the [Prowler Cloud guide](/user-guide/tutorials/prowler-app), which also applies to Prowler Local Server.

<Note>
Prowler will automatically scan all configured providers every **24 hours**, ensuring your cloud environment stays continuously monitored.

</Note>

---

### Getting Started Basic Usage Prowler Cli (`docs/getting-started/basic-usage/prowler-cli.mdx`)

---
title: 'Basic Usage'
---

import { VersionBadge } from "/snippets/version-badge.mdx"

## Running Prowler

Running Prowler requires specifying the provider (e.g. `aws`, `gcp`, `azure`, `kubernetes`, `m365`, `github`, `iac` or `mongodbatlas`):

<Note>
If no provider is specified, AWS is used by default for backward compatibility with Prowler v2.

</Note>
```console
prowler <provider>
```


<Note>
Running the `prowler` command without options will use environment variable credentials. Refer to the Authentication section of each provider for credential configuration details.

</Note>
## Verbose Output

If you prefer the former verbose output, use: `--verbose`. This allows seeing more info while Prowler is running, minimal output is displayed unless verbosity is enabled.

## Report Generation

By default, Prowler generates CSV, JSON-OCSF, and HTML reports. Additional provider-specific formats are available:

* **JSON-ASFF** (AWS only): Used by AWS Security Hub
* **SARIF** (IaC only): Used by GitHub Code Scanning

To specify output formats, use the `-M` or `--output-modes` flag:

```console
prowler <provider> -M csv json-asff json-ocsf html
```
The HTML report is saved in the output directory, alongside other reports. It will look like this:



## Listing Available Checks and Services

List all available checks or services within a provider using `-l`/`--list-checks` or `--list-services`.

```console
prowler <provider> --list-checks
prowler <provider> --list-services
```
## Running Specific Checks or Services

Execute specific checks or services using `-c`/`checks` or `-s`/`services`:

```console
prowler azure --checks storage_blob_public_access_level_is_disabled
prowler aws --services s3 ec2
prowler gcp --services iam compute
prowler kubernetes --services etcd apiserver
```
## Excluding Checks and Services

Checks and services can be excluded with `-e`/`--excluded-checks` or `--excluded-services`:

```console
prowler aws --excluded-checks s3_bucket_public_access
prowler azure --excluded-services defender iam
prowler gcp --excluded-services kms
prowler kubernetes --excluded-services controllermanager
```
## Additional Options

Explore more advanced time-saving execution methods in the [Miscellaneous](/user-guide/cli/tutorials/misc) section.

Access the help menu and view all available options with `-h`/`--help`:

```console
prowler --help
```

## AWS

Use a custom AWS profile with `-p`/`--profile` and/or specific AWS regions with `-f`/`--filter-region`:

```console
prowler aws --profile custom-profile -f us-east-1 eu-south-2
```

<Note>
By default, `prowler` will scan all AWS regions.

</Note>
See more details about AWS Authentication in the [Authentication Section](/user-guide/providers/aws/authentication) section.

- **AWS Retrier and Timeout Configuration**

    <VersionBadge version="5.42.0" />

    Tune the Boto3 standard retrier and the endpoint timeouts when AWS throttles the scan or when some endpoints are unreachable from the network Prowler runs in:

    ```console
    prowler aws --aws-retries-max-attempts 5 --aws-connect-timeout 5 --aws-read-timeout 30
    ```

    See the [Boto3 configuration](/user-guide/providers/aws/boto3-configuration) page for defaults and environment variables.

## Azure

Azure requires specifying the auth method:

```console
# To use service principal authentication
prowler azure --sp-env-auth

# To use az cli authentication
prowler azure --az-cli-auth

# To use browser authentication
prowler azure --browser-auth --tenant-id "XXXXXXXX"

# To use managed identity auth
prowler azure --managed-identity-auth
```

See more details about Azure Authentication in the [Authentication Section](/user-guide/providers/azure/authentication)

By default, Prowler scans all accessible subscriptions. Scan specific subscriptions using the following flag (using az cli auth as example):

```console
prowler azure --az-cli-auth --subscription-ids <subscription ID 1> <subscription ID 2> ... <subscription ID N>
```
## Google Cloud

- **User Account Credentials**

    By default, Prowler uses **User Account credentials**. Configure accounts using:

    - `gcloud init` – Set up a new account.
    - `gcloud config set account <account>` – Switch to an existing account.

    Once configured, obtain access credentials using: `gcloud auth application-default login`.

- **Service Account Authentication**

    Alternatively, you can use Service Account credentials:

    Generate and download Service Account keys in JSON format. Refer to [Google IAM documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) for details.

    Provide the key file location using this argument:

    ```console
    prowler gcp --credentials-file path
    ```

- **Scanning Specific GCP Projects**

    By default, Prowler scans all accessible GCP projects. Scan specific projects with the `--project-ids` flag:

    ```console
    prowler gcp --project-ids <Project ID 1> <Project ID 2> ... <Project ID N>
    ```

- **GCP Retry Configuration**

    Configure the maximum number of retry attempts for Google Cloud SDK API calls with the `--gcp-retries-max-attempts` flag:

    ```console
    prowler gcp --gcp-retries-max-attempts 5
    ```

    This is useful when experiencing quota exceeded errors (HTTP 429) to increase the number of automatic retry attempts.

## Kubernetes

Prowler enables security scanning of Kubernetes clusters, supporting both **in-cluster** and **external** execution.

- **Non In-Cluster Execution**

    ```console
    prowler kubernetes --kubeconfig-file path
    ```
    <Note>
        If no `--kubeconfig-file` is provided, Prowler will use the default KubeConfig file location (`~/.kube/config`).

    </Note>
- **In-Cluster Execution**

    To run Prowler inside the cluster, apply the provided YAML configuration to deploy a job in a new namespace:

    ```console
    kubectl apply -f kubernetes/prowler-sa.yaml
    kubectl apply -f kubernetes/job.yaml
    kubectl apply -f kubernetes/prowler-role.yaml
    kubectl apply -f kubernetes/prowler-rolebinding.yaml
    kubectl get pods --namespace prowler-ns --> prowler-XXXXX
    kubectl logs prowler-XXXXX --namespace prowler-ns
    ```

    <Note>
        By default, Prowler scans all namespaces in the active Kubernetes context. Use the `--context` flag to specify the context to be scanned and `--namespaces` to restrict scanning to specific namespaces.

    </Note>
## Microsoft 365

Microsoft 365 requires specifying the auth method:

```console

# To use service principal authentication for MSGraph and PowerShell modules
prowler m365 --sp-env-auth

# To use az cli authentication
prowler m365 --az-cli-auth

# To use browser authentication
prowler m365 --browser-auth --tenant-id "XXXXXXXX"

```

See more details about M365 Authentication in the [Authentication Section](/user-guide/providers/microsoft365/authentication) section.

## GitHub

Prowler enables security scanning of your **GitHub account**, including **Repositories**, **Organizations** and **Applications**.

- **Supported Authentication Methods**

    Authenticate using one of the following methods:

    ```console
    # Personal Access Token (PAT):
    prowler github --personal-access-token pat

    # OAuth App Token:
    prowler github --oauth-app-token oauth_token

    # GitHub App Credentials:
    prowler github --github-app-id app_id --github-app-key app_key
    ```

    <Note>
        If no login method is explicitly provided, Prowler will automatically attempt to authenticate using environment variables in the following order of precedence:

        1. `GITHUB_PERSONAL_ACCESS_TOKEN`
        2. `OAUTH_APP_TOKEN`
        3. `GITHUB_APP_ID` and `GITHUB_APP_KEY`

    </Note>
## Infrastructure as Code (IaC)

Prowler's Infrastructure as Code (IaC) provider enables you to scan 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 you to assess your code before deployment.

```console
# Scan a directory for IaC files
prowler iac --scan-path ./my-iac-directory

# Scan a remote GitHub repository (public or private)
prowler iac --scan-repository-url https://github.com/user/repo.git

# Authenticate to a private repo with GitHub username and PAT
prowler iac --scan-repository-url https://github.com/user/repo.git \
  --github-username <username> --personal-access-token <token>

# Authenticate to a private repo with OAuth App Token
prowler iac --scan-repository-url https://github.com/user/repo.git \
  --oauth-app-token <oauth_token>

# Specify frameworks to scan (default: all)
prowler iac --scan-path ./my-iac-directory --frameworks terraform kubernetes

# Exclude specific paths
prowler iac --scan-path ./my-iac-directory --exclude-path ./my-iac-directory/test,./my-iac-directory/examples
```

<Note>
- `--scan-path` and `--scan-repository-url` are mutually exclusive; only one can be specified at a time.
- For remote repository scans, authentication can be provided via CLI flags or environment variables (`GITHUB_OAUTH_APP_TOKEN`, `GITHUB_USERNAME`, `GITHUB_PERSONAL_ACCESS_TOKEN`). CLI flags take precedence.
- The IaC provider does not require cloud authentication for local scans.
- It is ideal for CI/CD pipelines and local development environments.
- For more details on supported scanners, see the [Trivy documentation](https://trivy.dev/latest/docs/scanner/vulnerability/)

</Note>
See more details about IaC scanning in the [IaC Tutorial](/user-guide/providers/iac/getting-started-iac) section.

## MongoDB Atlas

Prowler allows you to scan your MongoDB Atlas cloud database deployments for security and compliance issues.

Authentication is done using MongoDB Atlas API key pairs:

```console
# Using command-line arguments
prowler mongodbatlas --atlas-public-key <public_key> --atlas-private-key <private_key>

# Using environment variables
export ATLAS_PUBLIC_KEY=<public_key>
export ATLAS_PRIVATE_KEY=<private_key>
prowler mongodbatlas
```

You can filter scans to specific organizations or projects:

```console
# Scan specific project
prowler mongodbatlas --atlas-project-id <project_id>
```

See more details about MongoDB Atlas Authentication in [MongoDB Atlas Authentication](/user-guide/providers/mongodbatlas/authentication)

## Oracle Cloud

Prowler allows you to scan your Oracle Cloud deployments for security and compliance issues.

You have two options to authenticate:

1. OCI Config File Authentication: this config file can be generated using the OCI CLI with the `oci session authenticate` command or created manually using the OCI Console. For more details, see the [OCI Authentication Guide](/user-guide/providers/oci/authentication#oci-session-authentication).

    ```console
    prowler oci
    ```

    You can add different profiles to the config file to scan different tenancies or regions. In order to scan a specific profile, use the `--profile` flag:

    ```console
    prowler oci --profile <profile_name>
    ```

2. Instance Principal Authentication: when running Prowler on an OCI Compute instance, you can use Instance Principal authentication. For more details, see the [OCI Authentication Guide](/user-guide/providers/oci/authentication#instance-principal-authentication).

    ```console
    prowler oci --use-instance-principal
    ```

See more details about Oracle Cloud Authentication in [Oracle Cloud Authentication](/user-guide/providers/oci/authentication)

---

### Getting Started Basic Usage Prowler Mcp Tools (`docs/getting-started/basic-usage/prowler-mcp-tools.mdx`)

---
title: "Tools Reference"
---

Complete reference guide for all tools available in the Prowler MCP Server. Tools are organized by namespace.

## Tool Categories Summary

| Category | Tool Count | Authentication Required | Availability |
|----------|------------|------------------------|--------------|
| Prowler Hub | 10 tools | No | Cloud and Local MCP Server |
| Prowler Documentation | 2 tools | No | Cloud and Local MCP Server |
| Prowler Cloud, Private Cloud & Local Server | 49 tools | Yes | Cloud and Local MCP Server |
| Prowler Cloud management | 40 tools | Yes | Cloud MCP Server only |

<Note>
48 of the 49 Prowler tools are available on both servers. `prowler_schedule_daily_scan` is the exception: it is Local-only, because the Cloud MCP Server supersedes it with the `prowler_cloud_*` [Scan Scheduling](#scan-scheduling) tools. `prowler_send_findings_to_jira` is exposed by both servers but accepts two [extra parameters](#jira-operations) on the Cloud MCP Server.
</Note>

## Tool Naming Convention

All tools follow a consistent naming pattern with prefixes:

- `prowler_hub_*` - Prowler Hub catalog and compliance tools
- `prowler_docs_*` - Prowler documentation search and retrieval
- `prowler_*` - Prowler Cloud, Prowler Private Cloud & Prowler Local Server management tools
- `prowler_cloud_*` - Prowler Cloud-only management tools

<Note>
`prowler_cloud_*` tools are exposed only by the [Cloud MCP Server](/getting-started/products/prowler-mcp#cloud-vs-local-mcp-server) at `https://mcp.prowler.com/mcp`, because they manage features that exist only in Prowler Cloud. Every other tool is available on both the Cloud and Local MCP Server.
</Note>

## Prowler Tools

Manage your Prowler deployment — Prowler Cloud, Prowler Private Cloud, or Prowler Local Server. **Requires authentication.**

<Note>
These tools require a valid API key. See the [Configuration Guide](/getting-started/basic-usage/prowler-mcp) for authentication setup.
</Note>

### Findings Management

Tools for searching, viewing, and analyzing security findings across all cloud providers.

- **`prowler_search_security_findings`** - Search and filter security findings with advanced filtering options (severity, status, provider, region, service, check ID, date range, muted status)
- **`prowler_get_finding_details`** - Get comprehensive details about a specific finding including remediation guidance, check metadata, and resource relationships
- **`prowler_get_findings_overview`** - Get aggregate statistics and trends about security findings as a markdown report

### Finding Groups Management

Tools for listing finding groups aggregated by check ID, viewing complete group counters, and drilling down into affected resources.

- **`prowler_list_finding_groups`** - List latest or historical finding groups with filters for provider, region, service, resource, category, check, severity, status, muted state, delta, date range, and sorting
- **`prowler_get_finding_group_details`** - Get complete details for a specific finding group including counters, description, timestamps, and impacted providers
- **`prowler_list_finding_group_resources`** - List actionable unmuted resources affected by a finding group by default, including nested resource and provider data plus the `finding_id` for remediation details. Set `include_muted` to include suppressed resources

### Provider Management

Tools for managing cloud provider connections in Prowler.

- **`prowler_search_providers`** - Search and view configured providers with their connection status
- **`prowler_connect_provider`** - Register and connect a provider with credentials for security scanning
- **`prowler_delete_provider`** - Permanently remove a provider from Prowler

### Scan Management

Tools for managing and monitoring security scans.

- **`prowler_list_scans`** - List and filter security scans across all providers
- **`prowler_get_scan`** - Get comprehensive details about a specific scan (progress, duration, resource counts)
- **`prowler_trigger_scan`** - Trigger a manual security scan for a provider
- **`prowler_schedule_daily_scan`** - Schedule automated daily scans for continuous monitoring (**Local MCP Server only**)
- **`prowler_update_scan`** - Update scan name for better organization

<Note>
`prowler_schedule_daily_scan` is the scheduling tool for a self-hosted deployment, and it only does one thing: a daily scan. The Cloud MCP Server does not expose it — Prowler Cloud replaces it with the richer [Scan Scheduling](#scan-scheduling) tools, which add interval, weekly, and monthly frequencies, per-provider schedule retrieval, and bulk apply across providers.
</Note>

### Resources Management

Tools for searching, viewing, and analyzing cloud resources discovered by Prowler.

- **`prowler_list_resources`** - List and filter cloud resources with advanced filtering options (provider, region, service, resource type, tags)
- **`prowler_get_resource`** - Get comprehensive details about a specific resource including configuration, metadata, and finding relationships
- **`prowler_get_resource_events`** - Get the timeline of cloud API actions performed on a resource (AWS CloudTrail). Shows who did what and when, with full request/response payloads
- **`prowler_get_resources_overview`** - Get aggregate statistics about cloud resources as a markdown report

### Muting Management

Tools for managing finding muting, including pattern-based bulk muting (mutelist) and finding-specific mute rules.

#### Mutelist (Pattern-Based Muting)

- **`prowler_get_mutelist`** - Retrieve the current mutelist configuration for the tenant
- **`prowler_set_mutelist`** - Create or update the mutelist configuration for pattern-based bulk muting
- **`prowler_delete_mutelist`** - Remove the mutelist configuration from the tenant

#### Mute Rules (Finding-Specific Muting)

- **`prowler_list_mute_rules`** - Search and filter mute rules with pagination support
- **`prowler_get_mute_rule`** - Retrieve comprehensive details about a specific mute rule
- **`prowler_create_mute_rule`** - Create a new mute rule to mute specific findings with documentation and audit trail
- **`prowler_update_mute_rule`** - Update a mute rule's name, reason, or enabled status
- **`prowler_delete_mute_rule`** - Delete a mute rule from the system

### Integrations Management

Tools for managing where Prowler sends its results: Amazon S3 buckets, AWS Security Hub, and Jira. Requires the **Manage Integrations** permission.

#### Integration Lifecycle

- **`prowler_list_integrations`** - List the configured integrations with their enabled and connection state, optionally filtered by integration type
- **`prowler_get_integration`** - Get an integration with its complete, type-specific configuration (bucket and output directory, Security Hub settings and enabled regions, or Jira projects and issue types)
- **`prowler_update_integration`** - Update credentials, configuration, attached providers, or enabled state. Configuration changes are merged with the current one, and the connection is re-checked automatically whenever credentials, configuration, or attached providers change
- **`prowler_delete_integration`** - Permanently remove an integration and its stored credentials
- **`prowler_test_integration_connection`** - Check an integration connection and refresh the configuration Prowler discovers from the remote system (Jira projects, Security Hub regions)

#### Integration Setup

- **`prowler_create_amazon_s3_integration`** - Export scan outputs (CSV, HTML, OCSF JSON, compliance reports) to an S3 bucket, using an IAM role or static credentials
- **`prowler_create_aws_security_hub_integration`** - Send findings to AWS Security Hub in ASFF format for a single AWS provider, reusing the provider credentials or dedicated ones
- **`prowler_create_jira_integration`** - Connect an Atlassian Jira site so findings can be turned into work items. Tenant-wide, not attached to any provider

#### Jira Operations

- **`prowler_get_jira_issue_types`** - List the issue types available in a Jira project, fetched live from Jira
- **`prowler_send_findings_to_jira`** - Create Jira work items from findings, each carrying the check title, severity, status, provider, region, resource, risk, and remediation steps. Select the findings either by ID with `finding_ids`, or — on Prowler Cloud only — by check with `check_ids`, and choose between one work item per finding or one per check with `dispatch_mode`

<Note>
`check_ids` and `dispatch_mode` are **Prowler Cloud only**:

- **`check_ids`** - Send the failing findings of a check (for example `s3_bucket_public_access`) without listing their IDs. Prowler resolves them server-side, taking only the failed findings of the latest completed scan of every provider. Get the check IDs from `prowler_list_finding_groups`. Exactly one of `finding_ids` or `check_ids` is required — Prowler combines both filters, so sending both would only dispatch their intersection. A Local MCP Server rejects `check_ids` with a client error.
- **`dispatch_mode`** - `individual` (the default) creates one work item per finding. `grouped` creates one work item per check instead, listing up to 50 affected resources and linking back to the finding group in Prowler Cloud, which keeps a noisy check to a single ticket. Grouped dispatch only covers failed, unmuted findings of the latest completed scan of every provider. A Local MCP Server ignores `dispatch_mode` instead of rejecting it, and creates one work item per finding.

In `grouped` mode the response counters change meaning: `created_count` counts work items (one per check) rather than findings, `failed_count` counts the entries of the new `failed_groups` field, and `failed_groups` details each failure with its reason and the `check_id` whose work item could not be created.
</Note>

<Warning>
`prowler_send_findings_to_jira` creates real work items that Prowler cannot delete or update afterwards. Only retry the same dispatch when the previous response returned `safe_to_retry: true`, otherwise the work items already created are duplicated. Combining `check_ids` with the default `individual` mode opens one work item per failing resource, which can be hundreds of them — use `dispatch_mode="grouped"` to keep it to one per check.
</Warning>

### Attack Paths Analysis

Tools for analyzing privilege escalation chains and security misconfigurations using graph-based analysis. Attack Paths maps relationships between cloud resources, permissions, and security findings to detect how privileges can be escalated and how misconfigurations can be exploited.

- **`prowler_list_attack_paths_scans`** - List Attack Paths scans with filtering by provider, provider type, and scan state (available, scheduled, executing, completed, failed, cancelled)
- **`prowler_list_attack_paths_queries`** - Discover available Attack Paths queries for a completed scan, including query names, descriptions, and required parameters
- **`prowler_run_attack_paths_query`** - Execute an Attack Paths query against a completed scan and retrieve graph results with nodes (cloud resources, findings, virtual nodes) and relationships (access paths, role assumptions, security group memberships)
- **`prowler_get_attack_paths_cartography_schema`** - Retrieve the Cartography graph schema (node labels, relationships, properties) for writing accurate custom openCypher queries

### Compliance Management

Tools for viewing compliance status and framework details across all cloud providers.

- **`prowler_get_compliance_overview`** - Get high-level compliance status across all frameworks for a specific scan or provider, including pass/fail statistics per framework
- **`prowler_get_compliance_framework_state_details`** - Get detailed requirement-level breakdown for a specific compliance framework, including failed requirements and associated finding IDs

### User Management

Tools for viewing the users in your tenant and identifying the authenticated user.

- **`prowler_list_users`** - List the users in the tenant with their names and emails
- **`prowler_get_user`** - Get detailed information about a specific user by ID, including join date and role/membership IDs
- **`prowler_get_current_user`** - Identify which user the current credentials authenticate as

### Role Management

Tools for browsing RBAC roles and managing the role assigned to a user. A user holds exactly one role, so setting a role replaces the one they held before.

- **`prowler_list_roles`** - List the roles defined in the tenant with their permission scope
- **`prowler_get_role`** - Get detailed information about a specific role by ID, including granted capabilities, visibility scope, assigned users, and provider groups
- **`prowler_get_user_roles`** - List the roles assigned to a specific user, with the capabilities each role grants
- **`prowler_set_user_role`** - Set the role a user holds, replacing the role they had before (idempotent)

## Prowler Cloud Tools

Manage Prowler Cloud-only features and configuration. **Requires authentication.**

<Note>
These tools are available **only on the Cloud MCP Server** (`https://mcp.prowler.com/mcp`). A Local MCP Server does not expose them, because the features they manage exist only in Prowler Cloud.
</Note>

### Organizations

Tools for onboarding a cloud provider organization as a whole — an AWS Organization, an Azure tenant with its management groups, or a GCP organization with its folders. An organization holds org-level credentials, discovers the real account, subscription, or project structure in the cloud, and turns a selection from that discovery into Prowler providers linked into a hierarchy of nodes. Every tool that changes something — creating, updating, deleting, discovering, applying a discovery, or adjusting provider membership — requires the **Manage Providers** permission; listing and reading do not.

<Note>
Use these tools for the whole organization. To register providers one by one, use the [Provider Management](#provider-management) tools instead; to build arbitrary RBAC buckets of providers, use provider groups.
</Note>

- **`prowler_cloud_list_organizations`** - Browse the registered organizations with lightweight data (name, type, external id, provider and node counts), filtered by type or cloud-side external id
- **`prowler_cloud_get_organization`** - Get one organization in full: attributes, linked providers, credentials status, latest discovery, and the OU / management group / folder hierarchy. Set `include_hierarchy` to `false` to skip the tree on large organizations
- **`prowler_cloud_create_organization`** - Register an organization, optionally storing its org-level credentials in the same call. Idempotent: an organization with the same type and external id is reused and its credentials rotated, reported as `created: false`
- **`prowler_cloud_update_organization`** - Rename an organization, replace its metadata, and/or create or rotate its org-level credentials. `org_type` and `external_id` are immutable after creation
- **`prowler_cloud_delete_organization`** - Delete an organization, its entire hierarchy, and every linked provider
- **`prowler_cloud_discover_organization`** - Enumerate the real cloud structure: AWS accounts and OUs, Azure subscriptions and management groups, or GCP projects and folders. Each item comes back with its registration state so you can choose what to onboard
- **`prowler_cloud_apply_organization_discovery`** - Turn a discovery selection into Prowler providers and hierarchy nodes
- **`prowler_cloud_manage_organization_providers`** - Manually `add`, `replace`, or `remove` the providers linked to an organization or to one of its hierarchy nodes. Providers are detached, never deleted

### Scan Configurations

Tools for managing reusable scan configurations — per-provider check and compliance selections — and attaching them to providers. Providers without a configuration attached use the default.

- **`prowler_cloud_list_scan_configurations`** - List and filter the scan configurations defined in the tenant
- **`prowler_cloud_get_scan_configuration`** - Retrieve a scan configuration including its full configuration body
- **`prowler_cloud_get_scan_configuration_schema`** - Fetch the JSON Schema describing the keys a valid configuration body may set, optionally filtered to a single provider type
- **`prowler_cloud_create_scan_configuration`** - Create a scan configuration and optionally attach it to providers
- **`prowler_cloud_update_scan_configuration`** - Update a configuration's name, body, and/or attached providers
- **`prowler_cloud_delete_scan_configuration`** - Delete a scan configuration; attached providers revert to the default

### Findings Triage

Tools for recording a review decision on a finding and documenting the reasoning. Triage is keyed on the stable finding UID returned by `prowler_search_security_findings` and `prowler_get_finding_details`.

<Note>
Triage is distinct from [muting](#muting-management). Use mute rules and the mutelist to **suppress** findings; use triage to **record a decision** and its rationale while the finding stays visible. See the [Findings Triage tutorial](/user-guide/tutorials/prowler-app-findings-triage).
</Note>

- **`prowler_cloud_list_finding_triages`** - List and filter persisted triage records by status, provider, check, and more
- **`prowler_cloud_get_finding_triage`** - Retrieve a single finding's triage state by finding UID
- **`prowler_cloud_set_finding_triage_status`** - Set a finding's triage status (`open`, `under_review`, `remediating`, `risk_accepted`, `false_positive`), optionally attaching a note. The `resolved` and `reopened` statuses are system-managed and cannot be set directly
- **`prowler_cloud_list_finding_triage_notes`** - List the notes attached to a finding's triage, newest first
- **`prowler_cloud_create_finding_triage_note`** - Add a new note to a finding's triage
- **`prowler_cloud_update_finding_triage_note`** - Update the body of an existing note
- **`prowler_cloud_delete_finding_triage_note`** - Delete a note from a finding's triage

### Scan Scheduling

Tools for configuring recurring scans. One schedule exists per provider, with daily, interval, weekly, or monthly frequency. These replace the Local-only `prowler_schedule_daily_scan`, which can only set up a daily scan. See the [Scan Scheduling tutorial](/user-guide/tutorials/prowler-scan-scheduling).

- **`prowler_cloud_list_scan_schedules`** - List scan schedules, one per visible provider
- **`prowler_cloud_get_scan_schedule`** - Retrieve a provider's schedule including all per-frequency fields
- **`prowler_cloud_set_scan_schedule`** - Configure or update a single provider's recurring scan schedule
- **`prowler_cloud_bulk_set_scan_schedules`** - Apply one schedule to many providers at once
- **`prowler_cloud_delete_scan_schedule`** - Delete a provider's scan schedule

### Alerts

Tools for notifying recipients when scan results match a rule condition. See the [Alerts tutorial](/user-guide/tutorials/prowler-alerts).

#### Alert Rules

- **`prowler_cloud_list_alert_rules`** - List and filter the custom alert rules defined in the tenant
- **`prowler_cloud_get_alert_rule`** - Retrieve an alert rule including its condition DSL and recipient emails
- **`prowler_cloud_create_alert_rule`** - Create a tenant-scoped alert rule
- **`prowler_cloud_update_alert_rule`** - Update an alert rule; only the fields provided change
- **`prowler_cloud_delete_alert_rule`** - Delete an alert rule
- **`prowler_cloud_list_alert_rule_events`** - List the fired-alert history for a single rule, newest first
- **`prowler_cloud_build_alert_rule_condition`** - Build a condition from a findings filter and dry-run it in one call to preview what would match. Nothing is persisted

#### Alert Recipients

- **`prowler_cloud_list_alert_recipients`** - List alert recipients with their confirmation status
- **`prowler_cloud_get_alert_recipient`** - Retrieve a single recipient with its confirmation status
- **`prowler_cloud_create_alert_recipient`** - Register a new recipient email
- **`prowler_cloud_resend_alert_recipient_confirmation`** - Re-send the confirmation email to a pending or unsubscribed recipient
- **`prowler_cloud_delete_alert_recipient`** - Delete an alert recipient

#### Alert Events

- **`prowler_cloud_list_alert_events`** - List the fired alert events for the tenant
- **`prowler_cloud_get_alert_event`** - Retrieve a single alert event including its matched rule and scan

## Prowler Hub Tools

Access Prowler's security check catalog and compliance frameworks. **No authentication required.**

Tools follow a **two-tier pattern**: lightweight listing for browsing + detailed retrieval for complete information.

### Check Discovery and Details

- **`prowler_hub_list_checks`** - List security checks with lightweight data (id, title, severity, provider) and advanced filtering options
- **`prowler_hub_semantic_search_checks`** - Full-text search across check metadata with lightweight results
- **`prowler_hub_get_check_details`** - Get comprehensive details for a specific check including risk, remediation guidance, and compliance mappings

### Check Code

- **`prowler_hub_get_check_code`** - Fetch the Python implementation code for a security check
- **`prowler_hub_get_check_fixer`** - Fetch the automated fixer code for a check (if available)

### Compliance Frameworks

- **`prowler_hub_list_compliances`** - List compliance frameworks with lightweight data (id, name, provider) and filtering options
- **`prowler_hub_semantic_search_compliances`** - Full-text search across compliance frameworks with lightweight results
- **`prowler_hub_get_compliance_details`** - Get comprehensive compliance details including requirements and mapped checks

### Providers Information

- **`prowler_hub_list_providers`** - List Prowler official providers
- **`prowler_hub_get_provider_services`** - Get available services for a specific provider

## Prowler Documentation Tools

Search and access official Prowler documentation. **No authentication required.**

- **`prowler_docs_search`** - Search the official Prowler documentation using full-text search with the `term` parameter
- **`prowler_docs_get_document`** - Retrieve the full markdown content of a specific documentation file using the path from search results

## Usage Tips

- Use natural language to interact with the tools through your AI assistant
- Tools can be combined for complex workflows
- Filter options are available on most list tools
- Authentication is only required for the `prowler_*` and `prowler_cloud_*` tools; Prowler Hub and Prowler Documentation tools work without a key
- If a `prowler_cloud_*` tool is missing from your client, you are connected to a Local MCP Server — point it at `https://mcp.prowler.com/mcp` instead

## Additional Resources

- [MCP Protocol Specification](https://modelcontextprotocol.io)
- [Prowler API Documentation](https://api.prowler.com/api/v1/docs)
- [Prowler Hub API](https://hub.prowler.com/api/docs)
- [GitHub Repository](https://github.com/prowler-cloud/prowler)

---

### Getting Started Basic Usage Prowler Mcp (`docs/getting-started/basic-usage/prowler-mcp.mdx`)

---
title: "Configuration"
---

Configure your MCP client to connect to Prowler MCP Server.

## Step 1: Get Your API Key

<Note>
**Authentication is optional**: Prowler Hub and Prowler Documentation features work without authentication. An API key is only required for Prowler tools (Prowler Cloud, Prowler Private Cloud, or Prowler Local Server).
</Note>

An API key authenticates the Prowler tools (Prowler Cloud, Prowler Private Cloud, or Prowler Local Server). To get the API key, please refer to the [API Keys](/user-guide/tutorials/prowler-app-api-keys) guide.

<Warning>
Keep the API key secure. Never share it publicly or commit it to version control.
</Warning>

## Step 2: Configure Your MCP Host/Client

Most users should use the **Cloud MCP Server** — it needs no installation and is maintained by Prowler. The [Local MCP Server](#local-mcp-server-configuration) configuration is provided afterwards for users who run the server themselves.

- **Cloud MCP Server (HTTP)**: the managed server at `https://mcp.prowler.com/mcp` (or your own self-hosted HTTP server).
- **Local MCP Server (STDIO)**: local installation only (runs as a subprocess of your MCP client).

### Step-by-Step Guides Per Agent

The tabs below are a quick configuration reference. For a walkthrough with screenshots, troubleshooting, and client-specific caveats, follow the dedicated guide for your agent:

<CardGroup cols={2}>
  <Card title="Claude Code" icon="terminal" href="/user-guide/ai-agents/claude-code">
    Plugin vs. MCP-only, and which Claude surfaces work
  </Card>
  <Card title="Claude Desktop App (Chat)" icon="comment" href="/user-guide/ai-agents/claude-desktop">
    The Chat tab, via a local bridge
  </Card>
  <Card title="Codex" icon="code" href="/user-guide/ai-agents/codex">
    CLI and the VS Code extension
  </Card>
  <Card title="Cursor" icon="arrow-pointer" href="/user-guide/ai-agents/cursor">
    Global and project scopes
  </Card>
  <Card title="VS Code / Copilot" icon="microsoft" href="/user-guide/ai-agents/vscode">
    Agent mode with secure key prompts
  </Card>
</CardGroup>

## Cloud MCP Server Configuration (Recommended)

Connect to the **Cloud MCP Server** at `https://mcp.prowler.com/mcp` over HTTP. This is the recommended path — no installation, always up to date. The same configuration works for a self-hosted HTTP server: just swap the URL.

<Tabs>
  <Tab title="Generic Native HTTP Support">
    **Configuration:**
    ```json
    {
      "mcpServers": {
        "prowler": {
          "url": "https://mcp.prowler.com/mcp", // or your self-hosted Prowler MCP Server URL
          "headers": {
            "Authorization": "Bearer <your-api-key-here>"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Generic without Native HTTP Support">
    **Configuration:**
    <Warning>
    Avoid configuring MCP clients to run `npx mcp-remote` directly. `npx` can download and execute a new package version on each run. Install a reviewed version of `mcp-remote` in a dedicated local workspace, then point the MCP client to the installed binary.
    </Warning>
    ```bash
    mkdir -p ~/.local/share/prowler-mcp-bridge
    cd ~/.local/share/prowler-mcp-bridge
    npm init -y
    npm install --save-exact mcp-remote@0.1.38
    ```
    ```json
    {
      "mcpServers": {
        "prowler": {
          "command": "/absolute/path/to/.local/share/prowler-mcp-bridge/node_modules/.bin/mcp-remote",
          "args": [
            "https://mcp.prowler.com/mcp", // or your self-hosted Prowler MCP Server URL
            "--header",
            "Authorization: Bearer ${PROWLER_API_KEY}"
          ],
          "env": {
            "PROWLER_API_KEY": "<your-api-key-here>"
          }
        }
      }
    }
    ```
    <Info>
    The `mcp-remote` tool acts as a bridge for clients that don't support HTTP natively. Learn more at [mcp-remote on npm](https://www.npmjs.com/package/mcp-remote).
    </Info>
  </Tab>
</Tabs>

## Local MCP Server Configuration

STDIO mode is only available when running the **Local MCP Server** on your own machine. See the [Installation guide](/getting-started/installation/prowler-mcp) to set it up first.

<Tabs>
  <Tab title="Generic uvx installation">
    **Run from source or local installation**

    ```json
    {
      "mcpServers": {
        "prowler": {
          "command": "uvx",
          "args": ["/absolute/path/to/prowler/mcp_server/"],
          "env": {
            "PROWLER_API_KEY": "<your-api-key-here>",
            "API_BASE_URL": "https://api.prowler.com/api/v1"
          }
        }
      }
    }
    ```

    <Note>
    Replace `/absolute/path/to/prowler/mcp_server/` with the actual path. The `API_BASE_URL` is optional and defaults to Prowler Cloud API.
    </Note>

  </Tab>

  <Tab title="Generic Docker installation">
    **Run with Docker image**

    ```json
    {
      "mcpServers": {
        "prowler": {
          "command": "docker",
          "args": [
            "run",
            "--rm",
            "-i",
            "--env",
            "PROWLER_API_KEY=<your-api-key-here>",
            "--env",
            "API_BASE_URL=https://api.prowler.com/api/v1",
            "prowlercloud/prowler-mcp"
          ]
        }
      }
    }
    ```

    <Note>
    The `API_BASE_URL` is optional and defaults to Prowler Cloud API.
    </Note>

  </Tab>
</Tabs>

## Step 3: Start Using Prowler MCP

Restart your MCP client and start asking questions:
- *"Show me all critical findings from my AWS accounts"*
- *"What does the S3 bucket public access check do?"*
- *"Onboard this new AWS account in my Prowler Organization"*

## Authentication Methods

Prowler MCP Server supports two authentication methods to connect to Prowler (Prowler Cloud, Prowler Private Cloud, or Prowler Local Server):

### API Key (Recommended)

Use your Prowler API key directly in the Bearer token:

```
Authorization: Bearer <your-api-key-here>
```

This is the recommended method for most users.

### JWT Token

Alternatively, obtain a JWT token from Prowler:

```bash
curl -X POST https://api.prowler.com/api/v1/tokens \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "tokens",
      "attributes": {
        "email": "your-email@example.com",
        "password": "your-password"
      }
    }
  }'
```

Use the returned JWT token in place of the API key:

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

<Warning>
JWT tokens are only valid for 30 minutes. You need to generate a new token if you want to continue using the MCP server.
</Warning>

## Troubleshooting

### Server Not Detected

- Restart your MCP client after configuration changes
- Check the configuration file syntax (valid JSON)
- Review client logs for specific error messages
- Verify the server URL is correct

### Authentication Failures

**Error: Unauthorized (401)**
- Verify your API key is correct
- Ensure the key hasn't expired
- Check you're using the right API endpoint

### Connection Issues

**Cannot Reach Server:**
- Verify the server URL is correct
- Check network connectivity
- For local servers, ensure the server is running
- Check firewall settings

## Security Best Practices

1. **Protect Your API Key**
   - Never commit API keys to version control.
   - Use environment variables or secure vaults.
   - Rotate keys regularly.

2. **Network Security**
   - Use HTTPS for production deployments.
   - Restrict network access to the MCP server.
   - Consider VPN for remote access.

3. **Least Privilege**
   - API key gives the permission of the user who created the key, make sure to use the key with the minimal required permissions.
   - Review the tools that are gonna be used and how they are gonna be used to avoid prompt injections or unintended behavior.

## Next Steps

Now that your MCP server is configured:

<CardGroup cols={1}>
  <Card title="Tools Reference" icon="wrench" href="/getting-started/basic-usage/prowler-mcp-tools">
    Explore all available tools
  </Card>
</CardGroup>

## Getting Help

Need assistance with configuration?

- Search for existing [GitHub issues](https://github.com/prowler-cloud/prowler/issues)
- Ask for help in our [Slack community](https://goto.prowler.com/slack)
- Report a new issue on [GitHub](https://github.com/prowler-cloud/prowler/issues/new)

---

### Getting Started Comparison Awssecurityhub (`docs/getting-started/comparison/awssecurityhub.mdx`)

---
title: 'AWS Security Hub'
---

AWS Security Hub remains a managed service designed for centralizing security alerts and compliance status within AWS environments. It integrates with various AWS security services and provides a consolidated view of security findings.

## Key Features and Strengths

- **Centralized Dashboard for AWS:** Provides a single pane of glass to monitor and manage security findings from multiple AWS services like GuardDuty, Inspector, and Config.

- **Compliance Checks:** Automatically checks for compliance against standards like CIS and PCI DSS within AWS environments.

- **AWS Native Automation:** Offers seamless automation for incident response using AWS Lambda and CloudWatch Events, reducing the time to react to security issues.

- **User-Friendly Interface:** Accessible via the AWS Management Console, offering a streamlined experience for managing security across AWS accounts.

## Limitations

- **AWS-Centric:** Limited to AWS environments, with no direct support for multi-cloud or hybrid environments.

- **Dependency on AWS Config:** Some of its checks depend on AWS Config, which may not be enabled in all regions or accounts.

- **Vendor Lock-In:** Tightly coupled with AWS, making it less suitable for organizations with a cloud-agnostic strategy.

## Prowler

Prowler is an open-source, multi-cloud security tool that offers extensive customization and flexibility, making it ideal for organizations with complex or multi-cloud environments. Here are the updated features and advantages:

## Main Advantages of Prowler

- **Multi-Region and Multi-Account Scanning by Default:**
  - Prowler is inherently multi-region and can scan multiple AWS accounts without requiring additional configuration or enabling specific services like AWS Config.

- **Minimal Setup Requirements:**
  - All Prowler needs is a role with appropriate permissions to start scanning. There’s no need to enable specific services or configure complex setups.

- **Versatile Execution Environment:**
  - Prowler can be run from various environments, including a local workstation, container, AWS CloudShell, or even from another AWS account or cloud provider by assuming a role. This flexibility makes it easy to integrate into different operational workflows.

- **Flexible Results Storage and Sharing:**
  - Prowler results can be stored directly into an S3 bucket, allowing for quick analysis, or locally for easy sharing and discussion. This flexibility is particularly useful for collaborative security assessments.

- **Customizable Reporting and Analysis:**
  - Prowler supports exporting results in multiple formats, including JSON, CSV, OCSF format, and static HTML reports. It also supports integration with Amazon QuickSight for in-depth analysis and offers a SaaS model with resource-based pricing, making it adaptable to different organizational needs.

- **Security Hub Integration for Cost-Effective Operations:**
  - Prowler can send results directly into Security Hub in any AWS account, including only failed findings. This selective reporting can make Security Hub more cost-effective by reducing the volume of data processed.

- **Custom Checks and Compliance Frameworks:**
  - Users can write custom checks, remediations, and compliance frameworks in minutes, tailoring the tool to their specific security policies and operational needs.

- **Extensive Compliance Support:**
  - Prowler supports over 27 compliance frameworks out of the box for AWS, providing comprehensive coverage across various regulatory requirements and best practices.

- **Kubernetes and Multi-Cloud Support:**
  - Prowler extends its scanning capabilities beyond AWS, offering support for Kubernetes clusters (including EKS), as well as environments in Google Cloud Platform (GCP) and Azure. This multi-cloud capability is essential for organizations with diverse cloud footprints.

- **All-Region Checks:**
  - Prowler runs all checks in all regions, regardless of AWS Config resource type support, ensuring comprehensive coverage across your entire AWS environment.

## Comparison Summary

### Scope and Environment

- **Security Hub** is ideal for AWS-centric environments needing a managed service for monitoring and automating security across AWS resources.
- **Prowler** is better suited for organizations operating in multi-cloud or hybrid environments, offering flexibility, customization, and support for multiple cloud providers including AWS, Azure, GCP, and Kubernetes.

### Setup and Maintenance

- **Security Hub** requires enabling and configuring AWS services by region, per account, and can become more than one person's full-time role – including Config. Security Hub operates only within the AWS ecosystem.
- **Prowler** requires minimal setup, only needing appropriate permissions, and can be executed from various environments, making it more versatile in different operational contexts.

### Customization and Flexibility

- **Security Hub** offers predefined compliance checks and automation within AWS but is less flexible in terms of customization.
- **Prowler** allows for highly customizable checks, remediation actions, and compliance frameworks, with the ability to adapt quickly to organizational needs and regulatory changes.

### Cost Efficiency

- **Security Hub** may involve additional costs for processing and storing findings.
- **Prowler** can optimize costs by selectively sending failed findings to Security Hub and storing results locally or in S3, which can be more cost-effective.

### Multi-Cloud and Multi-Region Support

- **Security Hub** is confined to AWS, with region-specific checks depending on AWS Config.
- **Prowler** is inherently multi-region and multi-cloud, offering consistent and comprehensive checks across different cloud environments and regions.

## Conclusion

For a CISO or security professional evaluating these tools, the decision between AWS Security Hub and Prowler will depend on the organization’s cloud strategy, compliance needs, and the level of flexibility required:

- If the organization is heavily invested in AWS and prefers a managed, integrated security service that offers ease of use and automation within the AWS ecosystem, **AWS Security Hub** is the more appropriate choice.

- If the organization operates in a multi-cloud environment or requires a highly customizable tool that can run comprehensive, multi-region scans across AWS, Azure, GCP, and Kubernetes, **Prowler** provides a more powerful and flexible solution, especially for those needing to adapt quickly to evolving security and compliance requirements.

---

### Getting Started Comparison Gcp (`docs/getting-started/comparison/gcp.mdx`)

---
title: 'GCP Cloud Security Command Center (Cloud SCC)'
---

Google Cloud Security Command Center (Cloud SCC) is a centralized security and risk management platform for Google Cloud Platform (GCP). It provides visibility into assets, vulnerabilities, and threats across GCP environments, helping organizations to manage and improve their security posture.

## Key Features and Strengths

- **Centralized Security Visibility:** Cloud SCC provides a single pane of glass to monitor the security and risk status across your GCP resources. It aggregates findings from various GCP security services, such as Security Health Analytics, Web Security Scanner, and Event Threat Detection.

- **Asset Inventory and Classification:** Cloud SCC offers comprehensive asset discovery and classification across GCP, giving security teams a detailed inventory of their cloud resources, including their configurations and security states.

- **Threat Detection and Monitoring:** The platform integrates with GCP’s threat detection tools, such as Google’s Event Threat Detection, which analyzes logs for suspicious activities and potential threats.

- **Compliance Monitoring:** Cloud SCC helps monitor compliance with various regulatory standards by continuously assessing your GCP resources against best practices and security benchmarks.

- **Automated Remediation:** Cloud SCC can trigger automated responses to security findings through integrations with Google Cloud Functions or other orchestration tools, helping to mitigate risks quickly.

- **Native GCP Integration:** Cloud SCC is deeply integrated with the GCP ecosystem, offering seamless operation within Google Cloud environments and leveraging Google's extensive security expertise.

## Limitations

- **GCP-Centric:** While Cloud SCC is powerful within the GCP ecosystem, it is primarily focused on GCP and does not natively extend to multi-cloud environments without additional tools or connectors.

- **Cost Considerations:** As a managed service within GCP, costs can scale with the amount of data ingested and the complexity of the environment, especially as additional features or higher volumes of data are used.

- **Dependency on GCP Services:** Cloud SCC's capabilities depend on other GCP services being enabled, such as Security Health Analytics and Web Security Scanner, which may increase overall complexity and cost.

## Prowler

Prowler is an open-source, multi-cloud security tool designed to perform detailed security assessments and compliance checks across diverse cloud environments, including AWS, Azure, GCP, and Kubernetes. Here are the key advantages of Prowler when compared to GCP Cloud SCC:

## Main Advantages of Prowler

- **Multi-Region and Multi-Account Scanning by Default:**
  - Prowler inherently supports multi-region and multi-account scanning across multiple cloud providers, including GCP, AWS, Azure, and Kubernetes. It does not require additional configuration to perform these scans, making it immediately useful for organizations operating in multiple cloud environments.

- **Minimal Setup Requirements:**
  - Prowler requires only appropriate roles and permissions to start scanning. It doesn’t necessitate enabling specific services within GCP, which can simplify the setup process and reduce dependencies.

- **Versatile Execution Environment:**
  - Prowler can be run from various environments, such as a local workstation, container, Google Cloud Shell, or even other cloud providers by assuming a role. This versatility allows for flexible deployment and integration into existing security operations.

- **Flexible Results Storage and Sharing:**
  - Prowler results can be stored in an S3 bucket for AWS, Google Cloud Storage (GCS) for GCP, or locally, allowing for quick analysis and easy sharing. This flexibility is particularly advantageous for multi-cloud security assessments and collaborative security processes.

- **Customizable Reporting and Analysis:**
  - Prowler supports exporting results in multiple formats, including JSON, CSV, OCSF format, and static HTML reports. These reports can be tailored to specific needs and easily integrated with other security tools or dashboards, providing comprehensive insights across all cloud environments.

- **SIEM Integration and Cost Efficiency:**
  - Prowler can be configured to send findings directly into SIEM systems, including those integrated with GCP or other platforms. By sending only failed findings or selected results, Prowler helps manage costs associated with data ingestion and analysis in SIEM platforms.

- **Custom Checks and Compliance Frameworks:**
  - Prowler allows for the creation of custom security checks, remediation actions, and compliance frameworks, providing flexibility that can be adapted to the unique security policies and regulatory requirements of an organization.

- **Extensive Compliance Support:**
  - Prowler supports over 27 compliance frameworks out of the box, with capabilities to extend these frameworks to GCP environments as well as other cloud platforms. This broad compliance coverage ensures that organizations can maintain adherence to various regulatory requirements.

- **Kubernetes and Multi-Cloud Support:**
  - Prowler is designed to support security assessments across cloud environments, including Kubernetes clusters and GCP. This multi-cloud capability is essential for organizations that operate across diverse cloud platforms and require consistent security posture management.

- **All-Region Checks:**
  - Prowler runs all checks in all regions by default, ensuring comprehensive coverage across an organization’s cloud resources, regardless of the region or cloud provider.

## Comparison Summary

### Scope and Environment

- **GCP Cloud SCC** is ideal for organizations primarily using GCP, offering a centralized platform for managing security and compliance within the GCP ecosystem.
- **Prowler** excels in multi-cloud environments, offering flexibility and comprehensive security checks across AWS, Azure, GCP, and Kubernetes without being confined to a single cloud provider.

### Setup and Flexibility

- **GCP Cloud SCC** requires enabling various GCP services and may involve more complex setup, especially for multi-region or multi-account scenarios within GCP.
- **Prowler** requires minimal setup and can be deployed quickly across different cloud environments, offering a more straightforward approach to multi-cloud security management.

### Customization and Compliance

- **GCP Cloud SCC** provides predefined compliance checks within the GCP environment but may require additional tools or customization for broader or more specific requirements.
- **Prowler** allows for extensive customization of security checks, compliance frameworks, and reporting, providing a flexible solution that can be tailored to an organization’s specific needs across various cloud platforms.

### Cost Efficiency

- **GCP Cloud SCC** costs can scale with the volume of data processed and the number of enabled services, which may be significant in large or complex environments. SCC pricing is confusing to understand, and starts at $0.071 per vCPU hour for some tiers and depending on the scan service. Take a look at the pricing model [here](https://cloud.google.com/security-command-center/pricing), godspeed.
- **Prowler** helps manage costs by allowing selective reporting, such as sending only failed findings to SIEMs, and storing results in cost-effective ways, such as local storage or cloud buckets. Prowler is always $0.001 per resource per day - no per account charge.

### Multi-Cloud and Multi-Region Support

- **GCP Cloud SCC** is focused on GCP and may require additional tools for comprehensive multi-cloud support.
- **Prowler** is inherently multi-cloud, supporting AWS, Azure, GCP, and Kubernetes out of the box, making it an ideal choice for organizations with diverse cloud footprints.

## Conclusion

For a CISO evaluating these tools, the decision between GCP Cloud Security Command Center (Cloud SCC) and Prowler hinges on the organization’s cloud strategy, security management needs, and the level of flexibility and multi-cloud support required:

- If the organization is heavily invested in GCP and needs a centralized platform that integrates seamlessly with GCP services for asset management, threat detection, and compliance monitoring, **GCP Cloud SCC** is likely the better choice.
- If the organization operates in a multi-cloud environment or requires a highly customizable tool for performing detailed security assessments across AWS, Azure, GCP, and Kubernetes, **Prowler** offers a more flexible and cost-effective solution, especially for those needing quick deployment, minimal setup, and the ability to manage security across diverse cloud environments.

---

### Getting Started Comparison Microsoftdefender (`docs/getting-started/comparison/microsoftdefender.mdx`)

---
title: 'Microsoft Defender for Cloud'
---

**Use open-source scanning to validate and extend Microsoft Defender for Cloud**

---

## Overview

If you're using Microsoft Defender for Cloud to monitor your Azure infrastructure, Prowler can complement it with fully transparent, customizable scans across Azure, AWS, GCP, and Kubernetes. Prowler helps you validate policies, automate compliance, and gain deeper visibility—all from the CLI, API or our Prowler UI.

You can run Prowler alongside Defender for Cloud to:

* Double-check security posture with open-source checks.
* Customize rules for your organization’s policies.
* Bring your own, or community contributed policies.
* Automate multi-cloud scans in CI/CD or scheduled jobs.

---

## Why Use Prowler with Defender for Cloud

Microsoft Defender for Cloud offers centralized dashboards, alerting, and some cross-cloud coverage. Prowler provides full transparency and control over what’s being checked and how those checks work—no vendor lock-in, no surprises.

Use them together to get:

* More confidence in your security posture
* Checks you can inspect, modify, and version
* CLI-first, portable scanning across clouds
* Open-source tooling that integrates easily into pipelines and audits

---

## Quickstart

Here’s how to install Prowler and run a scan in your Azure account.

### 1\. Install Prowler

```
git clone https://github.com/prowler-cloud/prowler
cd prowler
./install.sh
```

### 2\. Authenticate with Azure

Make sure you're signed in and select your subscription:

```
az login
export AZURE_SUBSCRIPTION_ID=$(az account show --query id -o tsv)
```

### 3\. Run a Scan

```
./prowler -p Azure -f az-aks -f az-general
```

This will run checks focused on Azure Kubernetes Service (AKS) and general Azure best practices.

### 4\. Review Results

```
cat output/prowler-output-*.json
open output/prowler-output-*.html
```

You can export findings in JSON, CSV, JUnit, HTML, or AWS Security Hub–compatible formats.

---

## Compare Capabilities

| Feature | Microsoft Defender for Cloud | Prowler |
| ----- | ----- | ----- |
| Azure-native posture management | ✅ | ✅ |
| AWS, GCP, and Kubernetes support | ⚠️ (limited) | ✅ |
| Custom policy creation | ❌ | ✅ |
| CLI-first, scriptable | ❌ | ✅ |
| Open source | ❌ | ✅ |
| Compliance mappings (CIS, NIST, etc.) | ✅ (limited control) | ✅ (customizable) |
| Exportable detections | ❌ | ✅ |

---

## Common Use Cases

**✅ Validate policies**
 Run Prowler to confirm your Azure policies are configured as expected and compliant with frameworks like CIS or NIST.

**✅ Automate compliance scans**
 Schedule regular Prowler scans in your CI/CD pipeline or infrastructure monitoring workflows. Generate reports for auditors or internal reviews.

**✅ Extend detection coverage**
 If Defender for Cloud doesn’t cover all the services or resources in your environment, Prowler’s checks fill in the gaps.

**✅ Build custom checks**
 Security is never one-size-fits-all. Prowler lets you write your own checks for organization-specific policies.

---

### Getting Started Comparison Microsoftsentinel (`docs/getting-started/comparison/microsoftsentinel.mdx`)

---
title: 'Microsoft Sentinel'
---

Microsoft Sentinel is a scalable, cloud-native security information and event management (SIEM) and security orchestration automated response (SOAR) solution. It's designed to collect, detect, investigate, and respond to threats across the enterprise, primarily within the Azure cloud environment but also extending to on-premises and other cloud environments through various connectors.

## Key Features and Strengths

- **SIEM and SOAR Capabilities:** Microsoft Sentinel combines SIEM and SOAR functionalities, allowing it to collect and analyze large volumes of data from various sources and automate responses to detected threats.

- **Native Azure Integration:** As part of the Azure ecosystem, Sentinel integrates seamlessly with Azure services, providing deep visibility and analytics for Azure resources.

- **Advanced Threat Detection:** Sentinel uses AI and machine learning to detect potential threats and anomalous activities, leveraging Microsoft's extensive threat intelligence network.

- **Scalability and Flexibility:** Being cloud-native, Sentinel scales automatically to handle increasing data volumes and complexity without requiring extensive infrastructure management.

- **Customizable Dashboards and Analytics:** Sentinel offers customizable dashboards and analytics, allowing security teams to tailor their views and queries to specific needs.

- **Multi-Source Data Ingestion:** While focused on Azure, Sentinel can ingest data from multiple sources, including AWS, GCP, on-premises environments, and third-party security products.

## Limitations

- **Azure-Centric:** While it supports multi-cloud environments, its primary focus and strengths are within the Azure ecosystem. Integration with other cloud platforms and on-premises environments may require additional connectors and configurations.

- **Cost Considerations:** As a SIEM tool, Sentinel can become expensive, particularly as data ingestion and analysis volumes grow. The cost model is based on data volume, which can add up quickly in large environments.

- **Complexity in Customization:** Although Sentinel offers advanced customization, setting up and fine-tuning these customizations can require significant expertise and effort, particularly in multi-cloud environments.

## Prowler

Prowler is an open-source, multi-cloud security tool that offers extensive flexibility and customization, making it ideal for organizations that need to maintain a strong security posture across diverse cloud environments. Here are the key advantages of Prowler, particularly when compared to Microsoft Sentinel:

## Main Advantages of Prowler

- **Multi-Region and Multi-Account Scanning by Default:**
  - Prowler is inherently multi-region and multi-account, requiring no additional configuration to scan across these environments. This capability is available out of the box without needing to enable specific services or create complex setups.

- **Minimal Setup Requirements:**
  - Prowler requires only a role with appropriate permissions to begin scanning. There’s no need for extensive setup, making it easier and quicker to deploy across various environments.

- **Versatile Execution Environment:**
  - Prowler can be run from a local workstation, container, AWS CloudShell, or even from other cloud providers like Azure or GCP by assuming a role. This versatility allows security teams to integrate Prowler into a wide range of operational workflows without being tied to a single cloud environment.

- **Flexible Results Storage and Sharing:**
  - Prowler results can be stored directly into an S3 bucket, allowing for quick analysis or locally for easy sharing and collaboration. This flexibility is particularly useful for multi-cloud security assessments and incident response.

- **Customizable Reporting and Analysis:**
  - Prowler supports exporting results in multiple formats, including JSON, CSV, OCSF format, and static HTML reports. Additionally, it can integrate with Amazon QuickSight for advanced analytics, and offers a SaaS model with resource-based pricing, making it adaptable to various organizational needs.

- **SIEM Integration and Cost Efficiency:**
  - While Microsoft Sentinel has a built-in SIEM functionality, Prowler can send results directly into SIEM systems, including Microsoft Sentinel. By sending only failed findings, Prowler can help optimize costs associated with data ingestion and storage in SIEM platforms.

- **Custom Checks and Compliance Frameworks:**
  - Prowler enables users to write custom checks, remediations, and compliance frameworks quickly, allowing organizations to adapt the tool to their specific security policies and regulatory requirements.

- **Extensive Compliance Support:**
  - Prowler supports over 27 compliance frameworks out of the box, providing comprehensive coverage for AWS environments, which can be extended to multi-cloud scenarios.

- **Kubernetes and Multi-Cloud Support:**
  - Prowler is designed to support security assessments beyond AWS, including Kubernetes clusters (including EKS) and environments in Azure and GCP. This capability is critical for organizations that operate across multiple cloud platforms and require consistent security posture management.

- **All-Region Checks:**
  - Prowler runs all checks in all regions by default, ensuring comprehensive coverage without the limitations that may be imposed by region-specific configurations or services.

## Comparison Summary

### Scope and Environment
- **Microsoft Sentinel** is an advanced SIEM/SOAR tool optimized for Azure environments, with support for multi-cloud and on-premises systems through connectors.
- **Prowler** is a flexible, multi-cloud security tool that excels in environments where organizations need to manage security across AWS, Azure, GCP, and Kubernetes with minimal setup and high customizability.

### Setup and Flexibility
- **Microsoft Sentinel** requires more setup, especially when integrating with non-Azure environments, and its cost scales with data ingestion.
- **Prowler** requires minimal setup and can be easily deployed in any cloud or on-premises environment. Its ability to run from various environments and store results in flexible formats makes it particularly adaptable.

### Customization and Compliance
- **Microsoft Sentinel** offers powerful but complex customization options, primarily within the Azure ecosystem.
- **Prowler** provides straightforward customization of security checks, remediation actions, and compliance frameworks, with broad support for multiple compliance standards out of the box.

### Cost Efficiency
- **Microsoft Sentinel** can become costly as data volumes grow, particularly in large or multi-cloud environments.
- **Prowler** helps control costs by enabling selective reporting (e.g., sending only failed findings to SIEMs like Sentinel) and storing results in cost-effective ways, such as S3 or locally.

### Multi-Cloud and Multi-Region Support
- **Microsoft Sentinel** supports multi-cloud environments but may require additional configuration and connectors.
- **Prowler** is designed for multi-cloud environments from the ground up, with inherent support for AWS, Azure, GCP, Kubernetes, and all regions, making it an ideal tool for organizations with diverse cloud footprints.

## Conclusion

For a CISO or security professional evaluating these tools, the decision between Microsoft Sentinel and Prowler hinges on the organization's cloud strategy, SIEM needs, and the level of customization and flexibility required:

- If the organization is heavily invested in Azure and needs an integrated SIEM/SOAR solution with advanced threat detection, analytics, and automation capabilities, **Microsoft Sentinel** is likely the better choice.

- If the organization operates in a multi-cloud environment or requires a highly customizable tool for performing detailed security assessments across AWS, Azure, GCP, and Kubernetes, **Prowler** offers a more flexible and cost-effective solution, especially for those needing quick deployment, minimal setup, and the ability to manage security across diverse cloud environments.

---

### Getting Started Goto Prowler Api Reference (`docs/getting-started/goto/prowler-api-reference.mdx`)

---
title: "Prowler API Reference"
url: "https://api.prowler.com/api/v1/docs"
---

---

### Getting Started Goto Prowler Mcp (`docs/getting-started/goto/prowler-mcp.mdx`)

---
title: "Prowler MCP"
url: "https://github.com/prowler-cloud/prowler/tree/master/mcp_server"
tag: "new!"
---

---

### Getting Started Installation Prowler App (`docs/getting-started/installation/prowler-app.mdx`)

---
title: "Installation"
---

### 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.

<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.
</Warning>

<Tabs>
  <Tab title="Docker Compose">
    _Requirements_:

    - `Docker Compose` installed: https://docs.docker.com/compose/install/.

    _Commands_:

    <CodeGroup>
    ```bash macOS/Linux
    VERSION=$(curl -s https://api.github.com/repos/prowler-cloud/prowler/releases/latest | jq -r .tag_name)
    curl -sLO "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/${VERSION}/docker-compose.yml"
    # Environment variables can be customized in the .env file. Using default values in production environments is not recommended.
    curl -sLO "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/${VERSION}/.env"
    docker compose up -d
    ```

    ```powershell Windows PowerShell
    $VERSION = (Invoke-RestMethod -Uri "https://api.github.com/repos/prowler-cloud/prowler/releases/latest").tag_name
    Invoke-WebRequest -Uri "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/$VERSION/docker-compose.yml" -OutFile "docker-compose.yml"
    # Environment variables can be customized in the .env file. Using default values in production environments is not recommended.
    Invoke-WebRequest -Uri "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/$VERSION/.env" -OutFile ".env"
    docker compose up -d
    ```
    </CodeGroup>

    <Callout icon="lock" iconType="regular" color="#e74c3c">
      For a secure setup, the API auto-generates a unique key pair, `DJANGO_TOKEN_SIGNING_KEY` and `DJANGO_TOKEN_VERIFYING_KEY`, and stores it in `~/.config/prowler-api` (non-container) or the bound Docker volume in `_data/api` (container). Never commit or reuse static/default keys. To rotate keys, delete the stored key files and restart the API.
    </Callout>

  </Tab>
  <Tab title="GitHub">
    _Requirements_:

    - `git` installed.
    - `uv` installed: [uv installation](https://docs.astral.sh/uv/getting-started/installation/).
    - `pnpm` installed through [Corepack](https://pnpm.io/installation#using-corepack) or the standalone [pnpm installation](https://pnpm.io/installation).
    - `Docker Compose` installed: https://docs.docker.com/compose/install/.

    <Warning>
      Make sure to have `api/.env` and `ui/.env.local` files with the required environment variables. You can find the required environment variables in the [`api/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/api/.env.example) and [`ui/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/ui/.env.template) files.
    </Warning>
    _Commands to run the API_:

    ```bash
    git clone https://github.com/prowler-cloud/prowler \
    cd prowler/api \
    uv sync \
    source .venv/bin/activate \
    set -a \
    source .env \
    docker compose up postgres valkey -d \
    cd src/backend \
    python manage.py migrate --database admin \
    gunicorn -c config/guniconf.py config.wsgi:application
    ```

    > Now, you can access the API documentation at http://localhost:8080/api/v1/docs.

    _Commands to run the API Worker_:

    ```bash
    git clone https://github.com/prowler-cloud/prowler \
    cd prowler/api \
    uv sync \
    source .venv/bin/activate \
    set -a \
    source .env \
    cd src/backend \
    python -m celery -A config.celery worker -l info -E
    ```

    _Commands to run the API Scheduler_:

    ```bash
    git clone https://github.com/prowler-cloud/prowler \
    cd prowler/api \
    uv sync \
    source .venv/bin/activate \
    set -a \
    source .env \
    cd src/backend \
    python -m celery -A config.celery beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
    ```

    _Commands to run the UI_:

    ```bash
    git clone https://github.com/prowler-cloud/prowler \
    cd prowler/ui \
    corepack enable \
    corepack install \
    pnpm install --frozen-lockfile \
    pnpm run build \
    pnpm start
    ```

    > Enjoy Prowler Local Server at http://localhost:3000 by signing up with your email and password.

    <Warning>
      Google and GitHub authentication works out of the box in [Prowler Cloud](https://prowler.com). In Prowler Local Server it requires OAuth credentials: see [Social Login Configuration](/user-guide/tutorials/prowler-app-social-login).
    </Warning>
  </Tab>
</Tabs>

### Updating Prowler Local Server

Upgrade Prowler Local Server installation using one of two options:

#### Option 1: Updating the Environment File

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"
```

<Note>
  You can find the latest versions of Prowler Local Server in the [Releases GitHub section](https://github.com/prowler-cloud/prowler/releases) or in the [Container Versions](#container-versions) section of this documentation.
</Note>


#### Option 2: Using Docker Compose Pull

```bash
docker compose pull --policy always
```

The `--policy always` flag ensures that Docker pulls the latest images even if they already exist locally.

<Note>
  **What Gets Preserved During Upgrade**

  Everything is preserved, nothing will be deleted after the update.
</Note>

### Troubleshooting Installation Issues

If containers don't start, check logs for errors:

```bash
# Check logs for errors
docker compose logs

# Verify image versions
docker images | grep prowler
```

If issues are encountered, rollback to the previous version by changing the `.env` file back to the previous version and running:

```bash
docker compose pull
docker compose up -d
```

### Container Versions

The available versions of Prowler Local Server are the following:

- `latest`: in sync with `master` branch (please note that it is not a stable version)
- `v4-latest`: in sync with `v4` branch (please note that it is not a stable version)
- `v3-latest`: in sync with `v3` branch (please note that it is not a stable version)
- `<x.y.z>` (release): you can find the releases [here](https://github.com/prowler-cloud/prowler/releases), those are stable releases.
- `stable`: this tag always points to the latest release.
- `v4-stable`: this tag always points to the latest release for v4.
- `v3-stable`: this tag always points to the latest release for v3.

The container images are available here:

- Prowler Local Server:
  - [DockerHub - Prowler UI](https://hub.docker.com/r/prowlercloud/prowler-ui/tags)
  - [DockerHub - Prowler API](https://hub.docker.com/r/prowlercloud/prowler-api/tags)

---

### Getting Started Installation Prowler Cli (`docs/getting-started/installation/prowler-cli.mdx`)

---
title: 'Installation'
---

## Installation

To install Prowler as a Python package, use `Python >= 3.10, <= 3.13`. Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/):

<Tabs>
  <Tab title="pipx">
    [pipx](https://pipx.pypa.io/stable/) installs Python applications in isolated environments. Use `pipx` for global installation.

    _Requirements_:

    * `Python >= 3.10, <= 3.13`
    * `pipx` installed: [pipx installation](https://pipx.pypa.io/stable/installation/).
    * AWS, GCP, Azure and/or Kubernetes credentials

    _Commands_:

    ``` bash
    pipx install prowler
    prowler -v
    ```
  </Tab>
  <Tab title="pip">
    <Warning>
    This method modifies the chosen installation environment. Consider using [pipx](https://docs.prowler.com/projects/prowler-open-source/en/latest/#__tabbed_1_1) for global installation.
    </Warning>

    _Requirements_:

    * `Python >= 3.10, <= 3.13`
    * `Python pip >= 21.0.0`
    * AWS, GCP, Azure, M365 and/or Kubernetes credentials

    _Commands_:

    ``` bash
    pip install prowler
    prowler -v
    ```
  </Tab>
  <Tab title="Docker">
    _Requirements_:

    * Have `docker` installed: https://docs.docker.com/get-docker/.
    * In the command below, change `-v` to your local directory path to access the reports.
    * AWS, GCP, Azure and/or Kubernetes credentials

    _Commands_:

    ``` bash
    docker run -ti --rm -v /your/local/dir/prowler-output:/home/prowler/output \
    --name prowler \
    --env AWS_ACCESS_KEY_ID \
    --env AWS_SECRET_ACCESS_KEY \
    --env AWS_SESSION_TOKEN toniblyx/prowler:latest
    ```
  </Tab>
  <Tab title="GitHub">
    _Requirements for Developers_:

    * `git`
    * `uv` installed: [uv installation](https://docs.astral.sh/uv/getting-started/installation/).
    * AWS, GCP, Azure and/or Kubernetes credentials

    _Commands_:

    ```bash
    git clone https://github.com/prowler-cloud/prowler
    cd prowler
    uv sync
    uv run python prowler-cli.py -v
    ```

    <Note>
    If you want to clone Prowler from Windows, use `git config core.longpaths true` to allow long file paths.
    </Note>
  </Tab>
  <Tab title="Amazon Linux 2">
    _Requirements_:

    * `Python >= 3.10, <= 3.13`
    * AWS, GCP, Azure and/or Kubernetes credentials

    _Commands_:

    ```bash
    python3 -m pip install --user pipx
    python3 -m pipx ensurepath
    pipx install prowler
    prowler -v
    ```
  </Tab>
  <Tab title="Ubuntu">
    _Requirements_:

    * `Ubuntu 23.04` or above. For older Ubuntu versions, check [pipx installation](https://docs.prowler.com/projects/prowler-open-source/en/latest/#__tabbed_1_1) and ensure `Python >= 3.10, <= 3.13` is installed.
    * `Python >= 3.10, <= 3.13`
    * AWS, GCP, Azure and/or Kubernetes credentials

    _Commands_:

    ``` bash
    sudo apt update
    sudo apt install pipx
    pipx ensurepath
    pipx install prowler
    prowler -v
    ```
  </Tab>
  <Tab title="Brew">
    _Requirements_:

    * `Brew` installed on Mac or Linux
    * AWS, GCP, Azure and/or Kubernetes credentials

    _Commands_:

    ``` bash
    brew install prowler
    prowler -v
    ```
  </Tab>
  <Tab title="AWS CloudShell">
    After the migration of AWS CloudShell from Amazon Linux 2 to Amazon Linux 2023 [[1]](https://aws.amazon.com/about-aws/whats-new/2023/12/aws-cloudshell-migrated-al2023/) [[2]](https://docs.aws.amazon.com/cloudshell/latest/userguide/cloudshell-AL2023-migration.html), there is no longer a need to manually compile Python 3.9 as it is already included in AL2023. Prowler can thus be easily installed following the generic method of installation via pip. Follow the steps below to successfully execute Prowler v4 in AWS CloudShell:

    _Requirements_:

    * Open AWS CloudShell `bash`.

    _Commands_:

    ```bash
    sudo bash
    adduser prowler
    su prowler
    python3 -m pip install --user pipx
    python3 -m pipx ensurepath
    pipx install prowler
    cd /tmp
    prowler aws
    ```

    <Note>
    To download the results from AWS CloudShell, select Actions -> Download File and add the full path of each file. For the CSV file it will be something like `/tmp/output/prowler-output-123456789012-20221220191331.csv`
    </Note>
  </Tab>
  <Tab title="Azure CloudShell">
    _Requirements_:

    * Open Azure CloudShell `bash`.

    _Commands_:

    ```bash
    python3 -m pip install --user pipx
    python3 -m pipx ensurepath
    pipx install prowler
    cd /tmp
    prowler azure --az-cli-auth
    ```
  </Tab>
</Tabs>

## Updating Prowler CLI

Upgrade Prowler CLI to the latest release using the same method chosen for installation:

<Tabs>
  <Tab title="pipx">
    ```bash
    pipx upgrade prowler
    prowler -v
    ```
  </Tab>
  <Tab title="pip">
    ```bash
    pip install --upgrade prowler
    prowler -v
    ```
  </Tab>
  <Tab title="Docker">
    Pull the desired image tag to fetch the latest version:

    ```bash
    docker pull toniblyx/prowler:latest
    ```

    <Note>
      Replace `latest` with a specific release tag (for example, `stable` or `<x.y.z>`) to pin a version. Refer to the [Container Versions](#container-versions) section for the full list of available tags.
    </Note>
  </Tab>
  <Tab title="GitHub">
    Pull the latest changes and sync the environment:

    ```bash
    cd prowler
    git pull
    uv sync
    uv run python prowler-cli.py -v
    ```

    <Note>
      To upgrade to a specific release, check out the corresponding tag before syncing: `git checkout <x.y.z>`.
    </Note>
  </Tab>
  <Tab title="Brew">
    ```bash
    brew upgrade prowler
    prowler -v
    ```
  </Tab>
  <Tab title="CloudShell">
    Both AWS CloudShell and Azure CloudShell install Prowler with `pipx`, so the upgrade command is the same:

    ```bash
    pipx upgrade prowler
    prowler -v
    ```
  </Tab>
</Tabs>

<Note>
  To install a specific version instead of the latest release, pin it explicitly. For example, with `pipx`: `pipx install prowler==<x.y.z>`, or with `pip`: `pip install prowler==<x.y.z>`. The available releases are listed in the [Releases GitHub section](https://github.com/prowler-cloud/prowler/releases).
</Note>

## Container Versions

The available versions of Prowler CLI are the following:

- `latest`: in sync with `master` branch (please note that it is not a stable version)
- `v4-latest`: in sync with `v4` branch (please note that it is not a stable version)
- `v3-latest`: in sync with `v3` branch (please note that it is not a stable version)
- `<x.y.z>` (release): you can find the releases [here](https://github.com/prowler-cloud/prowler/releases), those are stable releases.
- `stable`: this tag always points to the latest release.
- `v4-stable`: this tag always points to the latest release for v4.
- `v3-stable`: this tag always points to the latest release for v3.

The container images are available here:

- Prowler CLI:

    - [DockerHub](https://hub.docker.com/r/toniblyx/prowler/tags)
    - [AWS Public ECR](https://gallery.ecr.aws/prowler-cloud/prowler)

---

### Getting Started Installation Prowler Mcp (`docs/getting-started/installation/prowler-mcp.mdx`)

---
title: "Installation"
---

There are **two ways** to use Prowler MCP Server:

<CardGroup cols={2}>
  <Card title="Cloud MCP Server (Recommended)" icon="cloud" color="#10B981">
    **No installation required** - Just configuration

    Use `https://mcp.prowler.com/mcp`
  </Card>
  <Card title="Local MCP Server" icon="server" color="#6366F1">
    **Local installation** - Full control

    Install via Docker, PyPI, or source code
  </Card>
</CardGroup>


For the Cloud MCP Server, go directly to the [Configuration Guide](/getting-started/basic-usage/prowler-mcp#cloud-mcp-server-configuration-recommended) to set up your Claude Desktop, Cursor, or other MCP client.
**This guide is focused on local installation, the Local MCP Server**.

## Installation Methods

Choose one of the following installation methods:

<Tabs>
  <Tab title="Docker (Recommended)">
    ### Pull from Docker Hub

    The easiest way to run locally is using the official Docker image:

    ```bash
    docker pull prowlercloud/prowler-mcp
    ```

    ### Run the Container

    ```bash
    # STDIO mode (for local MCP clients)
    docker run --rm -i prowlercloud/prowler-mcp

    # HTTP mode (for remote access)
    docker run --rm -p 8000:8000 \
      prowlercloud/prowler-mcp \
      --transport http --host 0.0.0.0 --port 8000
    ```

    ### With Environment Variables

    ```bash
    docker run --rm -i \
      -e PROWLER_API_KEY="pk_your_api_key" \
      -e API_BASE_URL="https://api.prowler.com/api/v1" \
      prowlercloud/prowler-mcp
    ```

    <Info>
    **Docker Hub:** [prowlercloud/prowler-mcp](https://hub.docker.com/r/prowlercloud/prowler-mcp)
    </Info>

  </Tab>

  <Tab title="PyPI Package">
    ### Install via pip

    <Warning>
    **Coming Soon** - PyPI package will be available shortly
    </Warning>
  </Tab>

  <Tab title="From Source (Development)">
    ### Install uv

    If `uv` is not installed, install it first. Visit [uv documentation](https://docs.astral.sh/uv/) for more installation options.

    ### Clone the Repository

    ```bash
    git clone https://github.com/prowler-cloud/prowler.git
    cd prowler/mcp_server
    ```

    ### Install Dependencies

    The MCP server uses `uv` for dependency management. Install dependencies with:

    ```bash
    uv sync
    ```

    ### Verify Installation

    Test that the server is properly installed:

    ```bash
    uv run prowler-mcp --help
    ```

    The help message with available command-line options should appear.

    <Note>
    **For Development:** This method is recommended if you're developing or contributing to the MCP server.
    </Note>

  </Tab>

  <Tab title="Build Docker Image">
    ### Prerequisites

    Ensure Docker is installed on your system. Visit [Docker documentation](https://docs.docker.com/get-docker/) for more installation options.

    ### Clone the Repository

    ```bash
    git clone https://github.com/prowler-cloud/prowler.git
    cd prowler/mcp_server
    ```

    ### Build the Docker Image

    ```bash
    docker build -t prowler-mcp .
    ```

    This creates a Docker image named `prowler-mcp` with all necessary dependencies.

    ### Verify Installation

    Test that the Docker image was built successfully:

    ```bash
    docker run --rm prowler-mcp --help
    ```

    The help message with available command-line options should appear.

  </Tab>
</Tabs>

---

## Updating Prowler MCP Server

When running the Local MCP Server, upgrade to the latest version using the same method chosen for installation. The Cloud MCP Server (`https://mcp.prowler.com/mcp`) is always kept up to date by Prowler and requires no action.

<Tabs>
  <Tab title="Docker">
    Pull the latest image and restart the container:

    ```bash
    docker pull prowlercloud/prowler-mcp
    ```

    <Note>
      Recreate any running container after pulling the new image so the updated version takes effect.
    </Note>
  </Tab>
  <Tab title="From Source">
    Pull the latest changes and sync the dependencies:

    ```bash
    cd prowler/mcp_server
    git pull
    uv sync
    uv run prowler-mcp --help
    ```
  </Tab>
  <Tab title="Build Docker Image">
    Pull the latest source and rebuild the image:

    ```bash
    cd prowler/mcp_server
    git pull
    docker build -t prowler-mcp .
    ```
  </Tab>
</Tabs>

---

## Command Line Options

The Prowler MCP Server supports the following command-line arguments:

```bash
prowler-mcp [--transport {stdio,http}] [--host HOST] [--port PORT]
```

| Argument | Values | Default | Description |
|----------|--------|---------|-------------|
| `--transport` | `stdio`, `http` | `stdio` | Transport method for MCP communication |
| `--host` | Any valid hostname/IP | `127.0.0.1` | Host to bind to (HTTP mode only) |
| `--port` | Port number | `8000` | Port to bind to (HTTP mode only) |

### Examples

```bash
# Default STDIO mode for local MCP client integration
prowler-mcp

# Explicit STDIO mode
prowler-mcp --transport stdio

# HTTP mode on default host and port (127.0.0.1:8000)
prowler-mcp --transport http

# HTTP mode accessible from any network interface
prowler-mcp --transport http --host 0.0.0.0

# HTTP mode with custom port
prowler-mcp --transport http --host 0.0.0.0 --port 8080
```

## Environment Variables

Configure the server using environment variables:

| Variable | Description | Required | Default |
|----------|-------------|----------|---------|
| `PROWLER_API_KEY` | Prowler API key | Only for STDIO mode | - |
| `API_BASE_URL` | Custom Prowler API endpoint | No | `https://api.prowler.com/api/v1` |
| `PROWLER_MCP_TRANSPORT_MODE` | Default transport mode (overwritten by `--transport` argument) | No | `stdio` |

<CodeGroup>
```bash macOS/Linux
export PROWLER_API_KEY="pk_your_api_key_here"
export API_BASE_URL="https://api.prowler.com/api/v1"
export PROWLER_MCP_TRANSPORT_MODE="http"
```

```bash Windows PowerShell
$env:PROWLER_API_KEY="pk_your_api_key_here"
$env:API_BASE_URL="https://api.prowler.com/api/v1"
$env:PROWLER_MCP_TRANSPORT_MODE="http"
```
</CodeGroup>

<Warning>
Never commit your API key to version control. Use environment variables or secure secret management solutions.
</Warning>

### Using Environment Files

For convenience, create a `.env` file in the `mcp_server` directory:

```bash .env
PROWLER_API_KEY=pk_your_api_key_here
API_BASE_URL=https://api.prowler.com/api/v1
PROWLER_MCP_TRANSPORT_MODE=stdio
```

When using Docker, pass the environment file:

```bash
docker run --rm --env-file .env -it prowler-mcp
```

## Running from Any Location

Run the MCP server from anywhere using `uvx`:

```bash
uvx /path/to/prowler/mcp_server/
```

This is particularly useful when configuring MCP clients that need to launch the server from a specific path.

## Production Deployment

For production deployments that require customization, it is recommended to use the ASGI application that can be found in `prowler_mcp_server.server`. This can be run with uvicorn:

```bash
uvicorn prowler_mcp_server.server:app --host 0.0.0.0 --port 8000
```

For more details on production deployment options, see the [FastMCP production deployment guide](https://gofastmcp.com/deployment/http#production-deployment) and [uvicorn settings](https://www.uvicorn.org/settings/).

### Entrypoint Script

The source tree includes `entrypoint.sh` to simplify switching between the
standard CLI runner and the ASGI app. The first argument selects the mode and
any additional flags are passed straight through:

```bash
# Default CLI experience (prowler-mcp console script)
./entrypoint.sh main --transport http --host 0.0.0.0

# ASGI app via uvicorn
./entrypoint.sh uvicorn --host 0.0.0.0 --port 9000
```

Omitting the mode defaults to `main`, matching the `prowler-mcp` console script.
When `uvicorn` mode is selected, the script exports `PROWLER_MCP_TRANSPORT_MODE=http` automatically.

This is the default entrypoint for the Docker container.

## Next Steps

Now that you have the Prowler MCP Server installed, proceed to configure your MCP client:

<CardGroup cols={1}>
  <Card title="Configuration" icon="gear" href="/getting-started/basic-usage/prowler-mcp">
    Configure Claude Desktop, Cursor, or other MCP clients
  </Card>
</CardGroup>

## Getting Help

If you encounter issues during installation:

- Search for existing [GitHub issues](https://github.com/prowler-cloud/prowler/issues)
- Ask for help in our [Slack community](https://goto.prowler.com/slack)
- Report a new issue on [GitHub](https://github.com/prowler-cloud/prowler/issues/new)

---

### Getting Started Products Prowler App (`docs/getting-started/products/prowler-app.mdx`)

---
title: 'Overview'
---

Prowler Local Server is a self-hosted web application that simplifies running Prowler. It provides:

- **User-friendly interface** for configuring and executing scans
- Dashboard to **view results** and manage **security findings**



## Components

Prowler Local Server consists of four main components:

- **Prowler UI**: User-friendly web interface for running Prowler and viewing results, powered by Next.js
- **Prowler API**: Backend API that executes Prowler scans and stores results, built with Django REST Framework
- **Prowler SDK**: Python SDK that integrates with Prowler CLI for advanced functionality
- **Prowler MCP Server**: Model Context Protocol server that exposes AI tools for Lighthouse, the AI-powered security assistant. Required dependency for Lighthouse.

Supporting infrastructure includes:

- **PostgreSQL**: Persistent storage of scan results
- **Celery Workers**: Asynchronous execution of Prowler scans
- **Celery Beat (API Scheduler)**: Schedules recurring scans and enqueues jobs on the broker
- **Valkey**: In-memory database serving as message broker for Celery workers
- **Neo4j**: Graph database used by the Attack Paths feature to combine cloud inventory with Prowler findings (currently populated by AWS scans)

```mermaid
flowchart TB
    user([User / Security Team])
    cli([Prowler CLI])

    subgraph APP["Prowler Local Server"]
        ui["Prowler UI<br/>(Next.js)"]
        api["Prowler API<br/>(Django REST Framework)"]
        worker["API Worker<br/>(Celery)"]
        beat["API Scheduler<br/>(Celery Beat)"]
        mcp["Prowler MCP Server<br/>(Lighthouse AI tools)"]
    end

    sdk["Prowler SDK<br/>(Python)"]

    subgraph DATA["Data Layer"]
        pg[("PostgreSQL")]
        valkey[("Valkey / Redis")]
        neo4j[("Neo4j")]
    end

    providers["Providers"]

    user --> ui
    user --> cli
    ui -->|REST| api
    ui -->|MCP HTTP| mcp
    mcp -->|REST| api
    api --> pg
    api --> valkey
    beat -->|enqueue jobs| valkey
    valkey -->|dispatch| worker
    worker --> pg
    worker -->|Attack Paths| neo4j
    worker -->|invokes| sdk
    cli --> sdk

    sdk --> providers
```

---

### Getting Started Products Prowler Cli (`docs/getting-started/products/prowler-cli.mdx`)

---
title: 'Overview'
---

Prowler CLI is a command-line interface for running Prowler scans from the terminal.

```console
prowler <provider>
```


## Prowler Local Dashboard

```console
prowler dashboard
```


Prowler includes hundreds of security controls aligned with widely recognized industry frameworks and standards, including:

- CIS Benchmarks (AWS, Azure, Microsoft 365, Kubernetes, GitHub)
- NIST SP 800-53 (rev. 4 and 5) and NIST SP 800-171
- NIST Cybersecurity Framework (CSF)
- CISA Guidelines
- FedRAMP Low & Moderate
- PCI DSS v3.2.1 and v4.0
- ISO/IEC 27001:2013 and 2022
- SOC 2
- GDPR (General Data Protection Regulation)
- HIPAA (Health Insurance Portability and Accountability Act)
- FFIEC (Federal Financial Institutions Examination Council)
- ENS RD2022 (Spanish National Security Framework)
- GxP 21 CFR Part 11 and EU Annex 11
- RBI Cybersecurity Framework (Reserve Bank of India)
- KISA ISMS-P (Korean Information Security Management System)
- MITRE ATT&CK
- AWS Well-Architected Framework (Security & Reliability Pillars)
- AWS Foundational Technical Review (FTR)
- Microsoft NIS2 Directive (EU)
- Custom threat scoring frameworks (prowler_threatscore)
- Custom security frameworks for enterprise needs

---

### Getting Started Products Prowler Cloud Aws Marketplace (`docs/getting-started/products/prowler-cloud-aws-marketplace.mdx`)

---
title: "AWS Marketplace"
---

This page explains how to subscribe to **Prowler Cloud** through the **AWS Marketplace**, how to activate the subscription in Prowler Cloud, how billing works, and how to fix the errors you may hit on the way.

## Plans

Prowler Cloud has two listings in the AWS Marketplace. Both give you the same product. They differ in how you commit and how you pay.

A **provider** is a cloud account you connect to Prowler Cloud: an AWS account, an Azure subscription, a GCP project, a Kubernetes cluster, and so on.

| | Monthly (pay as you go) | Annual (billed annually) |
| --- | --- | --- |
| Listing | [Prowler Cloud $99/provider per month (Pay as You Go)](https://aws.amazon.com/marketplace/pp/prodview-nmbvelz746oyk) | [Prowler Cloud $79/provider per month (Billed Annually)](https://aws.amazon.com/marketplace/pp/prodview-wwehsmzui25ri) |
| Billing | Monthly, per connected provider | Once, at checkout, for the full 12 months |
| Commitment | None. Cancel anytime. | 12 months for the number of providers you choose during checkout on AWS. |
| Resources beyond the included amount | Billed per resource | Billed per resource, at the annual rate |
| Providers beyond your commitment | Not applicable | Billed monthly at the pay-as-you-go rate |

For prices, included resources and per-resource rates, see the [Prowler Cloud pricing page](https://prowler.com/pricing/).

## Before You Subscribe

- **One AWS account pays for the whole organization.** Subscribe from a single AWS account. You then connect as many providers as you need, from any cloud, inside Prowler Cloud. You do not subscribe again for each AWS account you scan.
- **Decide which Prowler Cloud organization will pay.** Activation links the AWS purchase to the organization you are signed in to. If you belong to several organizations, switch to the right one before you activate. See [Activate the Subscription in Prowler Cloud](#activate-the-subscription-in-prowler-cloud).
- **Annual plan: decide the provider count first.** AWS asks for it during checkout, and AWS charges the full year at that moment. Providers you add beyond that count are billed monthly at the pay-as-you-go rate. AWS may not let you cancel an annual contract yourself from the console. See [Cancel the Subscription](#cancel-the-subscription) before you commit.
- **Cancel any card-billed subscription first.** An organization already billed by credit card cannot switch to AWS Marketplace billing until that subscription is cancelled. This prevents double charges.

## Subscribe on AWS Marketplace

<Steps>
  <Step title="Open the listing">
    Sign in to the AWS account that will pay, open the listing for the plan you want, and click **View purchase options**.

    <Tabs>
      <Tab title="Monthly">
        Open [Prowler Cloud $99/provider per month (Pay as You Go)](https://aws.amazon.com/marketplace/pp/prodview-nmbvelz746oyk).

        <Frame>
          <img src="/images/aws-marketplace/listing-monthly.png" alt="Monthly Prowler Cloud listing on AWS Marketplace with the View purchase options button highlighted" />
        </Frame>
      </Tab>
      <Tab title="Annual">
        Open [Prowler Cloud $79/provider per month (Billed Annually)](https://aws.amazon.com/marketplace/pp/prodview-wwehsmzui25ri).

        <Frame>
          <img src="/images/aws-marketplace/listing-annual.png" alt="Annual Prowler Cloud listing on AWS Marketplace with the View purchase options button highlighted" />
        </Frame>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Review the purchase and subscribe">
    <Tabs>
      <Tab title="Monthly">
        Scroll to **Purchase details** and click **Subscribe**. There is no upfront charge. AWS bills your usage at the end of each month.

        <Frame>
          <img src="/images/aws-marketplace/purchase-monthly.png" alt="Purchase details for the monthly listing with the Subscribe button highlighted" />
        </Frame>
      </Tab>
      <Tab title="Annual">
        Under **Pricing details**, select the **providers** dimension and enter the **Number of units**. One unit is one provider for the 12-month term. Review the **Contract total**, then click **Subscribe** at the bottom of the page. AWS charges the full year now.

        <Frame>
          <img src="/images/aws-marketplace/purchase-annual.png" alt="Purchase options for the annual listing showing the contract details, the providers dimension and the Number of units table" />
        </Frame>

        **Auto-renewal** is on by default. Leave it on to have AWS renew the contract for another 12 months at the end date, or choose **Do not automatically renew this contract**. You can change this from **Manage subscriptions** any time before the renewal date.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Click Set up your account">
    AWS confirms the purchase and shows a **Set up your account** button. Click it. AWS sends your browser to Prowler Cloud with a setup link.

    <Frame>
      <img src="/images/aws-marketplace/set-up-your-account.png" alt="AWS Marketplace purchase confirmation banner with the Set up your account button" />
    </Frame>

    <Note>
    The setup link expires quickly: AWS documents roughly one hour, and never more than four. If the link expires, you close the tab, or need to start over, return to [Manage subscriptions](https://console.aws.amazon.com/marketplace/subscriptions) in the AWS Marketplace console, open **Prowler Cloud**, and click **Set up your account** again to get a fresh link.
    </Note>
  </Step>
</Steps>

## Activate the Subscription in Prowler Cloud

Nothing is linked until you confirm it in Prowler Cloud. The purchase is safe on the AWS side while you complete these steps.

<Steps>
  <Step title="Sign in or create your account">
    Prowler Cloud shows the sign-in page with a banner that reads "To finish setting up your AWS Marketplace subscription, sign in or register if you don't have an account yet."

    <Frame>
      <img src="/images/aws-marketplace/sign-in-marketplace.png" alt="Prowler Cloud sign-in page with the AWS Marketplace banner" />
    </Frame>

    If you do not have an account yet, click **Sign up**. The purchase carries over once you sign in with the new account.

    <Frame>
      <img src="/images/aws-marketplace/sign-up-marketplace.png" alt="Prowler Cloud sign-up page with the AWS Marketplace banner" />
    </Frame>
  </Step>

  <Step title="Check the organization and activate">
    The billing page shows a card titled **Activate your AWS Marketplace subscription**. It names the organization the purchase will be linked to.

    <Frame>
      <img src="/images/aws-marketplace/activate-confirm.png" alt="Activation card naming the organization, with the Activate subscription button and the Continue to billing without activating link" />
    </Frame>

    <Warning>
    Read the organization name before you click. Activation is a billing decision: charges for that organization go to the AWS account you subscribed with. If it is not the organization you want to pay for, switch organizations first, then come back to the billing page.
    </Warning>

    Click **Activate subscription**. Prowler Cloud links the purchase and activates the subscription. Two short status messages appear while it works: "Linking your AWS Marketplace purchase…" and "Activating your subscription…".

    <Frame>
      <img src="/images/aws-marketplace/activate-working.png" alt="Activation card showing the Activating your subscription progress message" />
    </Frame>

    **Continue to billing without activating** discards the setup link and leaves the organization unchanged. Use it only if you landed in the wrong organization. To activate later, get a fresh link from the AWS Marketplace console as described above.
  </Step>

  <Step title="Confirm the subscription is active">
    The billing page now states that you subscribed through AWS Marketplace, shows the AWS account that pays, and links to your subscription in the AWS Marketplace console.

    <Frame>
      <img src="/images/aws-marketplace/billing-subscribed.png" alt="Prowler Cloud billing page for an AWS Marketplace subscription, showing the paying AWS account and the link to the AWS Marketplace console" />
    </Frame>

    There is no payment method, invoice history, or cancel button on this page. AWS handles all of that.
  </Step>
</Steps>

If something goes wrong during activation, see [Activation errors](#activation-errors).

## After Activation

- Add providers from the **Providers** page in Prowler Cloud. Every provider you add is covered by the same AWS subscription, whatever cloud it belongs to.
- All charges come from AWS. Find them under **Amazon Web Services, Inc. - Marketplace** in [AWS Billing](https://console.aws.amazon.com/billing/home#/bills).

<Frame>
  <img src="/images/aws-marketplace/marketplace-billing.png" alt="AWS Billing page showing the Amazon Web Services, Inc. - Marketplace line" />
</Frame>

### Monthly

AWS bills you at the end of each month for the providers connected and the resources scanned in Prowler Cloud during that month.

### Annual

AWS charges the full 12-month subscription once, at checkout. The providers covered by your commitment cost nothing more during the year. Your monthly AWS bill only includes usage beyond the commitment: extra providers at the pay-as-you-go rate, and resources beyond the included amount at the annual plan's per-resource rate.

If auto-renewal is on, AWS renews the contract for another 12 months at the end date and charges the new term the same way. Turn it off from **Manage subscriptions** before the renewal date if you do not want that.

## Cancel the Subscription

There is no cancel button in Prowler Cloud for AWS Marketplace subscriptions. How you cancel depends on the plan, following the [AWS Marketplace cancellation rules](https://docs.aws.amazon.com/marketplace/latest/buyerguide/cancel-subscription.html).

### Monthly

Cancel from the AWS Marketplace console at any time:

1. Open [Manage subscriptions](https://console.aws.amazon.com/marketplace/subscriptions) and open **Prowler Cloud**.
2. Under **Active agreements**, select the agreement and click **View details**.
3. Open the **Actions** menu and choose **Cancel subscription**.
4. Type `confirm` and choose **Yes, cancel subscription**.

<Frame>
  <img src="/images/aws-marketplace/aws-manage-subscriptions.png" alt="Prowler Cloud agreement page in the AWS Marketplace console with the Actions menu open on Cancel subscription" />
</Frame>

Cancelling stops future charges. Usage accrued before the cancellation still appears on your next AWS bill.

### Annual

Open the agreement from [Manage subscriptions](https://console.aws.amazon.com/marketplace/subscriptions) first. If the **Actions** menu shows **Cancel subscription**, follow the monthly steps above. If it does not, AWS does not allow self-service cancellation for that contract:

- **Within 48 hours of purchase**, contact AWS Customer Service. AWS can cancel the contract and refund the full amount, but only if you ask for the refund.
- **After 48 hours**, contact [support@prowler.com](mailto:support@prowler.com). Prowler starts the cancellation request through AWS Marketplace and you approve it in the AWS Marketplace console. A refund is not automatic. Ask for it when you request the cancellation.

See [Refunds and cancellations in AWS Marketplace](https://docs.aws.amazon.com/marketplace/latest/buyerguide/buyer-refunds.html) for the AWS side of the process.

### What happens after cancellation

Once AWS ends the subscription, Prowler Cloud pauses scans for the organization. Providers, findings and scan history stay readable. A **Subscription required** card in the sidebar, labeled **Subscription cancelled**, explains it: "Scans are paused and your assessment data remains available. Subscribe again to continue scanning." The billing page shows the plan selection page.

<Frame>
  <img src="/images/aws-marketplace/billing-cancelled.png" alt="Prowler Cloud after an AWS Marketplace cancellation, showing the Subscription cancelled card in the sidebar and the plan selection page" />
</Frame>

To subscribe again from the same AWS account into the same Prowler Cloud organization, subscribe from the listing and activate as before.

<Warning>
Deleting your Prowler Cloud organization does not cancel the AWS subscription. AWS keeps charging the agreement until you cancel it as described above.
</Warning>

## Troubleshooting

### Activation errors

Each error appears on the Prowler Cloud billing page with the title below. Your AWS purchase is never lost by any of them. **Continue to billing** closes the message without activating.

<AccordionGroup>
  <Accordion title="Your AWS Marketplace link has expired">
    AWS no longer accepts the setup link, usually because it timed out. AWS documents roughly one hour of validity, and never more than four.

    Open **Manage subscriptions** in the AWS Marketplace console, open **Prowler Cloud**, and click **Set up your account** to get a fresh link.

    <Frame>
      <img src="/images/aws-marketplace/error-token-expired.png" alt="Error notice: Your AWS Marketplace link has expired" />
    </Frame>
  </Accordion>

  <Accordion title="This AWS Marketplace product is not supported yet">
    The purchase went through on AWS, but this listing is not enabled for activation in Prowler Cloud. You see this when you subscribed to an earlier Prowler Cloud listing instead of one of the [two current plans](#plans).

    Contact [support@prowler.com](mailto:support@prowler.com) with the AWS account you subscribed with. Click **Try again** once support confirms.

    <Frame>
      <img src="/images/aws-marketplace/error-product-not-supported.png" alt="Error notice: This AWS Marketplace product is not supported yet" />
    </Frame>
  </Accordion>

  <Accordion title="This AWS subscription is already linked">
    The purchase cannot be linked to the organization you are signed in to. The message shows the reason reported for this purchase:

    - **"This AWS Marketplace subscription is no longer active."** The subscription was cancelled on AWS. Subscribe again from the listing. If you see this seconds after a new purchase, AWS may still be registering it: wait a minute, go back to **Manage subscriptions**, and click **Set up your account** again.
    - **"This AWS Marketplace purchase is already linked to another Prowler account."** AWS identifies a purchase by AWS account and product, so one AWS account backs one Prowler Cloud organization per plan. Sign in to the organization that already holds it, or subscribe from a different AWS account.
    - **"This account is already linked to another active AWS Marketplace purchase."** This organization already has an active AWS Marketplace subscription. Cancel that subscription in the AWS Marketplace console before activating a new one.

    If none of these applies, contact [support@prowler.com](mailto:support@prowler.com).

    <Frame>
      <img src="/images/aws-marketplace/error-agreement-in-use.png" alt="Error notice: This AWS subscription is already linked, with the reason shown below the message" />
    </Frame>
  </Accordion>

  <Accordion title="Your purchase is still being linked">
    Another request is linking the same purchase, usually a second click or a second tab. Wait a moment and click **Try again**.

    <Frame>
      <img src="/images/aws-marketplace/error-link-in-progress.png" alt="Error notice: Your purchase is still being linked" />
    </Frame>
  </Accordion>

  <Accordion title="You already have an active subscription">
    This organization is billed by credit card. Cancel that subscription first, so you are never charged twice. Then open **Manage subscriptions** in the AWS Marketplace console and click **Set up your account** again.

    <Frame>
      <img src="/images/aws-marketplace/error-billing-provider-conflict.png" alt="Error notice: You already have an active subscription" />
    </Frame>
  </Accordion>

  <Accordion title="This AWS purchase is billed under another organization">
    AWS Marketplace billing for this AWS account and plan is already set up in a different Prowler Cloud organization. This also happens after you delete the organization that held the subscription and subscribe again from a new one.

    Sign in to the organization that holds the billing and restart the setup from **Manage subscriptions** in the AWS Marketplace console. If that organization no longer exists, contact [support@prowler.com](mailto:support@prowler.com) so they can release the billing link.
  </Accordion>

  <Accordion title="We could not find your AWS purchase">
    Prowler Cloud received no purchase to activate. Open **Manage subscriptions** in the AWS Marketplace console and click **Set up your account** to start again.

    <Frame>
      <img src="/images/aws-marketplace/error-no-agreement.png" alt="Error notice: We could not find your AWS purchase" />
    </Frame>
  </Accordion>

  <Accordion title="We could not reach AWS Marketplace">
    A temporary problem talking to AWS. Click **Try again**. If it keeps failing, contact [support@prowler.com](mailto:support@prowler.com) and include the detail text shown under the message.

    <Frame>
      <img src="/images/aws-marketplace/error-resolve-failed.png" alt="Error notice: We could not reach AWS Marketplace" />
    </Frame>
  </Accordion>

  <Accordion title="We could not activate your subscription">
    The purchase was linked but the activation did not complete. Click **Try again**. If it keeps failing, contact [support@prowler.com](mailto:support@prowler.com) and include the detail text shown under the message.

    <Frame>
      <img src="/images/aws-marketplace/error-subscribe-failed.png" alt="Error notice: We could not activate your subscription" />
    </Frame>
  </Accordion>

  <Accordion title="Please sign in again">
    Your Prowler Cloud session expired before the activation finished. Click **Sign in again** and pick up where you left off. The setup link stays valid during its expiry window.

    <Frame>
      <img src="/images/aws-marketplace/error-unauthorized.png" alt="Error notice: Please sign in again" />
    </Frame>
  </Accordion>

  <Accordion title="We could not read your AWS purchase">
    The setup link arrived damaged. Open **Manage subscriptions** in the AWS Marketplace console and click **Set up your account** to get a fresh one.

    <Frame>
      <img src="/images/aws-marketplace/error-invalid-input.png" alt="Error notice: We could not read your AWS purchase" />
    </Frame>
  </Accordion>
</AccordionGroup>

### Errors before the sign-in page

If the redirect from AWS itself fails, Prowler Cloud shows a plain page instead of the app. Each page links back to Prowler Cloud billing and to your AWS Marketplace subscriptions.

<AccordionGroup>
  <Accordion title="We couldn't verify this request">
    The setup page only accepts the redirect from AWS Marketplace. You reach it by opening the setup URL directly or from a bookmark. Start again from **Set up your account** in the AWS Marketplace console.

    <Frame>
      <img src="/images/aws-marketplace/fulfillment-error-verify.png" alt="Error page: We couldn't verify this request" />
    </Frame>
  </Accordion>

  <Accordion title="We couldn't read your AWS purchase">
    The redirect arrived without a valid setup token. Start again from **Set up your account** in the AWS Marketplace console.

    <Frame>
      <img src="/images/aws-marketplace/fulfillment-error-read.png" alt="Error page: We couldn't read your AWS purchase" />
    </Frame>
  </Accordion>

  <Accordion title="Something went wrong">
    Prowler Cloud could not process the redirect. Your purchase is safe. Wait a moment, then start again from **Set up your account** in the AWS Marketplace console. Contact [support@prowler.com](mailto:support@prowler.com) if it happens again.

    <Frame>
      <img src="/images/aws-marketplace/fulfillment-error-generic.png" alt="Error page: Something went wrong" />
    </Frame>
  </Accordion>
</AccordionGroup>

### SEPA payment method issues

If AWS Marketplace notifies that payment failed due to an issue with the payment method, this typically occurs when a SEPA bank account is set as the default payment method. AWS Marketplace does not support SEPA bank accounts for product subscriptions, even when the account includes valid alternative payment methods. This is because AWS Marketplace invoices are issued by AWS Inc., a US entity. SEPA accounts do not recognize these invoices as valid, causing subscription failures.

To successfully subscribe to AWS Marketplace products with a SEPA account configuration:

1. Switch default payment method to credit card
2. Complete subscription
3. Switch the default payment method back to the bank account

<Warning>
**Renewal Considerations**

This issue will recur during subscription renewals. AWS service teams recommend maintaining credit card as the default payment method to prevent future disruptions. Update payment methods at https://console.aws.amazon.com/billing/home#/paymentmethods.

</Warning>

<Note>
**AWS Marketplace Statement**

The AWS Marketplace team acknowledges this limitation: "We apologize for these additional steps, and please know we are fully aware of this situation, and our internal teams are working on simplifying this process."

</Note>

### Credit and debit card storage restrictions (AISPL customers in India)

AWS Marketplace no longer supports payments using credit or debit cards stored on file for Amazon Internet Services Private Limited (AISPL) customers. This restriction stems from Reserve Bank of India (RBI) regulations regarding payment aggregators, which prohibit the storage of card data. As explained in [this AWS blog post](https://aws.amazon.com/blogs/awsmarketplace/restriction-on-credit-and-debit-card-purchases-for-aispl-customers-using-aws-marketplace/):

> AWS Marketplace can no longer support payments using credit or debit cards stored on file. [The Reserve Bank of India (RBI) has issued a notice regarding regulation of payment aggregators](https://www.rbi.org.in/Scripts/NotificationUser.aspx?Id=11822&Mode=0), which restricts the storage of card data. If you are currently using credit or debit card as your default payment instrument, your ability to use AWS Marketplace products will be restricted. However, you can switch your default payment instrument to Pay By Invoice to avoid disruption or restore your original experience.

To maintain uninterrupted access to AWS Marketplace products, change the default payment instrument from stored card data to Pay By Invoice billing.

## Support

If you get stuck at any step, contact [support@prowler.com](mailto:support@prowler.com) and include the AWS account you subscribed with and the exact error title you see.

---

### Getting Started Products Prowler Cloud Lighthouse (`docs/getting-started/products/prowler-cloud-lighthouse.mdx`)

---
title: 'Overview'
---

import { SubscriptionBanner } from "/snippets/subscription-banner.mdx"
import { VersionBadge } from "/snippets/version-badge.mdx"

Prowler Cloud runs an enhanced version of Lighthouse AI in Open Source repository, the Agentic Cloud Defender that helps teams understand, prioritize, and remediate security findings across cloud environments.

<SubscriptionBanner />

<img src="/images/prowler-app/lighthouse/prowler-cloud/chat-animation.gif" alt="Lighthouse AI on Prowler Cloud" />

## What's New

The Agentic Cloud Defender does more than answer questions, it helps teams **find and remediate what actually matters**, cutting through the noise to focus on the risk that counts. Prowler Cloud sharpens Lighthouse AI with the following improvements over the open-source version:

<CardGroup cols={2}>
  <Card title="Persistent Chat Sessions" icon="clock-rotate-left">
    Conversations are saved and can be revisited or resumed at any time.
  </Card>
  <Card title="GPT-5.5 by Default" icon="microchip">
    An upgraded default model delivers stronger reasoning and tool calling.
  </Card>
  <Card title="Normal and Agentic Views" icon="table-columns">
    Switch between the standard interface and a chat-first agentic view.
  </Card>
  <Card title="Side Panel on Every Page" icon="comment-dots">
    Open Lighthouse AI as a side panel from any page to get help in context.
  </Card>
  <Card title="Provider Connection Checks" icon="plug-circle-check">
    Credentials are validated automatically when a provider is configured.
  </Card>
</CardGroup>

## Chat View

<VersionBadge version="5.33.0" />

Lighthouse AI is no longer a separate section in the left navigation. Prowler Cloud now offers two application views: a normal view for browsing dashboards, findings, and configuration, and an agentic chat view, powered by Lighthouse AI, for conversational, multi-step security analysis. Conversations are saved automatically, so earlier sessions can be reopened and resumed at any time.

Promoting the chat to a top-level view gives Lighthouse AI the room it needs for a fully agentic workflow and makes the Agentic Cloud Defender a primary way to work in Prowler Cloud.

<img src="/images/prowler-app/lighthouse/prowler-cloud/main-chat-page.png" alt="Lighthouse AI chat view in Prowler Cloud" />

### Side Panel

<VersionBadge version="5.35.0" />

You do not have to switch to the full chat view to reach Lighthouse AI. A side panel is available on every page of Prowler Cloud. While collapsed it stays out of the way; open it from any dashboard, findings list, or configuration screen to ask questions without leaving what you are working on. Open it using the Lighthouse AI button, circled in red in the image below.

<img src="/images/prowler-app/lighthouse/prowler-cloud/side-panel-closed.png" alt="Collapsed Lighthouse AI side panel on a Prowler Cloud page, with the button to open it circled in red" />

Once open, the panel slides in alongside your current page and shares the same agent, tools, and persistent chat sessions as the full Chat View, so a conversation started in the panel can be reopened and continued later from either place.

<img src="/images/prowler-app/lighthouse/prowler-cloud/side-panel-open.png" alt="Lighthouse AI side panel open alongside a Prowler Cloud page" />

- **Available everywhere:** Summon the assistant from any page while you keep working in the normal view.
- **Context-aware help:** Ask about the findings, resources, or compliance data you are currently looking at.
- **Continuous sessions:** Conversations opened in the side panel are saved alongside the rest of your chat history.

### Context-Aware Chat

<VersionBadge version="5.37.0" />

The panel knows where you are in the app. Messages carry the page you are on and, when a finding or resource is open in the side panel, its metadata too, so questions like "explain this" resolve against what is on screen. The active context appears as a chip in the composer, circled in red in the image below, and each page offers contextual suggestions to start from.

<img src="/images/prowler-app/lighthouse/prowler-cloud/side-panel-context-aware.png" alt="Lighthouse AI answering a question about the open finding from the side panel, with the page context chip highlighted in red in the composer" />

### Tool Usage

Lighthouse AI on Prowler Cloud renders the agent's work as it happens, so responses are easier to follow and to trust. Tool calls and reasoning steps appear in the order they occur within the conversation.

- **Ordered steps:** Tool calls and reasoning are shown in sequence, reflecting how the agent reached its answer.
- **Tool visibility:** The data tools invoked to retrieve findings and other Prowler information are displayed as the agent uses them.
- **Thought process:** The agent's reasoning is presented alongside its actions, and can be expanded to review the full chain of steps.

<img src="/images/prowler-app/lighthouse/prowler-cloud/tool-usage.png" alt="Ordered tool usage and reasoning in the Lighthouse AI chat" />

## Configuration

Configure Lighthouse AI on Prowler Cloud from **Configuration** → **Lighthouse AI**:

1. Click the desired provider (OpenAI, Amazon Bedrock, or OpenAI Compatible).
2. Enter the required credentials.
3. Click **Save**. The connection is validated automatically before the provider becomes available.

<img src="/images/prowler-app/lighthouse/prowler-cloud/config-page.png" alt="Lighthouse AI configuration page in Prowler Cloud" />

### Business Context

At the top of the configuration page, the optional **Business Context** field lets teams add environment priorities, compliance requirements, and ownership details, so responses align with organizational needs.

Lighthouse AI on Prowler Cloud supports OpenAI, Amazon Bedrock, and OpenAI-compatible providers, with GPT-5.5 as the default. For per-provider setup and how to switch the default provider or model, see [Using Multiple LLM Providers](/user-guide/tutorials/prowler-cloud-lighthouse-multi-llm).

## Capabilities

Lighthouse AI works through the [Prowler MCP Server](/getting-started/products/prowler-mcp), which gives the agent a growing catalog of tools to explore and act on your security data. These actions run inside Prowler and never modify your cloud resources. Everything the agent can do maps to one of the following capability areas.

### Findings and Finding Groups

- Search and filter security findings across every connected provider by severity, status, region, service, check, date range, and muted state.
- Retrieve full finding details, including remediation guidance, check metadata, and affected resources.
- Summarize findings with aggregate statistics and trends.
- Browse finding groups aggregated by check and drill down into the specific resources each group affects.

### Resources

- List and filter cloud resources by provider, region, service, resource type, and tags.
- Inspect a resource's configuration, metadata, and related findings.
- Review the timeline of cloud API actions performed on a resource (AWS CloudTrail), including who did what and when.
- Get an aggregate overview of the resources Prowler has discovered.

### Compliance

- Review high-level compliance status across all frameworks, with pass/fail statistics per framework.
- Get a requirement-level breakdown for a specific framework, including failed requirements and their associated findings.

### Attack Paths

- List Attack Paths scans and discover the queries available for each completed scan.
- Run graph-based queries to reveal privilege-escalation chains and exploitable misconfigurations.
- Retrieve the Cartography graph schema to build accurate custom queries.

### Scans and Providers

- List, inspect, and rename security scans across providers.
- Trigger manual scans and schedule automated daily scans for continuous monitoring.
- Search connected providers and check their connection status, connect new providers, or remove existing ones.

### Muting

- Manage the mutelist for pattern-based bulk muting.
- Create, update, list, and delete finding-specific mute rules, each with a documented reason and audit trail.

### Security Check Catalog and Documentation

- Browse and search the Prowler Hub catalog of security checks and compliance frameworks, including check code and automated fixers.
- Search and retrieve official Prowler documentation to answer how-to and product questions.

For the complete list of underlying tools, see the [Prowler MCP Tools Reference](/getting-started/basic-usage/prowler-mcp-tools).

## FAQ

**Which LLM providers are supported?**

OpenAI (GPT models, including the default GPT-5.5), Amazon Bedrock (Claude, Llama, Titan, and others), and any OpenAI-compatible service such as OpenRouter.

**Can Lighthouse AI change my cloud environment?**

No. Lighthouse AI cannot modify the resources in your connected cloud providers (AWS, Azure, GCP, and others). It has read-only access to that environment and no tools to change it, even when the connected cloud credentials would allow it.

**Can Lighthouse AI change my Prowler Cloud environment?**

Yes. Lighthouse AI can take action within Prowler Cloud itself, such as connecting or removing providers, triggering and scheduling scans, and managing mute rules and the mutelist. See [Capabilities](#capabilities) for the full list of what it can do. These actions only affect your Prowler Cloud workspace, never the resources in your cloud providers.

## Looking for the Open Source Version?

Lighthouse AI is also available in the open-source Prowler Local Server. For its capabilities, FAQs, and limitations, see the open-source documentation.

<Card title="Lighthouse AI (Open Source)" icon="github" href="/getting-started/products/prowler-lighthouse-ai">
  Capabilities, FAQs, and limitations for Lighthouse AI in the open-source Prowler Local Server
</Card>

## Getting Help

For issues or suggestions with Lighthouse AI on Prowler Cloud, request support at [support.prowler.com](https://support.prowler.com) or [reach out through our Slack channel](https://goto.prowler.com/slack).

---

### Getting Started Products Prowler Cloud (`docs/getting-started/products/prowler-cloud.mdx`)

---
title: "Overview"
---

[Prowler Cloud](https://prowler.com) makes Cloud Security easy and enables your team to build trust in their deployed services and applications.

Prowler Cloud automates scanning single or multiple accounts and has all of the benefits of Prowler Open Source, plus hands-off continuous monitoring, auto-scaling workers for faster execution, integrations, personalized support options and out of the box social authentication.



<Card title="Create your account here to see Prowler Cloud in action" href="https://cloud.prowler.com/sign-up" />

With 100% consistency across our open source policies and APIs. Prowler Cloud provides the following added benefits:

<ul>
    <li> Immediate sign-up and account provisioning, including a trial period with zero billing details needed at registration. </li>
    <li> Simple, transparent pricing, with cloud account sizing and exact pricing being available to view in your account settings. </li>
    <li> SOC2 Security processes around the deployment, management, data protection and security updates of your prowler environment. </li>
    <li> Helpers for smooth onboarding of cloud environments (Eg. Automatic account ID and ExternalID for AWS assumed roles, Known Static IP for Kubernetes access). </li>
    <li> Zero touch third party notifications to Slack, Jira, and more. </li>
</ul>

The team who built [Prowler](https://github.com/prowler-cloud/prowler), has helped thousands of companies get Cloud Security under control, is now making it easier by taking
[Prowler](https://github.com/prowler-cloud/prowler) to the [Cloud](https://prowler.com)!

---

### Getting Started Products Prowler For Msps (`docs/getting-started/products/prowler-for-msps.mdx`)

---
title: "Prowler for MSPs and MSSPs"
sidebarTitle: "Overview"
---

Prowler for MSPs and MSSPs is a dedicated console for Managed Service Providers (MSPs), Managed Security Service Providers (MSSPs), resellers and consultants who run cloud security for other organizations. It lets a provider onboard customers, group them, manage a team, and operate each customer's Prowler Cloud tenant on their behalf.

The console is available at [partners.prowler.com](https://partners.prowler.com).

<Card title="Sign Up for Prowler for MSPs and MSSPs" icon="rocket" href="https://partners.prowler.com/sign-up" />

## What You Get

* **Customer onboarding:** provision a Prowler Cloud tenant for each customer, with a billing plan selected up front.
* **Delegated access:** open any customer's Prowler Cloud tenant from the console. Every action is attributed to you acting on behalf of that customer.
* **Team and roles:** invite team members by email and assign a role that governs what they can do.
* **Consolidated billing:** each customer carries its own plan, with month-to-date revenue reported across every customer.
* **Branding:** upload your logo and preview its intended placement in Settings.

## Core Concepts

Three objects make up the model. Getting these straight makes the rest of the documentation easy to follow.

| Object | What it is |
|---|---|
| **Partner organization** | The provider's own company. The top-level container for everything below, created at sign-up. |
| **Customer** | One of the provider's customers. Each customer maps to a Prowler Cloud tenant and carries its own billing plan. |
| **Team member** | A user in the partner organization, holding a role that governs what they can do. |

## How It Relates to Prowler Cloud

| | Prowler Cloud | Prowler for MSPs and MSSPs |
|---|---|---|
| **Audience** | End customers | MSPs, MSSPs, resellers, consultants |
| **Console** | [cloud.prowler.com](https://cloud.prowler.com) | [partners.prowler.com](https://partners.prowler.com) |
| **Scope** | One organization's own cloud accounts | Many customer organizations |
| **Billing** | Each organization pays for itself | The provider manages a plan per customer |
| **Branding** | Prowler-branded | Logo upload and placement preview in Settings |

Your customers keep signing in to Prowler Cloud with their own users. Provider-side access is **additive** — it does not replace or restrict customer-side users.

## The Console at a Glance

Signing in lands you on the **Dashboard**. The sidebar carries:

| Entry | What it does | Visible to |
|---|---|---|
| **Dashboard** | Partner Insights, a Billing Overview card and an Active Customers table | Everyone |
| **Customers** | Review customer posture and billing and open customer tenants; Superadmins can also add customers | Everyone |
| **Team** | Invite, re-invite, disable and remove team members | Roles with **Manage members** |
| **Settings** | Profile, Partner Code, branding and security | Everyone; editing requires **Manage settings** |



**Partner Insights** is the top row: **Total Customers**, broken down into active and non-paid; **Cloud Accounts**, broken down by cloud provider; and **Monitored Resources**, with a note on organizations whose critical risk has grown. Each card carries a 30-day trend.

Below it, **Billing Overview** reports monthly expenses against the previous month and splits revenue for the period into annual, monthly and overage. **Active Customers** lists your customers with their provider count, resource count and last completed scan and, for Superadmins, carries its own **Add Customer** button.

## Getting Access

Sign-up is self-service, approval is not. Register at [partners.prowler.com/sign-up](https://partners.prowler.com/sign-up), then verify your email address — the organization sits in **Pending email verification** until you do, and the Prowler team does not review it before that. Verifying moves the organization to **Pending approval**. Once approved, you can invite your team and start onboarding customers.

## Next Steps

<Columns cols={2}>
  <Card title="Sign Up and Sign In" icon="user-plus" href="/user-guide/tutorials/prowler-for-msps-sign-up">
    Register, verify your email, and get approved.
  </Card>
  <Card title="Your Partner Organization" icon="briefcase" href="/user-guide/tutorials/prowler-for-msps-organization">
    Lifecycle, settings, Partner Code and closing your organization.
  </Card>
  <Card title="Onboarding Customers" icon="building" href="/user-guide/tutorials/prowler-for-msps-customers">
    Add customers and open their Prowler Cloud tenants.
  </Card>
  <Card title="Managing Your Team" icon="users" href="/user-guide/tutorials/prowler-for-msps-team">
    Invite team members and assign roles.
  </Card>
</Columns>

---

### Getting Started Products Prowler Hub (`docs/getting-started/products/prowler-hub.mdx`)

---
title: "Overview"
---

**Prowler Hub** is our growing public library of versioned checks, cloud service artifacts, and compliance frameworks with its mappings. It’s searchable, explainable, and built to serve the community.

**Why this matters**: Every engineer has asked, “What does this check actually do?” Prowler Hub answers that question in one place, lets you pin to a specific version, and pulls definitions into your own tools or dashboards.



<Card title="Go to Prowler Hub" href="https://hub.prowler.com" />

Prowler Hub also provides a fully documented public API that you can integrate into your internal tools, dashboards, or automation workflows.

📚 Explore the API docs at: https://hub.prowler.com/api/docs

Whether you’re customizing policies, managing compliance, or enhancing visibility, Prowler Hub is built to support your security operations.

---

### Getting Started Products Prowler Lighthouse Ai (`docs/getting-started/products/prowler-lighthouse-ai.mdx`)

---
title: 'Overview'
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="5.8.0" />

Prowler Lighthouse AI is a Cloud Security Analyst chatbot that helps you understand, prioritize, and remediate security findings in your cloud environments. It's designed to provide security expertise for teams without dedicated resources, acting as your 24/7 virtual cloud security analyst.

<img src="/images/prowler-app/lighthouse/oss/intro.png" alt="Prowler Lighthouse" />

<CardGroup cols={2}>
  <Card title="Lighthouse AI on Prowler Cloud" icon="cloud" href="/getting-started/products/prowler-cloud-lighthouse">
    Discover the enhanced Cloud experience: persistent chat sessions, GPT-5.5 by default, a dedicated agentic view, and transparent reasoning
  </Card>
  <Card title="Set Up Lighthouse AI" icon="rocket" href="/user-guide/tutorials/prowler-app-lighthouse#set-up">
    Learn how to configure Lighthouse AI with your preferred LLM provider
  </Card>
</CardGroup>

## Capabilities

Prowler Lighthouse AI is designed to be your AI security team member, with capabilities including:

### Natural Language Querying

Ask questions in plain English about your security findings. Examples:

- "What are my highest risk findings?"
- "Show me all S3 buckets with public access."
- "What security issues were found in my production accounts?"

<img src="/images/prowler-app/lighthouse/oss/feature1.png" alt="Natural language querying" />

### Detailed Remediation Guidance

Get tailored step-by-step instructions for fixing security issues:

- Clear explanations of the problem and its impact
- Commands or console steps to implement fixes
- Alternative approaches with different solutions

<img src="/images/prowler-app/lighthouse/oss/feature2.png" alt="Detailed Remediation" />

### Enhanced Context and Analysis

Lighthouse AI can provide additional context to help you understand the findings:

- Explain security concepts related to findings in simple terms
- Provide risk assessments based on your environment and context
- Connect related findings to show broader security patterns

<img src="/images/prowler-app/lighthouse/oss/config.png" alt="Business Context" />

<img src="/images/prowler-app/lighthouse/oss/feature3.png" alt="Contextual Responses" />

## Important Notes

Prowler Lighthouse AI is powerful, but there are limitations:

- **Continuous improvement**: Please report any issues, as the feature may make mistakes or encounter errors, despite extensive testing.
- **Access limitations**: Lighthouse AI can only access data the logged-in user can view. If you can't see certain information, Lighthouse AI can't see it either.
- **NextJS session dependence**: If your Prowler application session expires or logs out, Lighthouse AI will error out. Refresh and log back in to continue.
- **Response quality**: The response quality depends on the selected LLM provider and model. Choose models with strong tool-calling capabilities for best results. We recommend `gpt-5` model from OpenAI.

## Architecture



## Extending Lighthouse AI

Lighthouse AI retrieves data through Prowler MCP. To add new capabilities, extend the Prowler MCP Server with additional tools and Lighthouse AI discovers them automatically.

For development details, see:
- [Lighthouse AI Architecture](/developer-guide/lighthouse-architecture) - Internal architecture and extension points
- [Extending the MCP Server](/developer-guide/mcp-server) - Adding new tools to Prowler MCP

### Getting Help

If you encounter issues with Prowler Lighthouse AI or have suggestions for improvements, please [reach out through our Slack channel](https://goto.prowler.com/slack).

### What Data Is Shared to LLM Providers?

The following API endpoints are accessible to Prowler Lighthouse AI. Data from the following API endpoints could be shared with LLM provider depending on the scope of user's query:

## FAQs

**1. Which LLM providers are supported?**

Lighthouse AI supports three providers:

- **OpenAI** - GPT models (GPT-5, GPT-4o, etc.)
- **Amazon Bedrock** - Claude, Llama, Titan, and other models via AWS
- **OpenAI Compatible** - Custom endpoints like OpenRouter, Ollama, or any OpenAI-compatible service

For detailed configuration instructions, see [Using Multiple LLM Providers with Lighthouse](/user-guide/tutorials/prowler-app-lighthouse-multi-llm).

**2. Why don't some models appear in Lighthouse AI?**

LLM providers offer different types of models. Not every model can be integrated with Lighthouse AI (for example, text-to-speech, vision, embedding, computer use, etc.).

Lighthouse AI requires models that support:

- Text input
- Text output
- Tool calling

Lighthouse AI [automatically filters](https://github.com/prowler-cloud/prowler/blob/master/api/src/backend/tasks/jobs/lighthouse_providers.py#L341-L353) out models that do not support these capabilities, so some provider models may not appear in the Lighthouse AI model list.

**3. Is my security data shared with LLM providers?**

Minimal data is shared to generate useful responses. Agent can access security findings and remediation details when needed. Provider secrets are protected by design and cannot be read. The LLM provider credentials configured with Lighthouse AI are only accessible to the Next.js server and are never sent to the LLM providers. Resource metadata (names, tags, account/project IDs, etc.) may be shared with the configured LLM provider based on query requirements.

**4. Can the Lighthouse AI change my cloud environment?**

No. The agent doesn't have the tools to make the changes, even if the configured cloud provider API keys contain permissions to modify resources.

---

### Getting Started Products Prowler Mcp (`docs/getting-started/products/prowler-mcp.mdx`)

---
title: "Overview"
---

**Prowler MCP Server** brings the entire Prowler ecosystem to AI assistants through the Model Context Protocol (MCP). It enables seamless integration with AI tools like Claude Desktop, Cursor, and other MCP clients, allowing interaction with Prowler's security capabilities through natural language.

<Warning>
**Preview Feature**: This MCP server is currently under active development. Features and functionality may change. We welcome your feedback—please report any issues on [GitHub](https://github.com/prowler-cloud/prowler/issues) or join our [Slack community](https://goto.prowler.com/slack) to discuss and share your thoughts.
</Warning>

## Quickest Way to Connect: Cloud MCP Server

The fastest way to get started is the **Cloud MCP Server** at `https://mcp.prowler.com/mcp` — no installation, always up to date, and maintained by Prowler. Just point your MCP client at the URL and authenticate with a [Prowler API key](/user-guide/tutorials/prowler-app-api-keys) as a Bearer token:

```json
{
  "mcpServers": {
    "prowler": {
      "url": "https://mcp.prowler.com/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key-here>"
      }
    }
  }
}
```

<Card title="Connect Your MCP Client to the Cloud MCP Server" icon="cloud" href="/getting-started/basic-usage/prowler-mcp#cloud-mcp-server-configuration-recommended" horizontal>
  Step-by-step setup for Claude Code, Codex, Cursor, VS Code, and other agents.
</Card>

<Note>
Prefer to run it yourself? The **Local MCP Server** runs on your own machine or infrastructure. The Cloud MCP Server additionally provides the `prowler_cloud_*` tools for Prowler Cloud-specific features: [Alerts](/user-guide/tutorials/prowler-alerts), [Findings Triage](/user-guide/tutorials/prowler-app-findings-triage), [Scan Scheduling](/user-guide/tutorials/prowler-scan-scheduling), and Scan Configurations. See [Cloud vs Local MCP Server](#cloud-vs-local-mcp-server).
</Note>

## What is the Model Context Protocol?

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard developed by Anthropic that enables AI assistants to securely connect to external data sources and tools. It functions as a universal adapter enabling AI assistants to interact with various services through a standardized interface.

## Key Capabilities

The Prowler MCP Server provides four integration points:

### 1. Prowler Cloud, Private Cloud & Local Server

Full access to your Prowler deployment — Prowler Cloud, Prowler Private Cloud, or Prowler Local Server — for:
- **Findings Analysis**: Query, filter, and analyze security findings across all your cloud environments
- **Provider Management**: Create, configure, and manage your configured Prowler providers (AWS, Azure, GCP, etc.)
- **Scan Orchestration**: Trigger on-demand scans, track their progress, and schedule a daily scan
- **Resource Inventory**: Search and view detailed information about your audited resources
- **Muting Management**: Create and manage muting lists/rules to suppress non-relevant findings
- **Attack Paths Analysis**: Analyze privilege escalation chains and security misconfigurations through graph-based analysis of cloud resource relationships
- **Integrations Management**: Set up and troubleshoot where Prowler sends its results (Amazon S3, AWS Security Hub, Jira), and turn findings into Jira work items
- **User & Role Management**: List the users in your tenant, identify the authenticated user, browse RBAC roles, and set the role a user holds

### 2. Prowler Cloud Management

Prowler Cloud-only tools for configuration and workflows that a Prowler Local Server does not provide. These are exposed only by the [Cloud MCP Server](#cloud-vs-local-mcp-server):

- **Scan Configurations**: Create reusable check and compliance selections and attach them to providers.
- **Findings Triage**: Record review statuses and notes for individual findings, without suppressing them.
- **Scan Scheduling**: Configure daily, interval, weekly, or monthly recurring scans, one provider at a time or in bulk.
- **Alerts**: Build and dry-run alert rule conditions, manage email recipients, and review fired alerts.

### 3. Prowler Hub

Access to Prowler's comprehensive security knowledge base:
- **Security Checks Catalog**: Browse and search **over 2,000 security checks** across multiple cloud providers.
- **Check Implementation**: View the Python code that powers each security check.
- **Automated Fixers**: Access remediation scripts for common security issues.
- **Compliance Frameworks**: Explore mappings to **over 70 compliance standards and frameworks**.
- **Provider Services**: View available services and checks for each cloud provider.

### 4. Prowler Documentation

Search and retrieve official Prowler documentation:
- **Intelligent Search**: Full-text search across all Prowler documentation.
- **Contextual Results**: Get relevant documentation pages with highlighted snippets.
- **Document Retrieval**: Access complete markdown content of any documentation file.

## MCP Server Architecture

The following diagram illustrates the Prowler MCP Server architecture and its integration points. MCP clients connect to either the **Cloud MCP Server** (recommended) or a **Local MCP Server**. Both reach the same Prowler backends and share the `prowler_*`, `prowler_hub_*`, and `prowler_docs_*` tools; the Cloud MCP Server additionally exposes the Cloud-only `prowler_cloud_*` tools:

```
/* Detailed source-code truncated for AI context efficiency. */
```

The architecture shows how AI assistants connect through the MCP protocol to access Prowler's four namespaced components:
- Prowler Cloud, Prowler Private Cloud, or Prowler Local Server for security operations
- Prowler Cloud management for Cloud-only configuration and workflows
- Prowler Hub for security knowledge
- Prowler Documentation for guidance and reference.

## Use Cases

The Prowler MCP Server enables powerful workflows through AI assistants:

**Security Operations**
- "Show me all critical findings from my AWS production accounts"
- "Register my new AWS account in Prowler and run a scheduled scan every day"
- "List all muted findings and flag the ones whose mute reason is too weak for their severity"
- "Run an attack paths query to find EC2 instances exposed to the Internet with access to sensitive S3 buckets"
- "Send my failed CIS findings for this provider to Jira as work items"

**Prowler Cloud Management** (Cloud MCP Server only)
- "Preview an alert rule for critical AWS findings and create it for my confirmed recipients"
- "Show the triage notes for this finding and mark it as under review"
- "Apply a weekly Monday 06:00 scan schedule to every AWS provider"
- "Create a scan configuration that runs only CIS checks and attach it to my production providers"

**Security Research**
- "Explain what the S3 bucket public access Prowler check does"
- "Find all Prowler checks related to encryption at rest"
- "What is the latest version of the CIS that Prowler is covering per provider?"

**Documentation & Learning**
- "How do I configure Prowler to scan my GCP organization?"
- "What authentication methods does Prowler support for Azure?"
- "How can I contribute with a new security check to Prowler?"

### Example: Creating a Custom Dashboard with Prowler Extracted Data

In the next example you can see how to create a dashboard using Prowler MCP Server and Claude Desktop.

**Used Prompt:**
```
Generate me a security dashboard for the Prowler open source project using live data from Prowler MCP tools.

REQUIREMENTS:
1. Fetch real-time data from Prowler Findings using MCP tools
2. Create a single self-contained HTML file and display it
3. Dashboard must be production-ready with modern design

DATA TO FETCH:
Use these MCP tools in this order:
1. Prowler list providers - To get all available configured provider in the account
2. Prowler get latest findings - To get findings information, if there are so many you can use the filter_fields to get less information, or pagination to get in different batches
3. For most critical findings you can get more context and remediation with Prowler Hub to get remediations for example

DESIGN REQUIREMENTS:
- Dark theme (gradient background: #0a0e27 to #131830)
- Card-based layout with glassmorphism effects
- Color scheme:
 * Primary green
 * Secondary purple
- Modern, professional look
- Animated "LIVE DATA" indicator (pulsing green badge)
- Hover effects on all cards (lift, glow, border color change)
- Responsive grid layout
- Mobile-responsive breakpoints at 768px
- Single HTML file with all CSS and JavaScript embedded
- No external dependencies

SPECIFIC DETAILS TO INCLUDE:
- Show actual counts from the data (don't hardcode numbers)
- Add timestamp showing when dashboard was generated
- Link to GitHub repository: https://github.com/prowler-cloud/prowler

OUTPUT:
Generate the complete HTML file and display it
```

**Video:**
<iframe
  className="w-full aspect-video rounded-xl"
  src="https://www.youtube.com/embed/li29KNmYd4g?si=P3m6eB2z0Cqqse_H"
  title="Prowler MCP Server - Creating a dashboard"
  frameBorder="0"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
  allowFullScreen
></iframe>


## Cloud vs Local MCP Server

There are two ways to run the Prowler MCP Server. For almost everyone, the **Cloud MCP Server** is the right choice — it needs no installation and is maintained by Prowler. The **Local MCP Server** exists for users who need to run it on their own machine or infrastructure.

| | ☁️ **Cloud MCP Server** (Recommended) | 💻 **Local MCP Server** |
|---|---|---|
| **Cloud-only tools** (`prowler_cloud_*`) | ✅ Alerts, Findings Triage, Scan Scheduling, Scan Configurations | ❌ Not available |
| **Endpoint** | `https://mcp.prowler.com/mcp` | Runs on your machine or infrastructure |
| **Setup** | Just configure your MCP client | Install via Docker, or source |
| **Transport** | HTTP | STDIO (subprocess) or self-hosted HTTP |
| **Maintenance** | Managed by Prowler, always up to date | You manage updates |
| **Requirements** | None (just an MCP client) | Python 3.12+ or Docker |
| **Authentication** | API key or JWT token | API key/JWT (HTTP) or env vars (STDIO) |

### ☁️ Cloud MCP Server (Recommended)

Prowler's managed MCP server at `https://mcp.prowler.com/mcp`. No installation, always up to date, and it includes the `prowler_cloud_*` tools for Prowler Cloud-specific features: Alerts, Findings Triage, Scan Scheduling, and Scan Configurations. This is the path we recommend for nearly all users — go straight to the [Configuration guide](/getting-started/basic-usage/prowler-mcp#cloud-mcp-server-configuration-recommended).

### 💻 Local MCP Server

Run the server yourself when you need full control over the deployment. It connects to Prowler Cloud, Prowler Private Cloud, or Prowler Local Server and can run in two modes:

- **STDIO mode** — the server runs as a subprocess of your MCP client. Authentication via environment variables.
- **Self-hosted HTTP mode** — deploy your own remote HTTP server. Authentication via API key or JWT token.

Both require Python 3.12+ or Docker, plus network access to `https://hub.prowler.com` (Prowler Hub), `https://docs.prowler.com` (Prowler Documentation), and the Prowler API or Prowler Local Server API (Prowler features). See the [Installation guide](/getting-started/installation/prowler-mcp) to get started.

<Note>
**No Authentication Required**: Prowler Hub and Prowler Documentation features work without authentication on both the Cloud and Local MCP Server. A Prowler API key is only required to access Prowler features (Prowler Cloud, Prowler Private Cloud, or Prowler Local Server).
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Configuration" icon="gear" href="/getting-started/basic-usage/prowler-mcp">
    Connect your MCP client to the Cloud MCP Server
  </Card>
  <Card title="Tools Reference" icon="wrench" href="/getting-started/basic-usage/prowler-mcp-tools">
    Explore all available tools and capabilities
  </Card>
</CardGroup>

<Card title="Local Installation" icon="download" href="/getting-started/installation/prowler-mcp" horizontal>
  Run the Local MCP Server yourself using Docker, source, or uvx
</Card>

---

### Getting Started Products Prowler Sdk (`docs/getting-started/products/prowler-sdk.mdx`)

---
title: 'Prowler SDK'
---

Prowler SDK is the Python library that powers Prowler CLI and Prowler Local Server. It implements the providers, services, and security checks that every Prowler product runs.

To use or extend Prowler SDK, start with the Developer Guide:

<Card title="Developer Guide" icon="code" href="/developer-guide/introduction">
  Providers, services, checks, and testing: everything needed to work with Prowler SDK.
</Card>

---

### User Guide Ai Agents Claude Code (`docs/user-guide/ai-agents/claude-code.mdx`)

---
title: "Connect Claude Code to Prowler MCP Server"
sidebarTitle: "Claude Code"
---

Connect [Claude Code](https://www.claude.com/product/claude-code) to the Prowler Cloud MCP Server at `https://mcp.prowler.com/mcp`.

## Where Claude Code Runs

Claude Code runs in two places. Both read the same configuration file, so you set it up **once from a terminal** and it works in both.

| Surface | How you open it | Reads | Covered by |
|---|---|---|---|
| **Claude Code CLI** | `claude` in a terminal | `~/.claude.json` | This guide |
| **Claude Code in the desktop app** | The **Code** tab inside the Claude app | `~/.claude.json` — the same file | This guide, [set up from a terminal](#claude-code-in-the-desktop-app-code-tab) |
| **Claude app Chat** | The **Chat** tab inside the Claude app | `claude_desktop_config.json` | [Claude App Chat](/user-guide/ai-agents/claude-desktop) — a separate setup |

<Warning>
**The Chat tab is not Claude Code.** It is a different product surface with its own configuration file and its own connection method (a local bridge). Nothing on this page applies to it. If you want Prowler in Chat, use the [Claude App Chat](/user-guide/ai-agents/claude-desktop) guide instead.
</Warning>

## Choose Your Setup

There are two ways to connect. Both end with the same MCP Server connection, the difference is what comes with it.

| | 🔌 **Prowler Plugin** | ⚙️ **MCP Connection Only** |
|---|---|---|
| **What you get** | The MCP connection **plus** the official Prowler skills for cloud security tasks | The MCP connection |
| **Setup** | Two slash commands, prompts for the API key | One `claude mcp add` command |
| **Guided workflows** | ✅ Skills drive multi-step security work end to end | ❌ You drive the conversation |
| **Best for** | Structured cloud security work, such as taking an account to compliance | Ad-hoc queries and your own workflows |
| **Where to use it** | Claude Code CLI | Claude Code CLI, and the **recommended setup for the desktop app's [Code tab](#claude-code-in-the-desktop-app-code-tab)** |

<Note>
**The plugin already includes the MCP connection.** If you install the plugin, do **not** also run `claude mcp add` — you would end up with the server configured twice.
</Note>

## Prerequisites

- **Claude Code** installed and signed in. See the [official install guide](https://www.claude.com/product/claude-code).
- **A Prowler Cloud account.** The free tier is enough to start. Sign up at [cloud.prowler.com](https://cloud.prowler.com).

## Get Your Prowler API Key

Create an API key in Prowler Cloud and copy it. The key begins with `pk_` and is shown only once. Check the [API Keys](/user-guide/tutorials/prowler-app-api-keys#creating-api-keys) guide for details.

---

# Option 1: Install the Prowler Plugin

<Warning>
**Preview**: this plugin is under active development. Please report issues on [GitHub](https://github.com/prowler-cloud/prowler/issues) or join the [Slack community](https://goto.prowler.com/slack) for feedback.
</Warning>

End-to-end cloud security from inside Claude Code, powered by the Prowler MCP server. The plugin bundles the official Prowler skills, task-specific workflows that let Claude carry out multi-step security work against a Prowler Cloud-connected account, rather than answering one question at a time.

### Included Skills

| Skill | What it does |
| --- | --- |
| `prowler:framework-compliance-triage` | Walks an account through a compliance assessment and remediates findings until the chosen security or industry framework is compliant. |

<Note>
More skills are on the way. Installing the plugin keeps you current — new skills arrive with plugin updates, no extra configuration required.
</Note>

## Installation (Claude Code CLI)

<Tabs>
  <Tab title="From GitHub (recommended)">
    Inside a Claude Code session:

    ```text
    /plugin marketplace add prowler-cloud/prowler
    /plugin install prowler@prowler-plugins
    ```
  </Tab>
  <Tab title="From a local clone">
    If you already have the repository checked out:

    ```text
    /plugin marketplace add /absolute/path/to/prowler
    /plugin install prowler@prowler-plugins
    ```
  </Tab>
</Tabs>

On first install, Claude Code prompts for your **Prowler API key**. The value is stored securely (macOS keychain or `~/.claude/.credentials.json`) and used to authenticate against Prowler Cloud.

## Verify the Installation

In a Claude Code session:

```text
/mcp          → "prowler" appears as a connected server
/plugin       → "prowler" enabled, with the bundled Prowler skills listed
```

If `/mcp` reports the `prowler` server as failed, the most common cause is a rejected API key, re-issue one in Prowler Cloud and reinstall the plugin so it re-prompts.

## Usage

Describe the security task you want done and Claude selects the matching skill.

### Framework Compliance Triage

Mention the framework you want to comply with:

- *"Make my AWS production account compliant with CIS 4.0."*
- *"Make my current Terraform project compliant with Prowler ThreatScore Compliance Framework based on the latest scan results."*
- *"Help me get to 100% on PCI-DSS for this GCP project."*

You pick a **primary tool** (Terraform, gh / az / aws CLI, web console, or mixed) and a **mode**:

<CardGroup cols={2}>
  <Card title="Claude-assisted (default)" icon="hand">
    Claude shows each fix — target resource, exact commands, side effects, reversibility — and waits for your go-ahead before applying.
  </Card>
  <Card title="Claude autonomous" icon="robot">
    Claude presents a single up-front plan grouped by shared fixes, waits for one confirmation, then proceeds. It pauses mid-loop if a fix has wide blast radius or a finding is not applicable.
  </Card>
</CardGroup>

Claude tracks progress in a markdown report under `.prowler/` at your project root — one file per framework × account. Open it any time to see exactly where the flow is. When all findings are addressed, Claude proposes a fresh Prowler scan to verify everything end-to-end.

## Uninstalling

```text
/plugin uninstall prowler@prowler-plugins
/plugin marketplace remove prowler-plugins
```

The stored API key is removed automatically.

---

# Option 2: Connect the MCP Server Only

Choose this when you want Prowler's tools available without the Prowler skills.

## Add the Server

Claude Code connects to remote HTTP MCP servers natively and supports custom headers, so no bridge is required.

```bash
export PROWLER_API_KEY="pk_your_api_key_here"

claude mcp add --transport http prowler https://mcp.prowler.com/mcp \
  --header "Authorization: Bearer $PROWLER_API_KEY" \
  --scope user
```

<Frame>
  <img src="/images/prowler-mcp/claude/claude-code-mcp-add.png" alt="Terminal showing the claude mcp add command and its confirmation output" />
</Frame>

<Warning>
**Always pass `--scope user`.** The default scope is `local`, which binds the server to the single directory you ran the command in. A locally-scoped server does not load when you open Claude Code anywhere else — this is the most common reason Prowler tools appear to vanish.
</Warning>

| Scope | Loads in | Shared | Stored in |
|-------|----------|--------|-----------|
| `user` | All your projects | No | `~/.claude.json`, top-level `mcpServers` |
| `project` | Current project only | Yes, via version control | `.mcp.json` in the project root |
| `local` (default) | Current project only | No | `~/.claude.json`, under that project's entry |

When the same server name exists in more than one scope, precedence is **local → project → user**. The winning entry is used whole; fields are not merged.

<Warning>
Avoid `--scope project` for Prowler. That writes `.mcp.json` into your repository, and committing the file would publish your API key.
</Warning>

<Note>
**Local server:** Replace the URL with your own HTTP endpoint. Everything else stays the same.
</Note>

## Verify the Connection

```bash
claude mcp get prowler   # shows which scope holds the definition
claude mcp list          # lists all servers and their status
```

Inside a Claude Code session, run `/mcp` to see connected servers and their tools.

<Frame>
  <img src="/images/prowler-mcp/claude/claude-code-mcp-command.png" alt="Claude Code session showing the /mcp command output with the Prowler server connected" />
</Frame>

## Start Using Prowler MCP

- *"Show me all critical findings from my AWS accounts"*
- *"What does the S3 bucket public access check do?"*
- *"Onboard this new AWS account in my Prowler organization"*

<Frame>
  <img src="/images/prowler-mcp/claude/claude-code-prowler-query.png" alt="Claude Code answering a question about critical findings using Prowler MCP tools" />
</Frame>

---

# Claude Code in the Desktop App (Code Tab)

The **Code** tab in the Claude desktop app runs the same Claude Code as the CLI, and reads the same `~/.claude.json`. There is no separate Prowler setup for it — you configure it **from a terminal** and the Code tab picks it up.

<Note>
**Use [Option 2](#option-2-connect-the-mcp-server-only) with `--scope user` here.** It is the recommended setup for the Code tab. The Prowler plugin ([Option 1](#option-1-install-the-prowler-plugin)) is not the recommended route for the desktop app — install it in the Claude Code CLI instead.
</Note>

<Warning>
**You cannot do this from inside the app.** The desktop app has no interface for adding an MCP server to a Claude Code session. **Settings → Connectors** configures the **Chat** tab, not the **Code** tab, so anything added there never reaches Claude Code. Trying to configure it from the app is the main reason this appears not to work.
</Warning>

<Steps>
  <Step title="Add the server at user scope from a terminal">
    In a normal terminal — not inside the app:

    ```bash
    export PROWLER_API_KEY="pk_your_api_key_here"

    claude mcp add --transport http prowler https://mcp.prowler.com/mcp \
      --header "Authorization: Bearer $PROWLER_API_KEY" \
      --scope user
    ```

    `--scope user` is what makes this work. It writes to `~/.claude.json`, the file the Code tab reads.
  </Step>

  <Step title="Confirm it landed at user scope">
    ```bash
    claude mcp get prowler
    ```

    The scope must be `user`. A `local`-scoped server is bound to the directory you ran the command in and will not load in an app session opened elsewhere.
  </Step>

  <Step title="Restart the Claude app">
    Quit the app completely and reopen it. Configuration is read at startup.
  </Step>

  <Step title="Verify in the Code tab">
    Open a **Code** tab session and ask for a Prowler tool: "Do you have access to the Prowler MCP tools?", it should respond with a list of available tools or confirming that it has access.
  </Step>
</Steps>

---

# Claude App Chat (Chat Tab)

Not covered by this page. The **Chat** tab is a separate surface: it does not read `~/.claude.json`, so a server added with `claude mcp add` appears in the CLI and in the Code tab but **never** in Chat. That is expected behavior, not a broken setup.

Chat reads `claude_desktop_config.json` and reaches the Prowler MCP Server through a local bridge.

<Card title="Connect the Claude App Chat" icon="comment" href="/user-guide/ai-agents/claude-desktop" horizontal>
  Separate guide: local bridge and its own configuration file
</Card>

---

# Troubleshooting

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| `/mcp` shows `prowler` as failed | Rejected API key | Generate a new one in Prowler Cloud. With the plugin, reinstall it to re-prompt. |
| No MCP servers configured | Server added at `local` scope from another directory | Run `claude mcp get prowler`, then re-add with `--scope user`. |
| A stale entry overrides a working one | Precedence is local → project → user | `claude mcp remove prowler --scope local` |
| Tools appear in the CLI but not in the app's **Code** tab | Server added at `local` scope, or the app was not restarted | Re-add with `--scope user`, then quit and reopen the app. See [Claude Code in the Desktop App](#claude-code-in-the-desktop-app-code-tab). |
| Tools appear in the **Code** tab but not the **Chat** tab | Chat is a different surface with its own config file | Expected. Set Chat up separately, see [Claude App Chat](/user-guide/ai-agents/claude-desktop). |
| No way to add the server from inside the app | The app has no MCP interface for Claude Code sessions | Configure it from a terminal with `--scope user`, then restart the app. See [Claude Code in the Desktop App](#claude-code-in-the-desktop-app-code-tab). |
| Skill not invoked when expected | The prompt didn't match any skill's description | Name the task explicitly. For compliance triage, mention the framework plus "compliance" or "compliant". |
| "Framework not supported" | Prowler Hub does not list the framework for that provider | Open an issue or PR at [github.com/prowler-cloud/prowler](https://github.com/prowler-cloud/prowler). |

### Authentication Fails With 401

- Confirm the header value includes the `Bearer ` prefix.
- Check that `PROWLER_API_KEY` was set when you ran `claude mcp add` — the shell expands it at that moment and stores the resulting literal value. If the variable was empty, the stored header reads `Bearer ` with nothing after it. Verify with `claude mcp get prowler`.
- Confirm the key has not been revoked in Prowler Cloud.

## Next Steps

<CardGroup cols={2}>
  <Card title="Tools Reference" icon="wrench" href="/getting-started/basic-usage/prowler-mcp-tools">
    Explore all available tools and capabilities
  </Card>
  <Card title="All MCP Clients" icon="plug" href="/getting-started/basic-usage/prowler-mcp">
    Configuration reference for every supported client
  </Card>
</CardGroup>

## Getting Help

- Search for existing [GitHub issues](https://github.com/prowler-cloud/prowler/issues)
- Ask for help in our [Slack community](https://goto.prowler.com/slack)
- Report a new issue on [GitHub](https://github.com/prowler-cloud/prowler/issues/new)

---

### User Guide Ai Agents Claude Desktop (`docs/user-guide/ai-agents/claude-desktop.mdx`)

---
title: "Connect the Claude App Chat to Prowler MCP Server"
sidebarTitle: "Claude App (Chat)"
---

Connect the **Chat** tab of the Claude desktop app to the Prowler Cloud MCP Server at `https://mcp.prowler.com/mcp`.

<Warning>
**This page covers the Chat tab only.** Looking for **Claude Code** — either the CLI or the app's **Code** tab? Those are a different surface, with a different configuration file and a different connection method. See [Connect Claude Code](/user-guide/ai-agents/claude-code).
</Warning>

## Prerequisites

- **Claude desktop app** installed and signed in.
- **Node.js and npm**, to install the bridge.
- **A Prowler Cloud account.** The free tier is enough to start. Sign up at [cloud.prowler.com](https://cloud.prowler.com).

## Why "Add Custom Connector" Does Not Work

The app's **Settings → Connectors → Add custom connector** dialog is the obvious place to paste an MCP URL, but it does not fit the Prowler Cloud MCP Server for two independent reasons:

1. **Connectors authenticate with OAuth.** Authenticating with a fixed API key sent as a request header is a separate mechanism that Anthropic documents as **beta**, rolled out on request. Without it, the dialog offers a URL and OAuth client credentials, with nowhere to supply `Authorization: Bearer pk_...`.
2. **Connectors do not connect from your machine.** Claude reaches your MCP server from Anthropic's cloud infrastructure rather than your local device. A Prowler MCP Server on `localhost`, behind a VPN, or restricted by an IP allowlist is unreachable that way regardless of authentication.

Use a local bridge instead, as described below.

## Step 1: Get Your Prowler API Key

Create an API key in Prowler Cloud and copy it. The key begins with `pk_` and is shown only once. Check the [API Keys](/user-guide/tutorials/prowler-app-api-keys#creating-api-keys) guide for details.

## Step 2: Install the Bridge

`mcp-remote` presents the remote HTTP server to Claude as a local STDIO server and injects the `Authorization` header. Install a pinned version into a dedicated directory:

```bash
mkdir -p ~/.local/share/prowler-mcp-bridge
cd ~/.local/share/prowler-mcp-bridge
npm init -y
npm install --save-exact mcp-remote@0.1.38
```

<Warning>
Do not configure Claude to run `npx mcp-remote` directly. `npx` can fetch and execute a new version on every launch, which means unreviewed code runs with access to your API key. Install a pinned version and point Claude at the installed binary.
</Warning>

<Note>
`mcp-remote` is community-maintained and is not an Anthropic product. Review it before use.
</Note>

## Step 3: Edit the Configuration File

In the Claude app, go to **Settings → Developer** and click **Edit Config**. This reveals `claude_desktop_config.json`:

- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

<Frame>
  <img src="/images/prowler-mcp/claude/claude-desktop-developer-settings.png" alt="Claude app Settings Developer tab showing the Edit Config button" />
</Frame>

Add the following, replacing the `command` path with the absolute path to the installed binary and the placeholder with your API key:

```json
{
  "mcpServers": {
    "prowler": {
      "command": "/absolute/path/to/.local/share/prowler-mcp-bridge/node_modules/.bin/mcp-remote",
      "args": [
        "https://mcp.prowler.com/mcp",
        "--header",
        "Authorization: Bearer ${PROWLER_API_KEY}"
      ],
      "env": {
        "PROWLER_API_KEY": "pk_your_api_key_here"
      }
    }
  }
}
```

<Note>
**Local server:** Replace the URL with your own HTTP endpoint. Everything else stays the same.
</Note>

## Step 4: Restart the App

Quit the Claude app completely and reopen it. Configuration is read at startup.

## Step 5: Start Using Prowler MCP

Open a Chat conversation and ask questions that use the Prowler tools:

- *"Show me all critical findings from my AWS accounts"*
- *"What does the S3 bucket public access check do?"*
- *"Summarize my CIS compliance status by provider"*

<Frame>
  <img src="/images/prowler-mcp/claude/claude-desktop-prowler-tools.png" alt="Claude app chat showing the Prowler MCP tools available" />
</Frame>

## Troubleshooting

### Server Does Not Appear After Editing the Config

- Quit and reopen the app entirely — closing the window is not enough on macOS.
- Confirm `claude_desktop_config.json` is valid JSON.
- Confirm the `command` path points at a real executable. A wrong path surfaces as the server failing to start rather than as an auth error.

### Tools Appear in Claude Code but Not in Chat

Expected. The Chat tab does not read `~/.claude.json`, so servers added with `claude mcp add` never appear here. The Chat tab needs an entry in `claude_desktop_config.json`, which is what this guide sets up.

### Authentication Fails With 401

- Confirm the header value includes the `Bearer ` prefix.
- Confirm the key has not been revoked in Prowler Cloud.

### Checking the Logs

- **macOS:** `~/Library/Logs/Claude/mcp*.log`
- **Windows:** `%APPDATA%\Claude\logs\mcp*.log`

```bash
tail -f ~/Library/Logs/Claude/mcp*.log
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Tools Reference" icon="wrench" href="/getting-started/basic-usage/prowler-mcp-tools">
    Explore all available tools and capabilities
  </Card>
  <Card title="All MCP Clients" icon="plug" href="/getting-started/basic-usage/prowler-mcp">
    Configuration reference for every supported client
  </Card>
</CardGroup>

## Getting Help

- Search for existing [GitHub issues](https://github.com/prowler-cloud/prowler/issues)
- Ask for help in our [Slack community](https://goto.prowler.com/slack)
- Report a new issue on [GitHub](https://github.com/prowler-cloud/prowler/issues/new)

---

### User Guide Ai Agents Codex (`docs/user-guide/ai-agents/codex.mdx`)

---
title: "Connect Codex / ChatGPT Desktop to Prowler MCP Server"
sidebarTitle: "Codex / ChatGPT"
---

Connect [OpenAI Codex](https://learn.chatgpt.com/docs/extend/mcp) to the Prowler Cloud MCP Server at `https://mcp.prowler.com/mcp` so Codex can query findings, inspect checks, and manage your Prowler providers.

## Which Codex Surfaces Work

Codex keeps MCP servers in one file, `~/.codex/config.toml`. You can set it up from either the **Codex / ChatGPT desktop app** or the **Codex CLI** — both write to that same file, so pick whichever you already use.

| Surface | Set it up here | Notes |
|---------|----------------|-------|
| **[Codex / ChatGPT desktop app](https://learn.chatgpt.com/docs/app)** (macOS, Windows) | ✅ Yes | **Settings → MCP servers** |
| **Codex CLI** (terminal) | ✅ Yes | `codex mcp` commands |
| **Codex IDE extension** (VS Code) | Inherits | Works automatically once the app or CLI is configured |
| **ChatGPT on the web** | ❌ No | Does not read local Codex configuration |

<Note>
**Codex and ChatGPT share one desktop app.** Since July 2026 the standalone Codex app and the ChatGPT desktop app are the same application: Codex is a dedicated coding surface inside it, alongside Chat and Work. If you already had the Codex app, updating turns it into the new ChatGPT desktop app and it still opens in Codex. Either way, this guide applies.

Not to be confused with **ChatGPT Classic**, the name given to the previous-generation ChatGPT desktop app.
</Note>

<Note>
**Configure once, use everywhere.** The Codex documentation states that the ChatGPT desktop app, Codex CLI, and IDE extension "share this configuration. Once you configure your MCP servers, you can switch among those clients without redoing setup." Set the server up in the app or the CLI and the IDE extension picks it up with no extra work.
</Note>

## Prerequisites

- **The Codex / ChatGPT desktop app, or Codex CLI 0.46.0 or later.** Remote MCP servers over streamable HTTP were added to the CLI in 0.46.0 — check with `codex --version` and upgrade if needed.
- **A Prowler Cloud account.** The free tier is enough to start. Sign up at [cloud.prowler.com](https://cloud.prowler.com).

## Step 1: Get Your Prowler API Key

Create an API key in Prowler Cloud and copy it. The key begins with `pk_` and is shown only once. Check the [API Keys](/user-guide/tutorials/prowler-app-api-keys#creating-api-keys) guide for details.

## Step 2: Add the Prowler MCP Server

The Prowler MCP Server needs two request headers: `Authorization` to authenticate you, and `User-Agent` because Codex does not send one by default.

Each tab below is a complete setup — follow the one that matches the surface you use.

<Tabs>
  <Tab title="Codex / ChatGPT desktop app">
    1. Open **Settings** and select **Plugins → MCPs**
    2. Click **Add server**
    3. Enter `prowler` as the name and choose type **Streamable HTTP**
    4. Enter the URL `https://mcp.prowler.com/mcp`
    5. Add two headers:

       | Header | Value |
       |--------|-------|
       | `Authorization` | `Bearer pk_your_api_key_here` |
       | `User-Agent` | `codex` |

    6. Save the server

    <Frame>
      <img src="/images/prowler-mcp/codex/codex-app-mcp-servers.png" alt="Codex / ChatGPT desktop app Settings showing the MCP servers panel with the Add server dialog and both headers filled in" />
    </Frame>

    <Note>
    **Enter the key directly here rather than using an environment variable.** Codex can read credentials from an environment variable, but desktop applications do not reliably inherit variables exported in a shell profile — on macOS an app launched from Finder or the Dock typically sees none of them. Pasting the key into the dialog is the approach that works consistently in the app.
    </Note>

    <Warning>
    **This stores your API key in plain text** in `~/.codex/config.toml`. Treat that file accordingly: exclude it from dotfile repositories and config sync, and create the key from an account with the minimum permissions you need so its exposure is limited. Revoke and re-issue the key in Prowler Cloud if the file is ever shared.
    </Warning>
  </Tab>

  <Tab title="Codex CLI">
    Register the server:

    ```bash
    codex mcp add prowler --url https://mcp.prowler.com/mcp
    ```

    Codex confirms with `Added global MCP server 'prowler'.`

    Then add both headers by hand, since `codex mcp add` has no flag for headers. Open `~/.codex/config.toml` and complete the entry:

    ```toml
    [mcp_servers.prowler]
    url = "https://mcp.prowler.com/mcp"
    http_headers = { Authorization = "Bearer pk_your_api_key_here", "User-Agent" = "codex" }
    ```

    <Note>
    **Write the key literally rather than using an environment variable.** This is the form that works across every Codex surface. All of them read this same file, but only the CLI reliably sees variables exported in your shell profile — see the warning below.
    </Note>

    <Warning>
    **This stores your API key in plain text** in `~/.codex/config.toml`. Treat that file accordingly: exclude it from dotfile repositories and config sync, and create the key from an account with the minimum permissions you need so its exposure is limited. Revoke and re-issue the key in Prowler Cloud if the file is ever shared.
    </Warning>
  </Tab>
</Tabs>

Restart Codex once you are done.

<Note>
**Local server:** Replace the URL with your own HTTP endpoint. Everything else stays the same.
</Note>

## Step 3: Verify the Connection

Run `/mcp` in the app or in a CLI session to list connected servers and their tools.

<Frame>
  <img src="/images/prowler-mcp/codex/codex-mcp-slash-command.png" alt="Codex composer showing the /mcp command output with Prowler tools listed" />
</Frame>

From the CLI you can also inspect the stored entry directly:

```bash
codex mcp list          # one row per server, with status and auth
codex mcp get prowler   # full entry, header values masked
```

<Warning>
**Verify rather than assume.** Codex silently ignores unrecognized keys in `config.toml` — a misspelled key name produces no error at all, and the server simply never receives your credentials. Always confirm with `codex mcp get prowler` after editing the file by hand.
</Warning>

## Step 4: Start Using Prowler MCP

Ask Codex questions that use the Prowler tools:

- *"Show me all critical findings from my AWS accounts"*
- *"What does the S3 bucket public access check do?"*
- *"List my connected Prowler providers and their last scan date"*

<Frame>
  <img src="/images/prowler-mcp/codex/codex-prowler-query.png" alt="Codex answering a question about critical findings using Prowler MCP tools" />
</Frame>

## Troubleshooting

### Startup Fails With HTTP 403 Forbidden

Codex reports a handshake failure on startup, with an HTML error page rather than a JSON response:

```
⚠ MCP client for `prowler` failed to start: MCP startup failed: handshaking with MCP server
  failed: ... unexpected server response: HTTP 403: <html>
  <head><title>403 Forbidden</title></head>
```

The `User-Agent` header is missing. Codex's HTTP client does not send one, and requests without it are rejected before reaching the MCP server. Note this is a **403**, not a 401 — so it is not an API key problem. Add the header as shown in [Step 2](#step-2-add-the-prowler-mcp-server); the value itself does not matter, only that the header is present.

### Authentication Fails With 401

- Run `codex mcp get prowler` and confirm the entry has the headers you expect. Values are masked, but a missing header shows as `-`.
- If you used a literal header, confirm the value starts with `Bearer ` and contains the full key.
- **If it works in the CLI but fails in the desktop app or the VS Code extension, you are almost certainly using an environment variable.** Those surfaces do not inherit your shell profile. Switch that entry to a literal `Authorization` header as shown in [Step 2](#step-2-add-the-prowler-mcp-server).
- If you use an environment variable, verify it is set in the environment Codex was launched from: `echo $PROWLER_API_KEY`.
- With `env_http_headers` the variable must include the `Bearer ` prefix. With `bearer_token_env_var` it must **not** — Codex adds the prefix itself.
- Confirm the key has not been revoked in Prowler Cloud.

### Server Not Listed

- Confirm your Codex CLI version is 0.46.0 or later with `codex --version`.
- Run `codex mcp get prowler`. If it reports the server is not found, the entry was not written or the TOML table name is misspelled.
- Check for a typo in the key names. Codex ignores unknown keys without warning.

### Project-Scoped Config Is Ignored

A `.codex/config.toml` inside a project is loaded **only when the project is trusted**. If your entry lives there and does nothing, trust the project or move the entry to `~/.codex/config.toml`.

### Tools Do Not Appear After Editing the Config

Restart Codex. Configuration is read at startup. In the app, quit completely and reopen it, sometimes just closing the window is not enough.

## Next Steps

<CardGroup cols={2}>
  <Card title="Tools Reference" icon="wrench" href="/getting-started/basic-usage/prowler-mcp-tools">
    Explore all available tools and capabilities
  </Card>
  <Card title="All MCP Clients" icon="plug" href="/getting-started/basic-usage/prowler-mcp">
    Configuration reference for every supported client
  </Card>
</CardGroup>

## Getting Help

- Search for existing [GitHub issues](https://github.com/prowler-cloud/prowler/issues)
- Ask for help in our [Slack community](https://goto.prowler.com/slack)
- Report a new issue on [GitHub](https://github.com/prowler-cloud/prowler/issues/new)

---

### User Guide Ai Agents Cursor (`docs/user-guide/ai-agents/cursor.mdx`)

---
title: "Connect Cursor to Prowler MCP Server"
sidebarTitle: "Cursor"
---

Connect [Cursor](https://cursor.com/docs/mcp) to the Prowler Cloud MCP Server at `https://mcp.prowler.com/mcp` so the Cursor agent can query findings, inspect security checks, and manage your Prowler providers while you work.

Cursor supports remote MCP servers over HTTP natively, so no bridge or local installation is required.

## Prerequisites

- **Cursor** installed and authenticated. See the [official install guide](https://cursor.com/download).
- **A Prowler Cloud account.** The free tier is enough to start. Sign up at [cloud.prowler.com](https://cloud.prowler.com).

## Step 1: Get Your Prowler API Key

Create an API key in Prowler Cloud and copy it. The key begins with `pk_` and is shown only once. Check the [API Keys](/user-guide/tutorials/prowler-app-api-keys#creating-api-keys) guide for details.

## Step 2: Add the Prowler MCP Server

Cursor reads MCP servers from an `mcp.json` file. Choose the scope that fits your use case:

| Scope | File | Applies to |
|-------|------|------------|
| **Global** | `~/.cursor/mcp.json` | Every project you open in Cursor |
| **Project** | `.cursor/mcp.json` in the project root | That project only |

Both files are merged. If the same server name appears in both, the project-level entry takes priority.

For Prowler, the **global** scope is usually the right choice — your findings are not tied to a single repository, and it keeps the API key out of any project directory that might be committed.

<Steps>
  <Step title="Open the MCP settings">
    From Agent Window open **Customize** in the Cursor sidebar, then select the MCP section.

    On earlier versions, press `Cmd + Shift + J` (macOS) or `Ctrl + Shift + J` (Windows/Linux) to open Cursor Settings, then click **Tools & MCP** in the sidebar.


  </Step>

  <Step title="Add a new MCP server">
    Click **New MCP Server** (or **Add Custom MCP**). Cursor opens `mcp.json` in the editor.

    <Frame>
      <img src="/images/prowler-mcp/cursor/cursor-customize-page.png" alt="Cursor Customize page with the MCP section open" />
    </Frame>
  </Step>

  <Step title="Add the Prowler configuration">
    Paste the following, replacing the placeholder with your API key:

    ```json
    {
      "mcpServers": {
        "prowler": {
          "url": "https://mcp.prowler.com/mcp",
          "headers": {
            "Authorization": "Bearer <your-api-key-here>"
          }
        }
      }
    }
    ```

    Save the file. Cursor picks up the change and connects to the server.

    <Frame>
      <img src="/images/prowler-mcp/cursor/cursor-mcp-json.png" alt="Cursor editor showing the completed mcp.json with the Prowler server entry" />
    </Frame>
  </Step>
</Steps>

<Note>
**Local server:** Replace the URL with your own HTTP endpoint. Everything else stays the same.
</Note>

### Keeping the API Key Out of the File

Cursor resolves variables in the `command`, `args`, `env`, `url`, and `headers` fields, so you can reference an environment variable instead of writing the key into `mcp.json`:

```json
{
  "mcpServers": {
    "prowler": {
      "url": "https://mcp.prowler.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:PROWLER_API_KEY}"
      }
    }
  }
}
```

Export the variable in your shell profile (`~/.zshrc`, `~/.bashrc`, or equivalent):

```bash
export PROWLER_API_KEY="pk_your_api_key_here"
```

<Note>
The syntax is `${env:NAME}`, not a bare `${NAME}`. Restart Cursor after changing your shell profile so it inherits the new value.
</Note>

<Warning>
The `envFile` option does **not** work for remote servers — it is STDIO-only. Use `${env:...}` interpolation with variables set in your shell profile instead.
</Warning>

This form is strongly recommended when using a **project-scoped** `.cursor/mcp.json`, since that file may be committed to version control.

## Step 3: Verify the Connection

Return to the MCP settings. The `prowler` server should be listed as enabled, with the Prowler tools shown beneath it.

<Frame>
  <img src="/images/prowler-mcp/cursor/cursor-prowler-connected.png" alt="Cursor MCP settings showing the Prowler server connected with its tools listed" />
</Frame>

## Step 4: Start Using Prowler MCP

Open the chat panel and ask questions that use the Prowler tools:

- *"Show me all critical findings from my AWS accounts"*
- *"What does the S3 bucket public access check do?"*
- *"Which of my providers failed the most CIS checks in the last scan?"*

Cursor asks for approval before running an MCP tool the first time.

<Frame>
  <img src="/images/prowler-mcp/cursor/cursor-prowler-query.png" alt="Cursor chat answering a question about critical findings using Prowler MCP tools" />
</Frame>

You can toggle individual tools on or off from the tools list at the top of the chat panel, which is useful for keeping the active tool count down.

## Troubleshooting

### Server Does Not Connect

- Check that `mcp.json` is valid JSON. A trailing comma or missing brace prevents the whole file from loading.
- Open **MCP Logs** in the Output panel for the specific error.
- Confirm the URL is exactly `https://mcp.prowler.com/mcp`.

### Authentication Fails With 401

- Verify the header value includes the `Bearer ` prefix: `"Bearer pk_..."`, not just the key.
- Confirm the key has not been revoked in Prowler Cloud.
- If using `${env:PROWLER_API_KEY}`, check the variable is set in the environment Cursor inherits. Restart Cursor after editing your shell profile — a value exported only in an already-open terminal will not reach the app.

### The Entire `mcp.json` Is Ignored

Remove any `"type": "streamable-http"` field. One such entry causes the Cursor CLI to drop every server in the file silently.

### Some Prowler Tools Are Missing

Cursor limits how many tools it exposes to the agent at once. With several MCP servers enabled you may exceed it, and some tools become unavailable. Disable servers you are not using, or turn off individual tools from the chat panel's tools list.

## Next Steps

<CardGroup cols={2}>
  <Card title="Tools Reference" icon="wrench" href="/getting-started/basic-usage/prowler-mcp-tools">
    Explore all available tools and capabilities
  </Card>
  <Card title="All MCP Clients" icon="plug" href="/getting-started/basic-usage/prowler-mcp">
    Configuration reference for every supported client
  </Card>
</CardGroup>

## Getting Help

- Search for existing [GitHub issues](https://github.com/prowler-cloud/prowler/issues)
- Ask for help in our [Slack community](https://goto.prowler.com/slack)
- Report a new issue on [GitHub](https://github.com/prowler-cloud/prowler/issues/new)

---

### User Guide Ai Agents Vscode (`docs/user-guide/ai-agents/vscode.mdx`)

---
title: "Connect VS Code and GitHub Copilot to Prowler MCP Server"
sidebarTitle: "VS Code / Copilot"
---

Connect [Visual Studio Code](https://code.visualstudio.com/docs/agents/reference/mcp-configuration) and GitHub Copilot agent mode to the Prowler Cloud MCP Server at `https://mcp.prowler.com/mcp` so Copilot can query findings, inspect security checks, and manage your Prowler providers.

## Prerequisites

- **VS Code 1.102 or later.** MCP support became generally available in 1.102.
- **GitHub Copilot** enabled, with access to agent mode.
- **A Prowler Cloud account.** The free tier is enough to start. Sign up at [cloud.prowler.com](https://cloud.prowler.com).

## Step 1: Get Your Prowler API Key

Create an API key in Prowler Cloud and copy it. The key begins with `pk_` and is shown only once. Check the [API Keys](/user-guide/tutorials/prowler-app-api-keys#creating-api-keys) guide for details.

## Step 2: Add the Prowler MCP Server

VS Code stores MCP servers in an `mcp.json` file. Choose the scope that fits your use case:

| Scope | How to open it | Applies to |
|-------|----------------|------------|
| **User** | Command palette → **MCP: Open User Configuration** | Every workspace |
| **Workspace** | `.vscode/mcp.json` in the project root | That workspace only |

For Prowler, the **user** scope is usually the right choice — your findings are not tied to a single repository, and it keeps the API key out of any project directory that might be committed.

<Steps>
  <Step title="Open the MCP configuration">
    Open the command palette with `Cmd + Shift + P` (macOS) or `Ctrl + Shift + P` (Windows/Linux), then run **MCP: Open User Configuration**.

    VS Code opens your user-level `mcp.json`. Use this command rather than navigating to the file by hand — the file lives inside your active profile folder, and the path differs per profile.

    <Frame>
      <img src="/images/prowler-mcp/vscode/vscode-command-palette.png" alt="VS Code command palette showing the MCP: Open User Configuration command" />
    </Frame>
  </Step>

  <Step title="Add the Prowler configuration">
    Paste the following. This version prompts you for the API key on first use and stores it securely, so the key is never written into the file:

    ```json
    {
      "inputs": [
        {
          "type": "promptString",
          "id": "prowler-api-key",
          "description": "Prowler API Key",
          "password": true
        }
      ],
      "servers": {
        "prowler": {
          "type": "http",
          "url": "https://mcp.prowler.com/mcp",
          "headers": {
            "Authorization": "Bearer ${input:prowler-api-key}"
          }
        }
      }
    }
    ```

    Save the file.

    <Frame>
      <img src="/images/prowler-mcp/vscode/vscode-mcp-json.png" alt="VS Code editor showing the completed mcp.json with the Prowler server entry" />
    </Frame>
  </Step>

  <Step title="Enter your API key">
    Start the server. VS Code prompts for the Prowler API key. Paste it and press Enter — VS Code stores it securely and does not ask again.
  </Step>

</Steps>

<Warning>
**The root key is `servers`, not `mcpServers`.** VS Code uses a different schema from Cursor, Claude, and most other clients. Copying a `mcpServers` snippet from elsewhere silently fails to register the server.
</Warning>

<Note>
**Local server:** Replace the URL with your own HTTP endpoint. Everything else stays the same.
</Note>

## Step 3: Verify the Connection

Run **MCP: List Servers** from the command palette. The `prowler` server should appear as running.

<Frame>
  <img src="/images/prowler-mcp/vscode/vscode-list-servers.png" alt="VS Code MCP: List Servers output showing the Prowler server running" />
</Frame>

Select the server to start, stop, or restart it, and to view its output log if the connection fails.

## Step 4: Start Using Prowler MCP

Open the Chat view and switch the mode selector to **Agent**. Click the tools icon to confirm the Prowler tools are available, then ask:

- *"Show me all critical findings from my AWS accounts"*
- *"What does the S3 bucket public access check do?"*
- *"Summarize my CIS compliance status by provider"*

<Frame>
  <img src="/images/prowler-mcp/vscode/vscode-agent-tools.png" alt="VS Code Copilot Chat in agent mode showing the Prowler tools in the tools picker" />
</Frame>

Copilot asks for confirmation before running an MCP tool for the first time.

## Troubleshooting

### Server Does Not Appear

- Confirm the root key is `servers`, not `mcpServers`.
- Confirm each server entry has `"type": "http"`.
- Check that `mcp.json` is valid JSON.
- Verify your VS Code version is 1.102 or later.

### Authentication Fails With 401

- Verify the header value includes the `Bearer ` prefix.
- Confirm the key has not been revoked in Prowler Cloud.
- If you mistyped the key at the prompt, run **MCP: List Servers**, select `prowler`, and restart it to be prompted again.

### Tools Do Not Appear in Chat

- Make sure the Chat view is in **Agent** mode. MCP tools are not available in Ask mode.
- Open the tools picker and confirm the Prowler tools are enabled.

## Next Steps

<CardGroup cols={2}>
  <Card title="Tools Reference" icon="wrench" href="/getting-started/basic-usage/prowler-mcp-tools">
    Explore all available tools and capabilities
  </Card>
  <Card title="All MCP Clients" icon="plug" href="/getting-started/basic-usage/prowler-mcp">
    Configuration reference for every supported client
  </Card>
</CardGroup>

## Getting Help

- Search for existing [GitHub issues](https://github.com/prowler-cloud/prowler/issues)
- Ask for help in our [Slack community](https://goto.prowler.com/slack)
- Report a new issue on [GitHub](https://github.com/prowler-cloud/prowler/issues/new)

---

### User Guide Cookbooks Cicd Pipeline (`docs/user-guide/cookbooks/cicd-pipeline.mdx`)

---
title: 'Run Prowler in CI/CD and Send Findings to Prowler Cloud'
---

<Warning>
For new projects, use the official [Prowler GitHub Action](/user-guide/tutorials/prowler-app-github-action) — a Docker-based reusable action that runs scans, optionally pushes findings to Prowler Cloud, and uploads SARIF results to GitHub Code Scanning. The GitHub Actions examples below document the legacy pip-based flow.
</Warning>

This cookbook demonstrates how to integrate Prowler into CI/CD pipelines so that security scans run automatically and findings are sent to Prowler Cloud via [Import Findings](/user-guide/tutorials/prowler-import-findings). Examples cover GitHub Actions and GitLab CI.

## Prerequisites

* A **Prowler Cloud** account with an active subscription (see [Prowler Cloud Pricing](https://prowler.com/pricing))
* A Prowler Cloud **API key** with the **Manage Ingestions** permission (see [API Keys](/user-guide/tutorials/prowler-app-api-keys))
* Cloud provider credentials configured in the CI/CD environment (e.g., AWS credentials for scanning AWS accounts)
* Access to configure pipeline workflows and secrets in the CI/CD platform

## Key Concepts

Prowler CLI provides the `--push-to-cloud` flag, which uploads scan results directly to Prowler Cloud after a scan completes. Combined with the `PROWLER_CLOUD_API_KEY` environment variable, this enables fully automated ingestion without manual file uploads.

For full details on the flag and API, refer to the [Import Findings](/user-guide/tutorials/prowler-import-findings) documentation.

<Note>
The examples in this guide use AWS as the target provider, but the same approach applies to any provider supported by Prowler (Azure, GCP, Kubernetes, and others). Replace `prowler aws` with the desired provider command (e.g., `prowler gcp`, `prowler azure`) and configure the corresponding credentials in the CI/CD environment.
</Note>

## GitHub Actions

### Store Secrets

Before creating the workflow, add the following secrets to the repository (under "Settings" > "Secrets and variables" > "Actions"):

* `PROWLER_CLOUD_API_KEY` — the Prowler Cloud API key
* Cloud provider credentials (e.g., `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, or configure OIDC-based role assumption)

### Workflow: Scheduled AWS Scan

This workflow runs Prowler against an AWS account on a daily schedule and on every push to the `main` branch:

```yaml
name: Prowler Security Scan

on:
  schedule:
    - cron: "0 3 * * *"  # Daily at 03:00 UTC
  push:
    branches: [main]
  workflow_dispatch:  # Allow manual triggers

permissions:
  id-token: write   # Required for OIDC
  contents: read

jobs:
  prowler-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/ProwlerScanRole
          aws-region: us-east-1

      - name: Install Prowler
        run: pip install prowler

      - name: Run Prowler Scan
        env:
          PROWLER_CLOUD_API_KEY: ${{ secrets.PROWLER_CLOUD_API_KEY }}
        run: |
          prowler aws --push-to-cloud
```

<Note>
Replace `123456789012` with the actual AWS account ID and `ProwlerScanRole` with the IAM role name. For IAM role setup, refer to the [AWS authentication guide](/user-guide/providers/aws/authentication).
</Note>

### Workflow: Scan Specific Services on Pull Request

To run targeted scans on pull requests without blocking the merge pipeline, use `continue-on-error`:

```yaml
name: Prowler PR Check

on:
  pull_request:
    branches: [main]

jobs:
  prowler-scan:
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/ProwlerScanRole
          aws-region: us-east-1

      - name: Install Prowler
        run: pip install prowler

      - name: Run Prowler Scan
        env:
          PROWLER_CLOUD_API_KEY: ${{ secrets.PROWLER_CLOUD_API_KEY }}
        run: |
          prowler aws --services s3,iam,ec2 --push-to-cloud
```

<Tip>
Limiting the scan to specific services with `--services` reduces execution time, making it practical for pull request checks.
</Tip>

## GitLab CI

### Store Variables

Add the following CI/CD variables in the GitLab project (under "Settings" > "CI/CD" > "Variables"):

* `PROWLER_CLOUD_API_KEY` — mark as **masked** and **protected**
* Cloud provider credentials as needed

### Pipeline: Scheduled AWS Scan

Add the following to `.gitlab-ci.yml`:

```yaml
prowler-scan:
  image: python:3.13-slim
  stage: test
  script:
    - pip install prowler
    - prowler aws --push-to-cloud
  variables:
    PROWLER_CLOUD_API_KEY: $PROWLER_CLOUD_API_KEY
    AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
    AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
    AWS_DEFAULT_REGION: "us-east-1"
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
```

To run the scan on a schedule, create a **Pipeline Schedule** in GitLab (under "Build" > "Pipeline Schedules") with the desired cron expression.

### Pipeline: Multi-Provider Scan

To scan multiple cloud providers in parallel:

```yaml
stages:
  - security

.prowler-base:
  image: python:3.13-slim
  stage: security
  before_script:
    - pip install prowler
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"

prowler-aws:
  extends: .prowler-base
  script:
    - prowler aws --push-to-cloud
  variables:
    PROWLER_CLOUD_API_KEY: $PROWLER_CLOUD_API_KEY
    AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
    AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY

prowler-gcp:
  extends: .prowler-base
  script:
    - prowler gcp --push-to-cloud
  variables:
    PROWLER_CLOUD_API_KEY: $PROWLER_CLOUD_API_KEY
    GOOGLE_APPLICATION_CREDENTIALS: $GCP_SERVICE_ACCOUNT_KEY
```

## Tips and Best Practices

### When to Run Scans

* **Scheduled scans** (daily or weekly) provide continuous monitoring and are ideal for baseline security assessments
* **On-merge scans** catch configuration changes introduced by new code
* **Pull request scans** provide early feedback but should target specific services to keep execution times reasonable

### Handling Scan Failures

By default, Prowler exits with a non-zero code when it finds failing checks. This causes the CI/CD job to fail. To prevent scan results from blocking the pipeline:

* **GitHub Actions**: Add `continue-on-error: true` to the job
* **GitLab CI**: Add `allow_failure: true` to the job

<Note>
Ingestion failures (e.g., network issues reaching Prowler Cloud) do not affect the Prowler exit code. The scan completes normally and only a warning is emitted. See [Import Findings troubleshooting](/user-guide/tutorials/prowler-import-findings#troubleshooting) for details.
</Note>

### Caching Prowler Installation

For faster pipeline runs, cache the Prowler installation:

**GitHub Actions:**
```yaml
- name: Cache pip packages
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-prowler
    restore-keys: ${{ runner.os }}-pip-

- name: Install Prowler
  run: pip install prowler
```

**GitLab CI:**
```yaml
prowler-scan:
  cache:
    paths:
      - .cache/pip
  variables:
    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
```

### Output Formats

To generate additional report formats alongside the cloud upload:

```bash
prowler aws --push-to-cloud -M csv,html -o /tmp/prowler-reports
```

This produces CSV and HTML files locally while also pushing OCSF findings to Prowler Cloud. The local files can be stored as CI/CD artifacts for archival purposes.

### Scanning Multiple AWS Accounts

To scan multiple accounts sequentially in a single job, use [role assumption](/user-guide/providers/aws/role-assumption):

```bash
prowler aws -R arn:aws:iam::111111111111:role/ProwlerScanRole --push-to-cloud
prowler aws -R arn:aws:iam::222222222222:role/ProwlerScanRole --push-to-cloud
```

Each scan run creates a separate ingestion job in Prowler Cloud.

---

### User Guide Cookbooks Kubernetes In Cluster (`docs/user-guide/cookbooks/kubernetes-in-cluster.mdx`)

---
title: 'Run Kubernetes In-Cluster and Send Findings to Prowler Cloud'
---

This cookbook walks through deploying Prowler inside a Kubernetes cluster on a recurring schedule and automatically sending findings to Prowler Cloud via [Import Findings](/user-guide/tutorials/prowler-import-findings). By the end, security scan results from the cluster appear in Prowler Cloud without any manual file uploads.

## Prerequisites

* A **Prowler Cloud** account with an active subscription (see [Prowler Cloud Pricing](https://prowler.com/pricing))
* A Prowler Cloud **API key** with the **Manage Ingestions** permission (see [API Keys](/user-guide/tutorials/prowler-app-api-keys))
* Access to a Kubernetes cluster with `kubectl` configured
* Permissions to create ServiceAccounts, Roles, RoleBindings, Secrets, and CronJobs in the cluster

## Step 1: Create the ServiceAccount and RBAC Resources

Prowler needs a ServiceAccount with read access to cluster resources. Apply the manifests from the [`kubernetes` directory](https://github.com/prowler-cloud/prowler/tree/master/kubernetes) of the Prowler repository:

```console
kubectl apply -f kubernetes/prowler-sa.yaml
kubectl apply -f kubernetes/prowler-role.yaml
kubectl apply -f kubernetes/prowler-rolebinding.yaml
```

This creates:

* A `prowler-sa` ServiceAccount in the `prowler-ns` namespace
* A ClusterRole with the read permissions Prowler requires
* A ClusterRoleBinding linking the ServiceAccount to the role

For more details on these resources, refer to [Getting Started with Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s).

## Step 2: Store the Prowler Cloud API Key as a Secret

Create a Kubernetes Secret to hold the API key securely:

```console
kubectl create secret generic prowler-cloud-api-key \
  --from-literal=api-key=pk_your_api_key_here \
  --namespace prowler-ns
```

Replace `pk_your_api_key_here` with the actual API key from Prowler Cloud.

<Warning>
Avoid embedding the API key directly in the CronJob manifest. Using a Kubernetes Secret keeps credentials out of version control and pod specs.
</Warning>

## Step 3: Create the CronJob Manifest

The CronJob runs Prowler on a schedule, scanning the cluster and pushing findings to Prowler Cloud with the `--push-to-cloud` flag.

Create a file named `prowler-cronjob.yaml`:

```
/* Detailed source-code truncated for AI context efficiency. */
```

<Note>
Replace `my-cluster` with a meaningful name for the cluster. This value appears in Prowler Cloud reports and helps identify the source of findings. See the `--cluster-name` flag documentation in [Getting Started with Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s) for more details.
</Note>

### Customizing the Schedule

The `schedule` field uses standard cron syntax. Common examples:

* `"0 2 * * *"` — daily at 02:00 UTC
* `"0 */6 * * *"` — every 6 hours
* `"0 2 * * 1"` — weekly on Mondays at 02:00 UTC

### Scanning Specific Namespaces

To limit the scan to specific namespaces, add the `--namespace` flag to the `args` array:

```yaml
args:
  - "kubernetes"
  - "--push-to-cloud"
  - "--namespace"
  - "production,staging"
```

## Step 4: Deploy and Verify

Apply the CronJob to the cluster:

```console
kubectl apply -f prowler-cronjob.yaml
```

To trigger an immediate test run without waiting for the schedule:

```console
kubectl create job prowler-test-run --from=cronjob/prowler-k8s-scan -n prowler-ns
```

Monitor the job execution:

```console
kubectl get pods -n prowler-ns -l app=prowler --watch
```

Check the logs to confirm findings were pushed successfully:

```console
kubectl logs -n prowler-ns -l app=prowler --tail=50
```

A successful upload produces output similar to:

```
Pushing findings to Prowler Cloud, please wait...

Findings successfully pushed to Prowler Cloud. Ingestion job: fa8bc8c5-4925-46a0-9fe0-f6575905e094
See more details here: https://cloud.prowler.com/scans
```

## Step 5: View Findings in Prowler Cloud

Once the job completes and findings are pushed:

1. Navigate to [Prowler Cloud](https://cloud.prowler.com/)
2. Open the "Scans" section to verify the ingestion job status
3. Browse findings under the Kubernetes provider

For details on the ingestion workflow and status tracking, refer to the [Import Findings](/user-guide/tutorials/prowler-import-findings) documentation.

## Tips and Troubleshooting

* **Resource limits**: For large clusters, consider setting `resources.requests` and `resources.limits` on the container to prevent the scan from consuming excessive cluster resources.
* **Network policies**: Ensure the Prowler pod can reach `api.prowler.com` over HTTPS (port 443). Adjust NetworkPolicies or egress rules if needed.
* **Job history**: Kubernetes retains completed and failed jobs by default. Set `successfulJobsHistoryLimit` and `failedJobsHistoryLimit` in the CronJob spec to control cleanup:

  ```yaml
  spec:
    successfulJobsHistoryLimit: 3
    failedJobsHistoryLimit: 1
  ```

* **API key rotation**: When rotating the API key, update the Secret and restart any running jobs:

  ```console
  kubectl delete secret prowler-cloud-api-key -n prowler-ns
  kubectl create secret generic prowler-cloud-api-key \
    --from-literal=api-key=pk_new_api_key_here \
    --namespace prowler-ns
  ```

* **Failed uploads**: If the push to Prowler Cloud fails, the scan still completes and findings are saved locally in the container. Check the [Import Findings troubleshooting section](/user-guide/tutorials/prowler-import-findings#troubleshooting) for common error messages.

---

### User Guide Cookbooks Powerbi Cis Benchmarks (`docs/user-guide/cookbooks/powerbi-cis-benchmarks.mdx`)

---
title: "Visualize Multi-Cloud CIS Benchmarks With Power BI"
description: "Ingest Prowler compliance CSV exports into a ready-made Microsoft Power BI template that surfaces CIS Benchmark posture across AWS, Azure, Google Cloud, and Kubernetes."
---

The Multi-Cloud CIS Benchmarks Power BI template turns Prowler compliance CSV exports into an interactive dashboard. The template ingests scan results from Prowler CLI or Prowler Cloud and renders cross-provider CIS Benchmark coverage, profile-level breakdowns, regional drill-downs, and time-series trends. Center for Internet Security (CIS) Benchmarks are industry-standard configuration baselines maintained by CIS.

The template and its source files live in the Prowler repository under [`contrib/PowerBI/Multicloud CIS Benchmarks`](https://github.com/prowler-cloud/prowler/tree/master/contrib/PowerBI/Multicloud%20CIS%20Benchmarks).

<img src="/images/powerbi/report-cover.png" alt="Multi-Cloud CIS Benchmarks Power BI report cover showing aggregated compliance posture across providers" width="900" />

## Prerequisites

The setup requires the following components:

* **Microsoft Power BI Desktop:** free download from Microsoft.
* **Prowler compliance CSV exports:** produced by Prowler CLI or downloaded from Prowler Cloud or Prowler Local Server.
* **Local directory:** holds the CSV exports that the template ingests at load time.

## Supported CIS Benchmarks

The template ships with predefined mappings for the following CIS Benchmark versions. Exports must match these versions for the dashboard to populate correctly:

| Compliance Framework                           | Version  |
| ---------------------------------------------- | -------- |
| CIS Amazon Web Services Foundations Benchmark  | v6.0     |
| CIS Microsoft Azure Foundations Benchmark      | v5.0     |
| CIS Google Cloud Platform Foundation Benchmark | v4.0     |
| CIS Kubernetes Benchmark                       | v1.12.0  |

<Warning>
Other CIS Benchmark versions are not recognized by the template. Confirm the framework version before running the scan or downloading the export.
</Warning>

## Setup

### Step 1: Install Microsoft Power BI Desktop

Download and install Microsoft Power BI Desktop from the official Microsoft site. The template is opened with this application.

### Step 2: Generate Compliance CSV Exports

Compliance CSV exports can be generated through Prowler CLI or downloaded from Prowler Cloud and Prowler Local Server.

#### Option A: Prowler CLI

Run a scan with the `--compliance` flag pointing to the appropriate CIS framework, for example:

```sh
prowler aws --compliance cis_6.0_aws
prowler azure --compliance cis_5.0_azure
prowler gcp --compliance cis_4.0_gcp
prowler kubernetes --compliance cis_1.12_kubernetes
```

The compliance CSV exports are written to `output/compliance/` by default.

#### Option B: Prowler Cloud or Prowler Local Server

Open the Compliance section, select the desired CIS Benchmark, and download the CSV export.

<img src="/images/powerbi/download-compliance-scan.png" alt="Compliance section in Prowler Cloud showing the CSV download option for a CIS Benchmark scan" width="900" />

### Step 3: Create a Local Directory for the Exports

Place every CSV export in a single local directory. The template parses filenames to detect the provider, so filenames must keep the provider keyword (`aws`, `azure`, `gcp`, or `kubernetes`).

<Note>
Time-series visualizations such as "Compliance Percent Over Time" require multiple scans from different dates in the same directory.
</Note>

### Step 4: Open the Power BI Template

Download the template file [`Prowler Multicloud CIS Benchmarks.pbit`](https://github.com/prowler-cloud/prowler/raw/master/contrib/PowerBI/Multicloud%20CIS%20Benchmarks/Prowler%20Multicloud%20CIS%20Benchmarks.pbit) and open it. Power BI Desktop prompts for the full filepath to the directory created in step 3.

### Step 5: Provide the Directory Filepath

Enter the absolute filepath without quotation marks. The Windows "copy as path" feature wraps the path in quotation marks automatically; remove them before submitting.

### Step 6: Save the Report as a `.pbix` File

Once the filepath is submitted, the template ingests the CSV exports and renders the report. Save the populated report as a `.pbix` file for future use. Re-running the `.pbit` template generates a fresh report against an updated directory.

## Validation

To confirm the CSV exports were ingested correctly, open the "Configuration" tab inside the report.

<img src="/images/powerbi/validation.png" alt="Configuration tab in the Power BI report displaying loaded CIS Benchmarks, the Prowler CSV folder path, and the list of ingested exports" width="900" />

The "Configuration" tab exposes three tables:

* **Loaded CIS Benchmarks:** lists the benchmarks and versions supported by the template. This table is defined by the template itself and is not editable. All benchmarks remain listed regardless of which provider exports were supplied.
* **Prowler CSV Folder:** displays the absolute path provided during template load.
* **Loaded Prowler Exports:** lists every CSV file detected in the directory. A green checkmark identifies the file used as the latest assessment for each provider and benchmark combination.

## Report Sections

The report is organized into three navigable pages:

| Report Page | Purpose                                                                              |
| ----------- | ------------------------------------------------------------------------------------ |
| Overview    | Aggregates CIS Benchmark posture across AWS, Azure, Google Cloud, and Kubernetes.    |
| Benchmark   | Focuses on a single CIS Benchmark with profile-level and regional filters.           |
| Requirement | Drill-through page that surfaces details for a single benchmark requirement.         |

### Overview Page

The Overview page summarizes CIS Benchmark posture across every supported provider.

<img src="/images/powerbi/overview-page.png" alt="Overview page in the Power BI report aggregating CIS Benchmark posture across AWS, Azure, Google Cloud, and Kubernetes" width="900" />

The Overview page contains the following components:

| Component                                | Description                                                                  |
| ---------------------------------------- | ---------------------------------------------------------------------------- |
| CIS Benchmark Overview                   | Table listing benchmark name, version, and overall compliance percentage.    |
| Provider by Requirement Status           | Bar chart breaking down requirements by status and provider.                 |
| Compliance Percent Heatmap               | Heatmap of compliance percentage by benchmark and profile level.             |
| Profile Level by Requirement Status      | Bar chart breaking down requirements by status and profile level.            |
| Compliance Percent Over Time by Provider | Line chart tracking overall compliance percentage over time by provider.     |

### Benchmark Page

The Benchmark page focuses on a single CIS Benchmark. The benchmark, profile level, and region can be selected through dropdown filters.

<img src="/images/powerbi/benchmark-page.png" alt="Benchmark page in the Power BI report showing region heatmap, section breakdown, time-series trend, and the requirements table" width="900" />

The Benchmark page contains the following components:

| Component                                | Description                                                                                                                       |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Compliance Percent Heatmap               | Heatmap of compliance percentage by region and profile level.                                                                     |
| Benchmark Section by Requirement Status  | Bar chart of requirements grouped by benchmark section and status.                                                                |
| Compliance Percent Over Time by Region   | Line chart tracking compliance percentage over time by region.                                                                    |
| Benchmark Requirements                   | Table listing requirement section, requirement number, requirement title, number of resources tested, status, and failing checks. |

### Requirement Page

The Requirement page is a drill-through view that exposes the full context of a single requirement. To populate the page, right-click a row in the "Benchmark Requirements" table on the Benchmark page and select "Drill through" > "Requirement".

<img src="/images/powerbi/requirement-page.png" alt="Requirement drill-through page in the Power BI report showing rationale, remediation, regional breakdown, and the resource-level check results" width="900" />

The Requirement page contains the following components:

| Component                                  | Description                                                                                  |
| ------------------------------------------ | -------------------------------------------------------------------------------------------- |
| Title                                      | Requirement title.                                                                           |
| Rationale                                  | Rationale for the requirement.                                                               |
| Remediation                                | Remediation guidance for the requirement.                                                    |
| Region by Check Status                     | Bar chart of Prowler check results grouped by region and status.                             |
| Resource Checks for Benchmark Requirements | Table listing resource ID, resource name, status, description, and the underlying Prowler check. |

## Walkthrough Video

A full walkthrough is available on YouTube:

[](https://www.youtube.com/watch?v=lfKFkTqBxjU)

## Related Resources

<CardGroup cols={2}>
  <Card title="Compliance Frameworks" icon="shield-check" href="/user-guide/compliance/tutorials/compliance">
    Review the Compliance workflow across Prowler Cloud, Prowler Local Server, and Prowler CLI.
  </Card>
  <Card title="Prowler Dashboard" icon="chart-line" href="/user-guide/cli/tutorials/dashboard">
    Explore the built-in local dashboard for Prowler CSV exports.
  </Card>
</CardGroup>

---

### User Guide Tutorials Aws Organizations Bulk Provisioning (`docs/user-guide/tutorials/aws-organizations-bulk-provisioning.mdx`)

---
title: 'AWS Organizations Bulk Provisioning in Prowler'
---

Prowler offers an automated tool to discover and provision all AWS accounts within an AWS Organization. This streamlines onboarding for organizations managing multiple AWS accounts by automatically generating the configuration needed for bulk provisioning.

For the cross-provider organization lifecycle and terminology, see [Organizations Across Cloud Providers](/user-guide/organizations).

The tool, `aws_org_generator.py`‎, complements the [Bulk Provider Provisioning](./bulk-provider-provisioning) tool and is available in the Prowler repository at: [util/prowler-bulk-provisioning](https://github.com/prowler-cloud/prowler/tree/master/util/prowler-bulk-provisioning)

<Note>
**Native AWS Organizations support is now available in Prowler Cloud.** You can onboard all accounts via the UI wizard — with automatic discovery, hierarchical tree selection, connection testing, and bulk scan launch — without any scripts or YAML files.

See [AWS Organizations](/user-guide/tutorials/prowler-cloud-aws-organizations) in Prowler Cloud.

The CLI-based tool below remains useful for Prowler Local Server and advanced automation scenarios.
</Note>

{/* TODO: Add screenshot of the tool in action */}

## Overview

The AWS Organizations Bulk Provisioning tool simplifies multi-account onboarding by:

* Automatically discovering all active accounts in an AWS Organization
* Generating YAML configuration files for bulk provisioning
* Supporting account filtering and custom role configurations
* Eliminating manual entry of account IDs and role ARNs

## Prerequisites

### Requirements

* Python 3.7 or higher
* AWS credentials with Organizations read access
* ProwlerRole (or custom role) deployed across all target accounts
* Prowler API key (from Prowler Cloud or Prowler Local Server)
    * For Prowler Local Server, remember to [point to your API base URL](./bulk-provider-provisioning#custom-api-endpoints)
    * Learn how to create API keys: [Prowler Cloud API Keys](../tutorials/prowler-app-api-keys)

### Deploying ProwlerRole Across AWS Organizations

Before using the AWS Organizations generator, deploy the ProwlerRole across all accounts in the organization using CloudFormation StackSets.

<Note>
**Follow the official documentation:**
[Deploying Prowler IAM Roles Across AWS Organizations](../providers/aws/organizations#deploying-prowler-iam-roles-across-aws-organizations)

**Key points:**

* Use CloudFormation StackSets from the management account
* Deploy to all organizational units (OUs) or specific OUs
* Use an external ID for enhanced security
* Ensure the role has necessary permissions for Prowler scans
</Note>

### Installation

Clone the repository and install required dependencies:

```bash
git clone https://github.com/prowler-cloud/prowler.git
cd prowler/util/prowler-bulk-provisioning
pip install -r requirements-aws-org.txt
```

### AWS Credentials Setup

Configure AWS credentials with Organizations read access:

* **Management account credentials**, or
* **Delegated administrator account** with `organizations:ListAccounts` permission

Required IAM permissions:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "organizations:ListAccounts",
        "organizations:DescribeOrganization"
      ],
      "Resource": "*"
    }
  ]
}
```

### Prowler API Key Setup

Configure your Prowler API key:

```bash
export PROWLER_API_KEY="pk_example-api-key"
```

To create an API key:

1. Log in to Prowler Cloud or Prowler Local Server
2. Click **Profile** → **Account**
3. Click **Create API Key**
4. Provide a descriptive name and optionally set an expiration date
5. Copy the generated API key (it will only be shown once)

For detailed instructions, see: [Prowler Cloud API Keys](../tutorials/prowler-app-api-keys)

## Basic Usage

### Generate Configuration for All Accounts

To generate a YAML configuration file for all active accounts in the organization:

```bash
python aws_org_generator.py -o aws-accounts.yaml --external-id prowler-ext-id-2024
```

This command:

1. Lists all ACTIVE accounts in the organization
2. Generates YAML entries for each account
3. Saves the configuration to `aws-accounts.yaml`

**Output:**

```
Fetching accounts from AWS Organizations...
Found 47 active accounts in organization
Generated configuration for 47 accounts

Configuration written to: aws-accounts.yaml

Next steps:
  1. Review the generated file: cat aws-accounts.yaml | head -n 20
  2. Run bulk provisioning: python prowler_bulk_provisioning.py aws-accounts.yaml
```

### Review Generated Configuration

Review the generated YAML configuration:

```bash
head -n 20 aws-accounts.yaml
```

**Example output:**

```yaml
- provider: aws
  uid: '111111111111'
  alias: Production-Account
  auth_method: role
  credentials:
    role_arn: arn:aws:iam::111111111111:role/ProwlerRole
    external_id: prowler-ext-id-2024

- provider: aws
  uid: '222222222222'
  alias: Development-Account
  auth_method: role
  credentials:
    role_arn: arn:aws:iam::222222222222:role/ProwlerRole
    external_id: prowler-ext-id-2024
```

### Dry Run Mode

Test the configuration without writing a file:

```bash
python aws_org_generator.py \
  --external-id prowler-ext-id-2024 \
  --dry-run
```

## Advanced Configuration

### Using a Specific AWS Profile

Specify an AWS profile when multiple profiles are configured:

```bash
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --profile org-management-admin \
  --external-id prowler-ext-id-2024
```

### Excluding Specific Accounts

Exclude the management account or other accounts from provisioning:

```bash
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --external-id prowler-ext-id-2024 \
  --exclude 123456789012,210987654321
```

Common exclusion scenarios:

* Management account (requires different permissions)
* Break-glass accounts (emergency access)
* Suspended or archived accounts

### Including Only Specific Accounts

Generate configuration for specific accounts only:

```bash
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --external-id prowler-ext-id-2024 \
  --include 111111111111,222222222222,333333333333
```

### Custom Role Name

Specify a custom role name if not using the default `ProwlerRole`:

```bash
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --role-name ProwlerExecutionRole \
  --external-id prowler-ext-id-2024
```

### Custom Alias Format

Customize account aliases using template variables:

```bash
# Use account name and ID
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --alias-format "{name}-{id}" \
  --external-id prowler-ext-id-2024

# Use email prefix
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --alias-format "{email}" \
  --external-id prowler-ext-id-2024
```

Available template variables:

* `{name}` - Account name
* `{id}` - Account ID
* `{email}` - Account email

### Additional Role Assumption Options

Configure optional role assumption parameters:

```bash
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --role-name ProwlerRole \
  --external-id prowler-ext-id-2024 \
  --session-name prowler-scan-session \
  --duration-seconds 3600
```

## Complete Workflow Example

<Steps>
  <Step title="Deploy ProwlerRole Using StackSets">
    1. Log in to the AWS management account
    2. Open CloudFormation → StackSets
    3. Create a new StackSet using the [Prowler role template](https://github.com/prowler-cloud/prowler/blob/master/permissions/templates/cloudformation/prowler-scan-role.yml)
    4. Deploy to all organizational units
    5. Use a unique external ID (e.g., `prowler-org-2024-abc123`)

    Alternatively, deploy the same template as a **single stack** with `DeployStackSet=true` and `AWSOrganizationalUnitId` set to your root/OU ID — it creates the StackSet for you. See [Native CloudFormation StackSet Deployment](../providers/aws/organizations#native-cloudformation-stackset-deployment-recommended).

    {/* TODO: Add screenshot of CloudFormation StackSets deployment */}
  </Step>

  <Step title="Generate YAML Configuration">
    Configure AWS credentials and generate the YAML file:

    ```bash
    # Using management account credentials
    export AWS_PROFILE=org-management

    # Generate configuration
    python aws_org_generator.py \
      -o aws-org-accounts.yaml \
      --external-id prowler-org-2024-abc123 \
      --exclude 123456789012
    ```

    **Output:**

    ```
    Fetching accounts from AWS Organizations...
    Using AWS profile: org-management
    Found 47 active accounts in organization
    Generated configuration for 46 accounts

    Configuration written to: aws-org-accounts.yaml

    Next steps:
      1. Review the generated file: cat aws-org-accounts.yaml | head -n 20
      2. Run bulk provisioning: python prowler_bulk_provisioning.py aws-org-accounts.yaml
    ```
  </Step>

  <Step title="Review Generated Configuration">
    Verify the generated YAML configuration:

    ```bash
    # View first 20 lines
    head -n 20 aws-org-accounts.yaml

    # Check for unexpected accounts
    grep "uid:" aws-org-accounts.yaml

    # Verify role ARNs
    grep "role_arn:" aws-org-accounts.yaml | head -5

    # Count accounts
    grep "provider: aws" aws-org-accounts.yaml | wc -l
    ```
  </Step>

  <Step title="Run Bulk Provisioning">
    Provision all accounts to Prowler Cloud or Prowler Local Server:

    ```bash
    # Set Prowler API key
    export PROWLER_API_KEY="pk_example-api-key"

    # Run bulk provisioning with connection testing
    python prowler_bulk_provisioning.py aws-org-accounts.yaml
    ```

    **With custom options:**

    ```bash
    python prowler_bulk_provisioning.py aws-org-accounts.yaml \
      --concurrency 10 \
      --timeout 120
    ```

    **Successful output:**

    ```
    [1] ✅ Created provider (id=db9a8985-f9ec-4dd8-b5a0-e05ab3880bed)
    [1] ✅ Created secret (id=466f76c6-5878-4602-a4bc-13f9522c1fd2)
    [1] ✅ Connection test: Connected

    [2] ✅ Created provider (id=7a99f789-0cf5-4329-8279-2d443a962676)
    [2] ✅ Created secret (id=c5702180-f7c4-40fd-be0e-f6433479b126)
    [2] ✅ Connection test: Connected

    Done. Success: 47  Failures: 0
    ```

    {/* TODO: Add screenshot of successful bulk provisioning output */}
  </Step>
</Steps>

## Command Reference

### Full Command-Line Options

```bash
python aws_org_generator.py \
  -o OUTPUT_FILE \
  --role-name ROLE_NAME \
  --external-id EXTERNAL_ID \
  --session-name SESSION_NAME \
  --duration-seconds SECONDS \
  --alias-format FORMAT \
  --exclude ACCOUNT_IDS \
  --include ACCOUNT_IDS \
  --profile AWS_PROFILE \
  --region AWS_REGION \
  --dry-run
```

## Troubleshooting

### Error: "No AWS credentials found"

**Solution:** Configure AWS credentials using one of these methods:

```bash
# Method 1: AWS CLI configure
aws configure

# Method 2: Environment variables
export AWS_ACCESS_KEY_ID=your-key-id
export AWS_SECRET_ACCESS_KEY=your-secret-key

# Method 3: Use AWS profile
export AWS_PROFILE=org-management
```

### Error: "Access denied to AWS Organizations API"

**Cause:** Current credentials don't have permission to list organization accounts.

**Solution:**

* Ensure management account credentials are used
* Verify IAM permissions include `organizations:ListAccounts`
* Check IAM policies for Organizations access

### Error: "AWS Organizations is not enabled"

**Cause:** The account is not part of an organization.

**Solution:** This tool requires an AWS Organization. Create one in the AWS Organizations console or use standard bulk provisioning for standalone accounts.

### No Accounts Generated After Filters

**Cause:** All accounts were filtered out by `--exclude` or `--include` options.

**Solution:** Review filter options and verify account IDs are correct:

```bash
# List all accounts in organization
aws organizations list-accounts --query "Accounts[?Status=='ACTIVE'].[Id,Name]" --output table
```

### Connection Test Failures During Bulk Provisioning

**Cause:** ProwlerRole may not be deployed correctly or credentials are invalid.

**Solution:**

* Verify StackSet deployment status in CloudFormation
* Check role trust policy includes correct external ID
* Test role assumption manually:

```bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/ProwlerRole \
  --role-session-name test \
  --external-id prowler-ext-id-2024
```

## Security Best Practices

### Use External ID

Always use an external ID when assuming cross-account roles:

```bash
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --external-id $(uuidgen | tr '[:upper:]' '[:lower:]')
```

The external ID must match the one configured in the ProwlerRole trust policy across all accounts.

### Exclude Sensitive Accounts

Exclude accounts that shouldn't be scanned or require special handling:

```bash
python aws_org_generator.py \
  -o aws-accounts.yaml \
  --external-id prowler-ext-id \
  --exclude 123456789012,111111111111  # management, break-glass accounts
```

### Review Generated Configuration

Always review the generated YAML before provisioning:

```bash
# Check for unexpected accounts
grep "uid:" aws-org-accounts.yaml

# Verify role ARNs
grep "role_arn:" aws-org-accounts.yaml | head -5

# Count accounts
grep "provider: aws" aws-org-accounts.yaml | wc -l
```

## Next Steps

<Columns cols={2}>
  <Card title="Bulk Provider Provisioning" icon="terminal" href="/user-guide/tutorials/bulk-provider-provisioning">
    Learn how to bulk provision providers in Prowler.
  </Card>
  <Card title="Prowler Cloud" icon="pen-to-square" href="/user-guide/tutorials/prowler-app">
    Detailed instructions on how to use Prowler.
  </Card>
</Columns>

---

### User Guide Tutorials Bulk Provider Provisioning (`docs/user-guide/tutorials/bulk-provider-provisioning.mdx`)

---
title: 'Bulk Provider Provisioning in Prowler'
---

Prowler enables automated provisioning of multiple cloud providers through the Bulk Provider Provisioning tool. This approach streamlines the onboarding process for organizations managing numerous cloud accounts, subscriptions, and projects across AWS, Azure, GCP, Kubernetes, Microsoft 365, and GitHub.

The tool is available in the Prowler repository at: [util/prowler-bulk-provisioning](https://github.com/prowler-cloud/prowler/tree/master/util/prowler-bulk-provisioning)



## Overview

The Bulk Provider Provisioning tool automates the creation of cloud providers in Prowler Cloud or Prowler Local Server by:

* Reading provider configurations from YAML files
* Creating providers with appropriate authentication credentials
* Testing connections to verify successful authentication
* Processing multiple providers concurrently for efficiency

<Tip>
**Using AWS Organizations?** For organizations with many AWS accounts, use the automated [AWS Organizations Bulk Provisioning](./aws-organizations-bulk-provisioning) tool to automatically discover and generate configuration for all accounts in your organization.
</Tip>

## Prerequisites

### Requirements

* Python 3.7 or higher
* Prowler API key (from Prowler Cloud or Prowler Local Server)
    * For Prowler Local Server, remember to [point to your API base URL](#custom-api-endpoints)
    * Learn how to create API keys: [Prowler Cloud API Keys](../tutorials/prowler-app-api-keys)
* Authentication credentials for target cloud providers

### Installation

Clone the repository and install the required dependencies:

```bash
git clone https://github.com/prowler-cloud/prowler.git
cd prowler/util/prowler-bulk-provisioning
pip install -r requirements.txt
```

### Authentication Setup

Configure your Prowler API key:

```bash
export PROWLER_API_KEY="pk_example-api-key"
```

To create an API key:

1. Log in to Prowler Cloud or Prowler Local Server
2. Click **Profile** → **Account**
3. Click **Create API Key**
4. Provide a descriptive name and optionally set an expiration date
5. Copy the generated API key (it will only be shown once)

For detailed instructions, see: [Prowler Cloud API Keys](../tutorials/prowler-app-api-keys)

## Configuration File Structure

Create a YAML file listing your cloud providers and credentials:

```yaml
# providers.yaml
- provider: aws
  uid: "123456789012"              # AWS Account ID
  alias: "production-account"
  auth_method: role
  credentials:
    role_arn: "arn:aws:iam::123456789012:role/ProwlerScanRole"
    external_id: "prowler-external-id"

- provider: azure
  uid: "00000000-1111-2222-3333-444444444444"  # Subscription ID
  alias: "azure-production"
  auth_method: service_principal
  credentials:
    tenant_id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
    client_id: "ffffffff-1111-2222-3333-444444444444"
    client_secret: "your-client-secret"

- provider: gcp
  uid: "my-gcp-project"            # Project ID
  alias: "gcp-production"
  auth_method: service_account
  credentials:
    service_account_key_json_path: "./service-account.json"
```

## Running the Bulk Provisioning Tool

### Basic Usage

To provision all providers from your configuration file:

```bash
python prowler_bulk_provisioning.py providers.yaml
```

The tool automatically tests each provider connection after creation (enabled by default).

### Dry Run Mode

Test your configuration without making API calls:

```bash
python prowler_bulk_provisioning.py providers.yaml --dry-run
```

### Skip Connection Testing

To provision providers without testing connections:

```bash
python prowler_bulk_provisioning.py providers.yaml --test-provider false
```

### Test Existing Providers Only

To verify connections for already provisioned providers:

```bash
python prowler_bulk_provisioning.py providers.yaml --test-provider-only
```

## Provider-Specific Configuration

### AWS Provider Configuration

#### Using IAM Role (Recommended)

```yaml
- provider: aws
  uid: "123456789012"
  alias: "aws-production"
  auth_method: role
  credentials:
    role_arn: "arn:aws:iam::123456789012:role/ProwlerScanRole"
    external_id: "optional-external-id"
    session_name: "prowler-scan-session"  # optional
    duration_seconds: 3600                # optional
```

#### Using Access Keys

```yaml
- provider: aws
  uid: "123456789012"
  alias: "aws-development"
  auth_method: credentials
  credentials:
    access_key_id: "AKIA..."
    secret_access_key: "..."
    session_token: "..."  # optional for temporary credentials
```

### Azure Provider Configuration

```yaml
- provider: azure
  uid: "subscription-uuid"
  alias: "azure-production"
  auth_method: service_principal
  credentials:
    tenant_id: "tenant-uuid"
    client_id: "client-uuid"
    client_secret: "client-secret"
```

### GCP Provider Configuration

#### Using Service Account JSON

```yaml
- provider: gcp
  uid: "project-id"
  alias: "gcp-production"
  auth_method: service_account
  credentials:
    service_account_key_json_path: "/path/to/key.json"
```

#### Using OAuth2 Credentials

```yaml
- provider: gcp
  uid: "project-id"
  alias: "gcp-production"
  auth_method: oauth2
  credentials:
    client_id: "123456789.apps.googleusercontent.com"
    client_secret: "GOCSPX-xxxx"
    refresh_token: "1//0exxxxxx"
```

### Kubernetes Provider Configuration

```yaml
- provider: kubernetes
  uid: "context-name"
  alias: "eks-production"
  auth_method: kubeconfig
  credentials:
    kubeconfig_path: "~/.kube/config"
    # OR inline configuration:
    # kubeconfig_inline: |
    #   apiVersion: v1
    #   clusters: ...
```

### Microsoft 365 Provider Configuration

```yaml
- provider: m365
  uid: "domain.onmicrosoft.com"
  alias: "m365-tenant"
  auth_method: service_principal
  credentials:
    tenant_id: "tenant-uuid"
    client_id: "client-uuid"
    client_secret: "client-secret"
```

### GitHub Provider Configuration

#### Using Personal Access Token

```yaml
- provider: github
  uid: "organization-name"
  alias: "github-org"
  auth_method: personal_access_token
  credentials:
    token: "ghp_..."
```

#### Using GitHub App

```yaml
- provider: github
  uid: "organization-name"
  alias: "github-org"
  auth_method: github_app
  credentials:
    app_id: "123456"
    private_key_path: "/path/to/private-key.pem"
```

## Advanced Configuration

### Concurrent Processing

Adjust the number of concurrent provider creations:

```bash
python prowler_bulk_provisioning.py providers.yaml --concurrency 10
```

### Custom API Endpoints

For Prowler Local Server installations:

```bash
python prowler_bulk_provisioning.py providers.yaml \
  --base-url http://localhost:8080/api/v1
```

### Timeout Configuration

Set custom timeout for API requests:

```bash
python prowler_bulk_provisioning.py providers.yaml --timeout 120
```

## Bulk Provider Management

### Deleting Multiple Providers

To remove all providers from your Prowler account:

```bash
python nuke_providers.py --confirm
```

Filter deletions by provider type:

```bash
python nuke_providers.py --confirm --filter-provider aws
```

Filter deletions by alias pattern:

```bash
python nuke_providers.py --confirm --filter-alias "test-*"
```

## Configuration File Format

The tool uses YAML format for provider configuration files. Each provider entry requires:

* `provider`: The cloud provider type (aws, azure, gcp, kubernetes, m365, github)
* `uid`: Unique identifier for the provider (account ID, subscription ID, project ID, etc.)
* `alias`: A friendly name for the provider
* `auth_method`: Authentication method to use
* `credentials`: Authentication credentials specific to the provider and method

Example YAML structure:

```yaml
- provider: aws
  uid: "123456789012"
  alias: "production"
  auth_method: role
  credentials:
    role_arn: "arn:aws:iam::123456789012:role/ProwlerScan"
```

## Example Output

Successful provider provisioning:

```
[1] ✅ Created provider (id=db9a8985-f9ec-4dd8-b5a0-e05ab3880bed)
[1] ✅ Created secret (id=466f76c6-5878-4602-a4bc-13f9522c1fd2)
[1] ✅ Connection test: Connected

[2] ✅ Created provider (id=7a99f789-0cf5-4329-8279-2d443a962676)
[2] ✅ Created secret (id=c5702180-f7c4-40fd-be0e-f6433479b126)
[2] ⚠️  Connection test: Not connected

Done. Success: 2  Failures: 0
```

## Troubleshooting

### Invalid API Key

```
Error: 401 Unauthorized
Solution: Verify your PROWLER_API_KEY environment variable or --api-key parameter
```

### Network Timeouts

```
Error: Connection timeout
Solution: Increase timeout with --timeout 120
```

### Provider Already Exists

```
Error: Provider with this UID already exists
Solution: Use different UID or delete existing provider first
```

### Authentication Failures

```
Connection test: Not connected
Solution: Verify credentials and IAM permissions
```

---

### User Guide Tutorials Prowler Alerts (`docs/user-guide/tutorials/prowler-alerts.mdx`)

---
title: 'Alerts'
sidebarTitle: 'Alerts'
description: 'Create alerts from Prowler Cloud findings, deliver them to email recipients and Slack channels, and monitor relevant security changes after scans or in daily digests.'
---

import { VersionBadge } from "/snippets/version-badge.mdx"
import { SubscriptionBanner } from "/snippets/subscription-banner.mdx"

<VersionBadge version="5.26.0" />

Alerts notify their destinations — email recipients, Slack channels, or both — when security findings match saved filter conditions. Use Alerts to track high-priority findings, monitor specific providers or services, and keep teams informed about scan results that match defined criteria.

<SubscriptionBanner />

## Prerequisites

Before creating Alerts, ensure that:

* At least one scan has completed and produced findings.
* The user role includes the `manage_alerts` permission.
* To deliver Alerts to Slack channels, a Slack workspace is connected, at least one channel is authorized on it, and the integration's connection check has confirmed that channel. See [Slack Integration](/user-guide/tutorials/prowler-app-slack-integration).

The `manage_alerts` permission is required to create, edit, test, enable, disable, and delete Alerts. See [RBAC Administrative Permissions](/user-guide/tutorials/prowler-app-rbac#rbac-administrative-permissions) for details.

## How Alerts Work

Alerts are created from Findings filters. When an Alert runs, Prowler Cloud evaluates the saved conditions against findings and notifies the Alert's destinations when matching findings exist: an email digest to each recipient, a message to each Slack channel, or both. Destination kinds are independent — neither requires the other, and neither displaces the other.

<Note>
Alerts evaluate findings with status `FAIL` only. Findings with status `PASS` or `MANUAL`, and muted findings, never trigger an Alert regardless of the saved filters.
</Note>

Alerts run on one of three schedules:

| Frequency | Description |
|-----------|-------------|
| After each scan | Evaluates the Alert after each completed scan. |
| Daily digest | Evaluates the Alert once per day and sends a digest when findings match. |
| After each scan and daily | Evaluates the Alert after every scan and in the daily digest. |

## Creating an Alert From Findings

To create an Alert:

1. Navigate to **Findings** in Prowler Cloud.
2. Apply at least one [Alert-compatible filter](#alert-compatible-filters) to define the findings that should trigger the Alert.
3. Click **Create Alert**.

    

4. Configure the Alert settings:
    * **Name:** Add a short, descriptive name.
    * **Description:** Add optional context for the Alert.
    * **Frequency:** Select when Prowler Cloud should evaluate the Alert.
    * **Recipients:** Select the recipients who should receive the email digest.
    * **Destination channels:** Select the Slack channels that should receive the Alert. See [Slack Channel Destinations](#slack-channel-destinations).

    

5. Click **Create**.

After the Alert is created, Prowler Cloud evaluates it based on the selected frequency.

## Alert-Compatible Filters

An **Alert-compatible filter** is a Findings-page filter that the Alert condition language can evaluate when the Alert runs. The Findings page exposes many filters, but only a specific subset can be saved into an Alert. Filters outside this subset, such as **Status**, free-text search, sort, or pagination, are ignored when seeding an Alert from the current Findings view.

When **Create Alert** is clicked on the Findings page, Prowler Cloud takes the active filters, keeps only the Alert-compatible ones, and uses them to build the Alert condition.

The following filters are Alert-compatible:

* Provider type
* Provider
* Severity
* Delta (new findings since the previous scan)
* Region
* Service
* Resource type
* Category
* Resource group

If only the **Status** filter is applied on the Findings page, Prowler Cloud substitutes all severities as the condition base so the Alert can still be created. Status itself never becomes part of the Alert condition.

## Managing Alerts

Navigate to **Alerts** to review and manage existing Alerts.



The **Destinations** column summarizes where each Alert delivers, without the Alert being opened:

* **Email recipients:** The first address, plus a count of the rest, such as `security@example.com +2 more`.
* **Slack channels:** The first channel, plus a count of the rest, such as `#sec-alerts +1 more`.

Each summary is omitted when that destination kind is empty, and the column reads **No destinations** when an Alert has neither.

Each Alert provides these actions:

| Action | Description |
|--------|-------------|
| Edit | Update name, description, recipients, Slack channels, frequency, or filters. |
| Enable/Disable | Start or stop Alert evaluation without deleting the Alert. |
| Delete | Permanently remove the Alert. |

## Testing Alert Filters

When editing an Alert, click **Test** to preview whether the current filters match existing findings.

The test result indicates whether the filters match findings and includes a summary of the matching results.



<Warning>
**The Test result is a snapshot, not a guarantee of future Alert triggers.**

The Test evaluates the current filters against existing findings at the moment **Test** is clicked. It does not predict whether the Alert will trigger on its next evaluation. The Alert trigger depends on the state at evaluation time:

* **After each scan:** The Alert is evaluated against the findings produced by that scan only. If the next scan produces no findings that match the filters, the Alert will not trigger, even if a Test run earlier in the day showed matches.
* **Daily digest:** The Alert is evaluated against the findings present on the digest day. If no matching findings exist for that day, the Alert will not trigger, even if previous days had matches.

The reverse is also true: a Test showing no matches does not guarantee the Alert will stay silent. Future scans may produce matching findings.

Use **Test** to validate that the filters are well-formed and target the intended findings, not to forecast future Alert behavior.
</Warning>

## Recipients

Alert recipients are selected from the email addresses available in the tenant. Recipients receive an email digest each time an Alert evaluates and matches findings.

<Note>
By default, the **organization owner** receives a **daily digest** for **critical findings**. Adjust the recipient, frequency, or filters in the Alert configuration to change this behavior.
</Note>

If a recipient unsubscribes from Alerts, that address stops receiving digests until it is reconfirmed.

An Alert does not require email recipients: an Alert that targets Slack channels only is accepted with those channels as its sole destinations. An Alert with no destinations at all stays valid and keeps evaluating its filters, but it delivers nothing.

## Slack Channel Destinations

<VersionBadge version="5.40.0" />

An Alert can post to Slack channels alongside its email recipients, or instead of them. The **Destination channels** field sits directly below **Recipients** in the Alert form, both when creating an Alert and when editing one. When the Alert matches findings, Prowler Cloud posts a message to each of its channels and sends the email digest to each of its recipients, independently of each other.

The channels offered are the confirmed channels of the connected Slack integration, never the whole Slack workspace. Widening the pool takes two steps on the integration: authorize the channel there, then run its connection check, which confirms the channel by posting a one-time confirmation message to it. Once confirmed, the channel is selectable on every Alert. See [Slack Integration](/user-guide/tutorials/prowler-app-slack-integration) for connecting a workspace, authorizing its channels, and confirming them.

A channel that was authorized a moment ago but does not appear in the Alert form has not been confirmed yet. Run **Test connection** on the Slack integration, then reopen the Alert form.

Private channels are identified as **Private** both in the open channel list and on the selected channels once the list is closed, so a private destination is never mistaken for a public one.

### When Slack Channels Cannot Be Selected

The field is always present, so channel delivery is never silently missing. It reports why it cannot be used:

| State | What the Alert form shows |
|-------|---------------------------|
| No Slack workspace connected | The field is visible but cannot be edited, explaining that posting Alerts to Slack channels needs a connected Slack workspace, with a link to the Slack integration. |
| Workspace connected, no confirmed channels | A notice that no channels are available yet and that they are authorized and confirmed on the Slack integration, with the same link. |

In both states the rest of the Alert is unaffected: it can still be created or saved with its filters, frequency, and email recipients.

<Note>
Slack destinations stay in step with the integration. Removing a channel from the integration's authorized set — or disconnecting the Slack integration altogether — removes that channel from every Alert that targeted it, so an Alert never keeps a destination Prowler can no longer deliver to. The Alert keeps its filters, frequency, and email recipients, and future delivery to that channel stops: nothing is posted to announce the removal, and the notifications already delivered stay in the channel. Restoring delivery means authorizing and confirming the channel again on the integration, then selecting it again on the Alert.
</Note>

Saving an Alert that names a channel which is not a confirmed channel of a connected Slack integration is refused, and the reason is reported on the Alert form. Authorize and confirm the channel on the Slack integration, or remove it from the Alert, and save again.

## Email Notifications

When an Alert matches findings, Prowler Cloud sends a security alert email that summarizes the matching findings. The email includes:

* The scan name and evaluation time.
* The total number of matching findings.
* The number of Alert rules that triggered.
* A preview of the affected findings, grouped by severity, with resource details and the originating rule.
* A direct link to view all matching findings in Prowler Cloud.



## Best Practices

* **Start with focused filters:** Create Alerts for specific high-priority scopes, such as critical findings, production providers, or important services.
* **Use clear names:** Choose names that explain the intent of the Alert.
* **Review destinations regularly:** Keep recipient lists and channel selections aligned with current ownership.
* **Test before saving edits:** Use **Test** after changing filters to confirm that the Alert matches the expected findings.
* **Disable instead of deleting during tuning:** Disable Alerts temporarily when adjusting filters or destinations.

---

### User Guide Tutorials Prowler App Api Keys (`docs/user-guide/tutorials/prowler-app-api-keys.mdx`)

---
title: 'API Keys'
---

import { VersionBadge } from "/snippets/version-badge.mdx"
import { AppliesTo } from "/snippets/applies-to.mdx"

<VersionBadge version="5.13.0" />

<AppliesTo />

API key authentication in Prowler Cloud provides an alternative to JWT tokens and empowers automation, CI/CD pipelines, and third-party integrations. This guide explains how to create, manage, and safeguard API keys when working with the Prowler API.

## API Key Advantages

- **Programmatic access:** Enables automated workflows and scripts to interact with Prowler Cloud.
- **Long-lived authentication:** Allows optional expiration dates, with a default of 1 year.
- **Granular control:** Supports multiple keys with distinct names and purposes.
- **Secure automation:** Simplifies safe integration into CI/CD pipelines and infrastructure-as-code tooling.

## How It Works

API keys provide a secure authentication mechanism for accessing the Prowler API:

1. API keys are created through Prowler Cloud with a user-defined name and optional expiration date.
2. The full API key appears only once upon creation and cannot be retrieved later.
3. Each API key consists of a prefix (visible in the interface) and an encrypted secret portion.
4. Requests include the API key in the header as `Authorization: Api-Key <api-key>`.
5. The system updates the Last Used timestamp after each authenticated request.
6. API keys automatically inherit the RBAC permissions of the creator (see [Permission Inheritance](#permission-inheritance)).
7. Revocation immediately disables an API key and prevents further access.

### Example curl Request

This example demonstrates how to invoke the Prowler API with an API key.

1. Define the API key as an environment variable to avoid exposing the secret in shell history.
2. Call the desired endpoint with `curl` and supply the `Authorization` header.

```bash
export PROWLER_API_KEY="pk_example_redacted"

curl -X GET \
  -H "Authorization: Api-Key ${PROWLER_API_KEY}" \
  -H "Content-Type: application/vnd.api+json" \
  https://api.prowler.com/api/v1/tenants
```

<Note>
**Authentication Priority**

When a request includes both a JWT token and an API key, the JWT token takes precedence for authentication.

</Note>

<Warning>
**Security Notice**

API keys are equivalent to passwords and grant the same access level as the creator. Handle every key with password-level safeguards.

</Warning>

## Required Permissions

Creating, viewing, or managing API keys requires the **MANAGE_ACCOUNT** RBAC permission within the tenant. This permission governs all API key management operations.

Without this permission, the API Keys section remains hidden. Access requests should be routed through the tenant administrator.

For more information about RBAC permissions, refer to the [Prowler Cloud RBAC documentation](/user-guide/tutorials/prowler-app-rbac).

## Creating API Keys

Follow these steps to create an API key in Prowler Cloud:

1. Navigate to **Profile** → **Account** in Prowler Cloud.
2. Select the **Create API Key** button.

    

3. Configure the API key settings:
    * **Name:** Provide a descriptive label with at least 3 characters (examples: "CI Pipeline Production", "Monitoring Script").
    * **Expiration Date (optional):** Set a custom expiration date. When omitted, the key expires automatically 1 year (365 days) after creation.

    

4. Select **Create API Key** to generate the key.
5. **Important:** Copy and securely store the API key immediately. The full value appears only once and cannot be retrieved later.

    

<Warning>
**Save the API Key Immediately**

After the creation dialog closes, only the key prefix remains visible in the interface. Lost keys require generating a replacement and updating dependent applications.

</Warning>

## Managing API Keys

### Viewing API Keys

The API Keys management interface displays every key associated with the signed-in account:

1. Navigate to **Profile** → **Account**.
2. Review the list of API keys, which includes:
    * **Name:** The descriptive label assigned to the key.
    * **Prefix:** The visible portion of the key for identification (for example, `pk_ABC12345`).
    * **Email:** The email address of the creator.
    * **Created:** The timestamp when the key was created.
    * **Last Used:** The timestamp of the most recent successful authentication.
    * **Expires:** The configured expiration date.
    * **Status:** Whether the key is active or revoked.

    

### Updating API Keys

API keys support limited updates to maintain security:

1. Locate the target API key in the list.
2. Select **Edit name** from the action menu.
3. Update the available field:
    * **Name:** Modify the descriptive label for clearer identification.
4. Review the fields that cannot be changed:
    * Prefix, expiration date, and the secret itself remain immutable.
    * Adjusting those properties requires creating a new API key and revoking the existing one.
5. Select **Save** to apply the change.

    

### Actions

Each API key provides management actions through dedicated buttons or the action menu:

| Action | Purpose | Effect | Notes |
|--------|---------|--------|-------|
| **Edit Name** | Update the key's descriptive name | Changes the display name only | Does not affect authentication |
| **Revoke** | Disable the API key | Sets revoked status to true, blocking all authentication | Maintains audit trail and key history |

<Note>
**API Keys Cannot Be Deleted**

For security and audit purposes, API keys cannot be permanently removed from the system. Use the **Revoke** action to disable a key. Revoked keys remain visible for audit purposes but cannot be used for authentication.

</Note>

## Permission Inheritance

API keys automatically inherit the RBAC permissions of the creator, ensuring that programmatic access mirrors user-level security boundaries.

### How Permission Inheritance Works

* **Current permissions apply:** When an API key is used, it operates with the creator's current RBAC permissions.
* **Dynamic updates:** Permission changes on the creator immediately propagate to every associated API key.
* **User downgrade:** Reduced user permissions result in reduced API key capabilities.
* **Tenant removal:** Removing a user from a tenant automatically revokes every key for that tenant.
* **User deletion:** Deleting a user from the application automatically revokes all associated API keys.

<Warning>
**Automatic Revocation**

API keys are automatically revoked when the creator is removed from the tenant or deleted from the application. This mechanism ensures that access ends as soon as the user loses access.

</Warning>

### Best Practices for Permission Management

* **Use service accounts for automation:** Create dedicated user accounts for API-based automation to separate human and programmatic access, ensuring continuity when team members transition.
* **Review API key ownership regularly:** Confirm that API keys remain associated with appropriate user accounts and document ownership.
* **Monitor permission changes:** Track user permission updates because every change affects associated API keys.
* **Plan for user offboarding:** Provision replacement API keys under service accounts before removing users to avoid disruptions.

## Security Best Practices

### Key Storage and Management

* **Never commit API keys to version control:** Add them to `.gitignore` and rely on environment variables or secure secret management systems.
* **Use secret managers:** Store keys in AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, GitHub Secrets, or equivalent solutions.
* **Rotate keys regularly:** Create new keys and revoke old ones on a scheduled basis as part of standard security hygiene.
* **Set expiration dates:** Enforce automatic rotation and reduce risk by applying expiration dates.
* **Monitor last used timestamps:** Review usage data to identify unused or potentially compromised keys.

### Key Usage

* **Create dedicated keys per application:** Isolate access by assigning separate keys to services, environments, or purposes.
* **Use descriptive names:** Label keys clearly, such as "ci-pipeline-prod", "monitoring-staging", or "terraform-automation".
* **Limit key distribution:** Share keys only with team members who require access.
* **Revoke immediately on breach:** Replace exposed or compromised keys without delay.

### Environment Variables

Store API keys in environment variables rather than hardcoding them in scripts or configuration files. Platform-specific secret management systems (GitHub Secrets, GitLab CI/CD Variables, AWS Secrets Manager, HashiCorp Vault, and similar tools) are recommended for production environments.

### CI/CD Integration Best Practices

When using API keys in CI/CD pipelines:

* **Use pipeline secrets:** Store keys in the CI/CD platform's secret management system.
* **Mask in logs:** Ensure the platform masks API keys in build and deployment logs.
* **Create pipeline-specific keys:** Issue separate keys for each pipeline or environment (development, staging, production).
* **Set shorter expirations:** Apply shorter expiration periods (for example, 90 days) to enforce rotation.
* **Use service accounts:** Create dedicated user accounts for CI/CD pipelines (see [Permission Inheritance](#permission-inheritance) for automatic revocation details).

## Troubleshooting

### Authentication Fails with "Invalid API Key"

* Verify that the API key is copied correctly with no extra spaces, line breaks, or hidden characters.
* Ensure the key has not been revoked by checking the Revoked column in the API Keys list.
* Confirm that the key has not expired by reviewing the expiration date.
* Confirm that the correct API key format is in use, including both prefix and secret portions.
* Verify that the key prefix matches what is displayed in Prowler Cloud.

### API Key Not Working After Creation

* Verify that the full API key was copied from the creation dialog, including both the prefix and encrypted portions.
* Check that the key has not expired by reviewing the expiration date in the management interface.
* Ensure the key is not revoked by reviewing its status in the API Keys list.
* Confirm that authentication targets the correct Prowler API environment.

### Last Used Timestamp Not Updating

* The timestamp updates only on successful authentication requests.
* Authentication failures prevent the timestamp from updating.
* Verify that requests complete successfully and do not return authentication errors.
* Check that the request reaches the Prowler API and is not blocked by network policies.

### Need to Retrieve a Lost API Key

* API keys cannot be retrieved after the creation dialog closes for security reasons.
* Create a new API key to replace the lost one.
* Update all applications and scripts that rely on the old key with the new value.
* Revoke the old key after confirming that the new key works to prevent security issues.
* Consider using a secret management system to avoid future loss.

### Key Was Exposed or Compromised

1. Immediately revoke the compromised key through the API Keys management interface.
2. Review recent activity for unauthorized access using the Last Used timestamp.
3. Create a new API key with a different name to replace the compromised one.
4. Update all legitimate applications with the new key.
5. Investigate the exposure to prevent similar incidents.
6. Implement additional security measures or rely on service accounts for better isolation.

---

### User Guide Tutorials Prowler App Attack Paths Active Queries (`docs/user-guide/tutorials/prowler-app-attack-paths-active-queries.mdx`)

---
title: "Active Queries"
sidebarTitle: "Active Queries"
description: "Focus on the Attack Paths queries active in the environment: Prowler Cloud and Prowler Private Cloud record query results after each scan and hide confirmed-empty queries from the selector."
---

import { VersionBadge } from "/snippets/version-badge.mdx";
import { SubscriptionBanner } from "/snippets/subscription-banner.mdx";

<VersionBadge version="5.36.0" />

<SubscriptionBanner />

Active Queries extends the base [Attack Paths](/user-guide/tutorials/prowler-app-attack-paths) feature with capabilities that rely on managed scan infrastructure. This capability is available only in Prowler Cloud and Prowler Private Cloud.

## Focusing on Queries with Data

Running an Attack Paths query against a scan that contains no matching pattern returns an empty graph. Without automatic filtering, identifying the queries that apply to an account means opening each one and checking whether it produces a result. Running the RDS inventory query on an account with no RDS instances, for example, returns a "No data found" message.



Prowler Cloud removes that trial and error. At the end of each scan, Prowler Cloud records which built-in queries returned data. The query selector then hides the queries confirmed empty for the selected scan, so only the queries that surface a real path remain visible. Following the example above, the RDS inventory query no longer appears in the selector.



A query stays available whenever its result is not a confirmed empty graph:

- **Errored queries** remain listed. An error is not the same as an empty result and still requires investigation.
- **Unknown queries** remain listed. Their result for the scan has not been recorded yet.
- **Parameterized queries** remain listed. Their output depends on the input values provided at run time.

## Browsing the Full Query Catalog on Prowler Hub

The query selector shows the queries relevant to the selected scan, not the entire catalog. To review every built-in Attack Paths query, including the ones hidden for a given scan, browse the complete catalog on [Prowler Hub](https://hub.prowler.com).

Prowler Hub lists each query with its name, description, and the technique it detects, so security teams can plan coverage and understand detection scope without running a scan first.

## Related Pages

- [Attack Paths](/user-guide/tutorials/prowler-app-attack-paths) - Run built-in and custom queries and explore the resulting graph.
- [Attack Paths Queries](/developer-guide/attack-paths-queries) - Write and maintain openCypher queries in the Developer Guide.

---

### User Guide Tutorials Prowler App Attack Paths (`docs/user-guide/tutorials/prowler-app-attack-paths.mdx`)

---
title: "Attack Paths"
sidebarTitle: "Overview"
description: "Identify privilege escalation chains and security misconfigurations across cloud environments using graph-based analysis."
---

import { VersionBadge } from "/snippets/version-badge.mdx";
import { AppliesTo } from "/snippets/applies-to.mdx";

<VersionBadge version="5.17.0" />

<AppliesTo />

Attack Paths analyzes relationships between cloud resources, permissions, and security findings to detect how privileges can be escalated and how misconfigurations can be exploited by threat actors.

By mapping these relationships as a graph, Attack Paths reveals risks that individual security checks cannot detect on their own, such as an IAM role that can escalate its own permissions, or a chain of policies that grants unintended access to sensitive resources.

<Note>
  Attack Paths is currently available for **AWS** providers. Support for additional providers is planned.
</Note>

## Prerequisites

The following prerequisites are required for Attack Paths:

- **An AWS provider is configured** with valid credentials. For setup instructions, see [Getting Started with AWS](/user-guide/providers/aws/getting-started-aws).
- **At least one scan has completed** on the configured AWS provider and produced graph data. Attack Paths scans run automatically alongside regular security scans, no separate configuration is required.

## How Attack Paths Scans Work

Attack Paths scans are generated automatically when a security scan runs on an AWS provider. When a scan produces graph data, it maps relationships between IAM principals, policies, trust configurations, and other resources. A scan can complete without producing graph data.

When graph data is ready, the scan appears in the Attack Paths scan table with a check in the **Graph** column and can be selected regardless of its current status. Scans without graph data remain visible but cannot be selected. If a new scan cycle starts after graph data is available, the previous cycle remains available while the new scan runs.

<Note>
  Prowler Cloud and Prowler Private Cloud scan configured providers every **24 hours** by default, so Attack Paths data stays up to date automatically.
</Note>

## Accessing Attack Paths

To open Attack Paths, click **Attack Paths** in the left navigation menu.

<img
  src="/images/prowler-app/attack-paths/navigation.png"
  alt="Attack Paths navigation menu entry"
  width="320"
/>

The Attack Paths page guides you through the workflow on one page:

- Select a scan with graph data.
- Choose a built-in query or a custom openCypher query.
- Add parameters when the selected query requires them.
- Execute the query and explore the resulting graph.

## Selecting a Scan

The scans table displays all Attack Paths scans with the following columns:

- **Select:** A radio button used to choose a scan. The radio button is disabled when graph data is not available.
- **Provider:** The AWS provider alias and account identifier.
- **Last Scan Date:** When the scan completed.
- **Status:** Current state of the scan, such as **Completed**, **Executing**, **Scheduled**, or **Failed**.
- **Graph:** Whether Attack Paths graph data is available for the scan.
- **Duration:** Total scan time.

To select a scan for analysis, click any row with a check in the **Graph** column. A row can remain selectable while a new scan cycle runs because Attack Paths keeps the graph from the previous completed cycle available.

<img
  src="/images/prowler-app/attack-paths/scan-list-table.png"
  alt="Attack Paths scan list table showing completed scans"
  width="700"
/>

<Note>
  Only scans with graph data can be selected. Disabled rows include a tooltip that explains why the graph is not available yet.
</Note>

## Choosing a Query

After selecting a scan, the query selector becomes available. Each query targets a specific privilege escalation, exposure, inventory, or misconfiguration pattern.

To choose a query, click the dropdown and select from the available options. Each option displays:

- **Query name:** A descriptive title, such as **Internet-Exposed EC2 with Sensitive S3 Access**.
- **Short description:** A brief summary of what the query detects.

<img
  src="/images/prowler-app/attack-paths/query-selector.png"
  alt="Attack Paths query selector dropdown showing available queries"
  width="700"
/>

Once selected, a description panel appears below the dropdown with more context about the query.

<Note>
  In Prowler Cloud and Prowler Private Cloud, the query selector hides built-in queries confirmed empty for the selected scan. Built-in queries without a confirmed empty result and the **Custom openCypher query** remain visible. See [Active Queries](/user-guide/tutorials/prowler-app-attack-paths-active-queries).
</Note>

## Configuring Query Parameters

Some queries accept optional or required parameters to narrow the scope of the analysis. When a query has parameters, a form appears below the query description.

- **Required fields** are marked with an asterisk (\*) and must be filled before executing.
- **Optional fields** refine the query results but are not mandatory.
- Queries without parameters show no parameter form.

For example, **Internet-Exposed EC2 with Sensitive S3 Access** uses **Tag key** and **Tag value** fields to identify sensitive S3 buckets.

<img
  src="/images/prowler-app/attack-paths/query-parameters.png"
  alt="Attack Paths query parameter form with fields"
  width="700"
/>

## Writing Custom openCypher Queries

In addition to the built-in queries, Attack Paths supports custom read-only [openCypher](https://opencypher.org/) queries. Custom queries provide direct access to the underlying graph so security teams can answer ad-hoc questions, prototype detections, or extend coverage beyond the built-in catalog.

To write a custom query, select **Custom openCypher query** from the query dropdown. A code editor with syntax highlighting and line numbers appears, ready to receive the query.

### Constraints and Safety Limits

Custom queries are sandboxed to keep the graph database safe and responsive:

- **Read-only:** Only read operations are allowed. Statements that mutate the graph (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`, `DROP`, `LOAD CSV`, `CALL { ... }` writes, etc.) are rejected before execution.
- **Length limit:** Each query is capped at **10,000 characters**.
- **Scoped to the selected scan:** Results are automatically scoped to the provider and scan selected in the scan table. There is no need to filter by tenant or scan identifier in the query body.

### Example Queries

The following examples are read-only and can be pasted directly into the editor. Each one demonstrates a different graph traversal pattern.

**Internet-exposed EC2 instances with their security group rules:**

```cypher
MATCH (i:EC2Instance)--(sg:EC2SecurityGroup)--(rule:IpPermissionInbound)
WHERE i.exposed_internet = true
RETURN i.instanceid AS instance, sg.name AS security_group,
       rule.fromport AS from_port, rule.toport AS to_port
LIMIT 25
```

**EC2 instances that can assume IAM roles:**

```cypher
MATCH (i:EC2Instance)-[:STS_ASSUMEROLE_ALLOW]->(r:AWSRole)
WHERE i.exposed_internet = true
RETURN i.instanceid AS instance, r.name AS role_name, r.arn AS role_arn
LIMIT 25
```

**IAM principals with wildcard Allow statements:**

```cypher
MATCH (principal:AWSPrincipal)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement {effect: 'Allow'})
MATCH (stmt)-[:HAS_ACTION]->(a:AWSPolicyStatementActionItem)
WHERE a.value = '*'
RETURN DISTINCT principal.arn AS principal, policy.arn AS policy
LIMIT 25
```

**Critical findings on internet-exposed resources:**

```cypher
MATCH (i:EC2Instance)-[:HAS_FINDING]->(f:ProwlerFinding)
WHERE i.exposed_internet = true AND f.status = 'FAIL'
  AND f.severity IN ['critical', 'high']
RETURN i.instanceid AS instance, f.check_id AS check,
       f.severity AS severity, f.status AS status
LIMIT 50
```

**Roles trusting an AWS service (building block for PassRole escalation):**

```cypher
MATCH (r:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]->(p:AWSPrincipal)
WHERE p.arn ENDS WITH '.amazonaws.com'
RETURN r.name AS role_name, r.arn AS role_arn, p.arn AS trusted_service
LIMIT 25
```

### Graph Schema and Advanced Patterns

Custom queries traverse the same Cartography graph the built-in queries use. Node labels, relationships, and properties follow the upstream [Cartography AWS Schema](https://cartography-cncf.github.io/cartography/modules/aws/schema.html), enriched by Prowler with `ProwlerFinding` nodes linked through `HAS_FINDING`, `Internet` exposure nodes, and list-typed properties such as `action` and `resource` modeled as child item nodes.

For the complete reference, including the graph model, list-typed and JSON-encoded properties, performance guidance, and openCypher compatibility rules, see [Attack Paths Queries](/developer-guide/attack-paths-queries) in the Developer Guide.

<Note>
  AI assistants connected through Prowler MCP Server can fetch the exact Cartography schema for the active scan via the `prowler_get_attack_paths_cartography_schema` tool. This guarantees that generated queries match the schema version pinned by the running Prowler release.
</Note>

## Executing a Query

To run the selected query against the scan data, click **Execute Query**. The button is disabled until a query is selected and all required parameters are valid.

The button displays a loading state while the query runs. After the query completes, the graph appears below the query builder.

If the query returns no results, an informational message appears. Common reasons include:

- **No matching patterns found:** The scanned environment does not contain the pattern the query targets.
- **Not enough permissions:** The scan credentials may not have captured all the data the query needs.
- **Server unavailable:** The graph service may be temporarily unavailable.

<img
  src="/images/prowler-app/attack-paths/execute-query.png"
  alt="Attack Paths query builder with query selected and execute button"
  width="700"
/>

## Exploring the Graph

After a successful execution, the graph visualization renders below the query builder. The graph maps relationships between cloud resources, IAM entities, public exposure, and security findings.

### Grouped Graphs and Query Outcomes

<VersionBadge version="5.39.0" />

Prowler Cloud and Prowler Private Cloud group resources of the same class and graph level into expandable nodes. Built-in query graphs also end with a query outcome, which states the result that the path can lead to.

Prowler Local Server keeps the flat graph view, including the provider root. Custom openCypher queries do not have a catalog outcome, so their graphs do not include an outcome node.

### Node Types

- **Grouped resource nodes:** Represent multiple resources of the same class in Prowler Cloud and Prowler Private Cloud. A number in the upper-right corner shows how many resources the node contains. A red outline indicates that one or more resources in the group have findings.
- **Resource nodes:** Represent individual cloud resources such as IAM roles, policies, EC2 instances, security groups, and S3 buckets. A class with one resource remains an individual node.
- **Internet nodes:** Represent exposure from the public internet.
- **Finding nodes:** Represent Prowler findings linked to resources. Finding colors indicate risk level, such as critical, high, medium, or low.
- **Outcome nodes:** Mark the terminal result of a built-in query in Prowler Cloud and Prowler Private Cloud. The orange node displays outcomes such as **Code execution**, **Privilege escalation**, **Public exposure**, or **Resource inventory**. A dashed ring and the label **Latent outcome** indicate an inventory or another partial outcome.
- **Provider root nodes:** Represent the AWS account or provider root in the Prowler Local Server flat graph.

### Edge Types

- **Normal edges:** Direct relationships between graph nodes, such as role-to-policy or resource-to-security-group relationships.
- **Finding edges:** Dashed relationships between resources and their associated findings.
- **Highlighted paths:** Green edges that show the active path when you hover a node or focus a finding.

The standard graph view includes a minimap and a legend below the canvas. The legend shows the visible node types, finding risk levels, node states, and edge types present in the current view. Prowler Local Server also displays the provider root in the legend.

<img
  src="/images/prowler-app/attack-paths/graph-visualization.png"
  alt="Attack Paths graph showing nodes and edges"
  width="700"
/>

## Interacting with the Graph

The graph supports these interactions:

- Click a node with a number in the upper-right corner to expand its resource group.
- Click a finding to focus its connected path.
- Click a resource with findings to show or hide its related findings.
- Hover a node to highlight its connected path.

### Expanding Resource Groups

In Prowler Cloud and Prowler Private Cloud, a node with a number in its upper-right corner represents a resource group. The number is the total number of resources in the group.

- Click the group node to display its resources. Multiple groups can remain expanded, and the graph automatically fits the visible nodes to the canvas.
- Double-click any resource revealed from a group to collapse that group.
- Click **Collapse all groups** in the graph toolbar to close every expanded group. This control appears only while at least one group is expanded.

<img
  src="/images/prowler-app/attack-paths/graph-visualization-expanded.png"
  alt="Attack Paths graph showing an expanded AWS Role group and remaining numbered resource groups"
  width="700"
/>

### Showing Related Findings

Resource nodes with related findings are clickable. Click one of these resources to show its finding nodes. Click the resource again to hide them.

The selected resource is highlighted in green while its findings are visible. The graph automatically fits the selected resource and its related findings when the findings are shown.

### Focusing a Finding Path

Click a finding node to focus the graph on the path connected to that finding. When the graph is focused:

- The graph shows **Back to Full View**.
- The status banner shows the selected finding.
- The graph keeps only the connected path in view.
- The finding detail drawer opens.

After you close the drawer, the graph remains focused on the selected path.

<img
  src="/images/prowler-app/attack-paths/graph-filtered.png"
  alt="Attack Paths graph focused on a selected finding path"
  width="700"
/>

### Graph Controls

The toolbar in the top-right corner of the graph provides:

- **Zoom in / Zoom out:** Adjust the zoom level
- **Fit graph to view:** Reset the view to fit the visible graph
- **Collapse all groups:** Close every expanded resource group. This control appears only when a group is expanded
- **Export graph:** Download the current graph, including its grouped or expanded state and outcome, as a PNG file
- **Fullscreen:** Open the graph in a full-size modal

<Note>
  Use **Ctrl + Scroll** (or **Cmd + Scroll** on macOS) to zoom directly within the graph area.
</Note>

## Viewing Finding Details

Click a finding node to open the finding detail drawer. The drawer uses the same finding detail layout as the Findings page and includes:

- The finding title, status, and severity.
- The affected resource summary.
- Overview, remediation, evidence, related findings, scans, and events tabs when data is available.
- A Lighthouse AI action when the account has access to Lighthouse AI.

Resource nodes do not open a node detail panel. When a resource has related findings, clicking it expands or collapses those finding nodes in the graph.

<img
  src="/images/prowler-app/attack-paths/node-details.png"
  alt="Attack Paths finding detail drawer"
  width="700"
/>

## Fullscreen Mode

To expand the graph for detailed exploration, click the fullscreen icon in the graph toolbar. The fullscreen modal provides:

- The graph in a full-size modal.
- The same zoom, fit, and export controls.
- The same node expansion, finding focus, hover highlight, and minimap interactions available in the standard view.

<img
  src="/images/prowler-app/attack-paths/fullscreen-mode.png"
  alt="Attack Paths fullscreen graph mode"
  width="700"
/>

## Available Queries

The query selector includes custom openCypher and built-in AWS queries for common security investigation workflows. Available queries are loaded from the selected scan and may change as new query packs are added.

Available queries include:

- **Custom openCypher query:** Write and run a read-only graph query.
- **Exposure queries:** Find internet-exposed EC2 instances, load balancers, open security groups, and resources by public IP.
- **Inventory queries:** List resources such as RDS instances.
- **Misconfiguration queries:** Find unencrypted RDS instances, public S3 buckets, and wildcard IAM statements.
- **Privilege escalation queries:** Detect IAM and AWS service paths based on known attack techniques, including queries based on [pathfinding.cloud](https://pathfinding.cloud) research by Datadog.

## Using Attack Paths with the MCP Server and Lighthouse AI

Attack Paths capabilities are also available through the [Prowler MCP Server](/getting-started/products/prowler-mcp), enabling interaction with Attack Paths data via AI assistants like Claude Desktop, Cursor, and other MCP clients.

[Prowler Lighthouse AI](/getting-started/products/prowler-lighthouse-ai) also supports Attack Paths queries, allowing you to analyze privilege escalation chains and security misconfigurations directly from the chat interface.

The following MCP tools are available for Attack Paths:

- **`prowler_list_attack_paths_scans`** - List and filter Attack Paths scans.
- **`prowler_list_attack_paths_queries`** - Discover available queries for a completed scan.
- **`prowler_run_attack_paths_query`** - Execute a query and retrieve graph results with nodes and relationships.
- **`prowler_get_attack_paths_cartography_schema`** - Retrieve the Cartography graph schema for custom openCypher queries.

### Example Questions

Ask through the MCP Server or Lighthouse AI:

- "Find EC2 instances exposed to the internet with access to sensitive S3 buckets"
- "Are there any IAM roles that can escalate their own privileges?"
- "Show me all internet-facing resources with open security groups"
- "Which principals can create Lambda functions with privileged roles?"
- "List all RDS instances with storage encryption disabled"
- "Find S3 buckets that allow anonymous access"
- "Are there any CloudFormation stacks that could be hijacked for privilege escalation?"
- "Show me all roles that can be assumed for lateral movement"

These tools enable workflows such as:

- Asking an AI assistant to identify privilege escalation paths in a specific AWS account
- Automating attack path analysis across multiple scans
- Combining attack path data with findings and compliance information for comprehensive security reports

For the complete list of MCP tools, see the [Tools Reference](/getting-started/basic-usage/prowler-mcp-tools#attack-paths-analysis).

---

### User Guide Tutorials Prowler App Finding Groups (`docs/user-guide/tutorials/prowler-app-finding-groups.mdx`)

---
title: 'Finding Groups'
sidebarTitle: 'Groups'
description: 'Organize and triage security findings by check to reduce noise and prioritize remediation effectively.'
---

import { VersionBadge } from "/snippets/version-badge.mdx"
import { AppliesTo } from "/snippets/applies-to.mdx"

<VersionBadge version="5.23.0" />

<AppliesTo />

Finding Groups transforms security findings triage by grouping them by check instead of displaying a flat list. This dramatically reduces noise and enables faster, more effective prioritization.

## Triage Challenges with Flat Finding Lists

A real cloud environment produces thousands of findings per scan. A flat list makes it impossible to triage effectively:

- **Signal buried in noise**: the same misconfiguration repeated across 200 resources shows up as 200 rows, burying the signal in repetitive data
- **Prioritization guesswork**: without grouping, understanding which issues affect the most resources requires manual counting and correlation
- **Tedious muting**: muting a false positive globally requires manually acting on each individual finding across the list
- **Lost context**: when investigating a single resource, related findings are scattered across the same flat list, making it hard to see the full picture

## How Finding Groups Addresses These Challenges

Finding Groups addresses these challenges by intelligently grouping findings by check.

### Grouped View at a Glance

Each row represents a single check title with key information immediately visible:

- **Severity** indicator for quick risk assessment
- **Impacted providers** showing which cloud platforms are affected
- **X of Y impacted resources** counter displaying how many resources fail this check

For example, `Vercel project has the Web Application Firewall enabled` across every affected project collapses to a single row — not one per project. Sort or filter by severity, provider, or status at the group level to triage top-down instead of drowning in per-resource rows.



### Expanding Groups for Details

Expand any group inline to see the failing resources with detailed information:

| Column | Description |
|--------|-------------|
| **UID** | Unique identifier for the resource |
| **Service** | The cloud service the resource belongs to |
| **Region** | Geographic region where the resource is deployed |
| **Severity** | Risk level of the finding |
| **Provider** | Cloud provider (AWS, Azure, GCP, Kubernetes, etc.) |
| **Last Seen** | When the finding was last detected |
| **Failing For** | Duration the resource has been in a failing state |



### Resource Detail Drawer

Select any resource to open the detail drawer with full finding context:

- **Risk**: the security risk associated with this finding
- **Description**: detailed explanation of what was detected
- **Status Extended**: additional status information and context
- **Remediation**: step-by-step guidance to resolve the issue
- **View in Prowler Hub**: direct link to explore the check in Prowler Hub
- **Analyze This Finding With Lighthouse AI**: one-click AI-powered analysis for deeper insights



### Bulk Actions

Bulk-mute an entire group instead of chasing duplicates across the list. This is especially useful for:

- Known false positives that appear across many resources
- Findings in development or test environments
- Accepted risks that have been documented and approved

<Warning>
Muting findings does not resolve underlying security issues. Review each finding carefully before muting to ensure it represents an acceptable risk or has been properly addressed.
</Warning>

## Other Findings for This Resource

Inside the resource detail drawer, the **Other Findings For This Resource** tab lists every finding that hits the same resource — passing, failing, and muted — alongside the one currently being reviewed.



### Why This Matters

When reviewing "WAF not enabled" on a Vercel project, the tab immediately shows:

- Skew protection status
- Rate limiting configuration
- IP blocking settings
- Custom firewall rules
- Password protection findings

All for that same project, without navigating back to the main list and filtering by resource UID.

### Complete Context Within the Drawer

Pair the Other Findings tab with:

- **Scans tab**: scan history for this resource
- **Events tab**: changes and events over time

This provides full context without leaving the drawer.

## Best Practices

1. **Start with high severity groups**: focus on critical and high severity groups first for maximum impact.
2. **Use filters strategically**: filter by provider or status at the group level to narrow the triage scope.
3. **Leverage bulk mute**: when a finding represents a confirmed false positive, mute the entire group at once.
4. **Check related findings**: review the Other Findings tab to understand the full security posture of a resource.
5. **Track failure duration**: use the "Failing For" column to prioritize long-standing issues that may indicate systemic problems.

## Getting Started

1. Navigate to the **Findings** section in Prowler Cloud.
2. Toggle to the **Grouped View** to see findings organized by check.
3. Select any group row to expand and see affected resources.
4. Select a resource to open the detail drawer with full context.
5. Use the **Other Findings For This Resource** tab to see all findings for that resource.

---

### User Guide Tutorials Prowler App Findings Triage (`docs/user-guide/tutorials/prowler-app-findings-triage.mdx`)

---
title: "Findings Triage"
sidebarTitle: 'Triage'
description: "Track finding review status and team notes in Prowler Cloud."
---

import { VersionBadge } from "/snippets/version-badge.mdx"
import { SubscriptionBanner } from "/snippets/subscription-banner.mdx"

<VersionBadge version="5.32.0" />

Findings Triage lets teams track review status and notes for individual findings in Prowler Cloud. Use it to record investigation state, remediation work, accepted risk, or false positive decisions without leaving the Findings workflow.

<SubscriptionBanner />

## What Is Findings Triage?

Findings Triage adds a **Triage** status and team note workflow to individual finding rows. It is available from:

- Expanded rows in **Finding Groups**
- Standalone finding tables
- Finding and resource detail drawers, including related findings tables

Finding Groups rows do not show triage controls because a group row represents several findings. Expand a group to work with each affected resource.



## Required Permissions

To update triage statuses and notes, the user role must have the **Manage Scans** permission. For more information, see [Role-Based Access Control (RBAC)](/user-guide/tutorials/prowler-app-rbac).

Users without this permission can still see existing triage context when it is available, but cannot change statuses or save notes.

## Triage Statuses

The status selector includes manual statuses. Prowler also sets automatic statuses after scans.

| Status | Type | Use It When |
| --- | --- | --- |
| **Open** | Manual | A failed finding has not been reviewed yet. A failed finding with no saved triage state also appears as **Open**. |
| **Under Review** | Manual | A team is investigating the finding. |
| **Remediating** | Manual | Work is in progress to fix the finding. |
| **Risk Accepted** | Manual | The team accepts the risk and wants to mute the finding. |
| **False Positive** | Manual | The finding does not apply and should be muted. |
| **Resolved** | Automatic / Manual | A finding changed from `FAIL` to `PASS` in a later scan. A passed finding with no saved triage state also appears as **Resolved**. On `MANUAL` findings, select it to verify the finding as passing (see [Verify a MANUAL Finding as Pass](#verify-a-manual-finding-as-pass)). |
| **Reopened** | Automatic | A finding changed from `PASS` to `FAIL` in a later scan. |



**Reopened** is never a manual selector option. **Resolved** appears in the selector only on `MANUAL` findings, where it starts the [Manual Pass verification](#verify-a-manual-finding-as-pass).

These automatic states keep triage tied to the finding UID across scans, even when each scan creates a new finding snapshot.

## Change a Triage Status

<Steps>
  <Step title="Open Findings">
    Go to **Findings** in Prowler Cloud.
  </Step>
  <Step title="Select an individual finding">
    Expand a Finding Group, open a resource findings table, or use a standalone finding row.
  </Step>
  <Step title="Open the triage selector">
    In the **Triage** column, click the current status.
  </Step>
  <Step title="Choose a status">
    Select **Open**, **Under Review**, **Remediating**, **Risk Accepted**, or **False Positive**.
  </Step>
</Steps>

Changing a finding to **Risk Accepted** or **False Positive** will mute the finding. Prowler asks for confirmation and creates a mute rule for the finding.

## Add or Edit a Triage Note

Triage notes are visible only to the team in the current organization. Each note supports up to 500 characters.

<Steps>
  <Step title="Open the finding actions menu">
    On an individual finding row, click the actions menu.
  </Step>
  <Step title="Open the note modal">
    Click **Add Triage Note**. If a note already exists, click **Open note**.
  </Step>
  <Step title="Set status and note text">
    Optionally change the status, then write the note.
  </Step>
  <Step title="Save changes">
    Click **Save changes**.
  </Step>
</Steps>



To remove an existing note, clear the note text and save the change.

## Verify a MANUAL Finding as Pass

<VersionBadge version="5.39.0" />

Checks that Prowler cannot judge automatically report `MANUAL` findings. When a team verifies such a control outside Prowler, the triage selector on that finding offers **Resolved**: choosing it records a Manual Pass attestation, and the finding reports an effective `PASS` while keeping the raw `MANUAL` scan result.



<Steps>
  <Step title="Filter MANUAL findings">
    Go to **Findings** and filter by status **Manual**.
  </Step>
  <Step title="Open the triage selector">
    Expand a Finding Group and click the current status in the **Triage** column of an individual finding.
  </Step>
  <Step title="Choose Resolved">
    Select **Resolved**. Prowler opens the triage note modal with a required **Manual pass evidence** field.
  </Step>
  <Step title="Record the evidence">
    Describe how the control was verified, then click **Save**. The evidence supports up to 500 characters.
  </Step>
</Steps>



After saving, the finding reports `PASS` in finding tables, finding groups, compliance reports, and scans. While the attestation is active, the triage status is managed automatically and cannot be changed. **View Manual Pass details** shows who verified the finding, the evidence, the attestation time, and its expiration.



### Attestation Expiration

A Manual Pass attestation is valid for 90 days. It also ends early when a later scan reports a real failure for the finding. In both cases the finding returns to its raw `MANUAL` status for a new review.

## Mutelist Behavior

Findings Triage uses Mutelist when a status means the finding should be muted:

- **Risk Accepted** creates a mute rule because the team accepts the finding as a known risk.
- **False Positive** creates a mute rule because the finding should not count as an active issue.

Use [Simple Mutelist](/user-guide/tutorials/prowler-app-simple-mutelist) to review, disable, or delete mute rules created through this workflow. For pattern-based muting, use [Advanced Mutelist](/user-guide/tutorials/prowler-app-mute-findings).

<Warning>
Muting a finding does not fix the underlying configuration. Review the finding before using **Risk Accepted** or **False Positive**.
</Warning>

## Troubleshooting

### Triage Controls Do Not Appear

Make sure the row is an individual finding row. Finding Groups rows do not show triage controls. Expand a group to see affected resources and their triage controls.

### Changes Cannot Be Saved

Confirm that the user role has **Manage Scans** permission. Prowler Local Server does not support Findings Triage writes.

### Resolved or Reopened Is Missing from the Selector

**Reopened** is always automatic. **Resolved** is set automatically from scan result changes and appears as a selector option only on `MANUAL` findings, where it records a [Manual Pass](#verify-a-manual-finding-as-pass). On findings with any other status, this is expected.

### Risk Accepted or False Positive Muted a Finding

This is expected. Those statuses create a mute rule through Mutelist.

---

### User Guide Tutorials Prowler App Github Action (`docs/user-guide/tutorials/prowler-app-github-action.mdx`)

---
title: 'GitHub Action'
description: 'Run Prowler scans in GitHub Actions using the official Docker-based action'
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="5.25.0" />

The official **Prowler GitHub Action** runs Prowler scans inside your GitHub workflows using the official [`prowlercloud/prowler`](https://hub.docker.com/r/prowlercloud/prowler) Docker image. It supports every [Prowler provider](/user-guide/providers/) (AWS, Azure, GCP, Kubernetes, GitHub, Cloudflare, IaC, and more), optionally pushes findings to Prowler Cloud, and uploads SARIF results to GitHub Code Scanning so findings appear in the **Security** tab and as inline PR annotations.

Source: [`prowler-cloud/prowler`](https://github.com/prowler-cloud/prowler) · Marketplace listing: [Prowler Security Scan](https://github.com/marketplace/actions/prowler-security-scan).

## Inputs

| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `provider` | yes | — | Cloud provider to scan (`aws`, `azure`, `gcp`, `github`, `kubernetes`, `iac`, `cloudflare`, etc.) |
| `image-tag` | no | `stable` | Docker image tag — `stable` (latest release), `latest` (master, not stable), or `<x.y.z>` (pinned). See [available tags](https://hub.docker.com/r/prowlercloud/prowler/tags). |
| `output-formats` | no | `json-ocsf` | Output format(s) for scan results. Space-separated (e.g. `sarif json-ocsf`) |
| `push-to-cloud` | no | `false` | Push findings to [Prowler Cloud](/user-guide/tutorials/prowler-import-findings). When `true`, `PROWLER_CLOUD_API_KEY` is auto-forwarded |
| `flags` | no | `""` | Additional CLI flags (e.g. `--severity critical high`). Values with spaces can be quoted: `--resource-tag 'Environment=My Server'` |
| `extra-env` | no | `""` | Space-, newline-, or comma-separated list of env var **names** to forward to the container (see [Authentication](#authentication)) |
| `upload-sarif` | no | `false` | Upload SARIF results to GitHub Code Scanning |
| `sarif-file` | no | `""` | Path to SARIF file (auto-detected from `output/` if not set) |
| `sarif-category` | no | `prowler` | Category for the SARIF upload (distinguishes multiple analyses) |
| `fail-on-findings` | no | `false` | Fail the workflow step when findings are detected (exit code 3) |

## Usage

### AWS Scan

```yaml
- uses: prowler-cloud/prowler@5.25
  with:
    provider: aws
    extra-env: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
```

### Push Findings to Prowler Cloud

Send scan results directly to [Prowler Cloud](/user-guide/tutorials/prowler-import-findings) for centralized visibility, compliance tracking, and team collaboration.

```yaml
- uses: prowler-cloud/prowler@5.25
  with:
    provider: aws
    push-to-cloud: true
    extra-env: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
    PROWLER_CLOUD_API_KEY: ${{ secrets.PROWLER_CLOUD_API_KEY }}
```

<Info>
When `push-to-cloud: true`, `PROWLER_CLOUD_API_KEY` is forwarded automatically — set it in `env:` but don't list it in `extra-env`. Requires a Prowler Cloud subscription and an API key with the **Manage Ingestions** permission. See [API Keys](/user-guide/tutorials/prowler-app-api-keys).
</Info>

### Upload SARIF to GitHub Code Scanning

Findings appear in the **Security** tab and as **inline PR annotations** when SARIF upload is enabled.

```yaml
name: Prowler IaC Scan
on:
  pull_request:

permissions:
  contents: read
  security-events: write
  actions: read

jobs:
  prowler:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: prowler-cloud/prowler@5.25
        with:
          provider: iac
          output-formats: sarif json-ocsf
          upload-sarif: true
          flags: --severity critical high
```

<Warning>
**Requirements:**
- Include `sarif` in `output-formats` (the action warns if this is missing).
- The workflow needs `security-events: write` and `actions: read` permissions.
- GitHub Code Scanning is free for public repositories. Private repositories require a [GitHub Code Security](https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security) license.
</Warning>

### Combine Push-to-Cloud with SARIF Upload

```yaml
- uses: prowler-cloud/prowler@5.25
  with:
    provider: aws
    output-formats: sarif json-ocsf
    push-to-cloud: true
    upload-sarif: true
    extra-env: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
    PROWLER_CLOUD_API_KEY: ${{ secrets.PROWLER_CLOUD_API_KEY }}
```

### Scan the Current Repository with the GitHub Provider

```yaml
name: Prowler GitHub Scan
on:
  schedule:
    - cron: '0 0 * * 0'
  workflow_dispatch:

jobs:
  prowler:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: prowler-cloud/prowler@5.25
        with:
          provider: github
          flags: --repository ${{ github.repository }}
          extra-env: GITHUB_PERSONAL_ACCESS_TOKEN
        env:
          GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.PROWLER_GITHUB_PAT }}
```

<Info>
`--repository` scans a single repo. Use `--organization <name>` instead to include org-level checks (MFA, security policies, etc.). See the [GitHub provider authentication](/user-guide/providers/github/authentication) for required token permissions.
</Info>

### Fail the PR on Findings

By default the action tolerates findings (exit code 3) and succeeds. Set `fail-on-findings: true` to fail the workflow step when Prowler detects findings. Combine with `--severity` to control which severity levels trigger the failure:

```yaml
- uses: prowler-cloud/prowler@5.25
  with:
    provider: iac
    output-formats: sarif
    upload-sarif: true
    fail-on-findings: true
    flags: --severity critical high
```

The scan step fails if critical/high findings are detected, blocking the PR via required checks. SARIF is still uploaded (the upload step runs with `if: always()`) so findings appear in the Security tab regardless.

## Authentication

Each provider requires its own credentials passed as environment variables. Credentials are **not forwarded automatically** — list every env var name you need in the `extra-env` input, and set its value via `env:` at the step, job, or workflow level (typically from `secrets.*`).

Refer to the [Prowler provider docs](/user-guide/providers/) for the full list of variables each provider supports. Common ones:

| Provider | Typical `extra-env` |
|----------|---------------------|
| AWS | `AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_DEFAULT_REGION` (OIDC exports these automatically) |
| Azure | `AZURE_CLIENT_ID AZURE_CLIENT_SECRET AZURE_TENANT_ID` |
| GCP | `GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_AUTH_ACCESS_TOKEN GOOGLE_CLOUD_PROJECT` |
| GitHub | `GITHUB_PERSONAL_ACCESS_TOKEN` *(or `GITHUB_OAUTH_APP_TOKEN`, or `GITHUB_APP_ID GITHUB_APP_KEY`)* |
| Kubernetes | `KUBECONFIG` |
| Cloudflare | `CLOUDFLARE_API_TOKEN` *(or `CLOUDFLARE_API_KEY CLOUDFLARE_API_EMAIL`)* |

<Info>
`PROWLER_CLOUD_API_KEY` is auto-forwarded when `push-to-cloud: true` — no need to add it to `extra-env`.
</Info>

### AWS

Use [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) with OIDC (recommended) or pass static credentials. OIDC sets `AWS_*` env vars on the runner, so you only forward them:

```yaml
permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/ProwlerRole
      aws-region: eu-west-1

  - uses: prowler-cloud/prowler@5.25
    with:
      provider: aws
      extra-env: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_DEFAULT_REGION
```

### Azure

Use [azure/login](https://github.com/Azure/login) with a service principal or pass credentials directly:

```yaml
steps:
  - uses: azure/login@v2
    with:
      creds: ${{ secrets.AZURE_CREDENTIALS }}

  - uses: prowler-cloud/prowler@5.25
    with:
      provider: azure
      extra-env: AZURE_CLIENT_ID AZURE_CLIENT_SECRET AZURE_TENANT_ID
    env:
      AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
```

### GCP

Use [google-github-actions/auth](https://github.com/google-github-actions/auth) with Workload Identity Federation (recommended):

```yaml
permissions:
  id-token: write
  contents: read

steps:
  - uses: google-github-actions/auth@v2
    with:
      workload_identity_provider: projects/123456/locations/global/workloadIdentityPools/my-pool/providers/my-provider
      service_account: prowler@my-project.iam.gserviceaccount.com

  - uses: prowler-cloud/prowler@5.25
    with:
      provider: gcp
      extra-env: GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_AUTH_ACCESS_TOKEN GOOGLE_CLOUD_PROJECT
```

### Cloudflare

Create a Cloudflare API Token with the `Account Settings:Read`, `Zone:Read`, `Zone Settings:Read`, `DNS:Read`, `SSL and Certificates:Read`, `Bot Management:Read`, and `Zone WAF:Read` permissions ([provider auth docs](/user-guide/providers/cloudflare/authentication)). Then:

```yaml
- uses: prowler-cloud/prowler@5.25
  with:
    provider: cloudflare
    extra-env: CLOUDFLARE_API_TOKEN
  env:
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
```

## Outputs

Scan results are written to `output/` in the workspace and uploaded as artifacts named `prowler-<provider>` with 30-day retention.

When `upload-sarif` is enabled, SARIF results are also uploaded to GitHub Code Scanning and appear on the repository's **Security → Code scanning** tab, filtered by the branch that ran the scan.

### Step Summary

The action writes a summary to the run page with a per-severity breakdown of failing checks, artifact and Code Scanning links, and (when `push-to-cloud: false`) a pointer to [Prowler Cloud](https://cloud.prowler.com) for continuous monitoring.

<img src="/images/github-action/scan-summary.png" alt="GitHub Actions run page showing the Prowler IaC Scan Summary with failing and passing counts, severity breakdown, scan log link, artifact link, and GitHub Code Security link" width="1400" />

---

### User Guide Tutorials Prowler App Jira Integration (`docs/user-guide/tutorials/prowler-app-jira-integration.mdx`)

---
title: "Jira Integration"
sidebarTitle: 'Jira'
---
import { VersionBadge } from "/snippets/version-badge.mdx"
import { AppliesTo } from "/snippets/applies-to.mdx"

<VersionBadge version="5.12.0" />

<AppliesTo />

Prowler Cloud enables automatic export of security Findings to Jira, providing seamless integration with Atlassian's work item tracking and project management platform. This comprehensive guide demonstrates how to configure and manage Jira integrations to streamline security incident management and enhance team collaboration across security workflows.

Integrating Prowler Cloud with Jira provides:

* **Streamlined management:** Convert security Findings directly into actionable Jira work items
* **Enhanced team collaboration:** Leverage existing project management workflows for security remediation
* **Automated ticket creation:** Reduce manual effort in tracking and assigning security work items

## How It Works

When enabled and configured:

1. Select one or more complete Finding Groups, or expand a Finding Group and select multiple Findings, from the Findings table.
2. Send the selection to Jira in one action.
3. Choose how Jira issues are created:
    * **Grouped issue:** Create one Jira issue that contains all selected Findings from the Finding Group.
    * **Separate issues:** Create one Jira issue for each selected Finding. Each Finding represents one affected resource.
4. Review the Finding Group summary and affected-resource details in Jira, then use the link at the bottom of the issue to open the complete Finding Group in Prowler Cloud.

## Prerequisites

<VersionBadge version="5.36.0" />

Configuring and using the Jira integration requires the **Manage Integrations** permission. The Jira integration is tenant-wide, so it does not require **Unlimited Visibility** or any specific Provider Group. Findings sent to Jira are still limited to the providers the role can access.

## Configuration

To configure Jira integration in Prowler Cloud:

1. Navigate to **Integrations** in Prowler Cloud
2. Locate the **Jira** card and click **Manage**, then select **Add integration**

    

3. Complete the integration settings:
    * **Jira domain:** Enter the Jira domain (e.g., from `https://your-domain.atlassian.net` -> `your-domain`)
    * **Email:** Your Jira account email
    * **API Token:** API token with the following scopes: `read:jira-user`, `read:jira-work`, `write:jira-work`
        

<Note>
**Generate Jira API Token**

To generate a Jira API token, visit: https://id.atlassian.com/manage-profile/security/api-tokens

</Note>

Once configured successfully, the integration is ready to send Findings to Jira.

## Sending Findings to Jira

Prowler Cloud can send a complete Finding Group or a selection of Findings within a group to Jira in one action.

### Sending a Complete Finding Group

<VersionBadge version="5.36.0" />

To send every Finding in a Finding Group:

1. Navigate to **Findings** in Prowler Cloud.
2. Select one or more Finding Groups.
3. Open the bulk actions menu and click **Send Finding Group to Jira**.

    

4. Select the Jira project and issue type.
5. Choose an issue creation mode:
    * **Create one Jira issue for all selected Findings in this Finding Group:** Keeps the complete Finding Group in one Jira issue.
    * **Create separate Jira issues:** Creates one Jira issue per selected Finding so that each affected resource can be tracked independently.
6. Click **Send to Jira**.

    

### Sending Multiple Findings from a Group

<VersionBadge version="5.36.0" />

To send only specific affected resources:

1. Expand a Finding Group.
2. Select the Findings to send. Each Finding represents one affected resource.
3. Open the bulk actions menu and click **Send Findings to Jira**.
4. Select the Jira project, issue type, and grouped or separate issue creation mode.
5. Click **Send to Jira**.

    

### Reviewing the Jira Issue

A grouped Jira issue starts with Finding Group summary information. This section identifies the check and provides context such as the check title and ID, severity, status, provider, service, number of affected failing resources, last-seen time, failure duration, and risk.



The affected-resources table lists the selected Findings included in the Jira issue. Each row represents an affected resource and includes the information needed to identify and triage it, such as resource and provider identifiers, provider, service, status, severity, region, last-seen time, failure duration, and triage status.



At the bottom of the affected-resources table, click **View this Finding Group in Prowler Cloud** to open the complete Finding Group in Prowler Cloud. The link opens the group, not only the Findings included in the Jira issue.



### Sending One Finding

<VersionBadge version="5.12.0" />

To manually send individual Findings to Jira:

1. Navigate to the **Findings** section in Prowler Cloud
2. Select one Finding you want to export
3. Click the action button on the table row and select **Send to Jira**
4. Select the Jira integration and project
5. Click **Send to Jira**

    

### Finding Reference in the Jira Issue

<VersionBadge version="5.41.0" />

Every Jira issue created from a single Finding carries a stable reference back to that Finding, so issues can be filtered, searched with Jira Query Language (JQL), or matched by automation:

* **Labels**: `prowler`, `prowler-<provider>`, `prowler-<severity>`, `prowler-<check-id>` and `prowler-finding-<finding-uid>`. Labels are sanitized deterministically: whitespace becomes `_`, control characters are removed, and values are truncated to Jira's 255-character label limit.
* **Finding URL**: a link that opens the Finding in Prowler, filtered by its unique identifier (UID) so it keeps working after later scans.
* **Tenant Info**: the name of the Prowler organization that sent the Finding.

Prowler Cloud always includes the Finding URL. In Prowler Local Server, set `DJANGO_UI_BASE_URL` in the API environment (for example, `https://prowler.example.com`) to enable it. When the variable is empty, the issue is created without the link.

## Integration Status

Monitor and manage your Jira integrations through the management interface:

1. Review configured integrations in the integrations dashboard
2. Each integration displays:

    * **Connection Status:** Connected or Disconnected indicator
    * **Instance Information:** Jira domain and last checked timestamp

### Actions

Each Jira integration provides management actions through dedicated buttons:

| Button | Purpose | Available Actions | Notes |
|--------|---------|------------------|-------|
| **Test** | Verify integration connectivity | • Test Jira API access<br/>• Validate credentials<br/>• Check project permissions<br/>• Verify work item creation capability | Results displayed in notification message |
| **Credentials** | Update authentication settings | • Change API token<br/>• Update email<br/>• Update Jira domain | Click "Update Credentials" to save changes |
| **Enable/Disable** | Toggle integration status | • Enable or disable integration<br/>| Status change takes effect immediately |
| **Delete** | Remove integration permanently | • Permanently delete integration<br/>• Remove all configuration data | ⚠️ **Cannot be undone** - confirm before deleting |

## Known Limitations

### Issue Types with Required Custom Fields

Certain Jira issue types (such as Epic) may require mandatory custom fields that Prowler does not currently populate when creating work items. If a selected issue type enforces required fields beyond the standard set (e.g., "Team", "Epic Name"), the work item creation will fail.

To avoid this, select an issue type that does not require additional custom fields — **Task**, **Bug**, or **Story** typically work without restrictions. If unsure which issue types are available for a project, Prowler automatically fetches and displays them in the "Issue Type" selector when sending a Finding.

<Note>
Support for custom field mapping is planned for a future release.
</Note>

## Troubleshooting

### Connection Test Fails

* Verify Jira instance domain is correct and accessible
* Confirm API token or credentials are valid
* Ensure API access is enabled in Jira settings and the needed scopes are granted

### Check Task Status (API)

If the Jira issue does not appear in your Jira project, follow these steps to verify the export task status via the API.

<Note>
Replace `http://localhost:8080` with the base URL where your Prowler API is accessible (for example, `https://api.yourdomain.com`).

</Note>

1) Get an access token (replace email and password):

```bash
curl --location 'http://localhost:8080/api/v1/tokens' \
  --header 'Content-Type: application/vnd.api+json' \
  --header 'Accept: application/vnd.api+json' \
  --data-raw '{
    "data": {
      "type": "tokens",
      "attributes": {
        "email": "YOUR_USER_EMAIL",
        "password": "YOUR_USER_PASSWORD"
      }
    }
  }'
```

2) List tasks filtered by the Jira task (`integration-jira`) using the access token:

```bash
curl --location --globoff 'http://localhost:8080/api/v1/tasks?filter[name]=integration-jira' \
  --header 'Accept: application/vnd.api+json' \
  --header 'Authorization: Bearer ACCESS_TOKEN' | jq
```

<Note>
If you don't have `jq` installed, run the command without `| jq`.

</Note>

3) Share the output so we can help. A typical result will look like:

```
/* Detailed source-code truncated for AI context efficiency. */
```

How to read it:

* "created_count": number of Jira issues successfully created.
* "failed_count": number of Jira issues that could not be created. If `failed_count > 0` or the issue does not appear in Jira, please contact us so we can assist while detailed logs are not available through the UI.

---

--- METRICS ---
- Files Extracted: 97
- Estimated Token Budget: ~270666 tokens
- Canonical Reference: https://codewiki.google/github.com/prowler-cloud/prowler
