Compare commits

..
129 changed files with 206 additions and 4183 deletions
+1 -1
View File
@@ -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.38.0
# Social login credentials
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
+1 -1
View File
@@ -69,7 +69,7 @@ runs:
- name: Upload Grype results to GitHub Security tab
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3.37.4
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
with:
sarif_file: 'grype-results.sarif'
category: 'grype-container'
+1 -1
View File
@@ -86,7 +86,7 @@ runs:
run: pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir "uv==${UV_VERSION}"
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ inputs.python-version }}
cache: 'pip'
+1 -1
View File
@@ -88,7 +88,7 @@ runs:
- name: Upload Trivy results to GitHub Security tab
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3.37.4
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy-container'
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
raw.githubusercontent.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
+3 -3
View File
@@ -58,17 +58,17 @@ jobs:
objects.githubusercontent.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/api-codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
category: '/language:${{ matrix.language }}'
@@ -73,7 +73,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -132,7 +132,7 @@ jobs:
www.powershellgallery.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -147,18 +147,18 @@ jobs:
(cd api && uv lock --upgrade-package prowler)
- name: Login to DockerHub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push API container for ${{ matrix.arch }}
id: container-push
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ${{ env.WORKING_DIRECTORY }}
push: true
@@ -200,7 +200,7 @@ jobs:
registry-1.docker.io:443
release-assets.githubusercontent.com:443
- name: Login to DockerHub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -255,7 +255,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+5 -5
View File
@@ -40,7 +40,7 @@ jobs:
github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -53,7 +53,7 @@ jobs:
- name: Lint Dockerfile with Hadolint
if: steps.dockerfile-changed.outputs.any_changed == 'true'
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: api/Dockerfile
ignore: DL3013
@@ -100,7 +100,7 @@ jobs:
releases.astral.sh:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -133,11 +133,11 @@ jobs:
- name: Set up Docker Buildx
if: steps.check-changes.outputs.any_changed == 'true'
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build container
if: steps.check-changes.outputs.any_changed == 'true'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ${{ env.API_WORKING_DIR }}
push: false
+1 -1
View File
@@ -59,7 +59,7 @@ jobs:
osv-vulnerabilities.storage.googleapis.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
+2 -2
View File
@@ -95,7 +95,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -128,7 +128,7 @@ jobs:
- name: Upload coverage reports to Codecov
if: steps.check-changes.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
+3 -3
View File
@@ -80,7 +80,7 @@ jobs:
egress-policy: audit
- name: Checkout master
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: master
persist-credentials: false
@@ -207,7 +207,7 @@ jobs:
egress-policy: audit
- name: Checkout version branch
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: v${{ needs.detect-release-type.outputs.major_version }}.${{ needs.detect-release-type.outputs.minor_version }}
persist-credentials: false
@@ -312,7 +312,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+2 -2
View File
@@ -22,12 +22,12 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+2 -2
View File
@@ -46,11 +46,11 @@ jobs:
api.github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5
with:
token: ${{ github.token }}
+3 -3
View File
@@ -65,7 +65,7 @@ jobs:
files.pythonhosted.org:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.target_branch }}
fetch-depth: 0 # PR attribution resolves each fragment's adding commit from history
@@ -73,9 +73,9 @@ jobs:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: '3.12.13'
python-version: '3.12'
- name: Install towncrier
run: pip install --no-cache-dir towncrier==25.8.0
+2 -2
View File
@@ -48,7 +48,7 @@ jobs:
hub.docker.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -78,7 +78,7 @@ jobs:
hub.docker.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -28,14 +28,14 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: block
allowed-endpoints: >
github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+2 -2
View File
@@ -36,7 +36,7 @@ jobs:
www.formbucket.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# PRs only need the diff range; push to master/release walks the new range from event.before.
# 50 is enough headroom for the longest realistic PR/push chain without paying for a full clone.
@@ -45,6 +45,6 @@ jobs:
- name: Scan diff for secrets with TruffleHog
# Action auto-injects --since-commit/--branch from event payload; passing them in extra_args produces duplicate flags.
uses: trufflesecurity/trufflehog@6f3c981e7b77f235fd2702dd74af25fc4b72bf11 # v3.96.0
uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3.95.3
with:
extra_args: --results=verified,unknown
+2 -2
View File
@@ -38,12 +38,12 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
- name: Update chart dependencies
run: helm dependency update ${{ env.CHART_PATH }}
+2 -2
View File
@@ -31,12 +31,12 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
- name: Set chart version and appVersion from release tag
run: |
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
egress-policy: audit
- name: Apply labels to PR
uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
with:
sync-labels: true
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
release-assets.githubusercontent.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -72,7 +72,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -124,23 +124,23 @@ jobs:
registry-1.docker.io:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Login to DockerHub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push MCP container for ${{ matrix.arch }}
id: container-push
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ${{ env.WORKING_DIRECTORY }}
push: true
@@ -183,7 +183,7 @@ jobs:
release-assets.githubusercontent.com:443
- name: Login to DockerHub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -238,7 +238,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+5 -5
View File
@@ -40,7 +40,7 @@ jobs:
github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -53,7 +53,7 @@ jobs:
- name: Lint Dockerfile with Hadolint
if: steps.dockerfile-changed.outputs.any_changed == 'true'
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: mcp_server/Dockerfile
@@ -92,7 +92,7 @@ jobs:
get.anchore.io:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -114,11 +114,11 @@ jobs:
- name: Set up Docker Buildx
if: steps.check-changes.outputs.any_changed == 'true'
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build MCP container
if: steps.check-changes.outputs.any_changed == 'true'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ${{ env.MCP_WORKING_DIR }}
push: false
+4 -4
View File
@@ -72,17 +72,17 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
with:
enable-cache: false
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -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@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
packages-dir: ${{ env.WORKING_DIRECTORY }}/dist/
print-hash: true
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
osv-vulnerabilities.storage.googleapis.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
+2 -2
View File
@@ -57,7 +57,7 @@ jobs:
api.github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -92,7 +92,7 @@ jobs:
- name: Upload coverage reports to Codecov
if: steps.check-changes.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -53,15 +53,15 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build ${{ matrix.component }} container (linux/arm64)
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
+4 -4
View File
@@ -39,7 +39,7 @@ jobs:
files.pythonhosted.org:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
@@ -63,9 +63,9 @@ jobs:
- name: Set up Python
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: '3.12.13'
python-version: '3.12'
- name: Test changelog attribution
if: steps.changed-files.outputs.any_changed == 'true'
@@ -93,7 +93,7 @@ jobs:
github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
# zizmor: ignore[artipacked]
@@ -43,7 +43,7 @@ jobs:
github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
# zizmor: ignore[artipacked]
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
egress-policy: audit
- name: Checkout PR head
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 1
+2 -2
View File
@@ -41,7 +41,7 @@ jobs:
gh variable set RELEASE_FREEZE --body true --repo "${GITHUB_REPOSITORY}"
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
@@ -376,7 +376,7 @@ jobs:
no-changelog
- name: Create draft release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
tag_name: ${{ env.PROWLER_VERSION }}
name: Prowler ${{ env.PROWLER_VERSION }}
@@ -45,12 +45,12 @@ jobs:
releases.astral.sh:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
- name: Install prek
run: uv tool install "prek==${PREK_VERSION}"
@@ -32,7 +32,7 @@ jobs:
github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
files.pythonhosted.org:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
+3 -3
View File
@@ -63,17 +63,17 @@ jobs:
uploads.github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/sdk-codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
category: '/language:${{ matrix.language }}'
+12 -12
View File
@@ -70,7 +70,7 @@ jobs:
pypi.org:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -105,7 +105,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -168,18 +168,18 @@ jobs:
www.powershellgallery.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Login to DockerHub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
with:
aws-region: us-east-1
role-to-assume: ${{ secrets.PUBLIC_ECR_PUSH_ROLE_ARN }}
@@ -190,12 +190,12 @@ jobs:
registry-type: public
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push SDK container for ${{ matrix.arch }}
id: container-push
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
file: ${{ env.DOCKERFILE_PATH }}
@@ -245,13 +245,13 @@ jobs:
- name: Login to DockerHub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
with:
aws-region: us-east-1
role-to-assume: ${{ secrets.PUBLIC_ECR_PUSH_ROLE_ARN }}
@@ -290,7 +290,7 @@ jobs:
# Push to toniblyx/prowler only for current version (latest/stable/release tags)
- name: Login to DockerHub (toniblyx)
if: needs.setup.outputs.latest_tag == 'latest'
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.TONIBLYX_DOCKERHUB_USERNAME }}
password: ${{ secrets.TONIBLYX_DOCKERHUB_PASSWORD }}
@@ -315,7 +315,7 @@ jobs:
# Re-login as prowlercloud for cleanup of intermediate tags
- name: Login to DockerHub (prowlercloud)
if: always()
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -348,7 +348,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+5 -5
View File
@@ -42,7 +42,7 @@ jobs:
github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -55,7 +55,7 @@ jobs:
- name: Lint Dockerfile with Hadolint
if: steps.dockerfile-changed.outputs.any_changed == 'true'
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: Dockerfile
ignore: DL3013
@@ -102,7 +102,7 @@ jobs:
releases.astral.sh:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -128,11 +128,11 @@ jobs:
- name: Set up Docker Buildx
if: steps.check-changes.outputs.any_changed == 'true'
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build SDK container
if: steps.check-changes.outputs.any_changed == 'true'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: false
+4 -4
View File
@@ -71,7 +71,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -85,7 +85,7 @@ jobs:
run: uv build
- name: Publish Prowler package to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
print-hash: true
@@ -107,7 +107,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -129,6 +129,6 @@ jobs:
run: uv build
- name: Publish prowler-cloud package to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
print-hash: true
@@ -32,13 +32,13 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: 'master'
persist-credentials: false
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
@@ -47,7 +47,7 @@ jobs:
run: pip install boto3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
with:
aws-region: ${{ env.AWS_REGION }}
role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }}
@@ -30,13 +30,13 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: 'master'
persist-credentials: false
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
+1 -1
View File
@@ -52,7 +52,7 @@ jobs:
osv-vulnerabilities.storage.googleapis.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
+23 -23
View File
@@ -64,7 +64,7 @@ jobs:
aka.ms:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -225,7 +225,7 @@ jobs:
- name: Upload AWS coverage to Codecov
if: steps.changed-aws.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -249,7 +249,7 @@ jobs:
- name: Upload Azure coverage to Codecov
if: steps.changed-azure.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -273,7 +273,7 @@ jobs:
- name: Upload GCP coverage to Codecov
if: steps.changed-gcp.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -297,7 +297,7 @@ jobs:
- name: Upload Kubernetes coverage to Codecov
if: steps.changed-kubernetes.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -321,7 +321,7 @@ jobs:
- name: Upload GitHub coverage to Codecov
if: steps.changed-github.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -345,7 +345,7 @@ jobs:
- name: Upload Okta coverage to Codecov
if: steps.changed-okta.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -369,7 +369,7 @@ jobs:
- name: Upload NHN coverage to Codecov
if: steps.changed-nhn.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -393,7 +393,7 @@ jobs:
- name: Upload M365 coverage to Codecov
if: steps.changed-m365.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -417,7 +417,7 @@ jobs:
- name: Upload IaC coverage to Codecov
if: steps.changed-iac.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -441,7 +441,7 @@ jobs:
- name: Upload MongoDB Atlas coverage to Codecov
if: steps.changed-mongodbatlas.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -465,7 +465,7 @@ jobs:
- name: Upload OCI coverage to Codecov
if: steps.changed-oraclecloud.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -489,7 +489,7 @@ jobs:
- name: Upload OpenStack coverage to Codecov
if: steps.changed-openstack.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -513,7 +513,7 @@ jobs:
- name: Upload Google Workspace coverage to Codecov
if: steps.changed-googleworkspace.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -537,7 +537,7 @@ jobs:
- name: Upload Vercel coverage to Codecov
if: steps.changed-vercel.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -561,7 +561,7 @@ jobs:
- name: Upload Scaleway coverage to Codecov
if: steps.changed-scaleway.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -585,7 +585,7 @@ jobs:
- name: Upload StackIT coverage to Codecov
if: steps.changed-stackit.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -609,7 +609,7 @@ jobs:
- name: Upload Linode coverage to Codecov
if: steps.changed-linode.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -633,7 +633,7 @@ jobs:
- name: Upload E2E Networks coverage to Codecov
if: steps.changed-e2enetworks.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -644,7 +644,7 @@ jobs:
- name: Check if External Provider files changed
if: steps.check-changes.outputs.any_changed == 'true'
id: changed-external
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
with:
files: |
./prowler/providers/common/**
@@ -660,7 +660,7 @@ jobs:
- name: Upload External Provider coverage to Codecov
if: steps.changed-external.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -684,7 +684,7 @@ jobs:
- name: Upload Lib coverage to Codecov
if: steps.changed-lib.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
@@ -708,7 +708,7 @@ jobs:
- name: Upload Config coverage to Codecov
if: steps.changed-config.outputs.any_changed == 'true'
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
+3 -3
View File
@@ -61,7 +61,7 @@ jobs:
files.pythonhosted.org:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -71,9 +71,9 @@ jobs:
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
- name: Setup Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.14.6'
python-version: '3.12.13'
- name: Install PyYAML
run: pip install pyyaml
+3 -3
View File
@@ -59,17 +59,17 @@ jobs:
uploads.github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/ui-codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
category: '/language:${{ matrix.language }}'
@@ -72,7 +72,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -125,23 +125,23 @@ jobs:
registry.npmjs.org:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Login to DockerHub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push UI container for ${{ matrix.arch }}
id: container-push
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ${{ env.WORKING_DIRECTORY }}
build-args: |
@@ -186,7 +186,7 @@ jobs:
release-assets.githubusercontent.com:443
- name: Login to DockerHub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -241,7 +241,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+5 -5
View File
@@ -40,7 +40,7 @@ jobs:
github.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -53,7 +53,7 @@ jobs:
- name: Lint Dockerfile with Hadolint
if: steps.dockerfile-changed.outputs.any_changed == 'true'
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: ui/Dockerfile
ignore: DL3018
@@ -93,7 +93,7 @@ jobs:
get.anchore.io:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -116,11 +116,11 @@ jobs:
- name: Set up Docker Buildx
if: steps.check-changes.outputs.any_changed == 'true'
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build UI container
if: steps.check-changes.outputs.any_changed == 'true'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ${{ env.UI_WORKING_DIR }}
target: prod
+6 -6
View File
@@ -102,7 +102,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -218,7 +218,7 @@ jobs:
echo "E2E prerequisite preflight passed."
- name: Create k8s Kind Cluster
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1
with:
cluster_name: kind
@@ -290,7 +290,7 @@ jobs:
'
- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: 'ui/.nvmrc'
@@ -304,7 +304,7 @@ jobs:
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
- name: Setup pnpm and Next.js cache
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
${{ env.STORE_PATH }}
@@ -324,7 +324,7 @@ jobs:
run: pnpm run build
- name: Cache Playwright browsers
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: playwright-cache
with:
path: ~/.cache/ms-playwright
@@ -392,7 +392,7 @@ jobs:
fi
- name: Upload test reports
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: failure()
with:
name: playwright-report
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
osv-vulnerabilities.storage.googleapis.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
+4 -4
View File
@@ -50,7 +50,7 @@ jobs:
playwright.download.prss.microsoft.com:443
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# zizmor: ignore[artipacked]
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
@@ -99,7 +99,7 @@ jobs:
- name: Setup Node.js
if: steps.check-changes.outputs.any_changed == 'true'
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: 'ui/.nvmrc'
@@ -117,7 +117,7 @@ jobs:
- name: Setup pnpm and Next.js cache
if: steps.check-changes.outputs.any_changed == 'true'
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
${{ env.STORE_PATH }}
@@ -171,7 +171,7 @@ jobs:
- name: Cache Playwright browsers
if: steps.check-changes.outputs.any_changed == 'true'
id: playwright-cache
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-chromium-${{ hashFiles('ui/pnpm-lock.yaml') }}
+1 -1
View File
@@ -167,7 +167,7 @@ runs:
- name: Upload SARIF to GitHub Code Scanning
if: always() && inputs.upload-sarif == 'true' && steps.find-sarif.outputs.sarif_path != ''
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
sarif_file: ${{ steps.find-sarif.outputs.sarif_path }}
category: ${{ inputs.sarif-category }}
@@ -1 +0,0 @@
`/api/v1/accounts/saml/{organization_slug}/acs/` rejects non-POST requests before SAML response processing
@@ -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
+2 -2
View File
@@ -45,7 +45,7 @@ dependencies = [
"gunicorn==26.0.0",
"uvloop==0.22.1",
"lxml==6.1.0",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.38",
"psycopg2-binary==2.9.9",
"pytest-celery[redis] (==1.3.0)",
"sentry-sdk[django] (==2.56.0)",
@@ -71,7 +71,7 @@ name = "prowler-api"
package-mode = false
# Needed for the SDK compatibility
requires-python = ">=3.11,<3.13"
version = "1.40.0"
version = "1.39.0"
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
# target-version tracks this project's lowest supported Python.
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
version: 1.40.0
version: 1.39.0
description: |-
Prowler API specification.
@@ -1590,8 +1590,8 @@ class TestAPIKeyMultiTenantWorkflows:
tenant1 = tenants_fixture[0]
tenant2 = tenants_fixture[1]
membership1 = Membership.objects.create(user=user, tenant=tenant1)
membership2 = Membership.objects.create(user=user, tenant=tenant2)
Membership.objects.create(user=user, tenant=tenant1)
Membership.objects.create(user=user, tenant=tenant2)
role1 = Role.objects.create(
tenant_id=tenant1.id,
@@ -1646,27 +1646,6 @@ class TestAPIKeyMultiTenantWorkflows:
assert me_response1.json()["data"]["id"] == str(user.id)
assert me_response2.json()["data"]["id"] == str(user.id)
memberships1 = {
item["id"]: item["meta"]["active"]
for item in me_response1.json()["data"]["relationships"]["memberships"][
"data"
]
}
memberships2 = {
item["id"]: item["meta"]["active"]
for item in me_response2.json()["data"]["relationships"]["memberships"][
"data"
]
}
assert memberships1 == {
str(membership1.id): True,
str(membership2.id): False,
}
assert memberships2 == {
str(membership1.id): False,
str(membership2.id): True,
}
def test_api_key_cannot_access_different_tenant_resources(
self, tenants_fixture, aws_provider
):
+1 -77
View File
@@ -78,9 +78,8 @@ from conftest import (
today_after_n_days,
)
from django.conf import settings
from django.db import close_old_connections, connection, connections
from django.db import close_old_connections, connection
from django.db.models import Count
from django.db.models.signals import pre_delete
from django.http import JsonResponse
from django.test import RequestFactory
from django.test.utils import CaptureQueriesContext
@@ -520,50 +519,6 @@ class TestUserViewSet:
assert error_field in response.json()["errors"][0]["source"]["pointer"]
@pytest.mark.requires_test_admin_alias
@pytest.mark.django_db(transaction=True, databases=["default", "admin"])
class TestTenantDeletionTransactions:
@patch("api.v1.views.delete_tenant_task.apply_async")
def test_delete_rolls_back_memberships_when_user_cleanup_fails(
self,
delete_tenant_mock,
authenticated_client,
tenants_fixture,
):
assert connections["default"] is not connections["admin"]
_, tenant, _ = tenants_fixture
exclusive_user = User.objects.create_user(
name="exclusive user",
password=TEST_PASSWORD,
email="exclusive-user@example.com",
)
membership = Membership.objects.create(
user=exclusive_user,
tenant=tenant,
role=Membership.RoleChoices.MEMBER,
)
def fail_user_cleanup(*, instance, **kwargs):
if instance.pk == exclusive_user.pk:
raise RuntimeError("Simulated user cleanup failure.")
pre_delete.connect(fail_user_cleanup, sender=User)
try:
with (
patch.object(MainRouter, "admin_db", "admin"),
pytest.raises(RuntimeError, match=r"Simulated user cleanup failure\."),
):
authenticated_client.delete(
reverse("tenant-detail", kwargs={"pk": tenant.id})
)
finally:
pre_delete.disconnect(fail_user_cleanup, sender=User)
assert Membership.objects.using("admin").filter(pk=membership.pk).exists()
delete_tenant_mock.assert_not_called()
@pytest.mark.django_db
class TestTenantViewSet:
@pytest.fixture
@@ -14673,37 +14628,6 @@ class TestSAMLConfigurationViewSet:
assert not SAMLConfiguration.objects.filter(id=config.id).exists()
@pytest.mark.django_db
class TestSAMLACSView:
def test_get_is_not_allowed(self, client, saml_setup):
response = client.get(
reverse(
"saml_acs",
kwargs={"organization_slug": saml_setup["domain"]},
)
)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
assert response.headers["Allow"] == "POST"
assert "saml-acs-session" not in response.cookies
def test_post_is_forwarded_to_allauth(self, client, saml_setup):
response = client.post(
reverse(
"saml_acs",
kwargs={"organization_slug": saml_setup["domain"]},
),
data={"SAMLResponse": "test-saml-response"},
)
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse(
"saml_finish_acs",
kwargs={"organization_slug": saml_setup["domain"]},
)
assert "saml-acs-session" in response.cookies
@pytest.mark.django_db
class TestTenantFinishACSView:
def test_dispatch_skips_if_user_not_authenticated(self, monkeypatch):
-15
View File
@@ -329,15 +329,6 @@ class TokenSwitchTenantSerializer(BaseSerializerV1):
# Users
class ActiveMembershipRelatedField(SerializerMethodResourceRelatedField):
def to_representation(self, value):
representation = super().to_representation(value)
representation["meta"] = {
"active": str(value.tenant_id) == str(self.context["request"].tenant_id),
}
return representation
class UserSerializer(BaseModelSerializerV1):
"""
Serializer for the User model.
@@ -399,12 +390,6 @@ class UserSerializer(BaseModelSerializerV1):
)
class UserMeSerializer(UserSerializer):
memberships = ActiveMembershipRelatedField(
many=True, read_only=True, source="memberships", method_name="get_memberships"
)
class UserIncludeSerializer(UserSerializer):
class Meta:
model = User
+1 -2
View File
@@ -46,7 +46,6 @@ from api.v1.views import (
from django.http import JsonResponse
from django.urls import include, path
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from drf_spectacular.views import SpectacularRedocView
from rest_framework_nested import routers
@@ -195,7 +194,7 @@ urlpatterns = [
),
path(
"accounts/saml/<organization_slug>/acs/",
require_POST(ACSView.as_view()),
ACSView.as_view(),
name="saml_acs",
),
path(
+2 -5
View File
@@ -238,7 +238,6 @@ from api.v1.serializers import (
TokenSocialLoginSerializer,
TokenSwitchTenantSerializer,
UserCreateSerializer,
UserMeSerializer,
UserRoleRelationshipSerializer,
UserSerializer,
UserUpdateSerializer,
@@ -1114,8 +1113,6 @@ class UserViewSet(BaseUserViewset):
return UserCreateSerializer
elif self.action == "partial_update":
return UserUpdateSerializer
elif self.action == "me":
return UserMeSerializer
else:
return UserSerializer
@@ -1133,7 +1130,7 @@ class UserViewSet(BaseUserViewset):
@action(detail=False, methods=["get"], url_name="me")
def me(self, request):
user = self.request.user
serializer = self.get_serializer(user)
serializer = UserSerializer(user, context=self.get_serializer_context())
return Response(
data=serializer.data,
status=status.HTTP_200_OK,
@@ -1445,7 +1442,7 @@ class TenantViewSet(BaseTenantViewset):
if not membership or membership.role != Membership.RoleChoices.OWNER:
raise PermissionDenied("Only owners can delete a tenant.")
with transaction.atomic(using=MainRouter.admin_db):
with transaction.atomic():
# Collect user IDs from this tenant's memberships before deleting them
tenant_user_ids = set(
Membership.objects.using(MainRouter.admin_db)
Generated
+3 -3
View File
@@ -4836,7 +4836,7 @@ wheels = [
[[package]]
name = "prowler"
version = "5.38.0"
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#b3d174d0c1eb202ed7cb9a9daf0500683f4443be" }
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=v5.38#226504982b1d1fb9887f00f2773aafc9adf2a2a8" }
dependencies = [
{ name = "alibabacloud-actiontrail20200706" },
{ name = "alibabacloud-credentials" },
@@ -4935,7 +4935,7 @@ dependencies = [
[[package]]
name = "prowler-api"
version = "1.40.0"
version = "1.39.0"
source = { virtual = "." }
dependencies = [
{ name = "cartography" },
@@ -5035,7 +5035,7 @@ requires-dist = [
{ name = "matplotlib", specifier = "==3.10.8" },
{ name = "neo4j", specifier = "==6.1.0" },
{ name = "openai", specifier = "==1.109.1" },
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=master" },
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=v5.38" },
{ name = "psycopg2-binary", specifier = "==2.9.9" },
{ name = "pytest-celery", extras = ["redis"], specifier = "==1.3.0" },
{ name = "reportlab", specifier = "==4.4.10" },
-75
View File
@@ -4,81 +4,6 @@ description: "New features and improvements in each Prowler release"
rss: true
---
<Update label="v5.38.0" description="August 6, 2026">
### 📌 Compliance Watchlist
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
Compliance Watchlist keeps the frameworks an organization tracks in one shared list. Pin frameworks from any compliance view, manage several at once through a searchable catalog, and filter the Compliance section to show only the pinned frameworks.
The Overview page now reports the latest score for every pinned framework, while finding details highlight the watched frameworks associated with each check. Universal frameworks remain a single watchlist entry across provider views, keeping the organization's priorities consistent everywhere.
![Compliance Watchlist editor](/images/compliance/prowler-app-compliance-watchlist-editor.png)
Read more in the [Compliance Watchlist documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance#tracking-frameworks-with-the-compliance-watchlist).
### 🔐 SAML SSO - Multiple Email Domains
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
One SAML configuration can now authorize a primary email domain and up to 19 additional domains through the same Identity Provider. Every domain shares one stable Assertion Consumer Service (ACS) URL based on the primary domain, so subsidiaries, acquired companies, regional domains, and multiple brands no longer require separate tenants or duplicated SAML applications.
Domain ownership remains tenant-bound throughout the authentication flow. During service provider-initiated sign-in, the discovery domain and the domain asserted by the Identity Provider must resolve to the same tenant before provisioning continues.
![SAML configuration with multiple email domains](/images/prowler-app/saml/saml-multiple-domains.png)
Read more in the [SAML SSO documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-sso#add-multiple-saml-domains).
### 👥 User Sign-In Methods
<Note>
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
</Note>
The Users table now shows each account's sign-in methods as tags, including email/password, Google, GitHub, SAML with linked domains, and Partner SSO. Accounts without a reported method display a placeholder.
![Users table showing sign-in method tags](/images/changelog/v5.38.0-user-sign-in-methods.png)
### 🕸️ Attack Paths - Expanded AWS Privilege-Escalation Coverage
Attack Paths adds 20 AWS privilege-escalation queries from [pathfinding.cloud](https://pathfinding.cloud), while `iam_policy_allows_privilege_escalation` gains 22 additional escalation combinations.
The new coverage includes service `iam:PassRole` paths across AWS Batch, Braket, Cognito Identity, ECS, EMR, EMR Serverless, GameLift, Glue, EC2 Image Builder, Kinesis Analytics, HealthOmics, EventBridge Scheduler, Systems Manager, and Step Functions. It also covers existing-resource abuse, permissions-boundary removal, role assumption, and IAM Identity Center permission-set policy injection.
The query catalog now exposes each AWS query's outcome category, distinguishing code execution, privilege escalation, public exposure, and resource inventory.
Explore the full Attack Paths query catalog at [Prowler Hub](https://hub.prowler.com/attack-paths).
Read more in the [Attack Paths documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths).
### 🔍 Checks
#### Microsoft 365
Twelve new checks expand the coverage of CIS Microsoft 365 Foundations Benchmark v7.0.0:
- **Admin Center:** Shared Bookings is disabled.
- **Defender:** Priority account protection and strict preset security policies are enabled.
- **Entra ID:** Six checks cover device registration restrictions, local administrator behavior, device limits, LAPS, and BitLocker key visibility.
- **Exchange Online:** Personal accounts in Outlook on the web are disabled and Direct Send is rejected.
- **Microsoft Teams:** External access from trial-only tenants is blocked.
Explore all Microsoft 365 checks at [Prowler Hub](https://hub.prowler.com/check?provider=m365).
### 🔐 Security
- Prowler API, UI, SDK, and MCP container images now publish per-architecture Software Bills of Materials (SBOMs) and build-provenance attestations. Prowler Cloud production and Prowler Private Cloud images carry the same attestations.
- SDK and API container builds verify the checksums of downloaded PowerShell, Trivy, and zizmor binaries before installation.
- Grype now complements Trivy across the container-image security gates, detecting components and vulnerabilities that manifest-based scanners can miss and blocking fixable high and critical findings.
- `aiohttp` was upgraded to 3.14.3 to address CVE-2026-69244. `cryptography` was upgraded to 50.0.0 to address CVE-2026-69247 and CVE-2026-69249.
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.38.0) for the complete list of changes.
</Update>
<Update label="v5.37.0" description="August 3, 2026">
### 💬 Lighthouse AI — Context-Aware Chat and a Bigger Toolbox
@@ -128,8 +128,8 @@ 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.37.0"
PROWLER_API_VERSION="5.37.0"
```
<Note>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

@@ -37,7 +37,6 @@
"glue:SearchTables",
"glue:GetMLTransforms",
"lambda:GetFunction*",
"lambda:GetLayerVersion",
"logs:FilterLogEvents",
"lightsail:GetRelationalDatabases",
"macie2:GetMacieSession",
@@ -1 +0,0 @@
`awslambda_layer_no_secrets_in_content` check for AWS provider, scanning Lambda layer package content for hardcoded secrets
@@ -1 +0,0 @@
`batch_job_definition_no_secrets` check for AWS provider, scanning Batch job definition environment variables and command parameters for hardcoded secrets
@@ -1 +0,0 @@
7 M365 Entra checks covering CIS Microsoft 365 Foundations Benchmark v7.0.0 password protection, default user permissions, and guest invitation domain restrictions
+7 -21
View File
@@ -1166,9 +1166,7 @@
{
"Id": "5.1.3.1",
"Description": "This setting allows users in the organization to create new security groups and add members to these groups in the Azure portal, API, or PowerShell. These new groups also show up in the Access Panel for all other users. If the policy setting on the group allows it, other users can create requests to join these groups. The recommended state is Users can create security groups in Azure portals, API or PowerShell set to No.",
"Checks": [
"entra_policy_default_user_cannot_create_security_groups"
],
"Checks": [],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
@@ -1231,9 +1229,7 @@
{
"Id": "5.1.3.4",
"Description": "All users within a Microsoft Entra organization are permitted to create new Microsoft 365 groups and add members to those groups through the Azure portal, API, or PowerShell. Newly created groups also appear in the Access Panel for all other users. When the applicable group policy settings allow it, users can submit requests to join these groups. The recommended state is No.",
"Checks": [
"entra_policy_default_user_cannot_create_m365_groups"
],
"Checks": [],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
@@ -1530,9 +1526,7 @@
{
"Id": "5.1.6.1",
"Description": "B2B collaboration is a feature within Microsoft Entra External ID that allows for guest invitations to an organization. Ensure users can only send invitations to specified domains. Note: This list works independently from OneDrive for Business and SharePoint Online allow/block lists. To restrict individual file sharing in SharePoint Online, set up an allow or blocklist for OneDrive for Business and SharePoint Online. For instance, in SharePoint or OneDrive users can still share with external users from prohibited domains by using Anyone links if they haven't been disabled.",
"Checks": [
"entra_policy_guest_invitations_restricted_to_allowed_domains"
],
"Checks": [],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
@@ -2022,9 +2016,7 @@
{
"Id": "5.2.3.2",
"Description": "With Entra Password Protection, default global banned password lists are automatically applied to all users in an Entra ID tenant. To support business and security needs, custom banned password lists can be defined. When users change or reset their passwords, these banned password lists are checked to enforce the use of strong passwords. A custom banned password list should include some of the following examples: - Brand names - Product names - Locations, such as company headquarters - Company-specific internal terms - Abbreviations that have specific company meaning",
"Checks": [
"entra_password_protection_custom_banned_list_enforced"
],
"Checks": [],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
@@ -2045,9 +2037,7 @@
{
"Id": "5.2.3.3",
"Description": "Microsoft Entra Password Protection provides a global and custom banned password list. A password change request fails if there's a match in these banned password list. To protect on-premises Active Directory Domain Services (AD DS) environment, install and configure Entra Password Protection. Note: This recommendation applies to Hybrid deployments only and will have no impact unless working with on-premises Active Directory.",
"Checks": [
"entra_password_protection_on_premises_enforced"
],
"Checks": [],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
@@ -2156,9 +2146,7 @@
{
"Id": "5.2.3.8",
"Description": "The account lockout threshold determines how many failed login attempts are permitted prior to placing the account in a locked-out state and initiating a variable lockout duration. The recommended Lockout threshold is 10 or less.",
"Checks": [
"entra_password_protection_lockout_threshold_limited"
],
"Checks": [],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
@@ -2179,9 +2167,7 @@
{
"Id": "5.2.3.9",
"Description": "The account lockout duration value determines how long an account retains the status of lockout, and therefore how long before a user can continue to attempt to login after passing the lockout threshold. The recommended state is Lockout duration in seconds is at least 60.",
"Checks": [
"entra_password_protection_lockout_duration_configured"
],
"Checks": [],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
+1 -1
View File
@@ -49,7 +49,7 @@ class _MutableTimestamp:
timestamp = _MutableTimestamp(datetime.today())
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
prowler_version = "5.39.0"
prowler_version = "5.38.0"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://raw.githubusercontent.com/prowler-cloud/prowler/dc7d2d5aeb92fdf12e8604f42ef6472cd3e8e889/docs/img/prowler-logo-black.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
-2
View File
@@ -27,8 +27,6 @@ aws:
max_lambda_functions: null
# aws.max_ecs_task_definitions --> ecs_task_definitions_* checks
max_ecs_task_definitions: null
# aws.max_batch_job_definitions --> batch_job_definition_* checks
max_batch_job_definitions: null
# aws.max_codeartifact_packages --> codeartifact_packages_* checks
max_codeartifact_packages: null
# aws.disallowed_regions --> List of AWS regions to exclude from the scan.
-6
View File
@@ -153,12 +153,6 @@ class AWSProviderConfig(ProviderConfigBase):
le=1_000_000,
description="Resource scan limit for ECS task definitions. Use 0 or -1 to disable.",
)
max_batch_job_definitions: ResourceScanLimit = Field(
default=None,
ge=-1,
le=1_000_000,
description="Resource scan limit for Batch job definitions. Use 0 or -1 to disable.",
)
max_codeartifact_packages: ResourceScanLimit = Field(
default=None,
ge=-1,
@@ -1,41 +0,0 @@
{
"Provider": "aws",
"CheckID": "awslambda_layer_no_secrets_in_content",
"CheckTitle": "Lambda layer content contains no hardcoded secrets",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices",
"Sensitive Data Identifications/Passwords",
"Effects/Data Exposure"
],
"ServiceName": "awslambda",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AwsLambdaLayerVersion",
"ResourceGroup": "serverless",
"Description": "**Lambda layer content** is analyzed for **embedded secrets** across files in the layer's package, detecting patterns like API keys, passwords, tokens, and connection strings. Findings reference file names and line numbers where potential secrets appear.",
"Risk": "**Hardcoded secrets** undermine confidentiality and integrity: a secret baked into a layer is pulled into every function that uses it, and is not covered by a function-code-only scan. If exposed, attackers can reuse credentials to access databases, APIs, or cloud resources, enabling data exfiltration and unauthorized changes. Rotation is harder, increasing dwell time and blast radius.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html",
"https://docs.aws.amazon.com/secretsmanager/latest/userguide/best-practices.html"
],
"Remediation": {
"Code": {
"CLI": "aws secretsmanager create-secret --name <secret-name> --secret-string <value>\naws iam put-role-policy --role-name <function-execution-role> --policy-name allow-get-secret --policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"secretsmanager:GetSecretValue\",\"Resource\":\"<secret-arn>\"}]}'\n# Remove the hardcoded value from the layer's code, then:\naws lambda publish-layer-version --layer-name <layer-name> --zip-file fileb://layer.zip",
"NativeIaC": "",
"Other": "1. In AWS Secrets Manager, click Store a new secret and create a secret for the value you hardcoded. Note the secret name/ARN.\n2. In IAM > Roles, open the execution role of every function that uses this layer and add an inline policy allowing secretsmanager:GetSecretValue on that secret only.\n3. Remove the hardcoded value from the layer's code and repackage it, retrieving the secret at runtime using the AWS SDK (GetSecretValue) with the secret name/ARN.\n4. Publish a new layer version and update dependent functions to use it.",
"Terraform": ""
},
"Recommendation": {
"Text": "Use **AWS Secrets Manager** (or Parameter Store) to store secrets and retrieve at runtime; never put them in layer code or packaged dependencies.\n- Apply **least privilege** IAM\n- Enable **rotation**\n- Prevent secret logging; encrypt\n- Add CI/CD secret scanning",
"Url": "https://hub.prowler.com/check/awslambda_layer_no_secrets_in_content"
}
},
"Categories": [
"secrets"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -1,164 +0,0 @@
import fnmatch
import os
import tempfile
from collections import defaultdict
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.logger import logger
from prowler.lib.utils.utils import (
SecretsScanError,
annotate_verified_secrets,
detect_secrets_scan_batch,
)
from prowler.providers.aws.services.awslambda.awslambda_client import awslambda_client
class awslambda_layer_no_secrets_in_content(Check):
"""Check if Lambda layer content contains hardcoded secrets.
Scans every file inside each Lambda layer version's package with the
secret scanner.
- PASS: No secrets are detected in the layer content.
- FAIL: At least one potential secret is detected in the layer content.
- MANUAL: The layer content could not be fetched or scanned.
"""
def execute(self) -> list[Check_Report_AWS]:
"""Execute the Lambda layer secrets scan.
Returns:
list[Check_Report_AWS]: One report per Lambda layer version used by
the audited functions, or an empty list when there are no layers.
"""
findings = []
if not awslambda_client.layers:
return findings
secrets_ignore_patterns = awslambda_client.audit_config.get(
"secrets_ignore_patterns", []
)
# Glob patterns of file names inside the layer package to skip
# when scanning for secrets (e.g. "*.deps.json" for .NET layers).
secrets_ignore_files = (
awslambda_client.audit_config.get("secrets_ignore_files", []) or []
)
validate = awslambda_client.audit_config.get("secrets_validate", False)
# Scan files of every layer version's package in batched
# Kingfisher invocations instead of one subprocess per file per layer.
# Each package is extracted one at a time and its files are
# read (byte-faithfully via latin-1) before the extraction is released,
# so only a single package is on disk at a time. Findings are keyed by
# (layer index, package-relative file name) so they can be grouped
# back per layer.
layers_with_code = []
def code_payloads():
for layer, layer_code in awslambda_client._get_layers_code():
if not layer_code:
continue
with tempfile.TemporaryDirectory() as tmp_dir_name:
try:
layer_code.code_zip.extractall(tmp_dir_name)
except Exception as error:
# A corrupt or truncated package must not abort the
# scan of the remaining layers: keep this layer out of
# layers_with_code so it is reported as MANUAL below.
logger.error(
f"{layer.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
index = len(layers_with_code)
layers_with_code.append(layer)
for root, _, files in os.walk(tmp_dir_name):
for file_name in files:
file_path = os.path.join(root, file_name)
relative_file_path = os.path.relpath(
file_path, tmp_dir_name
)
if any(
fnmatch.fnmatch(relative_file_path, pattern)
for pattern in secrets_ignore_files
):
continue
try:
with open(file_path, "rb") as code_file:
content = code_file.read().decode("latin-1")
except Exception:
continue
yield (index, relative_file_path), content
scan_error = None
try:
batch_results = detect_secrets_scan_batch(
code_payloads(),
excluded_secrets=secrets_ignore_patterns,
validate=validate,
)
except SecretsScanError as error:
batch_results = {}
scan_error = error
if scan_error:
# The scan failed before any layer's code could be cleared. Report
# MANUAL for every layer rather than risk a false PASS.
for layer in awslambda_client.layers.values():
report = Check_Report_AWS(metadata=self.metadata(), resource=layer)
report.status = "MANUAL"
report.status_extended = (
f"Could not scan Lambda layer {layer.name} (version "
f"{layer.version}) content for secrets: {scan_error}; "
"manual review is required."
)
findings.append(report)
return findings
findings_by_layer = defaultdict(dict)
for (index, file_name), file_findings in batch_results.items():
findings_by_layer[index][file_name] = file_findings
for index, layer in enumerate(layers_with_code):
report = Check_Report_AWS(metadata=self.metadata(), resource=layer)
report.status = "PASS"
report.status_extended = (
f"No secrets found in Lambda layer {layer.name} "
f"(version {layer.version}) content."
)
files_with_secrets = findings_by_layer.get(index)
if files_with_secrets:
all_secrets = []
secrets_findings = []
for file_name, file_findings in files_with_secrets.items():
all_secrets.extend(file_findings)
secrets_string = ", ".join(
f"{secret['type']} on line {secret['line_number']}"
for secret in file_findings
)
secrets_findings.append(f"{file_name}: {secrets_string}")
final_output_string = "; ".join(secrets_findings)
report.status = "FAIL"
report.status_extended = f"Potential {'secrets' if len(secrets_findings) > 1 else 'secret'} found in Lambda layer {layer.name} (version {layer.version}) content -> {final_output_string}."
annotate_verified_secrets(report, all_secrets)
findings.append(report)
# Layers whose content could not be fetched (network error, missing
# permissions, etc.) never reach layers_with_code above, so report
# them as MANUAL rather than silently omitting them from the scan.
fetched_arns = {layer.arn for layer in layers_with_code}
for layer in awslambda_client.layers.values():
if layer.arn in fetched_arns:
continue
report = Check_Report_AWS(metadata=self.metadata(), resource=layer)
report.status = "MANUAL"
report.status_extended = (
f"Could not retrieve content of Lambda layer {layer.name} "
f"(version {layer.version}) to scan for secrets; manual "
"review is required."
)
findings.append(report)
return findings
@@ -17,11 +17,6 @@ from prowler.lib.resource_limit import (
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService
# Presigned code/layer download URLs are short-lived S3 URLs, not AWS API
# calls, so a hung request here would otherwise block a worker thread
# indefinitely instead of failing like the surrounding boto3 calls do.
CODE_DOWNLOAD_TIMEOUT_SECONDS = 30
class Lambda(AWSService):
def __init__(self, provider):
@@ -30,7 +25,6 @@ class Lambda(AWSService):
# Functions are listed first, then trimmed to the subset selected for
# analysis before expensive per-function detail is hydrated.
self.functions = {}
self.layers = {}
self.security_groups_in_use = set()
self.regions_with_functions = set()
self.function_limit = get_resource_scan_limit(
@@ -38,7 +32,6 @@ class Lambda(AWSService):
)
self.__threading_call__(self._list_functions)
self._select_functions_for_analysis()
self._collect_layers()
self._list_tags_for_resource()
self.__threading_call__(self._get_policy)
self.__threading_call__(self._get_function_url_config)
@@ -113,11 +106,6 @@ class Lambda(AWSService):
)
}
def _collect_layers(self):
for function in self.functions.values():
for layer in function.layers:
self.layers.setdefault(layer.arn, layer)
def _list_event_source_mappings(self, regional_client):
logger.info("Lambda - Listing Event Source Mappings...")
try:
@@ -205,15 +193,6 @@ class Lambda(AWSService):
f"{function.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _download_code(self, code_location_uri):
raw_code_zip = requests.get(
code_location_uri, timeout=CODE_DOWNLOAD_TIMEOUT_SECONDS
).content
return LambdaCode(
location=code_location_uri,
code_zip=zipfile.ZipFile(io.BytesIO(raw_code_zip)),
)
def _fetch_function_code(self, function_name, function_region):
try:
regional_client = self.regional_clients[function_region]
@@ -221,52 +200,18 @@ class Lambda(AWSService):
FunctionName=function_name
)
if "Location" in function_information["Code"]:
return self._download_code(function_information["Code"]["Location"])
code_location_uri = function_information["Code"]["Location"]
raw_code_zip = requests.get(code_location_uri).content
return LambdaCode(
location=code_location_uri,
code_zip=zipfile.ZipFile(io.BytesIO(raw_code_zip)),
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
raise
def _get_layers_code(self):
logger.info("Lambda - Getting Layer Code...")
# Use a thread pool to handle the queueing and execution of the
# _fetch_layer_code tasks, up to max_workers tasks concurrently.
layers_to_fetch = {
self.thread_pool.submit(
self._fetch_layer_code, layer.arn, layer.region
): layer
for layer in self.layers.values()
}
for fetched_layer_code in as_completed(layers_to_fetch):
layer = layers_to_fetch[fetched_layer_code]
try:
layer_code = fetched_layer_code.result()
if layer_code:
yield layer, layer_code
except Exception as error:
logger.error(
f"{layer.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _fetch_layer_code(self, layer_arn, layer_region):
try:
regional_client = self.regional_clients[layer_region]
# Fetch by the full layer-version ARN: layers attached to a
# function may be owned by another account (e.g. vendor or
# AWS-provided layers), where a bare layer name would resolve
# against the audited account instead.
layer_version = regional_client.get_layer_version_by_arn(Arn=layer_arn)
if "Location" in (layer_version.get("Content") or {}):
return self._download_code(layer_version["Content"]["Location"])
return None
except Exception as error:
logger.error(
f"{layer_region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
raise
def _get_policy(self, regional_client):
logger.info("Lambda - Getting Policy...")
try:
@@ -363,28 +308,6 @@ class Layer(BaseModel):
parts = self.arn.split(":")
return parts[4] if len(parts) >= 5 else ""
@property
def region(self) -> str:
"""Extract the region from the layer ARN.
A layer can only be attached to a function in the same region, so
this is always one of the regions already being audited.
"""
parts = self.arn.split(":")
return parts[3] if len(parts) >= 4 else ""
@property
def name(self) -> str:
"""Extract the layer name from the ARN."""
parts = self.arn.split(":")
return parts[6] if len(parts) >= 7 else self.arn
@property
def version(self) -> str:
"""Extract the layer version from the ARN."""
parts = self.arn.split(":")
return parts[7] if len(parts) >= 8 else ""
class DeadLetterConfig(BaseModel):
target_arn: str
@@ -1,6 +0,0 @@
"""AWS Batch service client singleton."""
from prowler.providers.aws.services.batch.batch_service import Batch
from prowler.providers.common.provider import Provider
batch_client = Batch(Provider.get_global_provider())
@@ -1,41 +0,0 @@
{
"Provider": "aws",
"CheckID": "batch_job_definition_no_secrets",
"CheckTitle": "AWS Batch job definitions have no secrets in environment variables or command parameters",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices",
"Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices",
"Sensitive Data Identifications/Passwords",
"TTPs/Credential Access"
],
"ServiceName": "batch",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AwsBatchJobDefinition",
"ResourceGroup": "container",
"Description": "**AWS Batch job definitions** are analyzed for **plaintext secrets** placed in container `environment` variables and `command` parameters. It identifies values that resemble credentials (keys, tokens, passwords) within job definitions.",
"Risk": "Exposed secrets in env vars or command parameters undermine confidentiality via logs, job metadata, and introspection.\n\nWith container or read-only API access, attackers can reuse credentials to read databases, modify records (integrity), pivot to other services, and trigger outages or unauthorized costs (availability).",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/batch/latest/userguide/job_definition_parameters.html"
],
"Remediation": {
"Code": {
"CLI": "aws batch register-job-definition --job-definition-name <JOB_DEFINITION_NAME> --type container --container-properties '{\"image\":\"<IMAGE>\",\"secrets\":[{\"name\":\"<SECRET_NAME>\",\"valueFrom\":\"arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:<SECRET_NAME>-<RANDOM>\"}]}' # Register a new revision without plaintext secrets; reference Secrets Manager or SSM Parameter Store via valueFrom",
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::Batch::JobDefinition\n Properties:\n Type: container\n ContainerProperties:\n Image: <image>\n Environment:\n - Name: DB_PASSWORD\n Value: !Ref <secret_parameter> # Reference SSM Parameter or Secrets Manager\n```",
"Other": "1. In the AWS Console, go to Batch > Job Definitions and open your job definition\n2. Create a new revision\n3. Remove any sensitive values from Environment variables and command parameters\n4. Reference secrets from AWS Secrets Manager or SSM Parameter Store instead\n5. Save to create the new revision\n6. Update any Batch job queues to use the new job definition revision",
"Terraform": "```hcl\nresource \"aws_batch_job_definition\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n type = \"container\"\n\n container_properties = jsonencode({\n image = \"<image>\"\n environment = [\n {\n name = \"DB_PASSWORD\"\n value = var.db_password # Use variable from Secrets Manager or SSM\n }\n ]\n })\n}\n```"
},
"Recommendation": {
"Text": "Store secrets in **AWS Secrets Manager** or **SSM Parameter Store** and inject them at runtime instead of plaintext env vars.\n\nApply **least privilege** via job role, enable regular **rotation**, avoid logging secret values, and prefer **ephemeral credentials** for downstream services.",
"Url": "https://hub.prowler.com/check/batch_job_definition_no_secrets"
}
},
"Categories": [
"secrets"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Only container job definitions are evaluated (containerProperties.environment and command). Multi-node parallel (nodeProperties) and EKS (eksProperties) job definitions are not analyzed."
}
@@ -1,120 +0,0 @@
from json import dumps
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.utils.utils import (
SecretsScanError,
annotate_verified_secrets,
detect_secrets_scan_batch,
)
from prowler.providers.aws.services.batch.batch_client import batch_client
class batch_job_definition_no_secrets(Check):
"""Detect secrets in AWS Batch job definition environment variables and commands."""
def execute(self) -> list[Check_Report_AWS]:
"""Scan job definitions for hardcoded secrets in env vars and commands."""
findings = []
secrets_ignore_patterns = batch_client.audit_config.get(
"secrets_ignore_patterns", []
)
validate = batch_client.audit_config.get("secrets_validate", False)
job_definitions = list(batch_client.job_definitions.values())
def scan_payloads():
"""Yield index-keyed payloads for each env var and the command."""
for jd_index, job_definition in enumerate(job_definitions):
container = job_definition.container_properties
for env_index, env_var in enumerate(container.environment):
yield (jd_index, env_index), dumps(
{env_var.name: env_var.value}, indent=2
)
if container.command:
yield (
(jd_index, "command"),
" ".join(container.command),
)
scan_error = None
try:
batch_results = detect_secrets_scan_batch(
scan_payloads(),
excluded_secrets=secrets_ignore_patterns,
validate=validate,
)
except SecretsScanError as error:
batch_results = {}
scan_error = error
for jd_index, job_definition in enumerate(job_definitions):
report = Check_Report_AWS(
metadata=self.metadata(),
resource=job_definition,
)
report.resource_id = f"{job_definition.name}:{job_definition.revision}"
report.status = "PASS"
extended_status_parts = []
all_secrets = []
container = job_definition.container_properties
if scan_error and (container.environment or container.command):
report.status = "MANUAL"
report.status_extended = (
f"Could not scan Batch job definition "
f"{job_definition.name} with revision "
f"{job_definition.revision} for secrets: "
f"{scan_error}; manual review is required."
)
findings.append(report)
continue
for env_index, env_var in enumerate(container.environment):
env_secrets = batch_results.get((jd_index, env_index))
if env_secrets:
all_secrets.extend(env_secrets)
secrets_string = ", ".join(
f"{secret['type']} on the environment variable {env_var.name}"
for secret in env_secrets
)
extended_status_parts.append(
f"Secrets in environment variables -> {secrets_string}"
)
if container.command:
command_secrets = batch_results.get((jd_index, "command"))
if command_secrets:
all_secrets.extend(command_secrets)
secrets_string = ", ".join(
secret["type"] for secret in command_secrets
)
extended_status_parts.append(
f"Secrets in command -> {secrets_string}"
)
if extended_status_parts:
report.status = "FAIL"
report.status_extended = (
f"Potential secrets found in Batch job definition "
f"{job_definition.name} with revision "
f"{job_definition.revision}: "
+ "; ".join(extended_status_parts)
+ "."
)
annotate_verified_secrets(report, all_secrets)
else:
report.status_extended = (
f"No secrets found in Batch job definition "
f"{job_definition.name} with revision "
f"{job_definition.revision}."
)
findings.append(report)
return findings
@@ -1,105 +0,0 @@
from itertools import zip_longest
from typing import Optional
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
from prowler.lib.resource_limit import get_resource_scan_limit, limit_resources
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService
class ContainerEnvVariable(BaseModel):
"""An environment variable name-value pair."""
name: str
value: str
class BatchContainerProperties(BaseModel):
"""Container properties for an AWS Batch job definition."""
image: Optional[str]
command: list[str] = []
environment: list[ContainerEnvVariable] = []
class BatchJobDefinition(BaseModel):
"""An AWS Batch job definition with its container properties."""
name: str
arn: str
revision: int
region: str
container_properties: BatchContainerProperties
class Batch(AWSService):
"""AWS Batch service client for listing job definitions."""
def __init__(self, provider):
super().__init__(__class__.__name__, provider)
self.job_definitions = {}
self._job_definitions_by_region = {}
self.job_definition_limit = get_resource_scan_limit(
self.audit_config, "max_batch_job_definitions"
)
self.__threading_call__(self._list_job_definitions)
self._select_job_definitions_for_analysis()
def _list_job_definitions(self, regional_client):
"""List ACTIVE job definitions for a regional client."""
logger.info("Batch - Listing Job Definitions...")
try:
paginator = regional_client.get_paginator("describe_job_definitions")
regional_job_definitions = []
# Deregistered (INACTIVE) revisions are excluded: they cannot run
# new jobs, and reporting them would only produce noise.
for page in paginator.paginate(status="ACTIVE"):
for job in page.get("jobDefinitions", []):
if self.audit_resources and not is_resource_filtered(
job["jobDefinitionArn"], self.audit_resources
):
continue
container = job.get("containerProperties", {})
environment = [
ContainerEnvVariable(
name=env["name"], value=env.get("value", "")
)
for env in container.get("environment", [])
]
regional_job_definitions.append(
BatchJobDefinition(
name=job["jobDefinitionName"],
arn=job["jobDefinitionArn"],
revision=job["revision"],
region=regional_client.region,
container_properties=BatchContainerProperties(
image=container.get("image"),
command=container.get("command", []),
environment=environment,
),
)
)
self._job_definitions_by_region[regional_client.region] = (
regional_job_definitions
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _select_job_definitions_for_analysis(self):
"""Apply the global resource limit, interleaving regions fairly."""
interleaved = [
job_definition
for region_batch in zip_longest(*self._job_definitions_by_region.values())
for job_definition in region_batch
if job_definition
]
self.job_definitions = {
job_definition.arn: job_definition
for job_definition in limit_resources(
interleaved, self.job_definition_limit
)
}
@@ -1,37 +0,0 @@
{
"Provider": "m365",
"CheckID": "entra_password_protection_custom_banned_list_enforced",
"CheckTitle": "Entra custom banned password list is enforced",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "The tenant Password Rule Settings should enforce a **custom banned password list** (**EnableBannedPasswordCheck** true with a non-empty **BannedPasswordList**). This blocks organization-specific weak or predictable passwords (e.g., company name, products, locations) in addition to Microsoft's global banned list.",
"Risk": "Without a custom **banned password** list, users can choose passwords that are predictable for the specific organization (brand names, local terms), which are easy targets for **password spraying** and guessing attacks.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Enforce custom list** to **Yes**\n4. Add organization-specific terms to the **Custom banned password list**\n5. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable the custom banned password list and populate it with terms relevant to the organization to strengthen protection against weak passwords beyond the global banned list.",
"Url": "https://hub.prowler.com/check/entra_password_protection_custom_banned_list_enforced"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -1,63 +0,0 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
from prowler.providers.m365.services.entra.entra_service import (
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
class entra_password_protection_custom_banned_list_enforced(Check):
"""Check if the Entra custom banned password list is enforced.
The Password Rule Settings directory setting should enforce a custom banned
password list (EnableBannedPasswordCheck) with a non-empty BannedPasswordList so
that organization-specific weak passwords are rejected in addition to the global
banned list.
- PASS: The custom banned password list is enforced and non-empty.
- FAIL: The custom banned password list is not enforced or is empty.
"""
def execute(self) -> List[CheckReportM365]:
"""Execute the custom banned password list enforcement check.
Evaluate whether the Password Rule Settings directory setting enforces a
non-empty custom banned password list. When the settings object is absent,
no finding is produced.
Returns:
List[CheckReportM365]: A list with a single report when the Password Rule
Settings exist, or an empty list when they are absent.
"""
findings = []
settings = entra_client.directory_settings.get(
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
)
if not settings:
return findings
report = CheckReportM365(
metadata=self.metadata(),
resource=settings or {},
resource_name="Password Rule Settings",
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
report.status = "FAIL"
report.status_extended = (
"The custom banned password list is not enforced in the tenant."
)
if settings:
enforced = (
str(settings.get("EnableBannedPasswordCheck", "")).lower() == "true"
)
banned_list = settings.get("BannedPasswordList", "") or ""
if enforced and banned_list.strip():
report.status = "PASS"
report.status_extended = (
"The custom banned password list is enforced in the tenant."
)
findings.append(report)
return findings
@@ -1,37 +0,0 @@
{
"Provider": "m365",
"CheckID": "entra_password_protection_lockout_duration_configured",
"CheckTitle": "Smart lockout duration is set to 60 seconds or more",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "The tenant Password Rule Settings should set the smart **LockoutDurationInSeconds** to **60 or more**. The lockout duration determines how long an account remains locked out before the user can attempt to sign in again.",
"Risk": "A short lockout duration allows attackers to resume **brute-force** or **password-spray** attempts sooner, reducing the effectiveness of **smart lockout** as a throttling control.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/authentication/howto-password-smart-lockout"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Lockout duration in seconds** to **60** or higher\n4. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Set the smart lockout duration to 60 seconds or more so locked-out accounts remain locked long enough to throttle automated password attacks.",
"Url": "https://hub.prowler.com/check/entra_password_protection_lockout_duration_configured"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -1,62 +0,0 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
from prowler.providers.m365.services.entra.entra_service import (
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
# CIS recommends a lockout duration of 60 seconds or more.
MIN_LOCKOUT_DURATION_SECONDS = 60
class entra_password_protection_lockout_duration_configured(Check):
"""Check if the smart lockout duration is set to 60 seconds or more.
The Password Rule Settings directory setting should set LockoutDurationInSeconds
to 60 or more so a locked-out account remains locked long enough to slow down
automated attacks.
- PASS: The lockout duration is 60 seconds or more.
- FAIL: The lockout duration is less than 60 seconds or not configured.
"""
def execute(self) -> List[CheckReportM365]:
"""Execute the smart lockout duration check.
Returns:
List[CheckReportM365]: Reports for the Password Rule Settings, or an
empty list when the settings are absent.
"""
findings = []
settings = entra_client.directory_settings.get(
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
)
if not settings:
return findings
report = CheckReportM365(
metadata=self.metadata(),
resource=settings or {},
resource_name="Password Rule Settings",
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
report.status = "FAIL"
report.status_extended = (
"The smart lockout duration is not set to 60 seconds or more."
)
if settings:
try:
duration = int(settings.get("LockoutDurationInSeconds"))
except (TypeError, ValueError):
duration = None
if duration is not None and duration >= MIN_LOCKOUT_DURATION_SECONDS:
report.status = "PASS"
report.status_extended = (
f"The smart lockout duration is set to {duration} seconds, at or "
f"above the recommended minimum of {MIN_LOCKOUT_DURATION_SECONDS}."
)
findings.append(report)
return findings
@@ -1,37 +0,0 @@
{
"Provider": "m365",
"CheckID": "entra_password_protection_lockout_threshold_limited",
"CheckTitle": "Smart lockout threshold is set to 10 or less",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "The tenant Password Rule Settings should set the smart **LockoutThreshold** to **10 or less**. The lockout threshold determines how many failed sign-in attempts are permitted before an account is placed in a locked-out state.",
"Risk": "A high lockout threshold gives attackers more attempts per account during **password spraying** and **brute-force** attacks before lockout is triggered, increasing the chance of a successful credential compromise.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/authentication/howto-password-smart-lockout"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Lockout threshold** to **10** or less\n4. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Set the smart lockout threshold to 10 or less so accounts lock after a small number of failed sign-in attempts, limiting brute-force and password-spray attacks.",
"Url": "https://hub.prowler.com/check/entra_password_protection_lockout_threshold_limited"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -1,63 +0,0 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
from prowler.providers.m365.services.entra.entra_service import (
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
# CIS recommends a smart lockout threshold of 10 or less.
MAX_LOCKOUT_THRESHOLD = 10
class entra_password_protection_lockout_threshold_limited(Check):
"""Check if the smart lockout threshold is set to 10 or less.
The Password Rule Settings directory setting should set LockoutThreshold to 10 or
less so that accounts are locked after a small number of failed sign-in attempts.
- PASS: The lockout threshold is 10 or less.
- FAIL: The lockout threshold is greater than 10 or not configured.
"""
def execute(self) -> List[CheckReportM365]:
"""Execute the smart lockout threshold check.
Evaluate whether the Password Rule Settings directory setting limits the smart
lockout threshold to the recommended maximum. When the settings object is
absent, no finding is produced.
Returns:
List[CheckReportM365]: A list with a single report when the Password Rule
Settings exist, or an empty list when they are absent.
"""
findings = []
settings = entra_client.directory_settings.get(
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
)
if not settings:
return findings
report = CheckReportM365(
metadata=self.metadata(),
resource=settings or {},
resource_name="Password Rule Settings",
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
report.status = "FAIL"
report.status_extended = "The smart lockout threshold is not set to 10 or less."
if settings:
try:
threshold = int(settings.get("LockoutThreshold"))
except (TypeError, ValueError):
threshold = None
if threshold is not None and threshold <= MAX_LOCKOUT_THRESHOLD:
report.status = "PASS"
report.status_extended = (
f"The smart lockout threshold is set to {threshold}, within the "
f"recommended limit of {MAX_LOCKOUT_THRESHOLD}."
)
findings.append(report)
return findings
@@ -1,37 +0,0 @@
{
"Provider": "m365",
"CheckID": "entra_password_protection_on_premises_enforced",
"CheckTitle": "Entra password protection is enforced on on-premises Active Directory",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "The tenant Password Rule Settings should enable **password protection on Windows Server Active Directory** (**EnableBannedPasswordCheckOnPremises**) with the mode set to **Enforced**. This extends Entra banned-password checks to on-premises password changes in hybrid environments. This control only applies to tenants with on-premises directory synchronization.",
"Risk": "Without **on-premises** enforcement, users in hybrid environments can set weak or banned passwords directly in Active Directory, bypassing Entra password protection and weakening the organization's overall password posture.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad-on-premises"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Enable password protection on Windows Server Active Directory** to **Yes**\n4. Set **Mode** to **Enforced**\n5. Click **Save** (requires the Entra Password Protection agents deployed on-premises)",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable on-premises Entra password protection in Enforced mode and deploy the password protection proxy and DC agents so banned-password rules apply to on-premises password changes.",
"Url": "https://hub.prowler.com/check/entra_password_protection_on_premises_enforced"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -1,75 +0,0 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
from prowler.providers.m365.services.entra.entra_service import (
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
class entra_password_protection_on_premises_enforced(Check):
"""Check if Entra password protection is enforced on on-premises Active Directory.
The Password Rule Settings directory setting should enable password protection on
Windows Server Active Directory (EnableBannedPasswordCheckOnPremises) with the
mode set to Enforced, so banned-password rules apply to hybrid on-premises
password changes.
This check applies only to hybrid tenants with on-premises synchronization.
- PASS: On-premises password protection is enabled and set to Enforced.
- FAIL: On-premises password protection is disabled or set to Audit only.
"""
def execute(self) -> List[CheckReportM365]:
"""Execute the on-premises password protection enforcement check.
Evaluate whether the Password Rule Settings directory setting enables and
enforces banned-password protection for on-premises Active Directory. When the
settings object is absent or the tenant is confirmed cloud-only, no finding is
produced.
Returns:
List[CheckReportM365]: A list with a single report when the Password Rule
Settings exist for a hybrid or unknown tenant, or an empty list when they
are absent or the tenant is confirmed cloud-only.
"""
findings = []
organizations = entra_client.organizations or []
if organizations and not any(
organization.on_premises_sync_enabled for organization in organizations
):
return findings
settings = entra_client.directory_settings.get(
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
)
if not settings:
return findings
report = CheckReportM365(
metadata=self.metadata(),
resource=settings or {},
resource_name="Password Rule Settings",
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
report.status = "FAIL"
report.status_extended = (
"On-premises password protection is not enforced in the tenant."
)
if settings:
enabled = (
str(settings.get("EnableBannedPasswordCheckOnPremises", "")).lower()
== "true"
)
mode = str(settings.get("BannedPasswordCheckOnPremisesMode", "")).lower()
if enabled and mode == "enforced":
report.status = "PASS"
report.status_extended = (
"On-premises password protection is enabled and enforced in the "
"tenant."
)
findings.append(report)
return findings
@@ -1,37 +0,0 @@
{
"Provider": "m365",
"CheckID": "entra_policy_default_user_cannot_create_m365_groups",
"CheckTitle": "Non-admin users cannot create Microsoft 365 groups",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "The tenant Group.Unified directory setting should have **EnableGroupCreation** set to false so that non-admin users cannot create Microsoft 365 groups through the portal, API, or PowerShell. Microsoft 365 group creation should be delegated to a controlled set of users.",
"Risk": "When any user can create Microsoft 365 groups, they can provision associated resources (SharePoint sites, Teams, mailboxes) without oversight, leading to group sprawl, ungoverned data locations, and a larger attack surface.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/microsoft-365/solutions/manage-creation-of-groups"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Identity** > **Groups** > **General**\n3. Set **Users can create Microsoft 365 groups in Azure portals, API or PowerShell** to **No**\n4. Optionally grant creation rights to a specific security group\n5. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Disable self-service Microsoft 365 group creation for non-admin users and delegate creation to an approved security group as needed.",
"Url": "https://hub.prowler.com/check/entra_policy_default_user_cannot_create_m365_groups"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -1,55 +0,0 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
from prowler.providers.m365.services.entra.entra_service import (
GROUP_UNIFIED_SETTINGS_TEMPLATE_ID,
)
class entra_policy_default_user_cannot_create_m365_groups(Check):
"""Check if default users are restricted from creating Microsoft 365 groups.
The Group.Unified directory setting should have EnableGroupCreation set to false
so that non-admin users cannot create Microsoft 365 groups. If the setting does
not exist, the tenant uses the default, which allows all users to create groups.
- PASS: Non-admin users cannot create Microsoft 365 groups.
- FAIL: Non-admin users are allowed to create Microsoft 365 groups.
"""
def execute(self) -> List[CheckReportM365]:
"""Evaluate whether default users can create Microsoft 365 groups.
Inspects the Group.Unified directory setting to determine whether non-admin
users are allowed to create Microsoft 365 groups. When the setting is absent
the tenant default (group creation allowed) applies.
Returns:
List[CheckReportM365]: A single report indicating whether non-admin users
are restricted from creating Microsoft 365 groups.
"""
findings = []
settings = entra_client.directory_settings.get(
GROUP_UNIFIED_SETTINGS_TEMPLATE_ID
)
report = CheckReportM365(
metadata=self.metadata(),
resource=settings or {},
resource_name="Group.Unified Settings",
resource_id=GROUP_UNIFIED_SETTINGS_TEMPLATE_ID,
)
report.status = "FAIL"
report.status_extended = (
"Non-admin users are allowed to create Microsoft 365 groups."
)
if settings and str(settings.get("EnableGroupCreation", "")).lower() == "false":
report.status = "PASS"
report.status_extended = (
"Non-admin users are not allowed to create Microsoft 365 groups."
)
findings.append(report)
return findings
@@ -1,37 +0,0 @@
{
"Provider": "m365",
"CheckID": "entra_policy_default_user_cannot_create_security_groups",
"CheckTitle": "Non-admin users cannot create security groups",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "Microsoft Entra tenant's authorization policy should restrict **non-admin users** from creating **security groups**. Security groups can be used to grant access to resources across Microsoft 365, so their creation should be limited to administrators to preserve least privilege and prevent uncontrolled access grants.",
"Risk": "When any user can create security groups, they may grant themselves or others access to resources, circumventing governance controls. Uncontrolled group sprawl also complicates access reviews and increases the attack surface for privilege escalation.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/users/groups-self-service-management"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Identity** > **Groups** > **All groups** > **General**\n3. Under **Security groups**, set **Users can create security groups in Azure portals, API or PowerShell** to **No**\n4. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Restrict security group creation to administrators by disabling self-service security group creation for non-admin users. Grant group-creation rights only to specific roles or delegated owners as required.",
"Url": "https://hub.prowler.com/check/entra_policy_default_user_cannot_create_security_groups"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -1,49 +0,0 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
class entra_policy_default_user_cannot_create_security_groups(Check):
"""Check if default users are restricted from creating security groups.
This check verifies whether the authorization policy prevents non-admin users
from creating security groups in Microsoft Entra ID.
- PASS: Non-admin users cannot create security groups.
- FAIL: Non-admin users are allowed to create security groups.
"""
def execute(self) -> List[CheckReportM365]:
"""Execute the check for security group creation restrictions.
This method examines the authorization policy settings to determine if
non-admin users are allowed to create security groups. If security group
creation is restricted, the check passes.
Returns:
List[CheckReportM365]: A list containing the result of the check.
"""
findings = []
auth_policy = entra_client.authorization_policy
report = CheckReportM365(
metadata=self.metadata(),
resource=auth_policy if auth_policy else {},
resource_name=auth_policy.name if auth_policy else "Authorization Policy",
resource_id=auth_policy.id if auth_policy else "authorizationPolicy",
)
report.status = "FAIL"
report.status_extended = (
"Non-admin users are allowed to create security groups."
)
permissions = getattr(auth_policy, "default_user_role_permissions", None)
if permissions and permissions.allowed_to_create_security_groups is False:
report.status = "PASS"
report.status_extended = (
"Non-admin users are not allowed to create security groups."
)
findings.append(report)
return findings

Some files were not shown because too many files have changed in this diff Show More