Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7598d1a636 | ||
|
|
2b81fdcc04 | ||
|
|
db298c1d48 | ||
|
|
2877c3d6c0 | ||
|
|
ee64c17108 | ||
|
|
a610314eba | ||
|
|
301edea7ce | ||
|
|
c89d900aae | ||
|
|
4cfb4eeb96 | ||
|
|
f19478f2f6 | ||
|
|
2f11b16299 | ||
|
|
654d2c9f17 | ||
|
|
2721d42594 | ||
|
|
bd1956446d | ||
|
|
d26d7cf91a | ||
|
|
4c20bf1fac | ||
|
|
9dc53ffc60 | ||
|
|
95642fb220 | ||
|
|
6449f3a592 | ||
|
|
91e6cb798d | ||
|
|
bcd37988b5 | ||
|
|
7a64e1a1d1 | ||
|
|
39c85ffb77 | ||
|
|
301ca50541 | ||
|
|
f299e1d9ac | ||
|
|
465e35bf54 | ||
|
|
829af2e3f7 | ||
|
|
51c65bdc69 | ||
|
|
d0c088dd0a | ||
|
|
411d112165 | ||
|
|
8a4cc8780d | ||
|
|
cd4d2a27e3 | ||
|
|
9898cf7364 | ||
|
|
83d8cfa829 | ||
|
|
5202a68cf0 | ||
|
|
e3a3acc799 | ||
|
|
3e000faa31 | ||
|
|
f39c92b8f8 | ||
|
|
823efc5ab1 | ||
|
|
75d7fa5006 | ||
|
|
db25484ccb | ||
|
|
3da4209ee7 | ||
|
|
acb6ff0425 | ||
|
|
ba564af4f4 | ||
|
|
0b9791ffdc | ||
|
|
f6defefb58 | ||
|
|
f3224d0988 | ||
|
|
450e6ba553 | ||
|
|
2cd93fe119 | ||
|
|
f807b22ea6 | ||
|
|
b6e9967da6 | ||
|
|
16e62f7514 | ||
|
|
13ce9436b3 | ||
|
|
d3ced63397 | ||
|
|
758b696ca5 | ||
|
|
0d3ce45374 | ||
|
|
f35666ff0a | ||
|
|
0758c3585d | ||
|
|
dd882c70e7 | ||
|
|
d05c9fbb31 | ||
|
|
7bde42ffb9 | ||
|
|
0d3df0fd0b | ||
|
|
ab996417e6 | ||
|
|
5f109bc00e | ||
|
|
472e04f4cc | ||
|
|
b848aace33 | ||
|
|
94c20eb9fe | ||
|
|
02df22ca19 | ||
|
|
de64df11b9 | ||
|
|
37ebd9b6fd | ||
|
|
a28487cbff | ||
|
|
68471d2a0e | ||
|
|
d41b2eaa0f | ||
|
|
b480907484 | ||
|
|
6d7bc8a86e | ||
|
|
8bfca81e4b | ||
|
|
931612443a | ||
|
|
3074f02a63 | ||
|
|
5cfc22040a | ||
|
|
48ba1692e1 | ||
|
|
a8b12813f9 | ||
|
|
85c36bb812 | ||
|
|
ce037318cd | ||
|
|
356036fe1f | ||
|
|
286685a4f3 | ||
|
|
9daca2e4df |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "prowler-plugins",
|
||||
"description": "Prowler Cloud Security for Codex",
|
||||
"owner": {
|
||||
"name": "Prowler",
|
||||
"email": "support@prowler.com"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "prowler",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./codex_plugins/prowler"
|
||||
},
|
||||
"description": "Prowler for Codex — cloud security and compliance skills powered by the Prowler MCP server.",
|
||||
"category": "security",
|
||||
"homepage": "https://prowler.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -158,7 +158,7 @@ SENTRY_RELEASE=local
|
||||
# REO_DEV_CLIENT_ID=
|
||||
|
||||
#### Prowler release version ####
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.39.0
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.40.0
|
||||
|
||||
# Social login credentials
|
||||
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
||||
|
||||
@@ -64,7 +64,7 @@ runs:
|
||||
scanners: 'vuln'
|
||||
ignore-unfixed: 'true' # A finding with no available fix is not actionable, so it must not gate
|
||||
timeout: '5m'
|
||||
version: 'v0.72.0'
|
||||
version: 'v0.74.0'
|
||||
# Not trivyignores: that input drops the .yaml extension Trivy parses by.
|
||||
env:
|
||||
TRIVY_IGNOREFILE: '.trivyignore.yaml'
|
||||
@@ -81,7 +81,7 @@ runs:
|
||||
scanners: 'vuln'
|
||||
ignore-unfixed: 'true' # A finding with no available fix is not actionable, so it must not gate
|
||||
timeout: '5m'
|
||||
version: 'v0.72.0'
|
||||
version: 'v0.74.0'
|
||||
# Not trivyignores: that input drops the .yaml extension Trivy parses by.
|
||||
env:
|
||||
TRIVY_IGNOREFILE: '.trivyignore.yaml'
|
||||
|
||||
@@ -199,7 +199,7 @@ You MUST structure your response using this EXACT format. Do NOT include anythin
|
||||
|
||||
### For Check Logic Bug
|
||||
|
||||
```
|
||||
```markdown
|
||||
### AI Assessment [Experimental]: Check Logic Bug
|
||||
|
||||
**Component**: {component from issue template}
|
||||
@@ -297,7 +297,7 @@ Write tests FIRST (TDD). The skills contain all testing conventions and patterns
|
||||
|
||||
### For Bug (non-check)
|
||||
|
||||
```
|
||||
```markdown
|
||||
### AI Assessment [Experimental]: Bug
|
||||
|
||||
**Component**: {CLI/SDK | API | UI | Dashboard | MCP Server | Other}
|
||||
@@ -378,7 +378,7 @@ Write tests FIRST (TDD). The skills contain all testing conventions and patterns
|
||||
|
||||
### For Already Fixed
|
||||
|
||||
```
|
||||
```markdown
|
||||
### AI Assessment [Experimental]: Already Fixed
|
||||
|
||||
**Component**: {component}
|
||||
@@ -401,7 +401,7 @@ Upgrade to the latest version. Close the issue as resolved.
|
||||
|
||||
### For Feature Request
|
||||
|
||||
```
|
||||
```markdown
|
||||
### AI Assessment [Experimental]: Feature Request
|
||||
|
||||
**Component**: {component}
|
||||
@@ -419,7 +419,7 @@ Upgrade to the latest version. Close the issue as resolved.
|
||||
|
||||
### For Not a Bug
|
||||
|
||||
```
|
||||
```markdown
|
||||
### AI Assessment [Experimental]: Not a Bug
|
||||
|
||||
**Component**: {component}
|
||||
@@ -440,7 +440,7 @@ Upgrade to the latest version. Close the issue as resolved.
|
||||
|
||||
### For Needs More Information
|
||||
|
||||
```
|
||||
```markdown
|
||||
### AI Assessment [Experimental]: Needs More Information
|
||||
|
||||
**Component**: {component or "Unknown"}
|
||||
|
||||
@@ -52,6 +52,16 @@ provider/alibabacloud:
|
||||
- any-glob-to-any-file: "prowler/providers/alibabacloud/**"
|
||||
- any-glob-to-any-file: "tests/providers/alibabacloud/**"
|
||||
|
||||
provider/huaweicloud:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "prowler/providers/huaweicloud/**"
|
||||
- any-glob-to-any-file: "tests/providers/huaweicloud/**"
|
||||
|
||||
provider/image:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "prowler/providers/image/**"
|
||||
- any-glob-to-any-file: "tests/providers/image/**"
|
||||
|
||||
provider/cloudflare:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "prowler/providers/cloudflare/**"
|
||||
@@ -82,6 +92,11 @@ provider/linode:
|
||||
- any-glob-to-any-file: "prowler/providers/linode/**"
|
||||
- any-glob-to-any-file: "tests/providers/linode/**"
|
||||
|
||||
provider/stackit:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "prowler/providers/stackit/**"
|
||||
- any-glob-to-any-file: "tests/providers/stackit/**"
|
||||
|
||||
github_actions:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ".github/workflows/*"
|
||||
|
||||
@@ -8,11 +8,11 @@ These JSON templates are used with the `slackapi/slack-github-action` using the
|
||||
|
||||
### Available Templates
|
||||
|
||||
**Container Releases**
|
||||
#### Container Releases
|
||||
- `container-release-started.json`: Simple one-line notification when container push starts
|
||||
- `container-release-completed.json`: Simple one-line notification when container release completes
|
||||
|
||||
**Deployments**
|
||||
#### Deployments
|
||||
- `deployment-started.json`: Deployment start notification with Block Kit formatting
|
||||
- `deployment-completed.json`: Deployment completion notification (updates the start message)
|
||||
|
||||
@@ -416,17 +416,17 @@ For deployments that start with one message and update it with the final status:
|
||||
### Container Release (Simple One-Line)
|
||||
|
||||
**Start message:**
|
||||
```
|
||||
```text
|
||||
API container release 4.5.0 push started... View run
|
||||
```
|
||||
|
||||
**Completion message (success):**
|
||||
```
|
||||
```text
|
||||
[✓] API container release 4.5.0 push completed successfully! View run
|
||||
```
|
||||
|
||||
**Completion message (failure):**
|
||||
```
|
||||
```text
|
||||
[✗] API container release 4.5.0 push failed View run
|
||||
```
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
api/**
|
||||
.trivyignore.yaml
|
||||
.github/actions/trivy-scan/**
|
||||
.github/actions/grype-scan/**
|
||||
.grype.yaml
|
||||
|
||||
@@ -61,6 +61,7 @@ jobs:
|
||||
api.github.com:443
|
||||
github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ jobs:
|
||||
# Pin must match .pre-commit-config.yaml so prek and CI behave identically.
|
||||
# pnpm dlx doesn't accept --ignore-scripts as a flag; the env var
|
||||
# disables postinstall scripts on transitives the same way.
|
||||
#
|
||||
# Files come from `git ls-files` because markdownlint doesn't traverse
|
||||
# dot-directories, so `.github/**/*.md` went unlinted.
|
||||
# `.markdownlintignore` still applies to the listed paths.
|
||||
env:
|
||||
pnpm_config_ignore_scripts: 'true'
|
||||
run: pnpm dlx markdownlint-cli@0.45.0 '**/*.md'
|
||||
run: git ls-files -z '*.md' | xargs -0 -r pnpm dlx markdownlint-cli@0.45.0 --
|
||||
|
||||
@@ -114,6 +114,8 @@ jobs:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
auth.docker.io:443
|
||||
dl-cdn.alpinelinux.org:443
|
||||
dualstack.j.sni.global.fastly.net:443
|
||||
files.pythonhosted.org:443
|
||||
ghcr.io:443
|
||||
github.com:443
|
||||
|
||||
@@ -81,6 +81,8 @@ jobs:
|
||||
pkg-containers.githubusercontent.com:443
|
||||
files.pythonhosted.org:443
|
||||
pypi.org:443
|
||||
dl-cdn.alpinelinux.org:443
|
||||
dualstack.j.sni.global.fastly.net:443
|
||||
api.github.com:443
|
||||
mirror.gcr.io:443
|
||||
check.trivy.dev:443
|
||||
@@ -103,6 +105,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
mcp_server/**
|
||||
.trivyignore.yaml
|
||||
.github/actions/trivy-scan/**
|
||||
.github/actions/grype-scan/**
|
||||
.grype.yaml
|
||||
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
|
||||
- name: Publish prowler-mcp package to PyPI
|
||||
if: steps.pypi-check.outputs.skip != 'true'
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
with:
|
||||
packages-dir: ${{ env.WORKING_DIRECTORY }}/dist/
|
||||
print-hash: true
|
||||
|
||||
@@ -35,6 +35,7 @@ jobs:
|
||||
api.github.com:443
|
||||
github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
- 'Dockerfile*'
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
- '.trivyignore.yaml'
|
||||
- '.github/workflows/sdk-container-checks.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -116,6 +117,7 @@ jobs:
|
||||
Dockerfile*
|
||||
pyproject.toml
|
||||
uv.lock
|
||||
.trivyignore.yaml
|
||||
.github/workflows/sdk-container-checks.yml
|
||||
.github/actions/trivy-scan/**
|
||||
.github/actions/grype-scan/**
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
name: 'SDK: Package Checks'
|
||||
|
||||
# Rehearses the PyPI release on every packaging change and once a week, from the
|
||||
# consumer's side. Two incidents this guards against:
|
||||
#
|
||||
# - 5.38.0 shipped an unsatisfiable Requires-Dist (cryptography==50.0.0 while
|
||||
# alibabacloud-tea-openapi and pyopenssl cap it below 49). A [tool.uv] override hid
|
||||
# the conflict inside the repo; pip could not install the wheel and silently
|
||||
# resolved `pip install prowler` to 5.37.1 for a week.
|
||||
# - 5.39.0 never published: an unpinned build backend started emitting core metadata
|
||||
# 2.5 and the twine bundled in the publish action rejected it.
|
||||
#
|
||||
# Both were only detectable at release time because nothing built and installed the
|
||||
# artifact earlier. The weekly run also catches releases yanked from PyPI after we
|
||||
# pinned them (zstd 1.5.7.3, "buggy - not thread safe", sat in uv.lock for months).
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v5.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v5.*'
|
||||
schedule:
|
||||
# Monday 06:00 UTC. Yanks and upstream releases happen without a commit here.
|
||||
- cron: '0 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
# Must equal the twine bundled in the pypa/gh-action-pypi-publish pin used by
|
||||
# sdk-pypi-release.yml (requirements/runtime.txt in that repo at the pinned tag).
|
||||
# A metadata check that passes here must pass there.
|
||||
TWINE_VERSION: '7.0.0'
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
if: github.repository == 'prowler-cloud/prowler'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
# Scheduled and manual runs always execute; pushes and PRs only when a packaging
|
||||
# input changed. Jobs skipped this way still report success to branch protection.
|
||||
run: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || steps.filter.outputs.any_changed == 'true' }}
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
api.github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
- name: Detect packaging changes
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
id: filter
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
pyproject.toml
|
||||
uv.lock
|
||||
README.md
|
||||
util/replicate_pypi_package.py
|
||||
util/check_yanked_pins.py
|
||||
api/pyproject.toml
|
||||
api/uv.lock
|
||||
mcp_server/pyproject.toml
|
||||
mcp_server/uv.lock
|
||||
.github/workflows/sdk-package-checks.yml
|
||||
.github/workflows/sdk-pypi-release.yml
|
||||
.github/actions/setup-python-uv/**
|
||||
|
||||
install-from-wheel:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.run == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- '3.10'
|
||||
- '3.11'
|
||||
- '3.12'
|
||||
- '3.13'
|
||||
package:
|
||||
- 'prowler'
|
||||
include:
|
||||
# prowler-cloud is the same tree renamed by util/replicate_pypi_package.py;
|
||||
# one Python is enough to prove the rename and its build still work.
|
||||
- python-version: '3.12'
|
||||
package: 'prowler-cloud'
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
api.github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python with uv
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
install-dependencies: 'false'
|
||||
|
||||
- name: Rename package to prowler-cloud
|
||||
if: matrix.package == 'prowler-cloud'
|
||||
run: |
|
||||
pip install --no-cache-dir toml
|
||||
python util/replicate_pypi_package.py
|
||||
|
||||
- name: Build sdist and wheel
|
||||
run: uv build
|
||||
|
||||
- name: Check metadata with the release workflow's twine
|
||||
run: uvx --from "twine==${TWINE_VERSION}" twine check --strict dist/*
|
||||
|
||||
- name: Install the wheel with pip into a clean virtualenv
|
||||
# Plain pip, --isolated, from outside the repo: consumers never see [tool.uv]
|
||||
# override-dependencies or constraint-dependencies, so neither does this step.
|
||||
run: |
|
||||
python -m venv "${RUNNER_TEMP}/consumer"
|
||||
"${RUNNER_TEMP}/consumer/bin/python" -m pip install --quiet --upgrade pip
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/consumer/bin/python" -m pip install --isolated --no-cache-dir "${GITHUB_WORKSPACE}"/dist/*.whl
|
||||
|
||||
- name: Smoke test the installed CLI
|
||||
run: |
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/consumer/bin/prowler" --version
|
||||
# Loads every AWS check module from the installed wheel: catches files missing
|
||||
# from the package. grep fails the step if the summary line never appears.
|
||||
"${RUNNER_TEMP}/consumer/bin/prowler" aws --list-checks | grep 'available checks'
|
||||
|
||||
pinned-releases-not-yanked:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.run == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
api.github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Check every pinned and locked release against PyPI
|
||||
run: python util/check_yanked_pins.py . api mcp_server
|
||||
@@ -84,8 +84,18 @@ jobs:
|
||||
- name: Build Prowler package
|
||||
run: uv build
|
||||
|
||||
- name: Verify the wheel installs with pip
|
||||
# Same check as "SDK: Package Checks", repeated on the exact artifact about to be
|
||||
# published. Plain pip, --isolated, from outside the repo: an unsatisfiable
|
||||
# Requires-Dist fails here instead of on users' machines (5.38.0 shipped one).
|
||||
run: |
|
||||
python -m venv "${RUNNER_TEMP}/consumer"
|
||||
"${RUNNER_TEMP}/consumer/bin/python" -m pip install --quiet --upgrade pip
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/consumer/bin/python" -m pip install --isolated --no-cache-dir --dry-run "${GITHUB_WORKSPACE}"/dist/*.whl
|
||||
|
||||
- name: Publish Prowler package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
with:
|
||||
print-hash: true
|
||||
|
||||
@@ -128,7 +138,17 @@ jobs:
|
||||
- name: Build prowler-cloud package
|
||||
run: uv build
|
||||
|
||||
- name: Verify the wheel installs with pip
|
||||
# Same check as "SDK: Package Checks", repeated on the exact artifact about to be
|
||||
# published. Plain pip, --isolated, from outside the repo: an unsatisfiable
|
||||
# Requires-Dist fails here instead of on users' machines (5.38.0 shipped one).
|
||||
run: |
|
||||
python -m venv "${RUNNER_TEMP}/consumer"
|
||||
"${RUNNER_TEMP}/consumer/bin/python" -m pip install --quiet --upgrade pip
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/consumer/bin/python" -m pip install --isolated --no-cache-dir --dry-run "${GITHUB_WORKSPACE}"/dist/*.whl
|
||||
|
||||
- name: Publish prowler-cloud package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
with:
|
||||
print-hash: true
|
||||
|
||||
@@ -57,6 +57,7 @@ jobs:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
ui/**
|
||||
.trivyignore.yaml
|
||||
.github/actions/trivy-scan/**
|
||||
.github/actions/grype-scan/**
|
||||
.grype.yaml
|
||||
|
||||
@@ -9,6 +9,14 @@ ignore:
|
||||
|
||||
# Modules compiled into the Trivy binary we ship.
|
||||
# Only a Trivy rebuild by its vendor can change these; the version is pinned in our Dockerfile.
|
||||
# CVE-2026-71556 is the same temporary exception documented in .trivyignore.yaml:
|
||||
# Trivy 0.73.0 still embeds go-git 5.19.1, while the 5.19.2 fix is merged only on
|
||||
# Trivy main. Remove this entry with the Trivy exception by 2026-09-15.
|
||||
# https://github.com/aquasecurity/trivy/blob/v0.73.0/go.mod#L46
|
||||
# https://github.com/aquasecurity/trivy/commit/a2edba9a03987ba0d2ebc8212c1a9a1e6979497b
|
||||
- vulnerability: CVE-2026-71556
|
||||
package:
|
||||
name: github.com/go-git/go-git/v5
|
||||
- vulnerability: CVE-2026-56852
|
||||
package:
|
||||
name: golang.org/x/text
|
||||
@@ -25,6 +33,19 @@ ignore:
|
||||
package:
|
||||
name: Microsoft.Bcl.Memory
|
||||
|
||||
# The .NET runtime bundled inside the PowerShell tarball the Dockerfile pins.
|
||||
# CVE-2026-62901 is the same temporary exception documented in .trivyignore.yaml:
|
||||
# fixed in .NET 9.0.19 / 10.0.11 (2026-08-11), but no published PowerShell release
|
||||
# ships a patched runtime yet (7.5.9 bundles 9.0.18; 7.6.4 bundles 10.0.x < 10.0.11).
|
||||
# pwsh runs only local M365 module cmdlets; nothing listens for inbound WebSocket
|
||||
# connections. Remove with the Trivy exception by 2026-09-15.
|
||||
- vulnerability: CVE-2026-62901
|
||||
package:
|
||||
name: Microsoft.NETCore.App.Runtime.linux-x64
|
||||
- vulnerability: CVE-2026-62901
|
||||
package:
|
||||
name: Microsoft.NETCore.App.Runtime.linux-arm64
|
||||
|
||||
|
||||
# The CPython interpreter, compiled into the official base image.
|
||||
# TEMPORARY, unlike the entries above: moving to Python 3.13 clears seven of these, and
|
||||
|
||||
@@ -118,8 +118,36 @@ vulnerabilities:
|
||||
- "pkg:npm/ip-address"
|
||||
expired_at: 2027-01-31
|
||||
|
||||
# CVE-2026-62901 is a DoS in System.Net.WebSockets (unchecked input for loop condition,
|
||||
# CWE-606), fixed in .NET 9.0.19 / 10.0.11 (published 2026-08-11). The vulnerable runtime
|
||||
# ships inside the PowerShell tarball the Dockerfile pins: 7.5.9 is the latest 7.5.x and
|
||||
# bundles .NET 9.0.18; 7.6.4 bundles .NET 10.0.x < 10.0.11, so no published PowerShell
|
||||
# release contains the fix yet. Prowler only invokes pwsh locally to run M365 module
|
||||
# cmdlets; the image does not accept inbound WebSocket connections, so the DoS path is
|
||||
# not reachable from the network. Remove this temporary suppression as soon as a
|
||||
# PowerShell release shipping .NET 9.0.19+ is available.
|
||||
- id: CVE-2026-62901
|
||||
purls:
|
||||
- "pkg:nuget/Microsoft.NETCore.App.Runtime.linux-x64"
|
||||
- "pkg:nuget/Microsoft.NETCore.App.Runtime.linux-arm64"
|
||||
expired_at: 2026-09-15
|
||||
|
||||
# Modules compiled into the Trivy binary the images ship. The binary is pinned by version
|
||||
# and verified by checksum in the Dockerfile; only a rebuild by its vendor moves these.
|
||||
# CVE-2026-71556 affects go-git worktree operations that can follow symlinks outside a
|
||||
# cloned repository. Trivy 0.73.0, the latest published release and the version the
|
||||
# images ship, still pins that vulnerable version:
|
||||
# https://github.com/aquasecurity/trivy/blob/v0.73.0/go.mod#L46
|
||||
# Trivy main already contains the 5.19.2 fix, but no published release includes it yet:
|
||||
# https://github.com/aquasecurity/trivy/commit/a2edba9a03987ba0d2ebc8212c1a9a1e6979497b
|
||||
# Prowler invokes Trivy only with `fs` on an existing local path or with `image`; it does
|
||||
# not ask Trivy to clone or mutate a Git worktree, so the affected path is not reachable.
|
||||
# Remove this temporary suppression as soon as a fixed Trivy release is available.
|
||||
- id: CVE-2026-71556
|
||||
purls:
|
||||
- "pkg:golang/github.com/go-git/go-git/v5"
|
||||
expired_at: 2026-09-15
|
||||
|
||||
- id: CVE-2026-56852
|
||||
purls:
|
||||
- "pkg:golang/golang.org/x/text"
|
||||
@@ -136,8 +164,3 @@ vulnerabilities:
|
||||
purls:
|
||||
- "pkg:golang/oras.land/oras-go/v2"
|
||||
expired_at: 2026-12-31
|
||||
- id: CVE-2026-39822
|
||||
purls:
|
||||
- "pkg:golang/stdlib"
|
||||
expired_at: 2026-12-31
|
||||
|
||||
|
||||
@@ -8,24 +8,34 @@ ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
|
||||
# Opt out of PowerShell telemetry (Application Insights -> dc.services.visualstudio.com)
|
||||
ENV POWERSHELL_TELEMETRY_OPTOUT=1
|
||||
|
||||
ARG TRIVY_VERSION=0.72.0
|
||||
ARG TRIVY_VERSION=0.74.0
|
||||
ENV TRIVY_VERSION=${TRIVY_VERSION}
|
||||
|
||||
ARG ZIZMOR_VERSION=1.24.1
|
||||
ENV ZIZMOR_VERSION=${ZIZMOR_VERSION}
|
||||
|
||||
# Pinned here, not fetched with the artefact: a compromised release ships its own checksum.
|
||||
ARG TRIVY_SHA256_AMD64=bbb64b9695866ce4a7a8f5c9592002c5961cab378577fa3f8a040df362b9b2ea
|
||||
ARG TRIVY_SHA256_ARM64=2ca2c023109c2db6b2b77366b6717291452d4531167377d95c79547f0c8e3467
|
||||
ARG TRIVY_SHA256_AMD64=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a
|
||||
ARG TRIVY_SHA256_ARM64=b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5
|
||||
ARG POWERSHELL_SHA256_AMD64=492ff26bb958336bf61e597ce19e07648b4003bd2a08659e02f0e3e0446ebfe0
|
||||
ARG POWERSHELL_SHA256_ARM64=2503b71da3e83635592b092df59a0aca4c3606b4d9b068217bb00be989cb0d56
|
||||
ARG ZIZMOR_SHA256_AMD64=a8000f3c683319a523d3b20df0e75457ba591f049cfcbfa98966631b56733c03
|
||||
ARG ZIZMOR_SHA256_ARM64=d66e37ef8a375fb07939c630ebf9709a6e0f20242bdc3faf672a7ed97e0b768d
|
||||
|
||||
# High CVEs fixed in Debian trixie-security but not yet in the pinned base image:
|
||||
# openssl/libssl3t64/openssl-provider-legacy 3.5.7-1~deb13u2 CVE-2026-14456,
|
||||
# -14457, -18798, -54874, -63072, -63073, -63074, -63075, -63076, -75803
|
||||
# (image ships 3.5.6-1~deb13u2)
|
||||
# Taken as a targeted --only-upgrade rather than by moving the digest: the newest
|
||||
# published python:3.12-slim-trixie carries the same vulnerable version. The three
|
||||
# packages are all built from openssl and are flagged separately, so all are named.
|
||||
# Drop them once the base image ships 3.5.7-1~deb13u2 or later.
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
wget libicu76 libunwind8 libssl3 libcurl4 ca-certificates apt-transport-https gnupg \
|
||||
build-essential pkg-config libzstd-dev zlib1g-dev \
|
||||
&& apt-get install -y --no-install-recommends --only-upgrade \
|
||||
util-linux libssl3t64 openssl openssl-provider-legacy \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PowerShell
|
||||
|
||||
@@ -126,12 +126,12 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
|
||||
|
||||
| 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 | 621 | 86 | 47 | 19 | Official | UI, API, CLI |
|
||||
| AWS | 639 | 86 | 47 | 19 | Official | UI, API, CLI |
|
||||
| Azure | 191 | 22 | 21 | 16 | Official | UI, API, CLI |
|
||||
| GCP | 109 | 20 | 19 | 12 | Official | UI, API, CLI |
|
||||
| Kubernetes | 92 | 7 | 8 | 11 | Official | UI, API, CLI |
|
||||
| GitHub | 24 | 3 | 2 | 5 | Official | UI, API, CLI |
|
||||
| M365 | 111 | 10 | 6 | 10 | Official | UI, API, CLI |
|
||||
| M365 | 143 | 10 | 6 | 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 |
|
||||
@@ -147,7 +147,7 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
|
||||
| Huawei Cloud [Contact us](https://prowler.com/contact) | 25 | 10 | 1 | 6 | Unofficial | CLI |
|
||||
| E2E Networks [Contact us](https://prowler.com/contact) | 27 | 6 | 0 | 2 | Unofficial | CLI |
|
||||
| Scaleway [Contact us](https://prowler.com/contact) | 1 | 1 | 1 | 1 | Unofficial | CLI |
|
||||
| StackIT [Contact us](https://prowler.com/contact) | 7 | 2 | 1 | 3 | Unofficial | CLI |
|
||||
| StackIT [Contact us](https://prowler.com/contact) | 8 | 2 | 1 | 3 | Unofficial | CLI |
|
||||
| NHN | 6 | 2 | 2 | 0 | Unofficial | CLI |
|
||||
|
||||
> [!Note]
|
||||
|
||||
@@ -4,6 +4,37 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
|
||||
## [1.40.1] (Prowler v5.39.1)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- Bump alibabacloud-tea-openapi to 0.4.6, oci to 2.184.1 and pyopenssl to 26.4.0 to match the SDK; the cryptography override now names its actual blockers (azure-cli-core pins msal below 1.37, workos 8.3.0 requires cryptography 48) [(#12477)](https://github.com/prowler-cloud/prowler/pull/12477)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Pin zstd to 1.5.7.2; 1.5.7.3 was yanked from PyPI as not thread safe [(#12477)](https://github.com/prowler-cloud/prowler/pull/12477)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- Trivy from v0.72.0 to v0.73.0 in the container image, fixing HIGH CVE-2026-46600 in the bundled `golang.org/x/net` [(#12445)](https://github.com/prowler-cloud/prowler/pull/12445)
|
||||
- Trivy v0.74.0 and Debian util-linux 2.41.5-0+deb13u1 in the API container image, patching Go standard library vulnerabilities and CVE-2026-53615 [(#12470)](https://github.com/prowler-cloud/prowler/pull/12470)
|
||||
|
||||
---
|
||||
|
||||
## [1.40.0] (Prowler v5.39.0)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- `GET /api/v1/users/me` membership relationships identify the active tenant with `meta.active` for JWT and API key authentication [(#12388)](https://github.com/prowler-cloud/prowler/pull/12388)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Tenant deletion no longer leaves memberships partially removed when exclusive-user cleanup fails [(#12379)](https://github.com/prowler-cloud/prowler/pull/12379)
|
||||
- `/api/v1/accounts/saml/{organization_slug}/acs/` rejects non-POST requests before SAML response processing [(#12393)](https://github.com/prowler-cloud/prowler/pull/12393)
|
||||
- Social login derives a valid user name when identity providers omit the profile name [(#12413)](https://github.com/prowler-cloud/prowler/pull/12413)
|
||||
|
||||
---
|
||||
|
||||
## [1.39.0] (Prowler v5.38.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
@@ -7,20 +7,28 @@ ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
|
||||
# Opt out of PowerShell telemetry (Application Insights -> dc.services.visualstudio.com)
|
||||
ENV POWERSHELL_TELEMETRY_OPTOUT=1
|
||||
|
||||
ARG TRIVY_VERSION=0.72.0
|
||||
ARG TRIVY_VERSION=0.74.0
|
||||
ENV TRIVY_VERSION=${TRIVY_VERSION}
|
||||
|
||||
ARG ZIZMOR_VERSION=1.24.1
|
||||
ENV ZIZMOR_VERSION=${ZIZMOR_VERSION}
|
||||
|
||||
# Pinned here, not fetched with the artefact: a compromised release ships its own checksum.
|
||||
ARG TRIVY_SHA256_AMD64=bbb64b9695866ce4a7a8f5c9592002c5961cab378577fa3f8a040df362b9b2ea
|
||||
ARG TRIVY_SHA256_ARM64=2ca2c023109c2db6b2b77366b6717291452d4531167377d95c79547f0c8e3467
|
||||
ARG TRIVY_SHA256_AMD64=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a
|
||||
ARG TRIVY_SHA256_ARM64=b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5
|
||||
ARG POWERSHELL_SHA256_AMD64=492ff26bb958336bf61e597ce19e07648b4003bd2a08659e02f0e3e0446ebfe0
|
||||
ARG POWERSHELL_SHA256_ARM64=2503b71da3e83635592b092df59a0aca4c3606b4d9b068217bb00be989cb0d56
|
||||
ARG ZIZMOR_SHA256_AMD64=a8000f3c683319a523d3b20df0e75457ba591f049cfcbfa98966631b56733c03
|
||||
ARG ZIZMOR_SHA256_ARM64=d66e37ef8a375fb07939c630ebf9709a6e0f20242bdc3faf672a7ed97e0b768d
|
||||
|
||||
# High CVEs fixed in Debian trixie-security but not yet in the pinned base image:
|
||||
# openssl/libssl3t64/openssl-provider-legacy 3.5.7-1~deb13u2 CVE-2026-14456,
|
||||
# -14457, -18798, -54874, -63072, -63073, -63074, -63075, -63076, -75803
|
||||
# (image ships 3.5.6-1~deb13u2)
|
||||
# Taken as a targeted --only-upgrade rather than by moving the digest: the newest
|
||||
# published python:3.12-slim-trixie carries the same vulnerable version. The three
|
||||
# packages are all built from openssl and are flagged separately, so all are named.
|
||||
# Drop them once the base image ships 3.5.7-1~deb13u2 or later.
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
wget \
|
||||
@@ -36,6 +44,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libtool \
|
||||
libxslt1-dev \
|
||||
python3-dev \
|
||||
&& apt-get install -y --no-install-recommends --only-upgrade \
|
||||
util-linux libssl3t64 openssl openssl-provider-legacy \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PowerShell
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
`openssl`, `libssl3t64` and `openssl-provider-legacy` upgraded to 3.5.7-1~deb13u2 in the API container image, patching ten high OpenSSL CVEs
|
||||
@@ -1 +0,0 @@
|
||||
`/api/v1/accounts/saml/{organization_slug}/acs/` rejects non-POST requests before SAML response processing
|
||||
@@ -0,0 +1 @@
|
||||
`sqlparse` upgraded to 0.6.0, patching CVE-2026-54284, CVE-2026-59893, and CVE-2026-71491
|
||||
@@ -1 +0,0 @@
|
||||
Tenant deletion no longer leaves memberships partially removed when exclusive-user cleanup fails
|
||||
@@ -1 +0,0 @@
|
||||
`GET /api/v1/users/me` membership relationships identify the active tenant with `meta.active` for JWT and API key authentication
|
||||
@@ -61,7 +61,7 @@ dependencies = [
|
||||
"cartography (==0.138.1)",
|
||||
"gevent (==25.9.1)",
|
||||
"werkzeug (==3.1.7)",
|
||||
"sqlparse (==0.5.5)",
|
||||
"sqlparse (==0.6.0)",
|
||||
"fonttools (==4.62.1)",
|
||||
"uvicorn-worker (==0.4.0)"
|
||||
]
|
||||
@@ -71,7 +71,7 @@ name = "prowler-api"
|
||||
package-mode = false
|
||||
# Needed for the SDK compatibility
|
||||
requires-python = ">=3.11,<3.13"
|
||||
version = "1.40.0"
|
||||
version = "1.41.0"
|
||||
|
||||
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
|
||||
# target-version tracks this project's lowest supported Python.
|
||||
@@ -92,8 +92,7 @@ extend-select = [
|
||||
|
||||
[tool.uv]
|
||||
# Transitive pins matching master to avoid silent drift; bump deliberately.
|
||||
# workos and pyopenssl run ahead of master: the versions master pins cap cryptography
|
||||
# below 48, so both were bumped to versions that allow it (PROWLER-2310).
|
||||
# workos is api-only; pyopenssl matches master (PROWLER-2310).
|
||||
constraint-dependencies = [
|
||||
"about-time==4.2.1",
|
||||
"adal==1.2.7",
|
||||
@@ -130,7 +129,7 @@ constraint-dependencies = [
|
||||
"alibabacloud-sls20201230==5.9.0",
|
||||
"alibabacloud-sts20150401==1.1.6",
|
||||
"alibabacloud-tea==0.4.3",
|
||||
"alibabacloud-tea-openapi==0.4.5",
|
||||
"alibabacloud-tea-openapi==0.4.6",
|
||||
"alibabacloud-tea-util==0.3.14",
|
||||
"alibabacloud-tea-xml==0.0.3",
|
||||
"alibabacloud-vpc20160428==6.13.0",
|
||||
@@ -339,7 +338,7 @@ constraint-dependencies = [
|
||||
"nltk==3.9.4",
|
||||
"numpy==2.2.6",
|
||||
"oauthlib==3.3.1",
|
||||
"oci==2.183.0",
|
||||
"oci==2.184.1",
|
||||
"openai==1.109.1",
|
||||
"openstacksdk==4.2.0",
|
||||
"opentelemetry-api==1.39.1",
|
||||
@@ -380,7 +379,7 @@ constraint-dependencies = [
|
||||
"pylint==3.2.5",
|
||||
"pymsalruntime==0.18.1",
|
||||
"pynacl==1.6.2",
|
||||
"pyopenssl==26.2.0",
|
||||
"pyopenssl==26.4.0",
|
||||
"pyparsing==3.3.2",
|
||||
"pyreadline3==3.5.4",
|
||||
"pysocks==1.7.1",
|
||||
@@ -424,7 +423,7 @@ constraint-dependencies = [
|
||||
"six==1.17.0",
|
||||
"slack-sdk==3.39.0",
|
||||
"sniffio==1.3.1",
|
||||
"sqlparse==0.5.5",
|
||||
"sqlparse==0.6.0",
|
||||
"statsd==4.0.1",
|
||||
"std-uritemplate==2.0.8",
|
||||
"stevedore==5.6.0",
|
||||
@@ -458,7 +457,7 @@ constraint-dependencies = [
|
||||
"zipp==3.23.0",
|
||||
"zope-event==6.1",
|
||||
"zope-interface==8.2",
|
||||
"zstd==1.5.7.3"
|
||||
"zstd==1.5.7.2"
|
||||
]
|
||||
# prowler@master needs okta==3.4.2, but cartography 0.138.1 requires okta<1.0.0.
|
||||
# Attack Paths does not ingest Okta today, so override the Cartography
|
||||
@@ -485,7 +484,12 @@ constraint-dependencies = [
|
||||
# that request pyjwt[crypto] and leave cryptography (needed for RS256) only transitive.
|
||||
override-dependencies = [
|
||||
"okta==3.4.2",
|
||||
# alibabacloud-tea-openapi 0.4.5 caps cryptography below 49 and is the latest release.
|
||||
# prowler requires cryptography==50.0.0. Two api-only dependencies still cap it below
|
||||
# 49 and cannot move yet: msal, pinned exactly by azure-cli-core (2.83.0 -> 1.35.0b1,
|
||||
# 2.89.1 -> 1.36.0, both <49; cartography needs azure-cli-core), and workos 8.3.0
|
||||
# (~=48.0; workos 10.1.1+ needs ~=50.0 and is a separate SDK upgrade). This api is
|
||||
# deployed from this lock with `uv sync --locked`, so the override applies to what runs.
|
||||
# Remove when azure-cli-core pins msal>=1.37.0 and workos is on 10.x.
|
||||
"cryptography==50.0.0",
|
||||
"azure-mgmt-containerservice==34.1.0",
|
||||
"microsoft-kiota-abstractions==1.9.10",
|
||||
|
||||
@@ -12,11 +12,37 @@ from api.models import (
|
||||
UserRoleRelationship,
|
||||
)
|
||||
from api.utils import accept_invitation_for_user
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseForbidden
|
||||
|
||||
|
||||
class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
@staticmethod
|
||||
def _get_social_account_name(extra_data: dict, email: str) -> str:
|
||||
name_field = User._meta.get_field("name")
|
||||
for value in (
|
||||
extra_data.get("name"),
|
||||
extra_data.get("login"),
|
||||
extra_data.get("username"),
|
||||
email,
|
||||
):
|
||||
if not isinstance(value, str):
|
||||
continue
|
||||
|
||||
candidate = value.strip()[: name_field.max_length].rstrip()
|
||||
if not candidate:
|
||||
continue
|
||||
|
||||
try:
|
||||
name_field.run_validators(candidate)
|
||||
except ValidationError:
|
||||
continue
|
||||
|
||||
return candidate
|
||||
|
||||
raise ValueError("Social account does not provide a valid user identity.")
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_email(email: str):
|
||||
try:
|
||||
@@ -116,11 +142,8 @@ class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
|
||||
if provider != "saml":
|
||||
# Handle other providers (e.g., GitHub, Google)
|
||||
user.name = self._get_social_account_name(extra, user.email)
|
||||
user.save(using=MainRouter.admin_db)
|
||||
social_account_name = extra.get("name")
|
||||
if social_account_name:
|
||||
user.name = social_account_name
|
||||
user.save(using=MainRouter.admin_db)
|
||||
|
||||
invitation_token = self._get_invitation_token(request)
|
||||
if invitation_token:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Prowler API
|
||||
version: 1.40.0
|
||||
version: 1.41.0
|
||||
description: |-
|
||||
Prowler API specification.
|
||||
|
||||
|
||||
@@ -111,6 +111,110 @@ def _verify_local_email(user):
|
||||
)
|
||||
|
||||
|
||||
def test_social_account_name_falls_back_to_login_for_blank_name():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
name = adapter._get_social_account_name(
|
||||
{"name": " ", "login": "octocat"},
|
||||
"verified@example.com",
|
||||
)
|
||||
|
||||
assert name == "octocat"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("provider_name", [None, "", " ", 123, ["name"]])
|
||||
def test_social_account_name_ignores_unusable_provider_names(provider_name):
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
name = adapter._get_social_account_name(
|
||||
{"name": provider_name, "login": "octocat"},
|
||||
"verified@example.com",
|
||||
)
|
||||
|
||||
assert name == "octocat"
|
||||
|
||||
|
||||
def test_social_account_name_uses_login_when_name_is_missing():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
name = adapter._get_social_account_name(
|
||||
{"login": "octocat"},
|
||||
"verified@example.com",
|
||||
)
|
||||
|
||||
assert name == "octocat"
|
||||
|
||||
|
||||
def test_social_account_name_falls_back_to_username_then_email():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
username_name = adapter._get_social_account_name(
|
||||
{"name": "ab", "login": None, "username": " monalisa "},
|
||||
"verified@example.com",
|
||||
)
|
||||
email_name = adapter._get_social_account_name({}, " verified@example.com ")
|
||||
|
||||
assert username_name == "monalisa"
|
||||
assert email_name == "verified@example.com"
|
||||
|
||||
|
||||
def test_social_account_name_trims_and_limits_provider_name():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
max_length = User._meta.get_field("name").max_length
|
||||
|
||||
trimmed_name = adapter._get_social_account_name(
|
||||
{"name": " Ada Lovelace "},
|
||||
"verified@example.com",
|
||||
)
|
||||
limited_name = adapter._get_social_account_name(
|
||||
{"name": "a" * (max_length + 1)},
|
||||
"verified@example.com",
|
||||
)
|
||||
|
||||
assert trimmed_name == "Ada Lovelace"
|
||||
assert limited_name == "a" * max_length
|
||||
|
||||
|
||||
def test_social_account_name_rejects_missing_identity():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="Social account does not provide a valid user identity",
|
||||
):
|
||||
adapter._get_social_account_name({}, "")
|
||||
|
||||
|
||||
def test_save_user_applies_normalized_social_account_name(rf):
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
request = rf.post("/")
|
||||
request.session = {}
|
||||
sociallogin = MagicMock(spec=SocialLogin)
|
||||
sociallogin.provider = MagicMock()
|
||||
sociallogin.provider.id = "github"
|
||||
sociallogin.account = MagicMock()
|
||||
sociallogin.account.extra_data = {"name": None, "login": " octocat "}
|
||||
user = User(email="verified@example.com")
|
||||
user.save = MagicMock()
|
||||
invitation = SimpleNamespace(tenant_id="tenant-id")
|
||||
|
||||
with (
|
||||
patch("api.adapters.super") as mock_super,
|
||||
patch("api.adapters.transaction.atomic"),
|
||||
patch("api.adapters.write_db_alias"),
|
||||
patch.object(adapter, "_get_invitation_token", return_value="token"),
|
||||
patch(
|
||||
"api.adapters.accept_invitation_for_user",
|
||||
return_value=(invitation, True),
|
||||
),
|
||||
):
|
||||
mock_super.return_value.save_user.return_value = user
|
||||
saved_user = adapter.save_user(request, sociallogin)
|
||||
|
||||
assert saved_user.name == "octocat"
|
||||
assert request.prowler_invitation_token == "token"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestProwlerSocialAccountAdapter:
|
||||
def test_get_user_by_email_returns_user(self, create_test_user):
|
||||
|
||||
@@ -45,7 +45,7 @@ constraints = [
|
||||
{ name = "alibabacloud-sls20201230", specifier = "==5.9.0" },
|
||||
{ name = "alibabacloud-sts20150401", specifier = "==1.1.6" },
|
||||
{ name = "alibabacloud-tea", specifier = "==0.4.3" },
|
||||
{ name = "alibabacloud-tea-openapi", specifier = "==0.4.5" },
|
||||
{ name = "alibabacloud-tea-openapi", specifier = "==0.4.6" },
|
||||
{ name = "alibabacloud-tea-util", specifier = "==0.3.14" },
|
||||
{ name = "alibabacloud-tea-xml", specifier = "==0.0.3" },
|
||||
{ name = "alibabacloud-vpc20160428", specifier = "==6.13.0" },
|
||||
@@ -254,7 +254,7 @@ constraints = [
|
||||
{ name = "nltk", specifier = "==3.9.4" },
|
||||
{ name = "numpy", specifier = "==2.2.6" },
|
||||
{ name = "oauthlib", specifier = "==3.3.1" },
|
||||
{ name = "oci", specifier = "==2.183.0" },
|
||||
{ name = "oci", specifier = "==2.184.1" },
|
||||
{ name = "openai", specifier = "==1.109.1" },
|
||||
{ name = "openstacksdk", specifier = "==4.2.0" },
|
||||
{ name = "opentelemetry-api", specifier = "==1.39.1" },
|
||||
@@ -295,7 +295,7 @@ constraints = [
|
||||
{ name = "pylint", specifier = "==3.2.5" },
|
||||
{ name = "pymsalruntime", specifier = "==0.18.1" },
|
||||
{ name = "pynacl", specifier = "==1.6.2" },
|
||||
{ name = "pyopenssl", specifier = "==26.2.0" },
|
||||
{ name = "pyopenssl", specifier = "==26.4.0" },
|
||||
{ name = "pyparsing", specifier = "==3.3.2" },
|
||||
{ name = "pyreadline3", specifier = "==3.5.4" },
|
||||
{ name = "pysocks", specifier = "==1.7.1" },
|
||||
@@ -339,7 +339,7 @@ constraints = [
|
||||
{ name = "six", specifier = "==1.17.0" },
|
||||
{ name = "slack-sdk", specifier = "==3.39.0" },
|
||||
{ name = "sniffio", specifier = "==1.3.1" },
|
||||
{ name = "sqlparse", specifier = "==0.5.5" },
|
||||
{ name = "sqlparse", specifier = "==0.6.0" },
|
||||
{ name = "statsd", specifier = "==4.0.1" },
|
||||
{ name = "std-uritemplate", specifier = "==2.0.8" },
|
||||
{ name = "stevedore", specifier = "==5.6.0" },
|
||||
@@ -373,7 +373,7 @@ constraints = [
|
||||
{ name = "zipp", specifier = "==3.23.0" },
|
||||
{ name = "zope-event", specifier = "==6.1" },
|
||||
{ name = "zope-interface", specifier = "==8.2" },
|
||||
{ name = "zstd", specifier = "==1.5.7.3" },
|
||||
{ name = "zstd", specifier = "==1.5.7.2" },
|
||||
]
|
||||
overrides = [
|
||||
{ name = "azure-mgmt-containerservice", specifier = "==34.1.0" },
|
||||
@@ -860,7 +860,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0
|
||||
|
||||
[[package]]
|
||||
name = "alibabacloud-tea-openapi"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "alibabacloud-credentials" },
|
||||
@@ -869,9 +869,9 @@ dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "darabonba-core" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3b/73/fb0c4d44759791ecdf269fc715c1e810fa1aba3981bfaaf8a01f61899296/alibabacloud_tea_openapi-0.4.5.tar.gz", hash = "sha256:75fa1f4360a46e41f5bf5f8d4917e52efb6f64885839bc1328c35590670c97b9", size = 26616, upload-time = "2026-07-14T13:15:39.364Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ab/34/1918a2d780676494365c7f945bfab397ecddb988054d78025bd26f438977/alibabacloud_tea_openapi-0.4.6.tar.gz", hash = "sha256:dafc32401712f5b21c12dc3d05ba887a91ad156d9b49a7662279f9fd90526fb2", size = 26742, upload-time = "2026-08-17T08:34:11.55Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/ec/6b368a10e9c2e8b1b394c69b96ac213ae66e8c4895e0baa1ffaf7178fd32/alibabacloud_tea_openapi-0.4.5-py3-none-any.whl", hash = "sha256:338979095c7beda80a5b413c31262892cafdc12069dde4ce4fc2e4f7ce0fc609", size = 33333, upload-time = "2026-07-14T13:15:38.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/00/2f534f5884e5f299d9cb3a1e8be2def8071bc6a6e2a192ba4ff2a8cd5e02/alibabacloud_tea_openapi-0.4.6-py3-none-any.whl", hash = "sha256:c9e1727b9fb2936f487d050fc3590c99f9f2065256dc3a927e5b61f414674ed6", size = 33448, upload-time = "2026-08-17T08:34:10.472Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4426,7 +4426,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "oci"
|
||||
version = "2.183.0"
|
||||
version = "2.184.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
@@ -4439,9 +4439,9 @@ dependencies = [
|
||||
{ name = "pytz" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/2a/77bd6cbf1c69b2f368fe3d6462d84369b0cba15e37ce713cdc08d459b95a/oci-2.183.0.tar.gz", hash = "sha256:ff572ef5f2030a788796bb509d257e6a41c6510ef9b4b6a75a079efd06e533ce", size = 17759723, upload-time = "2026-07-28T06:02:29.76Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/2d/fa5368cfabb868f4111c6978e8b5f66aa3a55076c40c1a59ac3081b0227b/oci-2.184.1.tar.gz", hash = "sha256:617dad69caf8dd6e521d224dbc3e8a8bc289906943a0214fd2c3419094e26435", size = 17990631, upload-time = "2026-08-11T11:01:26.194Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/de/8574b3e527996a099d196e87794a4652d91a0c3185fcc7fdbb5649b75a8a/oci-2.183.0-py3-none-any.whl", hash = "sha256:bd789c98a94d7c5ea08c20d11dcf68c9cd1ad479b134727d80a930b84387070b", size = 36133501, upload-time = "2026-07-28T06:02:18.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/63/5ae22e42aaf96a5da74dc2b9de449c78b4d7418cce621d5da723b3e49f32/oci-2.184.1-py3-none-any.whl", hash = "sha256:bd814e38a70da2190e721937455a08689ab13c0750bd2ef8dd0c98b2dc5a38ea", size = 36628063, upload-time = "2026-08-11T11:01:18.178Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4835,8 +4835,8 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "prowler"
|
||||
version = "5.38.0"
|
||||
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#b3d174d0c1eb202ed7cb9a9daf0500683f4443be" }
|
||||
version = "5.40.0"
|
||||
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#b6e9967da6bebd6c7b8b237317a2a95e2e0c65bc" }
|
||||
dependencies = [
|
||||
{ name = "alibabacloud-actiontrail20200706" },
|
||||
{ name = "alibabacloud-credentials" },
|
||||
@@ -4935,7 +4935,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prowler-api"
|
||||
version = "1.40.0"
|
||||
version = "1.41.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "cartography" },
|
||||
@@ -5040,7 +5040,7 @@ requires-dist = [
|
||||
{ name = "pytest-celery", extras = ["redis"], specifier = "==1.3.0" },
|
||||
{ name = "reportlab", specifier = "==4.4.10" },
|
||||
{ name = "sentry-sdk", extras = ["django"], specifier = "==2.56.0" },
|
||||
{ name = "sqlparse", specifier = "==0.5.5" },
|
||||
{ name = "sqlparse", specifier = "==0.6.0" },
|
||||
{ name = "uuid6", specifier = "==2024.7.10" },
|
||||
{ name = "uvicorn-worker", specifier = "==0.4.0" },
|
||||
{ name = "uvloop", specifier = "==0.22.1" },
|
||||
@@ -5426,15 +5426,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyopenssl"
|
||||
version = "26.2.0"
|
||||
version = "26.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/51/27a5ad5f939d08f690a326ef9582cda7140555180db71695f6fb747d6a36/pyopenssl-26.2.0.tar.gz", hash = "sha256:8c6fcecd1183a7fc897548dfe388b0cdb7f37e018200d8409cf33959dbe35387", size = 182195, upload-time = "2026-05-04T23:06:09.72Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3f/e8/7325d258199b159eb2c03fe32107533e2832e70e63f4fb88a6aa00023201/pyopenssl-26.4.0.tar.gz", hash = "sha256:28dfcce0162b9211413e26dfbfdf1d24317fbeba18fc93c12400a1856b2a0bc7", size = 182046, upload-time = "2026-08-01T19:50:50.512Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/b8/a0e2790ae249d6f38c9f66de7a211621a7ab2650217bcd04e1262f578a56/pyopenssl-26.2.0-py3-none-any.whl", hash = "sha256:4f9d971bc5298b8bc1fab282803da04bf000c755d4ad9d99b52de2569ca19a70", size = 55823, upload-time = "2026-05-04T23:06:08.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/ad/2cf6d3fa2fae5c79e1ed9960c0d42badd0f94d81dd12b50604cdc839e648/pyopenssl-26.4.0-py3-none-any.whl", hash = "sha256:f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c", size = 56026, upload-time = "2026-08-01T19:50:48.94Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6049,11 +6049,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.5"
|
||||
version = "0.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/d3/3f06a1006f2261d1342aefb3c71eed02f5d4ca5bdbecd86ebc12ad38306e/sqlparse-0.6.0.tar.gz", hash = "sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9", size = 178477, upload-time = "2026-08-13T19:16:06.396Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/50/f00935da0ec7cbf325f8dc4f772ae46fbc7b672dd62876e73f0a94adda57/sqlparse-0.6.0-py3-none-any.whl", hash = "sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f", size = 50070, upload-time = "2026-08-13T19:16:04.062Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6623,39 +6623,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "1.5.7.3"
|
||||
version = "1.5.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/62/b9c075ad664e7c4cbb3d8d2be7c246506abe1bc7f778eb58d260ef9538c8/zstd-1.5.7.3.tar.gz", hash = "sha256:403e5205f4ac04b92e6b0cda654be2f51de268228a0db0067bc087faacf2f495", size = 672559, upload-time = "2026-01-08T16:24:43.361Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/78/9a476e09c825304df47b98be80d1ffe223733b03550af71325415028f615/zstd-1.5.7.2.tar.gz", hash = "sha256:6d8684c69009be49e1b18ec251a5eb0d7e24f93624990a8a124a1da66a92fc8a", size = 670481, upload-time = "2025-06-23T12:36:08.131Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/75/0d/8c89c0d010b58c21a7865a239790bb1c6822029c053b1ded858d6b573e3a/zstd-1.5.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a3c1781a24e2ced2c0ddee11d45b1f04018b03615eeb622a62eca4d56d3358a", size = 267641, upload-time = "2026-01-08T16:30:50.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/6d/155d8c344d96eca2a5a003a5ddd63373a5f13591fd5cf2b9490250d6805a/zstd-1.5.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c7c81056362b60a04baa34632e713d596662a860ec34efd8e9b109c10e6ec7", size = 230962, upload-time = "2026-01-08T16:30:49.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/c7/ab93916a26eb58cd501ad701974c31b4bc67a7f6abd6c24bef8fe4d7649b/zstd-1.5.7.3-cp311-cp311-manylinux_2_14_x86_64.whl", hash = "sha256:e564f34a55effc7d654eb293468edc80b64d476b0f899f82760ecd8323223ff5", size = 304166, upload-time = "2026-01-10T11:17:45.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/54/27a7040a360019a4602343e3c98c0c0a140f382186002c01e1992fd21837/zstd-1.5.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:fbc49a57188184931d5e3c9f1133cad7eea5a370a9e9418fb8122d58c14340a5", size = 1540288, upload-time = "2026-01-08T17:50:26.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/93/4a4d4edd1b2e809e0ebbb16000404bdcc9a09743c04ee1661442c9581b75/zstd-1.5.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d121d3e63722819e1fe5effbcd9628d8a7cfea0cddabcc5bb37ea861a6a83424", size = 1619134, upload-time = "2026-01-08T17:50:32.324Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/6b/cd6f0a7f4f0d98e4110aa77763cf3e85f594d983ea9ca3d64cc0cee10684/zstd-1.5.7.3-cp311-cp311-manylinux_2_4_i686.whl", hash = "sha256:621f2e7ca8e9eb52a83eb9c91ec3cd283d87591bf75cc658de486b65f44742c7", size = 300166, upload-time = "2026-01-10T11:12:27.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/3f/c717e0d15127d04b7fa58ba9b4c56e8b88b803048b9766cd9d158dbb22ea/zstd-1.5.7.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:c1950fcae690ba32d0f31702b335c548fb42547821565925e48576afdad774a5", size = 1525776, upload-time = "2026-01-08T17:50:35.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/a2/1813cd787d1a2f9ab8e8a90d28dcbc8e8098997dd04de38897ea8e75dd08/zstd-1.5.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bac4f0d03da69115878bedbfa03c4a3f64364e8396b432028c4ce0f05141a0fb", size = 2096057, upload-time = "2026-01-08T17:50:33.984Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/ce/f5a3c7c12de458dd9ce15c484d627fe5412b60c155da23dacb5fcf08d9d5/zstd-1.5.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:da0ab134b7fd28023dedf013751ca850de300a090eb11f689d2a1c178c87d9dc", size = 2132659, upload-time = "2026-01-08T17:50:29.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/66/151f9546498bfd8971a0b6ad67d87c26d7a0df17d57f724da674f3778666/zstd-1.5.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b9923175842ee8f7602ec9cc578f5fc396896f0e8460d3ac9a5adc3cea77244e", size = 2124811, upload-time = "2026-01-08T17:50:37.612Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/34/4d2dbb36cb2373d3f115c047cb901b64f89de0703d10779da39de9453812/zstd-1.5.7.3-cp311-cp311-win32.whl", hash = "sha256:0612b604948d7b58aecc6788c7ceb53c5f21d94a155bb6ea9bd0f54ffa43725d", size = 150363, upload-time = "2026-01-08T17:11:02.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/de/f53687e0dd8c0d0ebfaed9ae88f6a96a1a0388ae7424b469e74bb17ac57d/zstd-1.5.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:5b7f8c81b2bd3b62c0345242247d484cafa4b518d59d18619813d9225af5c5c3", size = 167577, upload-time = "2026-01-08T17:11:03.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/58/d4a6a902e229e953ed273fe9b78587ed31f57567aa68d3e34af6056e42af/zstd-1.5.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:ea112e3acd9e1765adca35df7b54ac75b36194290f64ea03a3a59664209c8527", size = 157238, upload-time = "2026-01-08T16:36:06.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/ed/5a3bf2e29dc56d4cc7619929bb51f0c758de6d02967cc73c5d8755a862c0/zstd-1.5.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01a39efb0eeab7cc45cb308618233b624b0840d5e16dcf85456b6cca0592f203", size = 268124, upload-time = "2026-01-08T16:29:57.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/1d/efc2074ac90af938e78f2ed4004639fe24f294d9086c5280f8d9a02b9897/zstd-1.5.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7a8e8838cf35fa3987bfe1958584cc22e1797efce8e155a63544b4144fc671f8", size = 230988, upload-time = "2026-01-08T16:29:55.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/52/178393b8d70e23fba67f42dfce4663e4e8a30867110168beb490a36d4639/zstd-1.5.7.3-cp312-cp312-manylinux_2_14_i686.whl", hash = "sha256:f3920ac1d1cc7e9f252f3e29f217fe3cd36f2191bb3dbcae826c29e189b7ad54", size = 300207, upload-time = "2026-01-10T11:26:58.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/7a/8dcd86a2efb2ed3f9dae39545a05d3c7ed26c7678330786ce4a44cd8b099/zstd-1.5.7.3-cp312-cp312-manylinux_2_14_x86_64.whl", hash = "sha256:143f9062953fb5590cbd47c1040d357336742c79696bf90b6d5b835279a68304", size = 304154, upload-time = "2026-01-10T11:17:40.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/ce/0c96905ab01ffe0e53a3cec8132123b82db26bd583a71608029bcc789ebc/zstd-1.5.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36d1fd8647e47e1f21b345e192f1a279e925678c23dad8236b547d04456cd699", size = 2162222, upload-time = "2026-01-08T18:02:22.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/c4/db4807d6a68b4628c74fd379de7e3c67ec34f19a2a80ac246b3837cde6cb/zstd-1.5.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1538db419afa62773cf534fc7f3009ff59ecf55ecee4e889587ac2ef0010ed8", size = 2201732, upload-time = "2026-01-08T18:02:20.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/99/c19a3c0f5580ff9c33a74f06d98d6060ed1fa6bd09b55aed9be852ec191f/zstd-1.5.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5efd16adb092e2a547a7d51cfdaf6fd5680528227684c5bafc7669ab4a55f41", size = 2096459, upload-time = "2026-01-08T18:02:25.336Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/fd/02eac30419475dbe50212c119043a2d0698a0cbc756da85fd3fd9abddf42/zstd-1.5.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:39b3438e64637d80a5b1860526903b92020acb9bae9ceb5adffd9838c1441328", size = 2125442, upload-time = "2026-01-08T18:02:17.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/43/3a16ff0a8c913bb9825379db1bd533c75c57c2d2f31dd9111aa9b53711f4/zstd-1.5.7.3-cp312-cp312-win32.whl", hash = "sha256:cbf48c53461e224ffc2490cfe5120a1ff40d14c84d2b512c6d6d99fc91685cf3", size = 150367, upload-time = "2026-01-08T17:03:40.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/83/b85875d7428e63dfa9247e41d17fac611443c774f7892f8643bd4164a6b2/zstd-1.5.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:943a189910f2fea997462e3e4d7fbf727a06d231ef801ebee557b1c87568981c", size = 167604, upload-time = "2026-01-08T17:03:41.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/42/cf291e26804de2f55500cdac93f5e9fa6267cf315def8aa402529bae3a87/zstd-1.5.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:85c4d508f8109afa7c51c4960626c3325af2cf1e442c6c36ebfea15d04757e3f", size = 157241, upload-time = "2026-01-08T16:47:34.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/b8/d13d584867d5eb1bc607877a870858e02a256d4706a4274e475413a000aa/zstd-1.5.7.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:76c49ea969bc08389ea59155cea7c5dea224522ffc62f443f3c0a915f5fd184d", size = 260025, upload-time = "2026-01-08T16:57:45.739Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/a1/1e5faf75bedfd2bfccfb83e18736b115bed6e348504bd21800cd8f30dcea/zstd-1.5.7.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b1a638ff3dfce8f4cb1203c662fb5606dd99b4a62c5ddc4c406d2d1326bcfdd", size = 221038, upload-time = "2026-01-08T17:16:32.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/2c/0fe74d8b2029eef8000bc71aac5b3e5b55d00581238711cf627814183ea3/zstd-1.5.7.3-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5e96a5cb100a0edc162935227f2d9784b1031ce4a8a83e96e66eae2673c10143", size = 326792, upload-time = "2026-01-08T16:57:35.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/e0/2c7f081f3524f872128ff31bea2acb6b21cb1dacccef920eb6a1a77a87c6/zstd-1.5.7.3-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bda0bbf3a9553720cd33f1f85940a259656c7ffba4be717ff82b7f062052188", size = 322283, upload-time = "2026-01-08T16:57:36.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/a7/3bebfcc18d66b90bc7b506a61b2ff4af5ee1b0b16e784ea644afa06241c5/zstd-1.5.7.3-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac36e4022422f6e49b3f07bdbb8a964fd348223d3dc9c82ad5398a4f0432a719", size = 311553, upload-time = "2026-01-08T16:57:38.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/75/8a791cae2c98e5e44a158e15db50d21b7ec0b37aeaffa68d151bc8ffb6d6/zstd-1.5.7.3-pp311-pypy311_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:fa4d760a220541b18ce732a3a2cf7547ea05afc76d05b3b39edebfeb721f6079", size = 317071, upload-time = "2026-01-08T16:36:07.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/25/b6624e6b08d515242154436c9d06fb20b790d300ac82e84f3c4c133e25e1/zstd-1.5.7.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a69e60146bf8aaa6a0e6c9a94a7c5f3133d68091e2e5c5a3c5ababf71fd5ec7a", size = 167654, upload-time = "2026-01-08T17:00:56.667Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/2a/0885f6f1921ec1ef4a8f8ab29ab0a335cc867abe4c7aaa4e5031435a32a5/zstd-1.5.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f799c1e9900ad77e7a3d994b9b5146d7cfd1cbd1b61c3db53a697bf21ffcc57b", size = 269702, upload-time = "2025-06-23T12:50:11.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/e6/629cf6b77e47fc7149f5724fb4853c48edcdeb10d8c64e391d7026cb10e1/zstd-1.5.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ff4c667f29101566a7b71f06bbd677a63192818396003354131f586383db042", size = 228145, upload-time = "2025-06-23T12:50:10.411Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/b8/9ddefd4670bfe9328ca6657ad335eb8d9c657466247e234a579818b6b0b9/zstd-1.5.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8526a32fa9f67b07fd09e62474e345f8ca1daf3e37a41137643d45bd1bc90773", size = 1536530, upload-time = "2025-06-23T13:51:38.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/6a/1bb836c18760dc1e28ca7a9706016e482ebdea633b980d8505dbb65e18f8/zstd-1.5.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:2cec2472760d48a7a3445beaba509d3f7850e200fed65db15a1a66e315baec6a", size = 1616141, upload-time = "2025-06-23T13:51:34.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/7a/bb6c6e2cb2a066e347dc27d45d5205058b69d6c8b8d4ae2ee7d6b91c64a5/zstd-1.5.7.2-cp311-cp311-manylinux_2_4_i686.whl", hash = "sha256:a200c479ee1bb661bc45518e016a1fdc215a1d8f7e4bf6c7de0af254976cfdf6", size = 322188, upload-time = "2025-06-23T13:01:48.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/4f/cf0669c8a89fdcc91814bf92bd05cc363d5d12a79b656418c0add6f2d266/zstd-1.5.7.2-cp311-cp311-manylinux_2_4_x86_64.whl", hash = "sha256:f5d159e57a13147aa8293c0f14803a75e9039fd8afdf6cf1c8c2289fb4d2333a", size = 302736, upload-time = "2025-06-23T13:05:33.649Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/bc/e5f8b7f61826323e39e099db1eb5c0e09b18315df1b1ff778f7ae9aadcac/zstd-1.5.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:7206934a2bd390080e972a1fed5a897e184dfd71dbb54e978dc11c6b295e1806", size = 1522687, upload-time = "2025-06-23T13:51:35.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/8c/7660a949a020ac9d02b3166a25dd1c12144572d77b11ae92a31d341016da/zstd-1.5.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e0027b20f296d1c9a8e85b8436834cf46560240a29d623aa8eaa8911832eb58", size = 2098794, upload-time = "2025-06-23T13:51:37.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/b2/730c811a78d670104d40c7f08cc8092577cdff870cba42b3158f20fceb57/zstd-1.5.7.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d6b17e5581dd1a13437079bd62838d2635db8eb8aca9c0e9251faa5d4d40a6d7", size = 2112266, upload-time = "2025-06-23T13:51:31.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/74/2c16e1632094db36c8920d4c13b8e2e843024d548ae26888c2d22af6a676/zstd-1.5.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b13285c99cc710f60dd270785ec75233018870a1831f5655d862745470a0ca29", size = 2109465, upload-time = "2025-06-23T13:51:32.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/6e/b9c9a834769d96cab2122da1be8c8c700d3f76be796d2b7516e85d2eca0e/zstd-1.5.7.2-cp311-cp311-win32.whl", hash = "sha256:cdb5ec80da299f63f8aeccec0bff3247e96252d4c8442876363ff1b438d8049b", size = 149448, upload-time = "2025-06-23T13:06:21.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/b7/fc22ad6292a32d7676ab815de3a23573beac3679e8abd9914288d1496ceb/zstd-1.5.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:4f6861c8edceb25fda37cdaf422fc5f15dcc88ced37c6a5b3c9011eda51aa218", size = 166592, upload-time = "2025-06-23T13:06:22.126Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/14/096bb77f3e5ef525b452cd6294da33de7f8a8c9647ba78293378fbb0a7ce/zstd-1.5.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ebe3e60dbace52525fa7aa604479e231dc3e4fcc76d0b4c54d8abce5e58734", size = 269408, upload-time = "2025-06-23T13:11:46.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/b8/2bc2590a34c733ea0570f366e6ad7d889d05c7825bd3ccab01f36ece71c6/zstd-1.5.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ef201b6f7d3a6751d85cc52f9e6198d4d870e83d490172016b64a6dd654a9583", size = 228188, upload-time = "2025-06-23T13:11:47.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/80/6252de3a70cfd7767718ad476893f1c7dc129f942cc7ed0322e3137c03d9/zstd-1.5.7.2-cp312-cp312-manylinux_2_14_x86_64.whl", hash = "sha256:ac7bdfedda51b1fcdcf0ab69267d01256fc97ddf666ce894fde0fae9f3630eac", size = 302720, upload-time = "2025-06-23T12:40:11.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/b6/af908387814b99172d3aea6aeb24b19583aadfa45f6021e5e2a0d6d8e99a/zstd-1.5.7.2-cp312-cp312-manylinux_2_4_i686.whl", hash = "sha256:b835405cc4080b378e45029f2fe500e408d1eaedfba7dd7402aba27af16955f9", size = 322237, upload-time = "2025-06-23T13:17:35.482Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/d7/ab9142e002a7eaa451cb4bb37a74c390c489ba8ae75ade543840496eda04/zstd-1.5.7.2-cp312-cp312-win32.whl", hash = "sha256:e4cf97bb97ed6dbb62d139d68fd42fa1af51fd26fd178c501f7b62040e897c50", size = 149453, upload-time = "2025-06-23T13:13:02.786Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/c7/c182ea7bc283f591e3f3c5f0f239e7a92c9bc1f626642ae2c4dfbe51d6f2/zstd-1.5.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:55e2edc4560a5cf8ee9908595e90a15b1f47536ea9aad4b2889f0e6165890a38", size = 166628, upload-time = "2025-06-23T13:13:03.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/c9/a6495a7bf168a78f0a0c01d61d830ebfb401315a64fd1ae8d725c458114c/zstd-1.5.7.2-pp311-pypy311_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:5fb2ff5718fe89181223c23ce7308bd0b4a427239379e2566294da805d8df68a", size = 315542, upload-time = "2025-06-23T12:39:27.598Z" },
|
||||
]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "prowler",
|
||||
"displayName": "Prowler Cloud Security",
|
||||
"version": "0.1.0",
|
||||
"description": "Cloud security and compliance skills powered by the Prowler MCP server.",
|
||||
"author": {
|
||||
"name": "Prowler",
|
||||
"email": "support@prowler.com",
|
||||
"url": "https://prowler.com"
|
||||
},
|
||||
"homepage": "https://docs.prowler.com",
|
||||
"repository": "https://github.com/prowler-cloud/prowler",
|
||||
"license": "Apache-2.0",
|
||||
"category": "security",
|
||||
"keywords": [
|
||||
"prowler",
|
||||
"security",
|
||||
"compliance",
|
||||
"cloud-security",
|
||||
"mcp"
|
||||
],
|
||||
"skills": "./skills/",
|
||||
"mcpServers": "./.mcp.json"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"prowler": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.prowler.com/mcp",
|
||||
"bearer_token_env_var": "PROWLER_API_KEY",
|
||||
"headers": {
|
||||
"User-Agent": "codex"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Prowler for Codex
|
||||
|
||||
Prowler for Codex adds Prowler Cloud security and compliance skills and connects Codex to the Prowler MCP server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Codex CLI with plugin support. Check with `codex --version` and `codex plugin --help`.
|
||||
- A Prowler Cloud API key. Create one in [Prowler Cloud](https://cloud.prowler.com).
|
||||
|
||||
## Install
|
||||
|
||||
Export the API key in the shell that launches Codex. Set `PROWLER_API_KEY` to the raw API key only, without the `Bearer` prefix. Codex adds the bearer prefix automatically. Do not add a real key to a repository, shell history, or shared configuration file.
|
||||
|
||||
```bash
|
||||
export PROWLER_API_KEY=...
|
||||
codex plugin marketplace add prowler-cloud/prowler --ref master
|
||||
codex plugin add prowler@prowler-plugins
|
||||
```
|
||||
|
||||
The marketplace does not install Prowler by default. The `codex plugin add` command explicitly installs it.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
codex plugin list --marketplace prowler-plugins
|
||||
```
|
||||
|
||||
Start Codex from the same shell, then ask it to list your Prowler providers or to help triage a compliance framework.
|
||||
|
||||
## Update and Uninstall
|
||||
|
||||
Refresh the marketplace snapshot:
|
||||
|
||||
```bash
|
||||
codex plugin marketplace upgrade prowler-plugins
|
||||
```
|
||||
|
||||
Remove the plugin:
|
||||
|
||||
```bash
|
||||
codex plugin remove prowler@prowler-plugins
|
||||
```
|
||||
|
||||
If you no longer use the marketplace, remove it after uninstalling the plugin:
|
||||
|
||||
```bash
|
||||
codex plugin marketplace remove prowler-plugins
|
||||
```
|
||||
|
||||
## Environment Limitation
|
||||
|
||||
The Codex CLI reliably receives `PROWLER_API_KEY` when it starts from the shell where you exported it. Codex Desktop and IDE integrations may not inherit variables from your shell profile, so the plugin can fail to authenticate there even when it works in the CLI.
|
||||
|
||||
The Codex plugin workflow does not securely prompt for or store arbitrary API keys. If your Codex surface cannot inherit `PROWLER_API_KEY`, use the manual MCP setup in the [Prowler Codex guide](https://docs.prowler.com/user-guide/ai-agents/codex) instead.
|
||||
@@ -0,0 +1,199 @@
|
||||
---
|
||||
name: prowler-framework-compliance-triage
|
||||
description: Make a cloud account compliant with a security or industry framework using Prowler Cloud.
|
||||
---
|
||||
|
||||
# Framework compliance
|
||||
|
||||
Iterative, interactive flow that takes a cloud account through setup, reporting, and remediation until it complies with the chosen security or industry framework.
|
||||
|
||||
## Checkpoints
|
||||
|
||||
This skill uses **checkpoints** to mark moments where you must stop, post a clear question or summary to the user, and wait for the reply before continuing. Each checkpoint is rendered like this:
|
||||
|
||||
> **Checkpoint — <name>**
|
||||
>
|
||||
> What to present, and what to wait for.
|
||||
|
||||
Treat every checkpoint as a hard stop:
|
||||
|
||||
- Do not skip a checkpoint because the user previously said "go ahead", "just do it", or similar. Confirmations are scoped to a single checkpoint and do not transfer to later ones.
|
||||
- Do not bundle two checkpoints into one message. Post one, wait for the reply, then continue.
|
||||
- Do not infer the user's answer from context or proceed on silence. Ask explicitly and wait.
|
||||
- If a checkpoint is conditional (e.g. only fires when multiple accounts exist), evaluate the condition first; if it does not apply, continue without prompting.
|
||||
- If the user's initial message already answers the question a checkpoint asks (e.g. "make my AWS subscription compliant with CIS using Terraform autonomously"), treat the checkpoint as satisfied for the parts they covered, and only ask for what is still missing.
|
||||
|
||||
## 1. Initial Prowler Cloud setup
|
||||
|
||||
> **Checkpoint — Provider and framework selection**
|
||||
>
|
||||
> If the user has not already specified both the provider and the framework, ask explicitly and wait for the answer. If they have specified them in their opening message, skip this checkpoint.
|
||||
|
||||
Confirm both are supported by the Prowler Hub MCP:
|
||||
|
||||
- Enumerate supported providers with `prowler_hub_list_providers`.
|
||||
- Enumerate frameworks for the chosen provider with `prowler_hub_list_compliances`, passing the provider `id` as the only element of the `provider` input list.
|
||||
|
||||
If the framework is not supported, tell the user, suggest they request it or contribute it themselves, and end the flow. Otherwise continue.
|
||||
|
||||
### 1.1 Connect to Prowler Cloud
|
||||
|
||||
Verify the Prowler MCP connection by calling `prowler_search_providers` — a successful response returns the list of providers. If the call fails, walk the user through troubleshooting: internet connectivity, Prowler Cloud credentials, and permissions on the Prowler Cloud account.
|
||||
For getting accurate information about configurations use `prowler_docs_search` to pull relevant instructions from the Prowler documentation.
|
||||
|
||||
### 1.2 Verify the provider is configured (or configure it)
|
||||
|
||||
Call `prowler_search_providers` to check whether the target provider (AWS account, Azure Subscription, GitHub Account...) exists in the user's Prowler Cloud account. Handle the result based on what's found:
|
||||
|
||||
- **Provider not present.** Guide the user through adding and configuring it. Retrieve the relevant connection, credential, and permission instructions with `prowler_docs_search`.
|
||||
- **Provider present but misconfigured** (missing credentials, insufficient permissions, etc.). Walk the user through fixing the configuration, pulling the relevant guidance with `prowler_docs_search`.
|
||||
- **Provider present and configured.** Continue.
|
||||
|
||||
> **Checkpoint — Account selection** *(conditional: more than one account of the chosen provider is configured)*
|
||||
>
|
||||
> List the accounts with helpful detail (account name, uid, last scan date) and ask which one to use. Wait for the answer. If only one account exists, skip this checkpoint and use it.
|
||||
|
||||
### 1.3 Review compliance report for the provider account
|
||||
|
||||
The flow needs at least one completed scan with a compliance report available.
|
||||
|
||||
Look for a completed scan first: call `prowler_list_scans` with the selected `provider_id` and `state: ["completed"]`, then call `prowler_get_compliance_overview` with each `scan_id` to find one whose compliance report is available. If one is found, continue to the next section.
|
||||
|
||||
If no completed scan has a report, call `prowler_list_scans` again with `state: ["available", "executing"]` to detect a scan in progress.
|
||||
|
||||
> **Checkpoint — Scan-in-progress decision** *(conditional: an in-progress scan was detected)*
|
||||
>
|
||||
> Tell the user a scan is already running and ask whether to wait for it to complete or start a fresh one. Wait for the answer.
|
||||
|
||||
If no scan is running (or the user chose to start a fresh one), trigger a new scan with `prowler_trigger_scan` and the `provider_id`. The link `https://cloud.prowler.com/scans?filter%5Bprovider_uid__in%5D={provider_id}` lets the user monitor progress.
|
||||
|
||||
When a scan is in progress (either pre-existing and elected to wait, or just triggered), stop the flow and ask the user to return when it's completed — restart this section to re-check the results.
|
||||
|
||||
## 2. Compliance report
|
||||
|
||||
Every iteration of the remediation loop reads and writes a single markdown file per provider account and framework, stored at `.prowler/compliance-<compliance_id>-<provider_uid>.md` relative to the current project root. Sanitize `<provider_uid>` to `[a-zA-Z0-9_-]` by replacing anything else with `-`. Create `.prowler/` if missing.
|
||||
|
||||
Across iterations, edit only: status tags on failed requirements and their findings, the per-requirement `Fix plan` / `Fix applied` sub-bullets added during sections 3.3–3.4, the **Global remediation approach** block, and the **Activity log** (append-only, newest on top). Requirement descriptions, finding IDs, and the entire **Manual review requirements** section are read-only after first render.
|
||||
|
||||
Status taxonomy for failed requirements and their findings:
|
||||
|
||||
- `[FAIL]` — failing in the latest scan.
|
||||
- `[IN PROGRESS]` — picked up by section 3.3.
|
||||
- `[FIXED-UNVERIFIED]` — remediation applied; not yet confirmed.
|
||||
- `[PASS]` — passing in the latest scan (set when a rescan in section 3.5 confirms the fix).
|
||||
- `[SKIPPED]` — user explicitly deferred.
|
||||
|
||||
### Report template
|
||||
|
||||
A fresh report is rendered like this (substituting values from the `prowler_get_compliance_framework_state_details` Prowler MCP tool response):
|
||||
|
||||
````markdown
|
||||
# Compliance report: <compliance_id>
|
||||
|
||||
**Provider account**: <display name + uid>
|
||||
**Scan ID**: <scan_id>
|
||||
**Generated**: <ISO timestamp>
|
||||
**Last update**: <ISO timestamp>
|
||||
**Status**: <passed>/<total> passing (<pct>%) · <failed> failing · <manual_review> manual review
|
||||
|
||||
## Global remediation approach
|
||||
<!-- Filled by section 3.1. -->
|
||||
- **Primary tool**: _Terraform | Azure CLI | AWS CLI | web console | mixed_
|
||||
- **Mode**: _Claude autonomous | Claude-assisted_
|
||||
- **Notes**:
|
||||
|
||||
## Activity log
|
||||
- <ISO timestamp> — Report initialized from scan `<scan_id>`.
|
||||
|
||||
## Failed requirements
|
||||
|
||||
### <code> — [FAIL]
|
||||
**Description**: <text>
|
||||
**Findings** (<n>):
|
||||
- [FAIL] `<finding_id>`
|
||||
|
||||
## Manual review requirements
|
||||
- **<code>** — [PENDING]: <description>
|
||||
````
|
||||
|
||||
### 2.1 Generate or refresh the report
|
||||
|
||||
Resolve the report path for the current `compliance_id` and provider account.
|
||||
|
||||
If the file does not exist, call `prowler_get_compliance_framework_state_details` for the target scan, render the template above, and write the file with one initialization entry in the activity log.
|
||||
|
||||
If the file exists, read it and compare its `Scan ID` to the target scan from section 1.3. When the scan matches, reuse the file and summarize remaining `[FAIL]` and `[IN PROGRESS]` items in chat.
|
||||
|
||||
> **Checkpoint — Report refresh** *(conditional: the file's `Scan ID` differs from the current target scan)*
|
||||
>
|
||||
> Tell the user the report on disk was generated from a different scan and ask whether to refresh it from the new scan. Wait for the answer.
|
||||
|
||||
On confirmation, regenerate the failed-requirements section from the new `prowler_get_compliance_framework_state_details` response, carry forward the **Global remediation approach** block and the full activity log, and append an activity-log entry noting the scan change.
|
||||
|
||||
Once the file is current, surface the top failing requirements in chat: sort by finding count descending, show the top 5 with their codes and counts, and point to the file path for the full list.
|
||||
|
||||
## 3. Remediation loop
|
||||
|
||||
### 3.1 Define the global remediation approach
|
||||
|
||||
Two modes are available:
|
||||
|
||||
- **Claude-assisted** (default when the user has not specified): per-requirement confirmation. For each requirement Claude shows the target resource, exact commands, side effects, and reversibility, then waits for explicit go-ahead before applying.
|
||||
- **Claude autonomous**: no per-requirement gate, but Claude still presents one batch-level fix plan up front (§3.2) and waits for a single confirmation, and pauses if a finding looks not applicable, requires a paid feature, or has wide blast radius (breaks dev workflow, forces collaborator changes, is hard to reverse).
|
||||
|
||||
If the user phrases their request as "just do it" or similar, treat that as autonomous **with** the batch-plan confirmation still required — the confirmation is a property of the skill, not the user's verbosity preference.
|
||||
|
||||
> **Checkpoint — Global remediation approach**
|
||||
>
|
||||
> Ask the user which tool to use for fixes (Terraform, gh / az / aws CLI, web console, mixed...) and which mode to operate in. Wait for the answer before continuing. This checkpoint is non-negotiable: never assume a default tool, and never assume autonomous mode.
|
||||
|
||||
Once answered, write the values into the **Global remediation approach** block of the report file.
|
||||
|
||||
> **Checkpoint — Overwriting an existing approach** *(conditional: the block is already populated from a previous session)*
|
||||
>
|
||||
> Show the previous values and the new ones, and ask the user to confirm before overwriting. Wait for the answer.
|
||||
|
||||
### 3.2 Present the batch fix plan *(autonomous mode only)*
|
||||
|
||||
In **assisted** mode, skip this section — the per-requirement gate in §3.3 confirms each fix as it comes up. Only run §3.2 in **autonomous** mode, where the loop will otherwise apply fixes without further input.
|
||||
|
||||
Before touching anything, post a single chat summary covering every `[FAIL]` requirement:
|
||||
|
||||
- Group findings that share a fix (e.g. ten branch-protection requirements satisfied by one PUT call → present as one group).
|
||||
- For each group: target resource, exact tool calls, side effects, reversibility.
|
||||
- Call out findings that look **not applicable** to this target (e.g. an Organization-only check evaluated against a User account, a feature gated by a paid plan, a resource type the user doesn't have) and propose `[SKIPPED]` with the reason.
|
||||
- Call out findings that require manual user action Claude cannot perform.
|
||||
|
||||
> **Checkpoint — Batch fix plan approval** *(conditional: autonomous mode)*
|
||||
>
|
||||
> Post the grouped plan and wait for explicit confirmation. Do not start any fix before the user replies.
|
||||
|
||||
Once approved, the loop proceeds through the batch without further prompts unless something deviates from the approved plan.
|
||||
|
||||
### 3.3 Pick the first FAIL requirement and inspect its findings
|
||||
|
||||
Pick the first `[FAIL]` requirement at the top of the failed-requirements section. Move its status and every finding under it to `[IN PROGRESS]`, and add a `**Fix plan**:` sub-bullet describing what will be done.
|
||||
|
||||
Call `prowler_get_finding_details` for each `finding_id` to retrieve the failing resource and the Prowler Hub's remediation guidance for that check using the tool `prowler_hub_get_check_details` with the `check_id` from the finding details. Summarize the guidance in chat, and append it to the `**Fix plan**` note for each finding.
|
||||
|
||||
If a finding does not apply to the target resource (Organization-only check on a User account, paid-tier feature, missing resource type, etc.), set the requirement status to `[SKIPPED]` with the reason, log it in the activity log, and move on without attempting the fix — even if it was missed during §3.2.
|
||||
|
||||
> **Checkpoint — Per-requirement approval** *(conditional: assisted mode)*
|
||||
>
|
||||
> Post the per-requirement plan in chat — resource, command, side effects, reversibility — and wait for confirmation before moving to §3.4. In **autonomous** mode, post the plan for transparency but proceed unless it deviates from the batch plan agreed in §3.2.
|
||||
|
||||
### 3.4 Diagnose, fix, verify
|
||||
|
||||
Read the remediation guidance returned in §3.3, identify the root cause, and apply the fix using the tool defined in the **Global remediation approach** block. After applying, verify via the same tool that applied the fix or via a provider API call when applicable. If the re-read shows the change did not land, leave the status at `[IN PROGRESS]`, surface the error to the user, and stop the loop for this requirement. On post-fix verification failure, record the failure in the activity log and set the requirement status back to `[FAIL]` so the next loop can retry or choose another remediation.
|
||||
|
||||
When the change is in place, append a `**Fix applied**: <tool, summary, refs>` sub-bullet to the requirement, move each fixed finding to `[FIXED-UNVERIFIED]`, and add one activity-log entry describing the change. If no programmatic verification was possible (e.g. web console action), note in the activity log that confirmation depends on the rescan in §3.5.
|
||||
|
||||
### 3.5 Loop
|
||||
|
||||
Move to the next `[FAIL]` requirement and repeat from section 3.3.
|
||||
|
||||
> **Checkpoint — Rescan trigger** *(conditional: no `[FAIL]` requirements remain; all are `[FIXED-UNVERIFIED]` or `[SKIPPED]`)*
|
||||
>
|
||||
> Summarize what was applied, list any `[SKIPPED]` items with reasons, and ask whether to trigger a fresh scan with `prowler_trigger_scan` to verify the fixes end-to-end. Wait for the answer.
|
||||
|
||||
On confirmation, trigger the rescan. When it completes, restart section 2.1 with the carry-forward path — requirements no longer in the new FAIL list move to `[PASS]`, anything still failing reverts to `[FAIL]` with the previous fix attempt visible in the activity log.
|
||||
@@ -4,6 +4,117 @@ description: "New features and improvements in each Prowler release"
|
||||
rss: true
|
||||
---
|
||||
|
||||
<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
|
||||
|
||||
|
||||
@@ -224,9 +224,9 @@ Each check **must** populate the report with a unique identifier for the audited
|
||||
- `resource_name`: Description of the configuration (e.g., "SharePoint Settings")
|
||||
- GitHub
|
||||
- Resource ID — `report.resource_id`.
|
||||
- The ID of the Github resource. This is a system-generated integer that uniquely identifies the resource within the Github platform.
|
||||
- The ID of the GitHub resource. This is a system-generated integer that uniquely identifies the resource within the GitHub platform.
|
||||
- Resource Name — `report.resource_name`.
|
||||
- The name of the Github resource. In the case of a repository, this is just the repository name. For full repository names use the resource `full_name`.
|
||||
- The name of the GitHub resource. In the case of a repository, this is just the repository name. For full repository names use the resource `full_name`.
|
||||
|
||||
### Configurable Checks in Prowler
|
||||
|
||||
|
||||
@@ -120,14 +120,14 @@ class NewFeatureTools(BaseTool):
|
||||
|
||||
Returns complete feature details including configuration and metadata.
|
||||
"""
|
||||
try:
|
||||
response = await self.api_client.get(f"/api/v1/features/{feature_id}")
|
||||
return DetailedFeature.from_api_response(response["data"]).model_dump()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to get feature {feature_id}: {e}")
|
||||
return {"error": str(e), "status": "failed"}
|
||||
response = await self.api_client.get(f"/api/v1/features/{feature_id}")
|
||||
return DetailedFeature.from_api_response(response["data"]).model_dump()
|
||||
```
|
||||
|
||||
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/`:
|
||||
@@ -369,18 +369,62 @@ async def search_items(self, status: str = Field(...)) -> dict:
|
||||
|
||||
### Error Handling
|
||||
|
||||
Return structured error responses instead of raising exceptions:
|
||||
**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:
|
||||
try:
|
||||
response = await self.api_client.get(f"/api/v1/items/{item_id}")
|
||||
return DetailedItem.from_api_response(response["data"]).model_dump()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to get item {item_id}: {e}")
|
||||
return {"error": str(e), "status": "failed"}
|
||||
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
|
||||
|
||||
@@ -107,7 +107,7 @@ Once you have decided the provider you want or need to add to Prowler, the next
|
||||
- **SDK Providers**: Low complexity. You have mature examples like AWS, Azure, GCP, Kubernetes, etc. that you can leverage to implement your provider.
|
||||
- **API Providers**: Medium complexity. You need to implement the authentication and session management, and the API calls to the provider. You now have NHN and MongoDB Atlas as example to follow.
|
||||
- **Tool/Wrapper Providers**: High complexity. You need to implement the argument/output mapping to the provider and handle problems that the tool/wrapper may have. You now have IAC and the PowerShell wrapper as example to follow.
|
||||
- **Hybrid Providers**: High complexity. You need to "customize" your provider, mixing the other types of providers in order to achieve the desired result. You have M365 (msgraph SDK + PowerShell wrapper) and Github (PyGithub SDK + graphql API requests) as examples.
|
||||
- **Hybrid Providers**: High complexity. You need to "customize" your provider, mixing the other types of providers to achieve the desired result. You have M365 (msgraph SDK + PowerShell wrapper) and GitHub (PyGithub SDK + graphql API requests) as examples.
|
||||
|
||||
### Determining Regional vs Non-Regional Architecture
|
||||
|
||||
@@ -814,7 +814,7 @@ class YourProviderMutelist(Mutelist):
|
||||
Region management is essential for cloud providers that operate across multiple geographic locations. This component handles region validation and provides region-specific functionality.
|
||||
|
||||
<Note>
|
||||
Regions are optional, only if the provider has regions, for example Github does not have regions, but AWS does.
|
||||
Regions are optional, only if the provider has regions, for example GitHub does not have regions, but AWS does.
|
||||
</Note>
|
||||
|
||||
**File:** `prowler/providers/<provider_name>/lib/regions/<provider_name>_regions.py`
|
||||
@@ -1773,7 +1773,7 @@ The implementation of the mutelist is the same as the [SDK providers](#step-5-im
|
||||
Region management is essential for cloud providers that operate across multiple geographic locations. This component handles region validation and provides region-specific functionality.
|
||||
|
||||
<Note>
|
||||
Regions are optional, only if the provider has regions, for example Github does not have regions, but AWS does.
|
||||
Regions are optional, only if the provider has regions, for example GitHub does not have regions, but AWS does.
|
||||
</Note>
|
||||
|
||||
**File:** `prowler/providers/<provider_name>/lib/regions/<provider_name>_regions.py`
|
||||
|
||||
@@ -525,7 +525,7 @@ with mock.patch(
|
||||
):
|
||||
```
|
||||
|
||||
As demonstrated in the code above, mocking both the AWS audit information and all utilized services is mandatory for proper test execution.
|
||||
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
|
||||
|
||||
|
||||
@@ -182,7 +182,8 @@
|
||||
"pages": [
|
||||
"user-guide/tutorials/prowler-app-s3-integration",
|
||||
"user-guide/tutorials/prowler-app-security-hub-integration",
|
||||
"user-guide/tutorials/prowler-app-jira-integration"
|
||||
"user-guide/tutorials/prowler-app-jira-integration",
|
||||
"user-guide/tutorials/prowler-app-slack-integration"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -301,6 +302,15 @@
|
||||
{
|
||||
"group": "Providers",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Organizations",
|
||||
"pages": [
|
||||
"user-guide/organizations",
|
||||
"user-guide/providers/aws/organizations",
|
||||
"user-guide/providers/gcp/organization",
|
||||
"user-guide/providers/azure/management-groups"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Alibaba Cloud",
|
||||
"pages": [
|
||||
@@ -330,6 +340,7 @@
|
||||
"user-guide/providers/azure/getting-started-azure",
|
||||
"user-guide/providers/azure/authentication",
|
||||
"user-guide/providers/azure/use-non-default-cloud",
|
||||
"user-guide/providers/azure/management-groups",
|
||||
"user-guide/providers/azure/subscriptions",
|
||||
"user-guide/providers/azure/resource-groups",
|
||||
"user-guide/providers/azure/create-prowler-service-principal"
|
||||
|
||||
@@ -11,10 +11,10 @@ Complete reference guide for all tools available in the Prowler MCP Server. Tool
|
||||
| Prowler Hub | 10 tools | No | Cloud and Local MCP Server |
|
||||
| Prowler Documentation | 2 tools | No | Cloud and Local MCP Server |
|
||||
| Prowler Cloud, Private Cloud & Local Server | 49 tools | Yes | Cloud and Local MCP Server |
|
||||
| Prowler Cloud management | 32 tools | Yes | Cloud MCP Server only |
|
||||
| Prowler Cloud management | 40 tools | Yes | Cloud MCP Server only |
|
||||
|
||||
<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.
|
||||
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
|
||||
@@ -124,7 +124,20 @@ Tools for managing where Prowler sends its results: Amazon S3 buckets, AWS Secur
|
||||
#### Jira Operations
|
||||
|
||||
- **`prowler_get_jira_issue_types`** - List the issue types available in a Jira project, fetched live from Jira
|
||||
- **`prowler_send_findings_to_jira`** - Create one Jira work item per finding, with its severity, resource, risk, and remediation steps
|
||||
- **`prowler_send_findings_to_jira`** - Create Jira work items from findings, each carrying the check title, severity, status, provider, region, resource, risk, and remediation steps. Select the findings either by ID with `finding_ids`, or — on Prowler Cloud only — by check with `check_ids`, and choose between one work item per finding or one per check with `dispatch_mode`
|
||||
|
||||
<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
|
||||
|
||||
@@ -167,6 +180,23 @@ Manage Prowler Cloud-only features and configuration. **Requires authentication.
|
||||
These tools are available **only on the Cloud MCP Server** (`https://mcp.prowler.com/mcp`). A Local MCP Server does not expose them, because the features they manage exist only in Prowler Cloud.
|
||||
</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.
|
||||
|
||||
@@ -22,7 +22,7 @@ Google Cloud Security Command Center (Cloud SCC) is a centralized security and r
|
||||
|
||||
- **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 utilized.
|
||||
- **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.
|
||||
|
||||
|
||||
@@ -128,12 +128,12 @@ To update the environment file:
|
||||
Edit the `.env` file and change version values:
|
||||
|
||||
```env
|
||||
PROWLER_UI_VERSION="5.38.0"
|
||||
PROWLER_API_VERSION="5.38.0"
|
||||
PROWLER_UI_VERSION="5.39.0"
|
||||
PROWLER_API_VERSION="5.39.0"
|
||||
```
|
||||
|
||||
<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.
|
||||
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>
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ To install Prowler as a Python package, use `Python >= 3.10, <= 3.13`. Prowler i
|
||||
_Requirements_:
|
||||
|
||||
* Have `docker` installed: https://docs.docker.com/get-docker/.
|
||||
* In the command below, change `-v` to your local directory path in order to access the reports.
|
||||
* In the command below, change `-v` to your local directory path to access the reports.
|
||||
* AWS, GCP, Azure and/or Kubernetes credentials
|
||||
|
||||
_Commands_:
|
||||
|
||||
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 563 KiB |
|
After Width: | Height: | Size: 401 KiB |
|
After Width: | Height: | Size: 374 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 286 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 366 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 401 KiB |
|
After Width: | Height: | Size: 456 KiB |
|
After Width: | Height: | Size: 374 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 93 KiB |
@@ -92,6 +92,7 @@ li[id="/user-guide/tutorials/prowler-alerts"] a > div > div > span:first-child::
|
||||
li[id="/user-guide/tutorials/prowler-app-attack-paths-active-queries"] a > div > div > span:first-child::after,
|
||||
li[id="/user-guide/tutorials/prowler-app-findings-triage"] a > div > div > span:first-child::after,
|
||||
li[id="/user-guide/tutorials/prowler-app-scan-configuration"] a > div > div > span:first-child::after,
|
||||
li[id="/user-guide/tutorials/prowler-app-slack-integration"] a > div > div > span:first-child::after,
|
||||
li[id="/user-guide/tutorials/prowler-cloud-aws-organizations"] a > div > div > span:first-child::after,
|
||||
li[id="/user-guide/tutorials/prowler-cloud-azure-management-groups"] a > div > div > span:first-child::after,
|
||||
li[id="/user-guide/tutorials/prowler-cloud-gcp-organizations"] a > div > div > span:first-child::after,
|
||||
|
||||
@@ -5,104 +5,106 @@ 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
|
||||
## Preferred Setup: Install the Prowler Plugin
|
||||
|
||||
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.
|
||||
The Prowler plugin is the recommended setup for Codex CLI. It adds Prowler Cloud security and compliance skills and configures the Prowler MCP Server with the required `User-Agent` header.
|
||||
|
||||
| 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>
|
||||
| Surface | Recommended Setup | Notes |
|
||||
|---------|-------------------|-------|
|
||||
| **Codex CLI** | Prowler plugin | Reliably inherits the API key from the launching shell. |
|
||||
| **Codex / ChatGPT desktop app** | Plugin when supported, otherwise [manual MCP setup](#manual-mcp-setup-advanced-or-fallback) | May require configuring the application launch environment. |
|
||||
| **Codex IDE extension** | Plugin when supported, otherwise [manual MCP setup](#manual-mcp-setup-advanced-or-fallback) | May require configuring the IDE launch environment. |
|
||||
| **ChatGPT on the web** | Not supported | Does not read local Codex configuration. |
|
||||
|
||||
## 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.
|
||||
- **Codex CLI with plugin support.** Check with `codex --version` and `codex plugin --help`.
|
||||
- **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.
|
||||
Create an API key in Prowler Cloud and copy it. The key 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
|
||||
## Step 2: Set the API Key Environment Variable
|
||||
|
||||
The Prowler MCP Server needs two request headers: `Authorization` to authenticate you, and `User-Agent` because Codex does not send one by default.
|
||||
Export the key in the shell that launches Codex. Do not add a real key to a repository, shell history, or shared configuration file.
|
||||
|
||||
Each tab below is a complete setup — follow the one that matches the surface you use.
|
||||
```bash
|
||||
export PROWLER_API_KEY="<your-Prowler-Cloud-API-key>"
|
||||
```
|
||||
|
||||
<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.
|
||||
`PROWLER_API_KEY` must contain the raw API key, without the `Bearer ` prefix. The plugin adds that prefix when it sends the `Authorization` header.
|
||||
|
||||
<Note>
|
||||
**Local server:** Replace the URL with your own HTTP endpoint. Everything else stays the same.
|
||||
**Codex CLI reliably inherits this variable** when it starts from the shell where you exported it. Codex Desktop and IDE integrations may not inherit variables from your shell profile. Configure the desktop application or IDE launch environment with `PROWLER_API_KEY` when you use those surfaces.
|
||||
</Note>
|
||||
|
||||
## Step 3: Verify the Connection
|
||||
## Step 3: Install the Plugin
|
||||
|
||||
Add the Prowler marketplace, then install the Prowler plugin:
|
||||
|
||||
```bash
|
||||
codex plugin marketplace add prowler-cloud/prowler --ref master
|
||||
codex plugin add prowler@prowler-plugins
|
||||
```
|
||||
|
||||
The marketplace does not install Prowler by default. The `codex plugin add` command explicitly installs it.
|
||||
|
||||
## Step 4: Verify the Plugin
|
||||
|
||||
```bash
|
||||
codex plugin list --marketplace prowler-plugins
|
||||
```
|
||||
|
||||
Start Codex from the same shell, then ask it to list your Prowler providers or to help triage a compliance framework.
|
||||
|
||||
## Manual MCP Setup: Advanced or Fallback
|
||||
|
||||
Use this setup only when the plugin is unavailable for your Codex surface or when you need a custom MCP configuration. The manual configuration must include the `User-Agent` header because Codex does not send one by default.
|
||||
|
||||
### Codex CLI
|
||||
|
||||
Register the server:
|
||||
|
||||
```bash
|
||||
codex mcp add prowler --url https://mcp.prowler.com/mcp
|
||||
```
|
||||
|
||||
Then update `~/.codex/config.toml` because `codex mcp add` has no flag for the required header:
|
||||
|
||||
```toml
|
||||
[mcp_servers.prowler]
|
||||
url = "https://mcp.prowler.com/mcp"
|
||||
bearer_token_env_var = "PROWLER_API_KEY"
|
||||
http_headers = { "User-Agent" = "codex" }
|
||||
```
|
||||
|
||||
This manual CLI configuration uses the same secure environment-backed contract as the plugin: `PROWLER_API_KEY` holds the raw key, and Codex adds the `Bearer ` prefix.
|
||||
|
||||
### Codex / ChatGPT Desktop App
|
||||
|
||||
First configure `PROWLER_API_KEY` in the desktop application's launch environment. An API key exported only in a shell profile may not be available when the app starts from Finder, the Dock, or a launcher.
|
||||
|
||||
If you cannot configure the launch environment, add the server manually:
|
||||
|
||||
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 the `User-Agent` header with the value `codex`
|
||||
6. If the app supports a reference to its launch environment, use `PROWLER_API_KEY`. Otherwise, add an `Authorization` header with `Bearer ` followed by the API key.
|
||||
|
||||
<Warning>
|
||||
A literal `Authorization` header stores the API key in plain text in `~/.codex/config.toml`. Use it only as a last resort, exclude the file from dotfile repositories and configuration sync, and revoke and re-issue the key if the file is shared.
|
||||
</Warning>
|
||||
|
||||
Restart Codex after installing the plugin or changing the MCP configuration.
|
||||
|
||||
<Note>
|
||||
**Local server:** Replace the URL with your own HTTP endpoint. Keep the same authentication and `User-Agent` configuration.
|
||||
</Note>
|
||||
|
||||
## Step 5: Verify the Connection
|
||||
|
||||
Run `/mcp` in the app or in a CLI session to list connected servers and their tools.
|
||||
|
||||
@@ -121,7 +123,7 @@ codex mcp get prowler # full entry, header values masked
|
||||
**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
|
||||
## Step 6: Start Using Prowler MCP
|
||||
|
||||
Ask Codex questions that use the Prowler tools:
|
||||
|
||||
@@ -145,15 +147,14 @@ Codex reports a handshake failure on startup, with an HTML error page rather tha
|
||||
<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.
|
||||
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. The plugin adds this header automatically. For a manual configuration, add it as shown in [Manual MCP Setup](#manual-mcp-setup-advanced-or-fallback); 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.
|
||||
- For a manual configuration, run `codex mcp get prowler` and confirm the entry has the headers you expect. Values are masked, but a missing header shows as `-`.
|
||||
- Verify the Codex process has `PROWLER_API_KEY` in its launch environment without printing the key: `test -n "$PROWLER_API_KEY" && echo "PROWLER_API_KEY is set"`.
|
||||
- `PROWLER_API_KEY` must contain the raw key without `Bearer `. The plugin and the `bearer_token_env_var` manual configuration add the prefix automatically.
|
||||
- If the CLI works but Codex Desktop or an IDE fails, configure `PROWLER_API_KEY` in that application's launch environment. Shell-profile exports may not be inherited. Use a literal `Authorization` header only as the last-resort desktop fallback described in [Manual MCP Setup](#manual-mcp-setup-advanced-or-fallback).
|
||||
- Confirm the key has not been revoked in Prowler Cloud.
|
||||
|
||||
### Server Not Listed
|
||||
|
||||
@@ -495,7 +495,7 @@ aws:
|
||||
|
||||
# AWS CloudTrail Configuration
|
||||
# aws.cloudtrail_threat_detection_privilege_escalation
|
||||
threat_detection_privilege_escalation_threshold: 0.2 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.2 (20%)
|
||||
threat_detection_privilege_escalation_threshold: 0.2 # Percentage of actions found to decide if it is a privilege_escalation attack event, by default is 0.2 (20%)
|
||||
threat_detection_privilege_escalation_minutes: 1440 # Past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours)
|
||||
threat_detection_privilege_escalation_actions:
|
||||
[
|
||||
|
||||
@@ -6,7 +6,7 @@ In certain organizations, the severity of specific checks might differ from the
|
||||
|
||||
The custom metadata option offers a means to override default metadata set by Prowler.
|
||||
|
||||
You can utilize `--custom-checks-metadata-file` followed by the path to your custom checks metadata YAML file.
|
||||
You can use `--custom-checks-metadata-file` followed by the path to your custom checks metadata YAML file.
|
||||
|
||||
## Available Fields
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ def get_table(data):
|
||||
|
||||
## S3 Integration
|
||||
|
||||
If you are using Prowler Cloud with the S3 integration or that integration from Prowler CLI and you want to use your data from your S3 bucket, you can run the following command in order to load the dashboard with the new files:
|
||||
If you are using Prowler Cloud with the S3 integration or that integration from Prowler CLI and you want to use your data from your S3 bucket, you can run the following command to load the dashboard with the new files:
|
||||
|
||||
```sh
|
||||
aws s3 cp s3://<your-bucket>/output/csv ./output --recursive
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
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.
|
||||
@@ -64,7 +64,7 @@ This method grants permanent access and is the recommended setup for production
|
||||
|
||||
7. Click "Submit" to deploy the stack
|
||||
|
||||

|
||||

|
||||
</Tab>
|
||||
<Tab title="Terraform">
|
||||
To provision the scan role using Terraform:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: 'Scanning Multiple AWS Accounts with Prowler'
|
||||
---
|
||||
|
||||
Prowler enables security scanning across multiple AWS accounts by utilizing the [Assume Role feature](/user-guide/providers/aws/role-assumption) and [integration with AWS Organizations feature](/user-guide/providers/aws/organizations).
|
||||
Prowler enables security scanning across multiple AWS accounts by using the [Assume Role feature](/user-guide/providers/aws/role-assumption) and [integration with AWS Organizations feature](/user-guide/providers/aws/organizations).
|
||||
|
||||
This approach allows execution from a single account with permissions to assume roles in the target accounts.
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ See [AWS Organizations](/user-guide/tutorials/prowler-cloud-aws-organizations) i
|
||||
|
||||
Prowler can integrate with AWS Organizations to manage the visibility and onboarding of accounts centrally.
|
||||
|
||||
For the cross-provider organization lifecycle and capability comparison, see [Organizations Across Cloud Providers](/user-guide/organizations).
|
||||
|
||||
When trusted access is enabled with the Organization, Prowler can discover accounts as they are created and even automate deployment of the Prowler Scan IAM Role.
|
||||
|
||||
> ℹ️ Trusted access can be enabled in the Management Account from the AWS Console under **AWS Organizations → Settings → Trusted access for AWS CloudFormation StackSets**.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: 'Azure Management Groups in Prowler'
|
||||
---
|
||||
|
||||
Azure Management Groups provide a hierarchy above subscriptions. They allow Azure role assignments and governance policies to apply to multiple subscriptions through a shared scope.
|
||||
|
||||
For the cross-provider concepts and lifecycle, see [Organizations Across Cloud Providers](/user-guide/organizations).
|
||||
|
||||
## Azure Hierarchy
|
||||
|
||||
Azure resources are organized in the following order:
|
||||
|
||||
1. Tenant
|
||||
2. Management groups
|
||||
3. Subscriptions
|
||||
4. Resource groups
|
||||
5. Resources
|
||||
|
||||
Prowler scans Azure subscriptions. Management groups help organize those subscriptions and provide a scope where permissions can be assigned, but a management group is not itself a scan target.
|
||||
|
||||
## Create a Management Group
|
||||
|
||||
To create a management group, follow the [official Azure guide](https://learn.microsoft.com/en-us/azure/governance/management-groups/create-management-group-portal).
|
||||
|
||||

|
||||
|
||||
After creating the management group, add the subscriptions that Prowler should access and scan.
|
||||
|
||||

|
||||
|
||||
## Assign Roles
|
||||
|
||||
Assign the roles required by Prowler at the management-group scope instead of assigning them separately to every subscription. Role assignments at a management group can inherit to its child subscriptions, subject to Azure role-assignment and inheritance rules.
|
||||
|
||||
Use the [subscription scope permissions](/user-guide/providers/azure/authentication#subscription-scope-permissions) guide to identify the permissions required for scans. The identity used by Prowler must be able to read the management-group hierarchy and access each subscription selected for scanning.
|
||||
|
||||
## Subscription Scope
|
||||
|
||||
Management groups organize subscriptions, but Azure scan results remain scoped to individual subscriptions:
|
||||
|
||||
- Prowler Cloud scans one subscription per scan.
|
||||
- Prowler CLI can scan multiple subscriptions by using the `--subscription-ids` option.
|
||||
- A subscription must be accessible to the configured identity before Prowler can scan it.
|
||||
- Moving a subscription between management groups can change the permissions it inherits and may require a connection test or rediscovery.
|
||||
|
||||
See [Azure Subscription Scope](/user-guide/providers/azure/subscriptions) for subscription selection and CLI options.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Management groups do not replace subscription providers in Prowler.
|
||||
- Azure role inheritance depends on the management-group hierarchy and the scope of each assignment; verify access on every subscription selected for scanning.
|
||||
- The Prowler Cloud workflow is designed around Azure management-group discovery and subscription onboarding. The Prowler CLI workflow still requires explicit subscription selection when restricting scans.
|
||||
- Changes to management-group membership or role assignments may not be reflected until the hierarchy is refreshed and access is tested again.
|
||||
@@ -25,14 +25,4 @@ Check the [Authentication > Subscription Scope Permissions](/user-guide/provider
|
||||
|
||||
## Recommendation for Managing Multiple Subscriptions
|
||||
|
||||
Scanning multiple subscriptions requires creating and assigning roles for each, which can be a time-consuming process. To streamline subscription management and auditing, use management groups in Azure. This approach allows Prowler to efficiently organize and audit multiple subscriptions collectively.
|
||||
|
||||
1. **Create a Management Group**: Follow the [official guide](https://learn.microsoft.com/en-us/azure/governance/management-groups/create-management-group-portal) to create a new management group.
|
||||
|
||||

|
||||
|
||||
2. **Assign Roles**: Assign necessary roles to the management group, similar to the [role assignment process](#assigning-permissions-for-subscription-scans).
|
||||
|
||||
Role assignment should be done at the management group level instead of per subscription.
|
||||
|
||||
3. **Add Subscriptions**: Add all subscriptions you want to audit to the newly created management group. 
|
||||
Scanning multiple subscriptions requires creating and assigning roles for each, which can be a time-consuming process. To streamline subscription management and auditing, use [Azure Management Groups](/user-guide/providers/azure/management-groups) to organize subscriptions and assign permissions collectively.
|
||||
|
||||
@@ -4,6 +4,8 @@ title: 'Scanning a Specific GCP Organization'
|
||||
|
||||
By default, Prowler scans all Google Cloud projects accessible to the authenticated user.
|
||||
|
||||
For the cross-provider organization lifecycle and capability comparison, see [Organizations Across Cloud Providers](/user-guide/organizations).
|
||||
|
||||
To limit the scan to projects within a specific Google Cloud organization, use the `--organization-id` option with the GCP organization’s ID:
|
||||
|
||||
```console
|
||||
|
||||
@@ -42,7 +42,7 @@ Required for scanning repository security settings:
|
||||
|
||||
| Permission | Access Level | Purpose | Checks Enabled |
|
||||
|------------|-------------|---------|----------------|
|
||||
| **Administration** | Read | Branch protection, security settings | All branch protection checks, secret scanning status |
|
||||
| **Administration** | Read | Branch protection, security and Actions settings | All branch protection checks, secret scanning status, `repository_default_workflow_permissions_read_only` |
|
||||
| **Contents** | Read | File existence checks | `repository_public_has_securitymd_file`, `repository_has_codeowners_file` |
|
||||
| **Metadata** | Read | Basic repository information | All checks (automatically granted) |
|
||||
| **Dependabot alerts** | Read | Dependency vulnerability scanning | `repository_dependency_scanning_enabled` |
|
||||
@@ -63,7 +63,7 @@ Required for scanning organization-level security settings:
|
||||
|
||||
| Permission | Access Level | Purpose | Checks Enabled |
|
||||
|------------|-------------|---------|----------------|
|
||||
| **Administration** | Read | Organization security policies | `organization_members_mfa_required`, `organization_repository_creation_limited`, `organization_default_repository_permission_strict` |
|
||||
| **Administration** | Read | Organization security policies and Actions settings | `organization_members_mfa_required`, `organization_repository_creation_limited`, `organization_default_repository_permission_strict`, `organization_default_workflow_permissions_read_only`, `organization_actions_pull_request_approval_disabled` |
|
||||
| **Members** | Read | Member access reviews | Organization membership auditing |
|
||||
|
||||
#### Account Permissions (Fine-Grained PAT only)
|
||||
@@ -83,8 +83,8 @@ With the **Read-only permissions** listed above, Prowler can run:
|
||||
| Check Category | Coverage | Notes |
|
||||
|----------------|----------|-------|
|
||||
| Branch protection checks (12 checks) | ✅ Full | Signed commits, status checks, PR reviews, etc. |
|
||||
| Repository security checks | ✅ Full | Secret scanning, Dependabot, SECURITY.md, CODEOWNERS |
|
||||
| Organization checks (3 checks) | ✅ Full | MFA, repo creation policies, default permissions |
|
||||
| Repository security checks | ✅ Full | Secret scanning, Dependabot, SECURITY.md, CODEOWNERS, default workflow permissions |
|
||||
| Organization checks (5 checks) | ✅ Full | MFA, repo creation policies, default permissions, Actions workflow permissions |
|
||||
| Compliance frameworks | ✅ Full | CIS GitHub Benchmark and others |
|
||||
| Merge settings (`delete_branch_on_merge`) | ⚠️ MANUAL | Requires write permission (see below) |
|
||||
|
||||
@@ -171,6 +171,7 @@ Use OAuth App Tokens when building applications that need delegated user permiss
|
||||
|
||||
- `repo`: Full control of repositories
|
||||
- `read:org`: Read organization and team membership
|
||||
- `admin:org`: Required by `organization_default_workflow_permissions_read_only` and `organization_actions_pull_request_approval_disabled` to read the organization Actions workflow permissions
|
||||
- `read:user`: Read user profile data
|
||||
|
||||
**Create an OAuth App:**
|
||||
@@ -214,7 +215,7 @@ If a GitHub App is required:
|
||||
|
||||
| Permission | Access Level | Purpose | Checks Enabled |
|
||||
|------------|-------------|---------|----------------|
|
||||
| **Administration** | Read | Branch protection, security settings | All branch protection checks, `repository_secret_scanning_enabled` |
|
||||
| **Administration** | Read | Branch protection, security and Actions settings | All branch protection checks, `repository_secret_scanning_enabled`, `repository_default_workflow_permissions_read_only` |
|
||||
| **Contents** | Read | File existence checks | `repository_public_has_securitymd_file`, `repository_has_codeowners_file` |
|
||||
| **Metadata** | Read | Basic repository information | All checks (automatically granted) |
|
||||
| **Dependabot alerts** | Read | Dependency vulnerability scanning | `repository_dependency_scanning_enabled` |
|
||||
@@ -223,7 +224,7 @@ If a GitHub App is required:
|
||||
|
||||
| Permission | Access Level | Purpose | Checks Enabled |
|
||||
|------------|-------------|---------|----------------|
|
||||
| **Administration** | Read | Organization security policies | `organization_members_mfa_required`, `organization_repository_creation_limited`, `organization_default_repository_permission_strict` |
|
||||
| **Administration** | Read | Organization security policies and Actions settings | `organization_members_mfa_required`, `organization_repository_creation_limited`, `organization_default_repository_permission_strict`, `organization_default_workflow_permissions_read_only`, `organization_actions_pull_request_approval_disabled` |
|
||||
| **Members** | Read | Member access reviews | Organization membership auditing |
|
||||
|
||||
**Create a GitHub App:**
|
||||
|
||||
@@ -39,10 +39,12 @@ When using service principal authentication, add these **Application Permissions
|
||||
|
||||
**Microsoft Graph API Permissions:**
|
||||
|
||||
- `AccessReview.Read.All`: Required for `entra_access_review_guest_users_configured` and `entra_access_review_privileged_roles_configured` checks. A Microsoft Entra ID P2 or Microsoft Entra ID Governance license is also required.
|
||||
- `AuditLog.Read.All`: Required for Entra service.
|
||||
- `Directory.Read.All`: Required for all services.
|
||||
- `OnPremDirectorySynchronization.Read.All`: Required for `entra_seamless_sso_disabled` check (hybrid deployments).
|
||||
- `Policy.Read.All`: Required for all services.
|
||||
- `RoleManagementPolicy.Read.Directory`: Required for `entra_pim_global_administrator_approval_required` and `entra_pim_privileged_role_administrator_approval_required` checks. A Microsoft Entra ID P2 or Microsoft Entra ID Governance license is also required.
|
||||
- `SecurityIdentitiesHealth.Read.All`: Required for `defenderidentity_health_issues_no_open` check.
|
||||
- `SecurityIdentitiesSensors.Read.All`: Required for `defenderidentity_health_issues_no_open` check.
|
||||
- `SharePointTenantSettings.Read.All`: Required for SharePoint service.
|
||||
@@ -110,10 +112,12 @@ Browser and Azure CLI authentication methods limit scanning capabilities to chec
|
||||
|
||||
3. Search and select the required permissions:
|
||||
|
||||
- `AccessReview.Read.All`: Required for `entra_access_review_guest_users_configured` and `entra_access_review_privileged_roles_configured` checks. A Microsoft Entra ID P2 or Microsoft Entra ID Governance license is also required
|
||||
- `AuditLog.Read.All`: Required for Entra service
|
||||
- `Directory.Read.All`: Required for all services
|
||||
- `OnPremDirectorySynchronization.Read.All`: Required for `entra_seamless_sso_disabled` check (hybrid deployments)
|
||||
- `Policy.Read.All`: Required for all services
|
||||
- `RoleManagementPolicy.Read.Directory`: Required for `entra_pim_global_administrator_approval_required` and `entra_pim_privileged_role_administrator_approval_required` checks. A Microsoft Entra ID P2 or Microsoft Entra ID Governance license is also required
|
||||
- `SecurityIdentitiesHealth.Read.All`: Required for `defenderidentity_health_issues_no_open` check
|
||||
- `SecurityIdentitiesSensors.Read.All`: Required for `defenderidentity_health_issues_no_open` check
|
||||
- `SharePointTenantSettings.Read.All`: Required for SharePoint service
|
||||
|
||||