From 191d51675ca25bfd589124c79fd5cb48a6a49198 Mon Sep 17 00:00:00 2001 From: Sergio Garcia Date: Wed, 5 Nov 2025 08:59:55 -0500 Subject: [PATCH 01/26] chore(ui): rename OCI provider to oraclecloud (#9166) --- .../(prowler)/new-overview/components/accounts-selector.tsx | 2 +- .../new-overview/components/provider-type-selector.tsx | 2 +- ui/components/filters/custom-select-provider.tsx | 2 +- .../overview/provider-overview/provider-overview.tsx | 4 ++-- ui/components/providers/enhanced-provider-selector.tsx | 2 +- ui/components/providers/radio-group-provider.tsx | 2 +- .../providers/workflow/forms/base-credentials-form.tsx | 2 +- .../providers/workflow/forms/connect-account-form.tsx | 2 +- ui/components/ui/entities/get-provider-logo.tsx | 4 ++-- ui/hooks/use-credentials-form.ts | 2 +- ui/lib/external-urls.ts | 4 ++-- ui/lib/provider-credentials/build-crendentials.ts | 2 +- ui/types/formSchemas.ts | 4 ++-- ui/types/providers.ts | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ui/app/(prowler)/new-overview/components/accounts-selector.tsx b/ui/app/(prowler)/new-overview/components/accounts-selector.tsx index 2d20ace068..210e1f6f6d 100644 --- a/ui/app/(prowler)/new-overview/components/accounts-selector.tsx +++ b/ui/app/(prowler)/new-overview/components/accounts-selector.tsx @@ -30,7 +30,7 @@ const PROVIDER_ICON: Record = { m365: , github: , iac: , - oci: , + oraclecloud: , }; interface AccountsSelectorProps { diff --git a/ui/app/(prowler)/new-overview/components/provider-type-selector.tsx b/ui/app/(prowler)/new-overview/components/provider-type-selector.tsx index 8329a13282..a4c3da7b14 100644 --- a/ui/app/(prowler)/new-overview/components/provider-type-selector.tsx +++ b/ui/app/(prowler)/new-overview/components/provider-type-selector.tsx @@ -91,7 +91,7 @@ const PROVIDER_DATA: Record< label: "Infrastructure as Code", icon: IacProviderBadge, }, - oci: { + oraclecloud: { label: "Oracle Cloud Infrastructure", icon: OracleCloudProviderBadge, }, diff --git a/ui/components/filters/custom-select-provider.tsx b/ui/components/filters/custom-select-provider.tsx index e2958110ab..5d0e4db52e 100644 --- a/ui/components/filters/custom-select-provider.tsx +++ b/ui/components/filters/custom-select-provider.tsx @@ -49,7 +49,7 @@ const providerDisplayData: Record< label: "Infrastructure as Code", component: , }, - oci: { + oraclecloud: { label: "Oracle Cloud Infrastructure", component: , }, diff --git a/ui/components/overview/provider-overview/provider-overview.tsx b/ui/components/overview/provider-overview/provider-overview.tsx index 03dbeb744c..e40dc25494 100644 --- a/ui/components/overview/provider-overview/provider-overview.tsx +++ b/ui/components/overview/provider-overview/provider-overview.tsx @@ -41,7 +41,7 @@ export const ProvidersOverview = ({ return ; case "iac": return ; - case "oci": + case "oraclecloud": return ; default: return null; @@ -56,7 +56,7 @@ export const ProvidersOverview = ({ kubernetes: "Kubernetes", github: "GitHub", iac: "IaC", - oci: "OCI", + oraclecloud: "OCI", }; const providers = PROVIDER_TYPES.map((providerType) => ({ diff --git a/ui/components/providers/enhanced-provider-selector.tsx b/ui/components/providers/enhanced-provider-selector.tsx index cd1453c3bf..3282ede810 100644 --- a/ui/components/providers/enhanced-provider-selector.tsx +++ b/ui/components/providers/enhanced-provider-selector.tsx @@ -18,7 +18,7 @@ const providerTypeLabels: Record = { kubernetes: "Kubernetes", github: "GitHub", iac: "Infrastructure as Code", - oci: "Oracle Cloud Infrastructure", + oraclecloud: "Oracle Cloud Infrastructure", }; interface EnhancedProviderSelectorProps { diff --git a/ui/components/providers/radio-group-provider.tsx b/ui/components/providers/radio-group-provider.tsx index c507823593..3b04cba457 100644 --- a/ui/components/providers/radio-group-provider.tsx +++ b/ui/components/providers/radio-group-provider.tsx @@ -88,7 +88,7 @@ export const RadioGroupProvider: React.FC = ({
diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx index 670c8b0bae..d7835386c9 100644 --- a/ui/components/providers/workflow/forms/base-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx @@ -167,7 +167,7 @@ export const BaseCredentialsForm = ({ control={form.control as unknown as Control} /> )} - {providerType === "oci" && ( + {providerType === "oraclecloud" && ( } /> diff --git a/ui/components/providers/workflow/forms/connect-account-form.tsx b/ui/components/providers/workflow/forms/connect-account-form.tsx index 5fbdb48692..f0ede535bf 100644 --- a/ui/components/providers/workflow/forms/connect-account-form.tsx +++ b/ui/components/providers/workflow/forms/connect-account-form.tsx @@ -56,7 +56,7 @@ const getProviderFieldDetails = (providerType?: ProviderType) => { label: "Repository URL", placeholder: "e.g. https://github.com/user/repo", }; - case "oci": + case "oraclecloud": return { label: "Tenancy OCID", placeholder: "e.g. ocid1.tenancy.oc1..aaaaaaa...", diff --git a/ui/components/ui/entities/get-provider-logo.tsx b/ui/components/ui/entities/get-provider-logo.tsx index 590688729b..918dd30c6c 100644 --- a/ui/components/ui/entities/get-provider-logo.tsx +++ b/ui/components/ui/entities/get-provider-logo.tsx @@ -28,7 +28,7 @@ export const getProviderLogo = (provider: ProviderType) => { return ; case "iac": return ; - case "oci": + case "oraclecloud": return ; default: return null; @@ -51,7 +51,7 @@ export const getProviderName = (provider: ProviderType): string => { return "GitHub"; case "iac": return "Infrastructure as Code"; - case "oci": + case "oraclecloud": return "Oracle Cloud Infrastructure"; default: return "Unknown Provider"; diff --git a/ui/hooks/use-credentials-form.ts b/ui/hooks/use-credentials-form.ts index 01b7c60fef..a9a6215ba3 100644 --- a/ui/hooks/use-credentials-form.ts +++ b/ui/hooks/use-credentials-form.ts @@ -157,7 +157,7 @@ export const useCredentialsForm = ({ }; } return baseDefaults; - case "oci": + case "oraclecloud": return { ...baseDefaults, [ProviderCredentialFields.OCI_USER]: "", diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts index d59d5dd729..9a7385ecce 100644 --- a/ui/lib/external-urls.ts +++ b/ui/lib/external-urls.ts @@ -37,10 +37,10 @@ export const getProviderHelpText = (provider: string) => { text: "Need help scanning your Infrastructure as Code repository?", link: "https://goto.prowler.com/provider-iac", }; - case "oci": + case "oraclecloud": return { text: "Need help connecting your Oracle Cloud account?", - link: "https://goto.prowler.com/provider-oci", + link: "https://goto.prowler.com/provider-oraclecloud", }; default: return { diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts index 5435296d7b..4972b5a7e0 100644 --- a/ui/lib/provider-credentials/build-crendentials.ts +++ b/ui/lib/provider-credentials/build-crendentials.ts @@ -304,7 +304,7 @@ export const buildSecretConfig = ( secretType: "static", secret: buildIacSecret(formData), }), - oci: () => ({ + oraclecloud: () => ({ secretType: "static", secret: buildOracleCloudSecret(formData, providerUid), }), diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index d5cca88218..a0d073d3a4 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -116,7 +116,7 @@ export const addProviderFormSchema = z providerUid: z.string(), }), z.object({ - providerType: z.literal("oci"), + providerType: z.literal("oraclecloud"), [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), providerUid: z.string(), }), @@ -210,7 +210,7 @@ export const addCredentialsFormSchema = ( .string() .optional(), } - : providerType === "oci" + : providerType === "oraclecloud" ? { [ProviderCredentialFields.OCI_USER]: z .string() diff --git a/ui/types/providers.ts b/ui/types/providers.ts index 9b2de65eb2..adbc717ccd 100644 --- a/ui/types/providers.ts +++ b/ui/types/providers.ts @@ -6,7 +6,7 @@ export const PROVIDER_TYPES = [ "m365", "github", "iac", - "oci", + "oraclecloud", ] as const; export type ProviderType = (typeof PROVIDER_TYPES)[number]; From b25ed9fd27921b9f0c535dd7eb3247263af83d91 Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:57:41 +0100 Subject: [PATCH 02/26] feat(github): add external resource link (#9153) Co-authored-by: alejandrobailo --- tests/providers/iac/iac_fixtures.py | 20 ++ ui/CHANGELOG.md | 1 + .../findings/table/finding-detail.tsx | 56 +++++- .../resources/table/resource-detail.tsx | 35 +++- ui/components/ui/custom/custom-section.tsx | 8 +- .../ui/entities/copy-link-button.tsx | 41 ---- ui/components/ui/entities/index.ts | 1 - ui/lib/iac-utils.ts | 188 ++++++++++++++++++ 8 files changed, 295 insertions(+), 55 deletions(-) delete mode 100644 ui/components/ui/entities/copy-link-button.tsx create mode 100644 ui/lib/iac-utils.ts diff --git a/tests/providers/iac/iac_fixtures.py b/tests/providers/iac/iac_fixtures.py index 9e4c1105f4..2e769a732a 100644 --- a/tests/providers/iac/iac_fixtures.py +++ b/tests/providers/iac/iac_fixtures.py @@ -17,6 +17,10 @@ SAMPLE_TRIVY_OUTPUT = { "Severity": "LOW", "PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0001", "RuleID": "AVD-AWS-0001", + "CauseMetadata": { + "StartLine": 10, + "EndLine": 15, + }, }, { "ID": "AVD-AWS-0002", @@ -27,6 +31,10 @@ SAMPLE_TRIVY_OUTPUT = { "Severity": "LOW", "PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0002", "RuleID": "AVD-AWS-0002", + "CauseMetadata": { + "StartLine": 20, + "EndLine": 25, + }, }, ], "Vulnerabilities": [], @@ -46,6 +54,10 @@ SAMPLE_TRIVY_OUTPUT = { "Severity": "LOW", "PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0003", "RuleID": "AVD-AWS-0003", + "CauseMetadata": { + "StartLine": 30, + "EndLine": 35, + }, } ], "Vulnerabilities": [], @@ -67,6 +79,10 @@ SAMPLE_FAILED_CHECK = { "Severity": "low", "PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0001", "RuleID": "AVD-AWS-0001", + "CauseMetadata": { + "StartLine": 10, + "EndLine": 15, + }, } SAMPLE_PASSED_CHECK = { @@ -78,6 +94,10 @@ SAMPLE_PASSED_CHECK = { "Severity": "low", "PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0003", "RuleID": "AVD-AWS-0003", + "CauseMetadata": { + "StartLine": 30, + "EndLine": 35, + }, } # Additional sample checks diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index e81eef512f..1e4ef29e0a 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the **Prowler UI** are documented in this file. - RSS feeds support [(#9109)](https://github.com/prowler-cloud/prowler/pull/9109) - Customer Support menu item [(#9143)](https://github.com/prowler-cloud/prowler/pull/9143) - IaC (Infrastructure as Code) provider support for scanning remote repositories [(#8751)](https://github.com/prowler-cloud/prowler/pull/8751) +- External resource link to IaC findings for direct navigation to source code in Git repositories [(#9151)](https://github.com/prowler-cloud/prowler/pull/9151) ### 🔄 Changed diff --git a/ui/components/findings/table/finding-detail.tsx b/ui/components/findings/table/finding-detail.tsx index 432f3a622a..61f7408f3d 100644 --- a/ui/components/findings/table/finding-detail.tsx +++ b/ui/components/findings/table/finding-detail.tsx @@ -1,18 +1,17 @@ "use client"; import { Snippet } from "@heroui/snippet"; +import { Tooltip } from "@heroui/tooltip"; +import { ExternalLink, Link } from "lucide-react"; import ReactMarkdown from "react-markdown"; import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; import { CustomSection } from "@/components/ui/custom"; import { CustomLink } from "@/components/ui/custom/custom-link"; -import { - CopyLinkButton, - EntityInfoShort, - InfoField, -} from "@/components/ui/entities"; +import { EntityInfoShort, InfoField } from "@/components/ui/entities"; import { DateWithTime } from "@/components/ui/entities/date-with-time"; import { SeverityBadge } from "@/components/ui/table/severity-badge"; +import { buildGitFileUrl, extractLineRangeFromUid } from "@/lib/iac-utils"; import { FindingProps, ProviderType } from "@/types"; import { Muted } from "../muted"; @@ -60,14 +59,32 @@ export const FindingDetail = ({ params.set("id", findingDetails.id); const url = `${window.location.origin}${currentUrl.pathname}?${params.toString()}`; + // Build Git URL for IaC findings + const gitUrl = + providerDetails.provider === "iac" + ? buildGitFileUrl( + providerDetails.uid, + resource.name, + extractLineRangeFromUid(attributes.uid) || "", + ) + : null; + return (
{/* Header */}
-

+

{renderValue(attributes.check_metadata.checktitle)} - + + +

@@ -238,7 +255,30 @@ export const FindingDetail = ({ {/* Resource Details */} - + + Resource Details + {gitUrl && ( + + + + + + )} + + ) : ( + "Resource Details" + ) + } + >
{renderValue(resource.name)} diff --git a/ui/components/resources/table/resource-detail.tsx b/ui/components/resources/table/resource-detail.tsx index 010cb5447f..e8e118cb91 100644 --- a/ui/components/resources/table/resource-detail.tsx +++ b/ui/components/resources/table/resource-detail.tsx @@ -2,7 +2,8 @@ import { Snippet } from "@heroui/snippet"; import { Spinner } from "@heroui/spinner"; -import { InfoIcon } from "lucide-react"; +import { Tooltip } from "@heroui/tooltip"; +import { ExternalLink, InfoIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { getFindingById } from "@/actions/findings"; @@ -17,6 +18,7 @@ import { } from "@/components/ui/entities"; import { SeverityBadge, StatusFindingBadge } from "@/components/ui/table"; import { createDict } from "@/lib"; +import { buildGitFileUrl } from "@/lib/iac-utils"; import { FindingProps, ProviderType, ResourceProps } from "@/types"; const renderValue = (value: string | null | undefined) => { @@ -162,6 +164,12 @@ export const ResourceDetail = ({ const providerData = resource.relationships.provider.data.attributes; const allFindings = findingsData; + // Build Git URL for IaC resources + const gitUrl = + providerData.provider === "iac" + ? buildGitFileUrl(providerData.uid, attributes.name, "") + : null; + if (selectedFindingId) { const findingTitle = findingDetails?.attributes?.check_metadata?.checktitle || @@ -186,7 +194,30 @@ export const ResourceDetail = ({ return (
{/* Resource Details section */} - + + Resource Details + {gitUrl && ( + + + + + + )} + + ) : ( + "Resource Details" + ) + } + >
diff --git a/ui/components/ui/custom/custom-section.tsx b/ui/components/ui/custom/custom-section.tsx index f7967e0ba1..197bac35c5 100644 --- a/ui/components/ui/custom/custom-section.tsx +++ b/ui/components/ui/custom/custom-section.tsx @@ -1,7 +1,9 @@ +import { type ReactNode } from "react"; + interface CustomSectionProps { - title: string; - children: React.ReactNode; - action?: React.ReactNode; + title: string | ReactNode; + children: ReactNode; + action?: ReactNode; } export const CustomSection = ({ diff --git a/ui/components/ui/entities/copy-link-button.tsx b/ui/components/ui/entities/copy-link-button.tsx deleted file mode 100644 index 1e3d5cdf8b..0000000000 --- a/ui/components/ui/entities/copy-link-button.tsx +++ /dev/null @@ -1,41 +0,0 @@ -"use client"; - -import { Tooltip } from "@heroui/tooltip"; -import { CheckCheck, ExternalLink } from "lucide-react"; -import { useState } from "react"; - -type CopyLinkButtonProps = { - url: string; -}; - -export const CopyLinkButton = ({ url }: CopyLinkButtonProps) => { - const [copied, setCopied] = useState(false); - - const handleCopy = async () => { - try { - await navigator.clipboard.writeText(url); - setCopied(true); - - setTimeout(() => setCopied(false), 500); - } catch (err) { - console.error("Failed to copy URL to clipboard:", err); - } - }; - - return ( - - - - ); -}; diff --git a/ui/components/ui/entities/index.ts b/ui/components/ui/entities/index.ts index bcff63efd9..fb13c99282 100644 --- a/ui/components/ui/entities/index.ts +++ b/ui/components/ui/entities/index.ts @@ -1,4 +1,3 @@ -export * from "./copy-link-button"; export * from "./date-with-time"; export * from "./entity-info-short"; export * from "./get-provider-logo"; diff --git a/ui/lib/iac-utils.ts b/ui/lib/iac-utils.ts new file mode 100644 index 0000000000..ea9ffa47d8 --- /dev/null +++ b/ui/lib/iac-utils.ts @@ -0,0 +1,188 @@ +/** + * Extracts line range from a Finding UID + * Finding UID format: {CheckID}-{resource_name}-{line_range} + * Example: "AVD-AWS-0001-main.tf-10:15" -> "10:15" + * + * @param findingUid - The finding UID + * @returns Line range string or null if not found + */ +export function extractLineRangeFromUid(findingUid: string): string | null { + if (!findingUid) { + return null; + } + + // Split by dash and get the last part (line range) + const parts = findingUid.split("-"); + const lastPart = parts[parts.length - 1]; + + // Check if the last part is a line range in format "number:number" + // This ensures we don't confuse numeric filenames with line ranges + if (/^\d+:\d+$/.test(lastPart)) { + return lastPart; + } + + return null; +} + +/** + * Builds a Git repository URL with file path and line numbers + * Supports GitHub, GitLab, Bitbucket, and generic Git URLs + * + * @param repoUrl - Repository URL (can be HTTPS or git@ format) + * @param filePath - Path to the file in the repository + * @param lineRange - Line range in format "10-15" or "10:15" or "10" + * @returns Complete URL to the file with line numbers, or null if URL cannot be built + */ +export function buildGitFileUrl( + repoUrl: string, + filePath: string, + lineRange: string, +): string | null { + if (!repoUrl || !filePath) { + return null; + } + + try { + // Normalize the repository URL + let normalizedUrl = repoUrl.trim(); + + // Convert git@ format to HTTPS (best effort) + if (normalizedUrl.startsWith("git@")) { + // git@github.com:user/repo.git -> https://github.com/user/repo + normalizedUrl = normalizedUrl + .replace(/^git@/, "https://") + .replace(/\.git$/, "") + .replace(/:([^:]+)$/, "/$1"); // Replace last : with / + } + + // Remove .git suffix if present + normalizedUrl = normalizedUrl.replace(/\.git$/, ""); + + // Parse URL to determine provider + const url = new URL(normalizedUrl); + const hostname = url.hostname.toLowerCase(); + + // Clean up file path (remove leading slashes) + const cleanFilePath = filePath.replace(/^\/+/, ""); + + // Parse line range + const { startLine, endLine } = parseLineRange(lineRange); + + // Build URL based on Git provider + if (hostname.includes("github")) { + return buildGitHubUrl(normalizedUrl, cleanFilePath, startLine, endLine); + } else if (hostname.includes("gitlab")) { + return buildGitLabUrl(normalizedUrl, cleanFilePath, startLine, endLine); + } else if (hostname.includes("bitbucket")) { + return buildBitbucketUrl( + normalizedUrl, + cleanFilePath, + startLine, + endLine, + ); + } else { + // Generic Git provider - try GitHub format as fallback + return buildGitHubUrl(normalizedUrl, cleanFilePath, startLine, endLine); + } + } catch (error) { + console.error("Error building Git file URL:", error); + return null; + } +} + +/** + * Parses line range string into start and end line numbers + */ +function parseLineRange(lineRange: string): { + startLine: number | null; + endLine: number | null; +} { + if (!lineRange || lineRange === "file") { + return { startLine: null, endLine: null }; + } + + // Handle formats: "10-15", "10:15", "10" + // Safe regex: anchored pattern for line numbers only (no ReDoS risk) + // eslint-disable-next-line security/detect-unsafe-regex + const match = lineRange.match(/^(\d+)[-:]?(\d+)?$/); + if (match) { + const startLine = parseInt(match[1], 10); + const endLine = match[2] ? parseInt(match[2], 10) : startLine; + return { startLine, endLine }; + } + + return { startLine: null, endLine: null }; +} + +/** + * Builds GitHub-style URL + * Format: https://github.com/user/repo/blob/main/path/file.tf#L10-L15 + */ +function buildGitHubUrl( + baseUrl: string, + filePath: string, + startLine: number | null, + endLine: number | null, +): string { + // Assume main/master branch for simplicity + const branch = "main"; + let url = `${baseUrl}/blob/${branch}/${filePath}`; + + if (startLine !== null) { + if (endLine !== null && endLine !== startLine) { + url += `#L${startLine}-L${endLine}`; + } else { + url += `#L${startLine}`; + } + } + + return url; +} + +/** + * Builds GitLab-style URL + * Format: https://gitlab.com/user/repo/-/blob/main/path/file.tf#L10-15 + */ +function buildGitLabUrl( + baseUrl: string, + filePath: string, + startLine: number | null, + endLine: number | null, +): string { + const branch = "main"; + let url = `${baseUrl}/-/blob/${branch}/${filePath}`; + + if (startLine !== null) { + if (endLine !== null && endLine !== startLine) { + url += `#L${startLine}-${endLine}`; + } else { + url += `#L${startLine}`; + } + } + + return url; +} + +/** + * Builds Bitbucket-style URL + * Format: https://bitbucket.org/user/repo/src/main/path/file.tf#lines-10:15 + */ +function buildBitbucketUrl( + baseUrl: string, + filePath: string, + startLine: number | null, + endLine: number | null, +): string { + const branch = "main"; + let url = `${baseUrl}/src/${branch}/${filePath}`; + + if (startLine !== null) { + if (endLine !== null && endLine !== startLine) { + url += `#lines-${startLine}:${endLine}`; + } else { + url += `#lines-${startLine}`; + } + } + + return url; +} From 2f61c88f744a072b468df0b03bd4775702a5ef87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:33:33 +0100 Subject: [PATCH 03/26] chore(github): improve container slack notifications (#9144) --- .github/actions/slack-notification/action.yml | 14 ++++++-------- .../container-release-completed.json | 15 ++++++++++++++- .../slack-messages/container-release-started.json | 15 ++++++++++++++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/.github/actions/slack-notification/action.yml b/.github/actions/slack-notification/action.yml index d073af9d53..1427fed3e0 100644 --- a/.github/actions/slack-notification/action.yml +++ b/.github/actions/slack-notification/action.yml @@ -12,7 +12,7 @@ inputs: required: false default: '' step-outcome: - description: 'Outcome of a step to determine status (success/failure) - automatically sets STATUS_EMOJI, STATUS_TEXT, and STATUS_COLOR env vars' + description: 'Outcome of a step to determine status (success/failure) - automatically sets STATUS_TEXT and STATUS_COLOR env vars' required: false default: '' outputs: @@ -27,16 +27,14 @@ runs: shell: bash run: | if [[ "${{ inputs.step-outcome }}" == "success" ]]; then - echo "STATUS_EMOJI=[✓]" >> $GITHUB_ENV - echo "STATUS_TEXT=succeeded" >> $GITHUB_ENV - echo "STATUS_COLOR=6aa84f" >> $GITHUB_ENV + echo "STATUS_TEXT=Completed" >> $GITHUB_ENV + echo "STATUS_COLOR=#6aa84f" >> $GITHUB_ENV elif [[ "${{ inputs.step-outcome }}" == "failure" ]]; then - echo "STATUS_EMOJI=[✗]" >> $GITHUB_ENV - echo "STATUS_TEXT=failed" >> $GITHUB_ENV - echo "STATUS_COLOR=fc3434" >> $GITHUB_ENV + echo "STATUS_TEXT=Failed" >> $GITHUB_ENV + echo "STATUS_COLOR=#fc3434" >> $GITHUB_ENV else # No outcome provided - pending/in progress state - echo "STATUS_COLOR=dbab09" >> $GITHUB_ENV + echo "STATUS_COLOR=#dbab09" >> $GITHUB_ENV fi - name: Send Slack notification (new message) diff --git a/.github/scripts/slack-messages/container-release-completed.json b/.github/scripts/slack-messages/container-release-completed.json index 6158809c1f..c1fc147191 100644 --- a/.github/scripts/slack-messages/container-release-completed.json +++ b/.github/scripts/slack-messages/container-release-completed.json @@ -1,4 +1,17 @@ { "channel": "${{ env.SLACK_CHANNEL_ID }}", - "text": "${{ env.STATUS_EMOJI }} ${{ env.COMPONENT }} container release ${{ env.RELEASE_TAG }} push ${{ env.STATUS_TEXT }} <${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|View run>" + "attachments": [ + { + "color": "${{ env.STATUS_COLOR }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Status:*\n${{ env.STATUS_TEXT }}\n\n${{ env.COMPONENT }} container release ${{ env.RELEASE_TAG }} push ${{ env.STATUS_TEXT }}\n\n<${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|View run>" + } + } + ] + } + ] } diff --git a/.github/scripts/slack-messages/container-release-started.json b/.github/scripts/slack-messages/container-release-started.json index a203fae913..f67129e3ba 100644 --- a/.github/scripts/slack-messages/container-release-started.json +++ b/.github/scripts/slack-messages/container-release-started.json @@ -1,4 +1,17 @@ { "channel": "${{ env.SLACK_CHANNEL_ID }}", - "text": "${{ env.COMPONENT }} container release ${{ env.RELEASE_TAG }} push started... <${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|View run>" + "attachments": [ + { + "color": "${{ env.STATUS_COLOR }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Status:*\nStarted\n\n${{ env.COMPONENT }} container release ${{ env.RELEASE_TAG }} push started...\n\n<${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|View run>" + } + } + ] + } + ] } From 33bb2782f0b31b4632444d91867963903bbf63a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:05:52 +0100 Subject: [PATCH 04/26] chore(deps): bump aws-actions/configure-aws-credentials from 5.0.0 to 5.1.0 (#9130) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sdk-refresh-aws-services-regions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk-refresh-aws-services-regions.yml b/.github/workflows/sdk-refresh-aws-services-regions.yml index 4df220fc49..884b677294 100644 --- a/.github/workflows/sdk-refresh-aws-services-regions.yml +++ b/.github/workflows/sdk-refresh-aws-services-regions.yml @@ -39,7 +39,7 @@ jobs: run: pip install boto3 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 # v5.0.0 + uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0 with: aws-region: ${{ env.AWS_REGION }} role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }} From 66590f2128a3501a116a0ab15e5bb53a6e7258da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:07:27 +0100 Subject: [PATCH 05/26] chore(deps): bump github/codeql-action from 3.30.5 to 4.31.2 (#9131) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/api-codeql.yml | 4 ++-- .github/workflows/sdk-codeql.yml | 4 ++-- .github/workflows/ui-codeql.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/api-codeql.yml b/.github/workflows/api-codeql.yml index f01e969134..16ea82538e 100644 --- a/.github/workflows/api-codeql.yml +++ b/.github/workflows/api-codeql.yml @@ -45,12 +45,12 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/api-codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/sdk-codeql.yml b/.github/workflows/sdk-codeql.yml index e092b8ae1a..590ba52d34 100644 --- a/.github/workflows/sdk-codeql.yml +++ b/.github/workflows/sdk-codeql.yml @@ -52,12 +52,12 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/sdk-codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/ui-codeql.yml b/.github/workflows/ui-codeql.yml index 798aa39636..2b55cf673c 100644 --- a/.github/workflows/ui-codeql.yml +++ b/.github/workflows/ui-codeql.yml @@ -48,12 +48,12 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/ui-codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: category: '/language:${{ matrix.language }}' From e0260b91e6e7978ebfd9f891200b10a913cd3281 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:08:38 +0100 Subject: [PATCH 06/26] chore(deps): bump peter-evans/create-or-update-comment from 4.0.0 to 5.0.0 (#9133) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr-check-changelog.yml | 2 +- .github/workflows/pr-conflict-checker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-check-changelog.yml b/.github/workflows/pr-check-changelog.yml index f8de212e2a..d1a3220402 100644 --- a/.github/workflows/pr-check-changelog.yml +++ b/.github/workflows/pr-check-changelog.yml @@ -83,7 +83,7 @@ jobs: - name: Update PR comment with changelog status if: github.event.pull_request.head.repo.full_name == github.repository - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 with: issue-number: ${{ github.event.pull_request.number }} comment-id: ${{ steps.find-comment.outputs.comment-id }} diff --git a/.github/workflows/pr-conflict-checker.yml b/.github/workflows/pr-conflict-checker.yml index 3761d252a3..c46fafc2d0 100644 --- a/.github/workflows/pr-conflict-checker.yml +++ b/.github/workflows/pr-conflict-checker.yml @@ -97,7 +97,7 @@ jobs: body-includes: '' - name: Create or update comment - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} From 1a4ae33235ba882e65e398fb6d2e20fa563d3f9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:09:29 +0100 Subject: [PATCH 07/26] chore(deps): bump softprops/action-gh-release from 2.3.3 to 2.4.1 (#9134) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/prepare-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 4fdfcbbf07..ae039bd5a6 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -382,7 +382,7 @@ jobs: no-changelog - name: Create draft release - uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: tag_name: ${{ env.PROWLER_VERSION }} name: Prowler ${{ env.PROWLER_VERSION }} From 3de8159de98abc2e6914df2cf03781203c85fa1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:10:29 +0100 Subject: [PATCH 08/26] chore(deps): bump actions/setup-node from 5.0.0 to 6.0.0 (#9135) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ui-e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ui-e2e-tests.yml b/.github/workflows/ui-e2e-tests.yml index 3339349943..ed3b692819 100644 --- a/.github/workflows/ui-e2e-tests.yml +++ b/.github/workflows/ui-e2e-tests.yml @@ -75,7 +75,7 @@ jobs: echo "All database fixtures loaded successfully!" ' - name: Setup Node.js environment - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: '20.x' cache: 'npm' From ace2b88c07f5e1552ad40141cb99f15cec0b9827 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:11:30 +0100 Subject: [PATCH 09/26] chore(deps): bump sorenlouv/backport-github-action from 9.5.1 to 10.2.0 (#9132) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index f8df9a9abc..974d919fc6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -38,7 +38,7 @@ jobs: - name: Backport PR if: steps.label_check.outputs.label_check == 'success' - uses: sorenlouv/backport-github-action@ad888e978060bc1b2798690dd9d03c4036560947 # v9.5.1 + uses: sorenlouv/backport-github-action@516854e7c9f962b9939085c9a92ea28411d1ae90 # v10.2.0 with: github_token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }} auto_backport_label_prefix: ${{ env.BACKPORT_LABEL_PREFIX }} From 54294c862b20d23b5ebccce66ba69753b3849626 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:11:46 +0100 Subject: [PATCH 10/26] chore(deps): bump trufflesecurity/trufflehog from 3.90.11 to 3.90.12 (#9128) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/find-secrets.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml index 6428cf8f08..80b49e3279 100644 --- a/.github/workflows/find-secrets.yml +++ b/.github/workflows/find-secrets.yml @@ -28,6 +28,6 @@ jobs: fetch-depth: 0 - name: Scan for secrets with TruffleHog - uses: trufflesecurity/trufflehog@ad6fc8fb446b8fafbf7ea8193d2d6bfd42f45690 # v3.90.11 + uses: trufflesecurity/trufflehog@b84c3d14d189e16da175e2c27fa8136603783ffc # v3.90.12 with: extra_args: '--results=verified,unknown' From 2e5f17538dd3a29c69d72946067d8473efa6541d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:12:17 +0100 Subject: [PATCH 11/26] chore(deps): bump agenthunt/conventional-commit-checker-action from 2.0.0 to 2.0.1 (#9127) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/conventional-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conventional-commit.yml b/.github/workflows/conventional-commit.yml index c5ba446edf..58e1653b74 100644 --- a/.github/workflows/conventional-commit.yml +++ b/.github/workflows/conventional-commit.yml @@ -26,6 +26,6 @@ jobs: steps: - name: Check PR title format - uses: agenthunt/conventional-commit-checker-action@9e552d650d0e205553ec7792d447929fc78e012b # v2.0.0 + uses: agenthunt/conventional-commit-checker-action@f1823f632e95a64547566dcd2c7da920e67117ad # v2.0.1 with: pr-title-regex: '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\([^)]+\))?!?: .+' From e038b2fd111fd11c58dc16c2c73900d1b694fb47 Mon Sep 17 00:00:00 2001 From: Sergio Garcia Date: Thu, 6 Nov 2025 05:46:21 -0500 Subject: [PATCH 12/26] chore(sdk): add validation for invalid checks, services, and categories (#8971) Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + prowler/lib/check/checks_loader.py | 66 +++++++++++- tests/lib/check/check_loader_test.py | 154 +++++++++++++++++++++++++-- 3 files changed, 214 insertions(+), 7 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 334aea97cd..fd34a5e1af 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - C5 compliance framework for Azure provider [(#9081)](https://github.com/prowler-cloud/prowler/pull/9081) - C5 compliance framework for the GCP provider [(#9097)](https://github.com/prowler-cloud/prowler/pull/9097) - HIPAA compliance framework for the GCP provider [(#8955)](https://github.com/prowler-cloud/prowler/pull/8955) +- Added validation for invalid checks, services, and categories in `load_checks_to_execute` function [(#8971)](https://github.com/prowler-cloud/prowler/pull/8971) ### Changed - Update AWS Direct Connect service metadata to new format [(#8855)](https://github.com/prowler-cloud/prowler/pull/8855) diff --git a/prowler/lib/check/checks_loader.py b/prowler/lib/check/checks_loader.py index c03034b0e4..dab5535d77 100644 --- a/prowler/lib/check/checks_loader.py +++ b/prowler/lib/check/checks_loader.py @@ -1,3 +1,5 @@ +import sys + from colorama import Fore, Style from prowler.lib.check.check import parse_checks_from_file @@ -57,8 +59,24 @@ def load_checks_to_execute( # Handle if there are checks passed using -c/--checks if check_list: + # Validate that all checks exist + available_checks = set(bulk_checks_metadata.keys()) + available_checks.update(check_aliases.keys()) + invalid_checks = [] for check_name in check_list: - checks_to_execute.add(check_name) + if check_name not in available_checks: + invalid_checks.append(check_name) + else: + checks_to_execute.add(check_name) + + if invalid_checks: + logger.critical( + f"Invalid check(s) specified: {', '.join(invalid_checks)}" + ) + logger.critical( + f"Please provide valid check names. Use 'prowler {provider} --list-checks' to see available checks." + ) + sys.exit(1) # Handle if there are some severities passed using --severity elif severities: @@ -66,6 +84,23 @@ def load_checks_to_execute( checks_to_execute.update(check_severities[severity]) if service_list: + # Validate that all services exist + available_services = set() + for metadata in bulk_checks_metadata.values(): + available_services.add(metadata.ServiceName) + + invalid_services = [ + s for s in service_list if s not in available_services + ] + if invalid_services: + logger.critical( + f"Invalid service(s) specified: {', '.join(invalid_services)}" + ) + logger.critical( + f"Please provide valid service names. Use 'prowler {provider} --list-services' to see available services." + ) + sys.exit(1) + checks_from_services = set() for service in service_list: service_checks = CheckMetadata.list( @@ -81,6 +116,21 @@ def load_checks_to_execute( # Handle if there are services passed using -s/--services elif service_list: + # Validate that all services exist + available_services = set() + for metadata in bulk_checks_metadata.values(): + available_services.add(metadata.ServiceName) + + invalid_services = [s for s in service_list if s not in available_services] + if invalid_services: + logger.critical( + f"Invalid service(s) specified: {', '.join(invalid_services)}" + ) + logger.critical( + f"Please provide valid service names. Use 'prowler {provider} --list-services' to see available services." + ) + sys.exit(1) + for service in service_list: checks_to_execute.update( CheckMetadata.list( @@ -103,6 +153,20 @@ def load_checks_to_execute( # Handle if there are categories passed using --categories elif categories: + # Validate that all categories exist + available_categories = set(check_categories.keys()) + invalid_categories = [ + c for c in categories if c not in available_categories + ] + if invalid_categories: + logger.critical( + f"Invalid category(ies) specified: {', '.join(invalid_categories)}" + ) + logger.critical( + f"Please provide valid category names. Use 'prowler {provider} --list-categories' to see available categories." + ) + sys.exit(1) + for category in categories: checks_to_execute.update(check_categories[category]) diff --git a/tests/lib/check/check_loader_test.py b/tests/lib/check/check_loader_test.py index 12e9b3e547..740ed67844 100644 --- a/tests/lib/check/check_loader_test.py +++ b/tests/lib/check/check_loader_test.py @@ -1,3 +1,4 @@ +import pytest from mock import patch from prowler.lib.check.checks_loader import ( @@ -190,18 +191,22 @@ class TestCheckLoader: def test_load_checks_to_execute_with_severities_and_services_not_within_severity( self, ): + """Test that service not in metadata causes sys.exit(1) when used with severities""" bulk_checks_metatada = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } service_list = ["ec2"] severities = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_SEVERITY] - assert set() == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, - service_list=service_list, - severities=severities, - provider=self.provider, - ) + # ec2 service doesn't exist in the metadata, so it should exit with error + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + service_list=service_list, + severities=severities, + provider=self.provider, + ) + assert exc_info.value.code == 1 def test_load_checks_to_execute_with_checks_file( self, @@ -382,3 +387,140 @@ class TestCheckLoader: categories=categories, provider=self.provider, ) + + def test_load_checks_to_execute_with_invalid_check(self): + """Test that invalid check names cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + check_list = ["invalid_check_name"] + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + check_list=check_list, + provider=self.provider, + ) + assert exc_info.value.code == 1 + + def test_load_checks_to_execute_with_multiple_invalid_checks(self): + """Test that multiple invalid check names cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + check_list = ["invalid_check_1", "invalid_check_2", "invalid_check_3"] + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + check_list=check_list, + provider=self.provider, + ) + assert exc_info.value.code == 1 + + def test_load_checks_to_execute_with_mixed_valid_invalid_checks(self): + """Test that mix of valid and invalid checks cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + check_list = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME, "invalid_check"] + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + check_list=check_list, + provider=self.provider, + ) + assert exc_info.value.code == 1 + + def test_load_checks_to_execute_with_invalid_service(self): + """Test that invalid service names cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + service_list = ["invalid_service"] + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + service_list=service_list, + provider=self.provider, + ) + assert exc_info.value.code == 1 + + def test_load_checks_to_execute_with_invalid_service_and_severity(self): + """Test that invalid service names with severity cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + service_list = ["invalid_service"] + severities = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_SEVERITY] + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + service_list=service_list, + severities=severities, + provider=self.provider, + ) + assert exc_info.value.code == 1 + + def test_load_checks_to_execute_with_multiple_invalid_services(self): + """Test that multiple invalid service names cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + service_list = ["invalid_service_1", "invalid_service_2"] + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + service_list=service_list, + provider=self.provider, + ) + assert exc_info.value.code == 1 + + def test_load_checks_to_execute_with_invalid_category(self): + """Test that invalid category names cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + categories = {"invalid_category"} + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + categories=categories, + provider=self.provider, + ) + assert exc_info.value.code == 1 + + def test_load_checks_to_execute_with_multiple_invalid_categories(self): + """Test that multiple invalid category names cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + categories = {"invalid_category_1", "invalid_category_2"} + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + categories=categories, + provider=self.provider, + ) + assert exc_info.value.code == 1 + + def test_load_checks_to_execute_with_mixed_valid_invalid_categories(self): + """Test that mix of valid and invalid categories cause sys.exit(1)""" + bulk_checks_metatada = { + S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() + } + categories = {"internet-exposed", "invalid_category"} + + with pytest.raises(SystemExit) as exc_info: + load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metatada, + categories=categories, + provider=self.provider, + ) + assert exc_info.value.code == 1 From 8ab77b7dbaf4fae9077548d41c3e8ae55e054c48 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:37:49 +0100 Subject: [PATCH 13/26] fix(gcp): check `check_name` has no `resource_name` error (#9169) --- prowler/CHANGELOG.md | 3 +++ prowler/lib/check/models.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index fd34a5e1af..7d9f2a9857 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -36,6 +36,9 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update AWS CodeArtifact service metadata to new format [(#8850)](https://github.com/prowler-cloud/prowler/pull/8850) - Rename OCI provider to oraclecloud with oci alias [(#9126)](https://github.com/prowler-cloud/prowler/pull/9126) +### Fixed +- Check `check_name` has no `resource_name` error for GCP provider [(#9169)](https://github.com/prowler-cloud/prowler/pull/9169) + --- ## [v5.13.1] (Prowler UNRELEASED) diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index 2398dc8267..37150f38db 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -588,8 +588,17 @@ class Check_Report_GCP(Check_Report): or getattr(resource, "name", None) or "" ) + + # Prefer the explicit resource_name argument, otherwise look for a name attribute on the resource + resource_name_candidate = resource_name or getattr(resource, "name", None) + if not resource_name_candidate and isinstance(resource, dict): + # Some callers pass a dict, so fall back to the dict entry if available + resource_name_candidate = resource.get("name") + if isinstance(resource_name_candidate, str): + # Trim whitespace so empty strings collapse to the default + resource_name_candidate = resource_name_candidate.strip() self.resource_name = ( - resource_name or getattr(resource, "name", "") or "GCP Project" + str(resource_name_candidate) if resource_name_candidate else "GCP Project" ) self.project_id = project_id or getattr(resource, "project_id", "") self.location = ( From 788113b539153cd472acb55d2a0ad68e2e53cf60 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:57:51 +0100 Subject: [PATCH 14/26] fix: changelog (#9179) --- prowler/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 7d9f2a9857..b4224a563b 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -36,6 +36,10 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update AWS CodeArtifact service metadata to new format [(#8850)](https://github.com/prowler-cloud/prowler/pull/8850) - Rename OCI provider to oraclecloud with oci alias [(#9126)](https://github.com/prowler-cloud/prowler/pull/9126) +--- + +## [v5.13.2] (Prowler UNRELEASED) + ### Fixed - Check `check_name` has no `resource_name` error for GCP provider [(#9169)](https://github.com/prowler-cloud/prowler/pull/9169) From edfef51e7a85f67f85e2c5db4dd204f755952f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Thu, 6 Nov 2025 13:06:59 +0100 Subject: [PATCH 15/26] feat(compliance): add naming and visual improvements (#9145) --- prowler/CHANGELOG.md | 1 + prowler/compliance/aws/ens_rd2022_aws.json | 2 +- prowler/compliance/aws/nis2_aws.json | 2 +- prowler/compliance/azure/ens_rd2022_azure.json | 2 +- prowler/compliance/azure/nis2_azure.json | 2 +- prowler/compliance/gcp/ens_rd2022_gcp.json | 2 +- prowler/compliance/gcp/nis2_gcp.json | 2 +- tests/lib/outputs/compliance/ens/ens_aws_test.py | 2 +- tests/lib/outputs/compliance/ens/ens_azure_test.py | 2 +- tests/lib/outputs/compliance/ens/ens_gcp_test.py | 2 +- tests/lib/outputs/compliance/fixtures.py | 6 +++--- 11 files changed, 13 insertions(+), 12 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index b4224a563b..b7691574b0 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - C5 compliance framework for Azure provider [(#9081)](https://github.com/prowler-cloud/prowler/pull/9081) - C5 compliance framework for the GCP provider [(#9097)](https://github.com/prowler-cloud/prowler/pull/9097) - HIPAA compliance framework for the GCP provider [(#8955)](https://github.com/prowler-cloud/prowler/pull/8955) +- Add multiple compliance improvements [(#9145)](https://github.com/prowler-cloud/prowler/pull/9145) - Added validation for invalid checks, services, and categories in `load_checks_to_execute` function [(#8971)](https://github.com/prowler-cloud/prowler/pull/8971) ### Changed diff --git a/prowler/compliance/aws/ens_rd2022_aws.json b/prowler/compliance/aws/ens_rd2022_aws.json index 5d713c2d1d..1d25aeb3b3 100644 --- a/prowler/compliance/aws/ens_rd2022_aws.json +++ b/prowler/compliance/aws/ens_rd2022_aws.json @@ -1,6 +1,6 @@ { "Framework": "ENS", - "Name": "ENS RD 311/2022", + "Name": "ENS RD 311/2022 - Categoría Alta", "Version": "RD2022", "Provider": "AWS", "Description": "The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.", diff --git a/prowler/compliance/aws/nis2_aws.json b/prowler/compliance/aws/nis2_aws.json index c9dcb37390..dc6b75657f 100644 --- a/prowler/compliance/aws/nis2_aws.json +++ b/prowler/compliance/aws/nis2_aws.json @@ -1,6 +1,6 @@ { "Framework": "NIS2", - "Name": "Network and Information Security Directive (Directive (EU) 2022/2555)", + "Name": "NIS2 - Network and Information Security Directive (Directive (EU) 2022/2555)", "Version": "", "Provider": "AWS", "Description": "ANNEX to the Commission Implementing Regulation laying down rules for the application of Directive (EU) 2022/2555 as regards technical and methodological requirements of cybersecurity risk-management measures and further specification of the cases in which an incident is considered to be significant with regard to DNS service providers, TLD name registries, cloud computing service providers, data centre service providers, content delivery network providers, managed service providers, managed security service providers, providers of online market places, of online search engines and of social networking services platforms, and trust service providers", diff --git a/prowler/compliance/azure/ens_rd2022_azure.json b/prowler/compliance/azure/ens_rd2022_azure.json index 9c5fd543cd..5078c6567e 100644 --- a/prowler/compliance/azure/ens_rd2022_azure.json +++ b/prowler/compliance/azure/ens_rd2022_azure.json @@ -1,6 +1,6 @@ { "Framework": "ENS", - "Name": "ENS RD 311/2022", + "Name": "ENS RD 311/2022 - Categoría Alta", "Version": "RD2022", "Provider": "AZURE", "Description": "The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.", diff --git a/prowler/compliance/azure/nis2_azure.json b/prowler/compliance/azure/nis2_azure.json index 052ee1e449..5c48c22f6b 100644 --- a/prowler/compliance/azure/nis2_azure.json +++ b/prowler/compliance/azure/nis2_azure.json @@ -1,6 +1,6 @@ { "Framework": "NIS2", - "Name": "Network and Information Security Directive (Directive (EU) 2022/2555)", + "Name": "NIS2 - Network and Information Security Directive (Directive (EU) 2022/2555)", "Version": "", "Provider": "Azure", "Description": "ANNEX to the Commission Implementing Regulation laying down rules for the application of Directive (EU) 2022/2555 as regards technical and methodological requirements of cybersecurity risk-management measures and further specification of the cases in which an incident is considered to be significant with regard to DNS service providers, TLD name registries, cloud computing service providers, data centre service providers, content delivery network providers, managed service providers, managed security service providers, providers of online market places, of online search engines and of social networking services platforms, and trust service providers", diff --git a/prowler/compliance/gcp/ens_rd2022_gcp.json b/prowler/compliance/gcp/ens_rd2022_gcp.json index cc0a1bfcd4..8315123f79 100644 --- a/prowler/compliance/gcp/ens_rd2022_gcp.json +++ b/prowler/compliance/gcp/ens_rd2022_gcp.json @@ -1,6 +1,6 @@ { "Framework": "ENS", - "Name": "ENS RD 311/2022", + "Name": "ENS RD 311/2022 - Categoría Alta", "Version": "RD2022", "Provider": "GCP", "Description": "The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.", diff --git a/prowler/compliance/gcp/nis2_gcp.json b/prowler/compliance/gcp/nis2_gcp.json index 9768b2dd3d..14d5477dc0 100644 --- a/prowler/compliance/gcp/nis2_gcp.json +++ b/prowler/compliance/gcp/nis2_gcp.json @@ -1,6 +1,6 @@ { "Framework": "NIS2", - "Name": "Network and Information Security Directive (Directive (EU) 2022/2555)", + "Name": "NIS2 - Network and Information Security Directive (Directive (EU) 2022/2555)", "Version": "", "Provider": "GCP", "Description": "ANNEX to the Commission Implementing Regulation laying down rules for the application of Directive (EU) 2022/2555 as regards technical and methodological requirements of cybersecurity risk-management measures and further specification of the cases in which an incident is considered to be significant with regard to DNS service providers, TLD name registries, cloud computing service providers, data centre service providers, content delivery network providers, managed service providers, managed security service providers, providers of online market places, of online search engines and of social networking services platforms, and trust service providers", diff --git a/tests/lib/outputs/compliance/ens/ens_aws_test.py b/tests/lib/outputs/compliance/ens/ens_aws_test.py index 9252b6d8c0..97d45c74c6 100644 --- a/tests/lib/outputs/compliance/ens/ens_aws_test.py +++ b/tests/lib/outputs/compliance/ens/ens_aws_test.py @@ -135,5 +135,5 @@ class TestAWSENS: mock_file.seek(0) content = mock_file.read() - expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\naws;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;123456789012;eu-west-1;{datetime.now()};op.exp.8.aws.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022\r\naws;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;;;{datetime.now()};op.exp.8.aws.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022\r\n" + expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\naws;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;123456789012;eu-west-1;{datetime.now()};op.exp.8.aws.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022 - Categoría Alta\r\naws;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;;;{datetime.now()};op.exp.8.aws.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022 - Categoría Alta\r\n" assert content == expected_csv diff --git a/tests/lib/outputs/compliance/ens/ens_azure_test.py b/tests/lib/outputs/compliance/ens/ens_azure_test.py index 8923f45547..50538976da 100644 --- a/tests/lib/outputs/compliance/ens/ens_azure_test.py +++ b/tests/lib/outputs/compliance/ens/ens_azure_test.py @@ -144,6 +144,6 @@ class TestAzureENS: mock_file.seek(0) content = mock_file.read() - expected_csv = f"PROVIDER;DESCRIPTION;SUBSCRIPTIONID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\nazure;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;123456789012;global;{datetime.now()};op.exp.8.azure.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022\r\nazure;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;;;{datetime.now()};op.exp.8.azure.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022\r\n" + expected_csv = f"PROVIDER;DESCRIPTION;SUBSCRIPTIONID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\nazure;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;123456789012;global;{datetime.now()};op.exp.8.azure.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022 - Categoría Alta\r\nazure;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;;;{datetime.now()};op.exp.8.azure.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022 - Categoría Alta\r\n" assert content == expected_csv diff --git a/tests/lib/outputs/compliance/ens/ens_gcp_test.py b/tests/lib/outputs/compliance/ens/ens_gcp_test.py index afdbcdb3b0..eacc21c3ae 100644 --- a/tests/lib/outputs/compliance/ens/ens_gcp_test.py +++ b/tests/lib/outputs/compliance/ens/ens_gcp_test.py @@ -144,6 +144,6 @@ class TestGCPENS: mock_file.seek(0) content = mock_file.read() - expected_csv = f"PROVIDER;DESCRIPTION;PROJECTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\ngcp;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;123456789012;global;{datetime.now()};op.exp.8.gcp.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022\r\ngcp;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;;;{datetime.now()};op.exp.8.gcp.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022\r\n" + expected_csv = f"PROVIDER;DESCRIPTION;PROJECTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\ngcp;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;123456789012;global;{datetime.now()};op.exp.8.gcp.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022 - Categoría Alta\r\ngcp;The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.;;;{datetime.now()};op.exp.8.gcp.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022 - Categoría Alta\r\n" assert content == expected_csv diff --git a/tests/lib/outputs/compliance/fixtures.py b/tests/lib/outputs/compliance/fixtures.py index caba697417..7b29411663 100644 --- a/tests/lib/outputs/compliance/fixtures.py +++ b/tests/lib/outputs/compliance/fixtures.py @@ -490,7 +490,7 @@ MITRE_ATTACK_GCP = Compliance( ENS_RD2022_AWS = Compliance( Framework="ENS", - Name="ENS RD 311/2022", + Name="ENS RD 311/2022 - Categoría Alta", Provider="AWS", Version="RD2022", Description="The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.", @@ -538,7 +538,7 @@ ENS_RD2022_AWS = Compliance( ENS_RD2022_AZURE = Compliance( Framework="ENS", - Name="ENS RD 311/2022", + Name="ENS RD 311/2022 - Categoría Alta", Provider="Azure", Version="RD2022", Description="The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.", @@ -585,7 +585,7 @@ ENS_RD2022_AZURE = Compliance( ) ENS_RD2022_GCP = Compliance( Framework="ENS", - Name="ENS RD 311/2022", + Name="ENS RD 311/2022 - Categoría Alta", Provider="GCP", Version="RD2022", Description="The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.", From 7bbc0d8e1b04d3534640d8724b1ad3f2e33b0884 Mon Sep 17 00:00:00 2001 From: Alan Buscaglia Date: Thu, 6 Nov 2025 13:48:19 +0100 Subject: [PATCH 16/26] feat: add claude code validation to pre-commit hook (#9177) Co-authored-by: alejandrobailo --- .env | 9 +- .github/pull_request_template.md | 7 + ui/.husky/pre-commit | 121 ++++++- .../code-review/CODE_REVIEW_QUICK_START.md | 115 +++++++ ui/docs/code-review/CODE_REVIEW_SETUP.md | 296 ++++++++++++++++++ ui/docs/code-review/README.md | 241 ++++++++++++++ 6 files changed, 786 insertions(+), 3 deletions(-) mode change 100644 => 100755 ui/.husky/pre-commit create mode 100644 ui/docs/code-review/CODE_REVIEW_QUICK_START.md create mode 100644 ui/docs/code-review/CODE_REVIEW_SETUP.md create mode 100644 ui/docs/code-review/README.md diff --git a/.env b/.env index 6787267f6f..f83f8ea702 100644 --- a/.env +++ b/.env @@ -14,7 +14,11 @@ UI_PORT=3000 AUTH_SECRET="N/c6mnaS5+SWq81+819OrzQZlmx1Vxtp/orjttJSmw8=" # Google Tag Manager ID NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID="" - +#### Code Review Configuration #### +# Enable Claude Code standards validation on pre-push hook +# Set to 'true' to validate changes against AGENTS.md standards via Claude Code +# Set to 'false' to skip validation +CODE_REVIEW_ENABLED=true #### Prowler API Configuration #### PROWLER_API_VERSION="stable" @@ -132,4 +136,5 @@ LANGCHAIN_PROJECT="" # Example with one source: RSS_FEED_SOURCES='[{"id":"prowler-releases","name":"Prowler Releases","type":"github_releases","url":"https://github.com/prowler-cloud/prowler/releases.atom","enabled":true}]' # Example with multiple sources (no trailing comma after last item): -# RSS_FEED_SOURCES='[{"id":"prowler-releases","name":"Prowler Releases","type":"github_releases","url":"https://github.com/prowler-cloud/prowler/releases.atom","enabled":true},{"id":"prowler-blog","name":"Prowler Blog","type":"blog","url":"https://prowler.com/blog/rss","enabled":false}]' \ No newline at end of file +# RSS_FEED_SOURCES='[{"id":"prowler-releases","name":"Prowler Releases","type":"github_releases","url":"https://github.com/prowler-cloud/prowler/releases.atom","enabled":true},{"id":"prowler-blog","name":"Prowler Blog","type":"blog","url":"https://prowler.com/blog/rss","enabled":false}]' + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 94122d8349..497c4d6214 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -22,6 +22,13 @@ Please add a detailed description of how to review this PR. - [ ] Review if is needed to change the [Readme.md](https://github.com/prowler-cloud/prowler/blob/master/README.md) - [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/prowler/CHANGELOG.md), if applicable. +#### UI +- [ ] All issue/task requirements work as expected on the UI +- [ ] Screenshots/Video of the functionality flow (if applicable) - Mobile (X < 640px) +- [ ] Screenshots/Video of the functionality flow (if applicable) - Table (640px > X < 1024px) +- [ ] Screenshots/Video of the functionality flow (if applicable) - Desktop (X > 1024px) +- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/ui/CHANGELOG.md), if applicable. + #### API - [ ] Verify if API specs need to be regenerated. - [ ] Check if version updates are required (e.g., specs, Poetry, etc.). diff --git a/ui/.husky/pre-commit b/ui/.husky/pre-commit old mode 100644 new mode 100755 index 129b71298f..e58c0fab37 --- a/ui/.husky/pre-commit +++ b/ui/.husky/pre-commit @@ -1 +1,120 @@ -npm run healthcheck +#!/bin/bash + +# Prowler UI - Pre-Commit Hook +# Optionally validates ONLY staged files against AGENTS.md standards using Claude Code +# Controlled by CODE_REVIEW_ENABLED in .env + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "🚀 Prowler UI - Pre-Commit Hook" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Load .env file (look in git root directory) +GIT_ROOT=$(git rev-parse --show-toplevel) +if [ -f "$GIT_ROOT/ui/.env" ]; then + CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" "$GIT_ROOT/ui/.env" | cut -d'=' -f2 | tr -d ' ') +elif [ -f "$GIT_ROOT/.env" ]; then + CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" "$GIT_ROOT/.env" | cut -d'=' -f2 | tr -d ' ') +elif [ -f ".env" ]; then + CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" .env | cut -d'=' -f2 | tr -d ' ') +else + CODE_REVIEW_ENABLED="false" +fi + +# Normalize the value to lowercase +CODE_REVIEW_ENABLED=$(echo "$CODE_REVIEW_ENABLED" | tr '[:upper:]' '[:lower:]') + +echo -e "${BLUE}ℹ️ Code Review Status: ${CODE_REVIEW_ENABLED}${NC}" +echo "" + +# Get staged files (what will be committed) +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(tsx?|jsx?)$' || true) + +if [ "$CODE_REVIEW_ENABLED" = "true" ]; then + if [ -z "$STAGED_FILES" ]; then + echo -e "${YELLOW}⚠️ No TypeScript/JavaScript files staged to validate${NC}" + echo "" + else + echo -e "${YELLOW}🔍 Running Claude Code standards validation...${NC}" + echo "" + echo -e "${BLUE}📋 Files to validate:${NC}" + echo "$STAGED_FILES" | sed 's/^/ - /' + echo "" + + echo -e "${BLUE}📤 Sending to Claude Code for validation...${NC}" + echo "" + + # Build prompt with git diff of changes + VALIDATION_PROMPT=$( + cat <<'PROMPT_EOF' +You are a code reviewer for the Prowler UI project. Analyze the code changes (git diff) below and validate they comply with AGENTS.md standards. + +**RULES TO CHECK:** +1. React Imports: NO `import * as React` or `import React, {` → Use `import { useState }` +2. TypeScript: NO union types like `type X = "a" | "b"` → Use const-based: `const X = {...} as const` +3. Tailwind: NO `var()` or hex colors in className → Use `className="bg-card-bg"` +4. cn(): ONLY for conditionals → NOT for static classes +5. React 19: NO `useMemo`/`useCallback` without reason +6. Zod v4: Use `.min(1)` not `.nonempty()`, `z.email()` not `z.string().email()`. All inputs must be validated with Zod. +7. File Org: 1 feature = local, 2+ features = shared +8. Directives: Server Actions need "use server", clients need "use client" +9. Implement DRY, KISS principles. (example: reusable components, avoid repetition) +10. Layout must work for all the responsive breakpoints (mobile, tablet, desktop) +11. ANY types cannot be used +12. Use the components inside components/shadcn if possible +13. Check Accessibility best practices (like alt tags in images, semantic HTML, Aria labels, etc.) + +=== GIT DIFF OF CHANGES === +PROMPT_EOF + ) + + # Add git diff to prompt (shows only actual changes) + VALIDATION_PROMPT="$VALIDATION_PROMPT +$(git diff --cached -U0)" + + VALIDATION_PROMPT="$VALIDATION_PROMPT + +=== END DIFF === + +**RESPOND WITH:** +STATUS: [PASSED | FAILED] +[If FAILED: list violations with File, Rule, Issue, and line reference] +[If PASSED: confirm all changes comply with AGENTS.md standards]" + + # Send to Claude Code + if VALIDATION_OUTPUT=$(echo "$VALIDATION_PROMPT" | claude 2>&1); then + echo "$VALIDATION_OUTPUT" + echo "" + + # Check result + if echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: PASSED"; then + echo -e "${GREEN}✅ VALIDATION PASSED${NC}" + elif echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: FAILED"; then + echo "" + echo -e "${RED}❌ VALIDATION FAILED${NC}" + echo -e "${RED}Fix violations before committing${NC}" + echo "" + exit 1 + else + echo -e "${YELLOW}⚠️ Could not determine validation status${NC}" + echo "Continuing (validation inconclusive)" + fi + else + echo -e "${YELLOW}⚠️ Claude Code not available${NC}" + fi + echo "" + fi +else + echo -e "${YELLOW}⏭️ Code review disabled (CODE_REVIEW_ENABLED=false)${NC}" + echo "" +fi diff --git a/ui/docs/code-review/CODE_REVIEW_QUICK_START.md b/ui/docs/code-review/CODE_REVIEW_QUICK_START.md new file mode 100644 index 0000000000..91dd2ad4af --- /dev/null +++ b/ui/docs/code-review/CODE_REVIEW_QUICK_START.md @@ -0,0 +1,115 @@ +# Code Review - Quick Start + +## 3 Steps to Enable + +### 1. Open `.env` +```bash +nano ui/.env +# or your favorite editor +``` + +### 2. Find this line +```bash +CODE_REVIEW_ENABLED=false +``` + +### 3. Change it to +```bash +CODE_REVIEW_ENABLED=true +``` + +**Done! ✅** + +--- + +## What Happens Now + +Every time you `git commit`: + +``` +✅ If your code complies with AGENTS.md standards: + → Commit executes normally + +❌ If there are standard violations: + → Commit is BLOCKED + → You see the errors in the terminal + → Fix the code + → Commit again +``` + +--- + +## Example + +```bash +$ git commit -m "feat: add new component" + +🏁 Prowler UI - Pre-Commit Hook + +ℹ️ Code Review Status: true + +🔍 Running Claude Code standards validation... + +📋 Files to validate: + - components/my-feature.tsx + +📤 Sending to Claude Code for validation... + +STATUS: FAILED +- File: components/my-feature.tsx:45 + Rule: React Imports + Issue: Using 'import * as React' + Expected: import { useState } from "react" + +❌ VALIDATION FAILED +Fix violations before committing + +# Fix the file and commit again +$ git commit -m "feat: add new component" + +🏁 Prowler UI - Pre-Commit Hook + +ℹ️ Code Review Status: true + +🔍 Running Claude Code standards validation... + +✅ VALIDATION PASSED + +# Commit successful ✅ +``` + +--- + +## Disable Temporarily + +If you need to commit without validation: + +```bash +# Option 1: Change in .env +CODE_REVIEW_ENABLED=false + +# Option 2: Bypass (use with caution!) +git commit --no-verify +``` + +--- + +## What Gets Validated + +- ✅ Correct React imports +- ✅ TypeScript patterns (const-based types) +- ✅ Tailwind CSS (no var() or hex in className) +- ✅ cn() utility (only for conditionals) +- ✅ No useMemo/useCallback without reason +- ✅ Zod v4 syntax +- ✅ File organization +- ✅ Directives "use client"/"use server" + +--- + +## More Info + +Read `CODE_REVIEW_SETUP.md` for: +- Troubleshooting +- Complete details +- Advanced configuration diff --git a/ui/docs/code-review/CODE_REVIEW_SETUP.md b/ui/docs/code-review/CODE_REVIEW_SETUP.md new file mode 100644 index 0000000000..7d4d133cf2 --- /dev/null +++ b/ui/docs/code-review/CODE_REVIEW_SETUP.md @@ -0,0 +1,296 @@ +# Code Review Setup - Prowler UI + +Guide to set up automatic code validation with Claude Code in the pre-commit hook. + +## Overview + +The code review system works like this: + +1. **When you enable `CODE_REVIEW_ENABLED=true` in `.env`** + - When you `git commit`, the pre-commit hook runs + - Only validates TypeScript/JavaScript files you're committing + - Uses Claude Code to check if they comply with AGENTS.md + - If there are violations → **BLOCKS the commit** + - If everything is fine → Continues normally + +2. **When `CODE_REVIEW_ENABLED=false` (default)** + - The pre-commit hook does not run validation + - No standards validation + - Developers can commit without restrictions + +## Installation + +### 1. Ensure Claude Code is in your PATH + +```bash +# Verify that claude is available in terminal +which claude + +# If it doesn't appear, check your Claude Code CLI installation +``` + +### 2. Enable validation in `.env` + +In `/ui/.env`, find the "Code Review Configuration" section: + +```bash +#### Code Review Configuration #### +# Enable Claude Code standards validation on pre-commit hook +# Set to 'true' to validate changes against AGENTS.md standards via Claude Code +# Set to 'false' to skip validation +CODE_REVIEW_ENABLED=false # ← Change this to 'true' +``` + +**Options:** +- `CODE_REVIEW_ENABLED=true` → Enables validation +- `CODE_REVIEW_ENABLED=false` → Disables validation (default) + +### 3. The hook is ready + +The `.husky/pre-commit` file already contains the logic. You don't need to install anything else. + +## How It Works + +### Normal Flow (with validation enabled) + +```bash +$ git commit -m "feat: add new component" + +# Pre-commit hook executes automatically +🚀 Prowler UI - Pre-Commit Hook + ℹ️ Code Review Status: true + +📋 Files to validate: + - components/new-feature.tsx + - types/new-feature.ts + +📤 Sending to Claude Code for validation... + +# Claude analyzes the files... + +=== VALIDATION REPORT === +STATUS: PASSED +All files comply with AGENTS.md standards. + +✅ VALIDATION PASSED +# Commit continues ✅ +``` + +### If There Are Violations + +```bash +$ git commit -m "feat: add new component" + +# Claude detects issues... + +=== VALIDATION REPORT === +STATUS: FAILED + +- File: components/new-feature.tsx:15 + Rule: React Imports + Issue: Using 'import * as React' instead of named imports + Expected: import { useState } from "react" + +❌ VALIDATION FAILED + +Please fix the violations before committing: + 1. Review the violations listed above + 2. Fix the code according to AGENTS.md standards + 3. Commit your changes + 4. Try again + +# Commit is BLOCKED ❌ +``` + +## What Gets Validated + +The system verifies that files comply with: + +### 1. React Imports +```typescript +// ❌ WRONG +import * as React from "react" +import React, { useState } from "react" + +// ✅ CORRECT +import { useState } from "react" +``` + +### 2. TypeScript Type Patterns +```typescript +// ❌ WRONG +type SortOption = "high-low" | "low-high" + +// ✅ CORRECT +const SORT_OPTIONS = { + HIGH_LOW: "high-low", + LOW_HIGH: "low-high", +} as const +type SortOption = typeof SORT_OPTIONS[keyof typeof SORT_OPTIONS] +``` + +### 3. Tailwind CSS +```typescript +// ❌ WRONG +className="bg-[var(--color)]" +className="text-[#ffffff]" + +// ✅ CORRECT +className="bg-card-bg text-white" +``` + +### 4. cn() Utility +```typescript +// ❌ WRONG +className={cn("flex items-center")} + +// ✅ CORRECT +className={cn("h-3 w-3", isCircle ? "rounded-full" : "rounded-sm")} +``` + +### 5. React 19 Hooks +```typescript +// ❌ WRONG +const memoized = useMemo(() => value, []) + +// ✅ CORRECT +// Don't use useMemo (React Compiler handles it) +const value = expensiveCalculation() +``` + +### 6. Zod v4 Syntax +```typescript +// ❌ WRONG +z.string().email() +z.string().nonempty() + +// ✅ CORRECT +z.email() +z.string().min(1) +``` + +### 7. File Organization +``` +// ❌ WRONG +Code used by 2+ features in feature-specific folder + +// ✅ CORRECT +Code used by 1 feature → local in that feature +Code used by 2+ features → in shared/global +``` + +### 8. Use Directives +```typescript +// ❌ WRONG +export async function updateUser() { } // Missing "use server" + +// ✅ CORRECT +"use server" +export async function updateUser() { } +``` + +## Disable Temporarily + +If you need to commit without validation temporarily: + +```bash +# Option 1: Change in .env +CODE_REVIEW_ENABLED=false +git commit + +# Option 2: Use git hook bypass +git commit --no-verify + +# Option 3: Disable the hook +chmod -x .husky/pre-commit +git commit +chmod +x .husky/pre-commit +``` + +**⚠️ Note:** `--no-verify` skips ALL hooks. + +## Troubleshooting + +### "Claude Code CLI not found" + +``` +⚠️ Claude Code CLI not found in PATH +To enable: ensure Claude Code is in PATH and CODE_REVIEW_ENABLED=true +``` + +**Solution:** +```bash +# Check where claude-code is installed +which claude-code + +# If not found, add to your ~/.zshrc: +export PATH="$HOME/.local/bin:$PATH" # or where it's installed + +# Reload the terminal +source ~/.zshrc +``` + +### "Validation inconclusive" + +If Claude Code cannot determine the status: + +``` +⚠️ Could not determine validation status +Allowing commit (validation inconclusive) +``` + +The commit is allowed automatically. If you want to be stricter, you can: + +1. Manually review files against AGENTS.md +2. Report the analysis problem to Claude + +### Build fails after validation + +``` +❌ Build failed +``` + +If validation passes but build fails: + +1. Check the build error +2. Fix it locally +3. Commit and try again + +## View the Full Report + +Reports are saved in temporary files that are deleted afterward. To see the detailed report in real-time, watch the hook output: + +```bash +git commit 2>&1 | tee commit-report.txt +``` + +This will save everything to `commit-report.txt`. + +## For the Team + +### Enable on your machine + +```bash +cd ui +# Edit .env locally and set: +CODE_REVIEW_ENABLED=true +``` + +### Recommended Flow + +1. **During development**: `CODE_REVIEW_ENABLED=false` + - Iterate faster + - Build check still runs + +2. **Before final commit**: `CODE_REVIEW_ENABLED=true` + - Verify you meet standards + - Prevent PRs rejected for violations + +3. **In CI/CD**: You could add additional validation + - (future) Server-side validation in GitHub Actions + +## Questions? + +If you have questions about the standards being validated, check: +- `AGENTS.md` - Complete architecture guide +- `CLAUDE.md` - Project-specific instructions diff --git a/ui/docs/code-review/README.md b/ui/docs/code-review/README.md new file mode 100644 index 0000000000..ad546f77f4 --- /dev/null +++ b/ui/docs/code-review/README.md @@ -0,0 +1,241 @@ +# Code Review System Documentation + +Complete documentation for the Claude Code-powered pre-commit validation system. + +## Quick Navigation + +**Want to get started in 3 steps?** +→ Read: [`CODE_REVIEW_QUICK_START.md`](./CODE_REVIEW_QUICK_START.md) + +**Want complete technical details?** +→ Read: [`CODE_REVIEW_SETUP.md`](./CODE_REVIEW_SETUP.md) + +--- + +## What This System Does + +Automatically validates code against AGENTS.md standards when you commit using Claude Code. + +``` +git commit + ↓ +(Optional) Claude Code validation + ↓ +If violations found → Commit is BLOCKED ❌ +If code complies → Commit continues ✅ +``` + +**Key Feature:** Configurable with a single variable in `.env` +- `CODE_REVIEW_ENABLED=true` → Validates (recommended before commits) +- `CODE_REVIEW_ENABLED=false` → Skip validation (default, for iteration) + +--- + +## File Guide + +| File | Purpose | Read Time | +|------|---------|-----------| +| [`CODE_REVIEW_QUICK_START.md`](./CODE_REVIEW_QUICK_START.md) | 3-step setup & examples | 5 min | +| [`CODE_REVIEW_SETUP.md`](./CODE_REVIEW_SETUP.md) | Complete technical guide | 15 min | + +--- + +## What Gets Validated + +When validation is enabled, the system checks: + +✅ **React Imports** +- Must use: `import { useState } from "react"` +- Not: `import * as React` or `import React, {` + +✅ **TypeScript Types** +- Must use: `const STATUS = {...} as const; type Status = typeof STATUS[...]` +- Not: `type Status = "a" | "b"` + +✅ **Tailwind CSS** +- Must use: `className="bg-card-bg text-white"` +- Not: `className="bg-[var(...)]"` or `className="text-[#fff]"` + +✅ **cn() Utility** +- Must use for: `cn("h-3", isActive && "bg-blue")` +- Not for: `cn("static-class")` + +✅ **React 19 Hooks** +- No: `useMemo()` / `useCallback()` without documented reason +- Use: Nothing (React Compiler handles optimization) + +✅ **Zod v4 Syntax** +- Must use: `z.email()`, `.min(1)` +- Not: `z.string().email()`, `.nonempty()` + +✅ **File Organization** +- 1 feature uses → Keep local in feature folder +- 2+ features use → Move to shared/global + +✅ **Directives** +- Server Actions must have: `"use server"` +- Client Components must have: `"use client"` + +--- + +## Installation (For Your Team) + +### Step 1: Decide if you want validation +- **Optional:** Each developer decides +- **Team policy:** Consider making it standard before commits + +### Step 2: Enable in your environment +```bash +# Edit ui/.env +CODE_REVIEW_ENABLED=true +``` + +### Step 3: Done! +Your next `git commit` will validate automatically. + +--- + +## Support + +| Question | Answer | +|----------|--------| +| How do I enable it? | Change `CODE_REVIEW_ENABLED=true` in `.env` | +| How do I disable it? | Change `CODE_REVIEW_ENABLED=false` in `.env` | +| How do I bypass? | Use `git commit --no-verify` (emergency only) | +| What if Claude Code isn't found? | Check PATH: `which claude` | +| What if hook doesn't run? | Check executable: `chmod +x .husky/pre-commit` | +| How do I test it? | Enable validation and commit code with violations to test | +| What if I don't have Claude Code? | Validation is skipped gracefully | + +--- + +## Key Features + +✅ **No Setup Required** +- Uses Claude Code already in your PATH +- No API keys needed +- Works offline (if Claude Code supports it) + +✅ **Smart Validation** +- Only checks files being committed +- Not the entire codebase +- Fast: ~10-30 seconds with validation enabled + +✅ **Flexible** +- Can be enabled/disabled per developer +- Can be disabled temporarily with `git commit --no-verify` +- Default is disabled (doesn't interrupt workflow) + +✅ **Clear Feedback** +- Shows exactly what violates standards +- Shows file:line references +- Explains how to fix each issue + +✅ **Well Documented** +- 5 different documentation files +- For different needs and levels +- Examples and troubleshooting included + +--- + +## Architecture + +``` +┌─────────────────────────────────────────┐ +│ Developer commits code │ +└────────────────┬────────────────────────┘ + ↓ + ┌─────────────────┐ + │ Pre-Commit Hook │ + │ (.husky/pre-commit) + └────────┬────────┘ + ↓ + Read CODE_REVIEW_ENABLED from .env + ↓ + ┌──────────────────────────┐ + │ If false (disabled) │ + └────────┬─────────────────┘ + ↓ + exit 0 (OK) + ↓ + Commit continues ✅ + + ┌──────────────────────────┐ + │ If true (enabled) │ + └────────┬─────────────────┘ + ↓ + Extract staged files + (git diff --cached) + ↓ + Build prompt with git diff + ↓ + Send to: claude < prompt + ↓ + Analyze against AGENTS.md + ↓ + Return: STATUS: PASSED or FAILED + ↓ + Parse with: grep "^STATUS:" + ↓ + ┌──────────────────┐ + │ PASSED detected │ + └────────┬─────────┘ + ↓ + exit 0 (OK) + ↓ + Commit continues ✅ + + ┌──────────────────┐ + │ FAILED detected │ + └────────┬─────────┘ + ↓ + Show violations + ↓ + exit 1 (FAIL) + ↓ + Commit is BLOCKED ❌ + ↓ + Developer fixes code + Developer commits again +``` + +--- + +## Getting Started + +1. **Read:** [`CODE_REVIEW_QUICK_START.md`](./CODE_REVIEW_QUICK_START.md) (5 minutes) +2. **Enable:** Set `CODE_REVIEW_ENABLED=true` in your `ui/.env` +3. **Test:** Commit some code and see validation in action +4. **For help:** See the troubleshooting section in [`CODE_REVIEW_SETUP.md`](./CODE_REVIEW_SETUP.md) + +--- + +## Implementation Details + +- **Files Modified:** 1 (`.husky/pre-commit`) +- **Files Created:** 3 (documentation) +- **Hook Size:** ~120 lines of bash +- **Dependencies:** Claude Code CLI (already available) +- **Setup Time:** 1 minute +- **Default:** Disabled (no workflow interruption) + +--- + +## Questions? + +- **How to enable?** → `CODE_REVIEW_QUICK_START.md` +- **How does it work?** → `CODE_REVIEW_SETUP.md` +- **Troubleshooting?** → See troubleshooting section in `CODE_REVIEW_SETUP.md` + +--- + +## Status + +✅ **Ready to Use** + +The system is fully implemented, documented, and tested. You can enable it immediately with a single variable change. + +--- + +**Last Updated:** November 6, 2024 +**Status:** Complete Implementation From 38f60966e5eab09ea764310d5395461957d1ec72 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Thu, 6 Nov 2025 14:32:06 +0100 Subject: [PATCH 17/26] fix(ui): improve pre commit (#9180) --- ui/.husky/pre-commit | 40 +++++++++++++++++--------- ui/README.md | 51 +++++++++++++++++++++++++++++++++ ui/package.json | 2 +- ui/scripts/setup-git-hooks.js | 54 +++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 14 deletions(-) create mode 100755 ui/scripts/setup-git-hooks.js diff --git a/ui/.husky/pre-commit b/ui/.husky/pre-commit index e58c0fab37..8e982d4c8a 100755 --- a/ui/.husky/pre-commit +++ b/ui/.husky/pre-commit @@ -54,10 +54,12 @@ if [ "$CODE_REVIEW_ENABLED" = "true" ]; then echo -e "${BLUE}📤 Sending to Claude Code for validation...${NC}" echo "" - # Build prompt with git diff of changes + # Build prompt with git diff of changes AND full context VALIDATION_PROMPT=$( cat <<'PROMPT_EOF' -You are a code reviewer for the Prowler UI project. Analyze the code changes (git diff) below and validate they comply with AGENTS.md standards. +You are a code reviewer for the Prowler UI project. Analyze the code changes (git diff with full context) below and validate they comply with AGENTS.md standards. + +**CRITICAL: You MUST check BOTH the changed lines AND the surrounding context for violations.** **RULES TO CHECK:** 1. React Imports: NO `import * as React` or `import React, {` → Use `import { useState }` @@ -70,35 +72,41 @@ You are a code reviewer for the Prowler UI project. Analyze the code changes (gi 8. Directives: Server Actions need "use server", clients need "use client" 9. Implement DRY, KISS principles. (example: reusable components, avoid repetition) 10. Layout must work for all the responsive breakpoints (mobile, tablet, desktop) -11. ANY types cannot be used +11. ANY types cannot be used - CRITICAL: Check for `: any` in all visible lines 12. Use the components inside components/shadcn if possible 13. Check Accessibility best practices (like alt tags in images, semantic HTML, Aria labels, etc.) -=== GIT DIFF OF CHANGES === +=== GIT DIFF WITH CONTEXT === PROMPT_EOF ) - # Add git diff to prompt (shows only actual changes) + # Add git diff to prompt with more context (U5 = 5 lines before/after) VALIDATION_PROMPT="$VALIDATION_PROMPT -$(git diff --cached -U0)" +$(git diff --cached -U5)" VALIDATION_PROMPT="$VALIDATION_PROMPT === END DIFF === -**RESPOND WITH:** -STATUS: [PASSED | FAILED] -[If FAILED: list violations with File, Rule, Issue, and line reference] -[If PASSED: confirm all changes comply with AGENTS.md standards]" +**IMPORTANT: Your response MUST start with exactly one of these lines:** +STATUS: PASSED +STATUS: FAILED + +**If FAILED:** List each violation with File, Line Number, Rule Number, and Issue. +**If PASSED:** Confirm all visible code (including context) complies with AGENTS.md standards. + +**Start your response now with STATUS:**" # Send to Claude Code if VALIDATION_OUTPUT=$(echo "$VALIDATION_PROMPT" | claude 2>&1); then echo "$VALIDATION_OUTPUT" echo "" - # Check result + # Check result - STRICT MODE: fail if status unclear if echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: PASSED"; then + echo "" echo -e "${GREEN}✅ VALIDATION PASSED${NC}" + echo "" elif echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: FAILED"; then echo "" echo -e "${RED}❌ VALIDATION FAILED${NC}" @@ -106,8 +114,14 @@ STATUS: [PASSED | FAILED] echo "" exit 1 else - echo -e "${YELLOW}⚠️ Could not determine validation status${NC}" - echo "Continuing (validation inconclusive)" + echo "" + echo -e "${RED}❌ VALIDATION ERROR${NC}" + echo -e "${RED}Could not determine validation status from Claude Code response${NC}" + echo -e "${YELLOW}Response must start with 'STATUS: PASSED' or 'STATUS: FAILED'${NC}" + echo "" + echo -e "${YELLOW}To bypass validation temporarily, set CODE_REVIEW_ENABLED=false in .env${NC}" + echo "" + exit 1 fi else echo -e "${YELLOW}⚠️ Claude Code not available${NC}" diff --git a/ui/README.md b/ui/README.md index 3e334368ff..e0dcb3be33 100644 --- a/ui/README.md +++ b/ui/README.md @@ -87,6 +87,12 @@ You can use one of them `npm`, `yarn`, `pnpm`, `bun`, Example using `npm`: npm install ``` +**Note:** The `npm install` command will automatically configure Git hooks for code quality checks. If you experience issues, you can manually configure them: + +```bash +git config core.hooksPath "ui/.husky" +``` + #### Run the development server ```bash @@ -112,3 +118,48 @@ After modifying the `.npmrc` file, you need to run `pnpm install` again to ensur - [TypeScript](https://www.typescriptlang.org/) - [Framer Motion](https://www.framer.com/motion/) - [next-themes](https://github.com/pacocoursey/next-themes) + +## Git Hooks & Code Review + +This project uses Git hooks to maintain code quality. When you commit changes to TypeScript/JavaScript files, the pre-commit hook can optionally validate them against our coding standards using Claude Code. + +### Enabling Code Review + +To enable automatic code review on commits, add this to your `.env` file in the project root: + +```bash +CODE_REVIEW_ENABLED=true +``` + +When enabled, the hook will: +- ✅ Validate your staged changes against `AGENTS.md` standards +- ✅ Check for common issues (any types, incorrect imports, styling violations, etc.) +- ✅ Block commits that don't comply with the standards +- ✅ Provide helpful feedback on how to fix issues + +### Disabling Code Review + +To disable code review (faster commits, useful for quick iterations): + +```bash +CODE_REVIEW_ENABLED=false +``` + +Or remove the variable from your `.env` file. + +### Requirements + +- [Claude Code CLI](https://github.com/anthropics/claude-code) installed and authenticated +- `.env` file in the project root with `CODE_REVIEW_ENABLED` set + +### Troubleshooting + +If hooks aren't running after commits: + +```bash +# Verify hooks are configured +git config --get core.hooksPath # Should output: ui/.husky + +# Reconfigure if needed +git config core.hooksPath "ui/.husky" +``` diff --git a/ui/package.json b/ui/package.json index db33a08fac..8394a221d9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,7 +7,7 @@ "start": "next start", "start:standalone": "node .next/standalone/server.js", "deps:log": "node scripts/update-dependency-log.js", - "postinstall": "node -e \"const fs=require('fs'); if(fs.existsSync('scripts/update-dependency-log.js')) require('./scripts/update-dependency-log.js'); else console.log('skip deps:log (script missing)');\"", + "postinstall": "node -e \"const fs=require('fs'); if(fs.existsSync('scripts/update-dependency-log.js')) require('./scripts/update-dependency-log.js'); else console.log('skip deps:log (script missing)');\" && node scripts/setup-git-hooks.js", "typecheck": "tsc", "healthcheck": "npm run typecheck && npm run lint:check", "lint:check": "eslint . --ext .ts,.tsx -c .eslintrc.cjs", diff --git a/ui/scripts/setup-git-hooks.js b/ui/scripts/setup-git-hooks.js new file mode 100755 index 0000000000..a3f219813f --- /dev/null +++ b/ui/scripts/setup-git-hooks.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +/** + * Setup Git Hooks for Prowler UI + * + * This script configures Git to use Husky hooks located in ui/.husky + * It runs automatically after npm install via the postinstall script. + */ + +const { execSync } = require('child_process'); +const path = require('path'); + +try { + // Get the git root directory + const gitRoot = execSync('git rev-parse --show-toplevel', { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }).trim(); + + // Check if we're in a git repository + if (!gitRoot) { + console.log('⚠️ Not in a git repository. Skipping git hooks setup.'); + process.exit(0); + } + + // Get current hooks path + let currentHooksPath; + try { + currentHooksPath = execSync('git config --get core.hooksPath', { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }).trim(); + } catch { + // core.hooksPath not set yet + currentHooksPath = null; + } + + const expectedHooksPath = 'ui/.husky'; + + // Only configure if not already set correctly + if (currentHooksPath !== expectedHooksPath) { + execSync(`git config core.hooksPath "${expectedHooksPath}"`, { + stdio: 'inherit' + }); + console.log('✅ Git hooks configured: core.hooksPath = ui/.husky'); + } else { + console.log('✅ Git hooks already configured correctly'); + } + +} catch (error) { + // Don't fail the installation if git hooks setup fails + console.warn('⚠️ Could not setup git hooks:', error.message); + console.warn(' You may need to run manually: git config core.hooksPath "ui/.husky"'); +} From 0ad5bbf350f2b9b02c62cb11215385c4326c71ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Harley?= Date: Thu, 6 Nov 2025 12:13:10 -0300 Subject: [PATCH 18/26] feat(github): Add GitHub check ensuring repository creation is limited (#8844) Signed-off-by: Mauricio Harley Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Co-authored-by: HugoPBrito --- poetry.lock | 217 ++++++++++-------- prowler/CHANGELOG.md | 1 + prowler/compliance/github/cis_1.0_github.json | 4 +- .../__init__.py | 0 ..._repository_creation_limited.metadata.json | 30 +++ ...rganization_repository_creation_limited.py | 106 +++++++++ .../organization/organization_service.py | 82 ++++++- ...zation_repository_creation_limited_test.py | 170 ++++++++++++++ 8 files changed, 499 insertions(+), 111 deletions(-) create mode 100644 prowler/providers/github/services/organization/organization_repository_creation_limited/__init__.py create mode 100644 prowler/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited.metadata.json create mode 100644 prowler/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited.py create mode 100644 tests/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited_test.py diff --git a/poetry.lock b/poetry.lock index 847a31f878..12bc3ea2de 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "about-time" @@ -4420,106 +4420,127 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" -version = "2024.11.6" +version = "2025.9.18" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, - {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, - {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, - {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, - {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, - {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, - {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, - {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, - {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, - {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, - {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, - {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, - {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, - {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29"}, + {file = "regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444"}, + {file = "regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450"}, + {file = "regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95"}, + {file = "regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07"}, + {file = "regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9"}, + {file = "regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282"}, + {file = "regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459"}, + {file = "regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77"}, + {file = "regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f"}, + {file = "regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d"}, + {file = "regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d"}, + {file = "regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7"}, + {file = "regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e"}, + {file = "regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730"}, + {file = "regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773"}, + {file = "regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788"}, + {file = "regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3"}, + {file = "regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41"}, + {file = "regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096"}, + {file = "regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a"}, + {file = "regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25"}, + {file = "regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478"}, + {file = "regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd"}, + {file = "regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35"}, + {file = "regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4"}, ] [[package]] diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index b7691574b0..49265bf3c7 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `cloudstorage_bucket_logging_enabled` check for GCP provider [(#9091)](https://github.com/prowler-cloud/prowler/pull/9091) - C5 compliance framework for Azure provider [(#9081)](https://github.com/prowler-cloud/prowler/pull/9081) - C5 compliance framework for the GCP provider [(#9097)](https://github.com/prowler-cloud/prowler/pull/9097) +- `organization_repository_creation_limited` check for GitHub provider [(#8844)](https://github.com/prowler-cloud/prowler/pull/8844) - HIPAA compliance framework for the GCP provider [(#8955)](https://github.com/prowler-cloud/prowler/pull/8955) - Add multiple compliance improvements [(#9145)](https://github.com/prowler-cloud/prowler/pull/9145) - Added validation for invalid checks, services, and categories in `load_checks_to_execute` function [(#8971)](https://github.com/prowler-cloud/prowler/pull/8971) diff --git a/prowler/compliance/github/cis_1.0_github.json b/prowler/compliance/github/cis_1.0_github.json index d488b92a00..f016acaabb 100644 --- a/prowler/compliance/github/cis_1.0_github.json +++ b/prowler/compliance/github/cis_1.0_github.json @@ -476,7 +476,9 @@ { "Id": "1.2.2", "Description": "Limit the ability to create repositories to trusted users and teams.", - "Checks": [], + "Checks": [ + "organization_repository_creation_limited" + ], "Attributes": [ { "Section": "1 Source Code", diff --git a/prowler/providers/github/services/organization/organization_repository_creation_limited/__init__.py b/prowler/providers/github/services/organization/organization_repository_creation_limited/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited.metadata.json b/prowler/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited.metadata.json new file mode 100644 index 0000000000..9c83855af4 --- /dev/null +++ b/prowler/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "github", + "CheckID": "organization_repository_creation_limited", + "CheckTitle": "Ensure repository creation is limited to trusted organization members.", + "CheckType": [], + "ServiceName": "organization", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "GitHubOrganization", + "Description": "Ensure that repository creation is restricted so that only trusted owners or specific teams can create new repositories within the organization.", + "Risk": "Allowing all members to create repositories increases the likelihood of shadow repositories, data leakage, or malicious projects being introduced without oversight.", + "RelatedUrl": "https://docs.github.com/en/organizations/managing-organization-settings/restricting-repository-creation-in-your-organization", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Disable repository creation for members or limit it to specific trusted teams by adjusting Member privileges in the organization's settings.", + "Url": "https://docs.github.com/en/organizations/managing-organization-settings/restricting-repository-creation-in-your-organization" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited.py b/prowler/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited.py new file mode 100644 index 0000000000..15e5b4b0aa --- /dev/null +++ b/prowler/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited.py @@ -0,0 +1,106 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportGithub +from prowler.providers.github.services.organization.organization_client import ( + organization_client, +) + + +def _join_human_readable(items: List[str]) -> str: + """Return a simple human readable comma-separated list.""" + if not items: + return "" + if len(items) == 1: + return items[0] + return ", ".join(items[:-1]) + f" and {items[-1]}" + + +class organization_repository_creation_limited(Check): + """Check if repository creation is limited to trusted organization members.""" + + def execute(self) -> List[CheckReportGithub]: + findings = [] + for org in organization_client.organizations.values(): + repo_data = [ + getattr(org, "members_can_create_repositories", None), + getattr(org, "members_can_create_public_repositories", None), + getattr(org, "members_can_create_private_repositories", None), + getattr(org, "members_can_create_internal_repositories", None), + getattr(org, "members_allowed_repository_creation_type", None), + ] + + if all(value is None for value in repo_data): + continue + + report = CheckReportGithub(metadata=self.metadata(), resource=org) + + global_creation = getattr(org, "members_can_create_repositories", None) + public_creation = getattr( + org, "members_can_create_public_repositories", None + ) + private_creation = getattr( + org, "members_can_create_private_repositories", None + ) + internal_creation = getattr( + org, "members_can_create_internal_repositories", None + ) + creation_type = getattr( + org, "members_allowed_repository_creation_type", None + ) + + type_flags = [] + enabled_types = [] + + if global_creation is not None: + if global_creation: + enabled_types.append("repositories of any type") + else: + type_flags.append(False) + + visibility_flags = [ + (public_creation, "public repositories"), + (private_creation, "private repositories"), + (internal_creation, "internal repositories"), + ] + + for flag, label in visibility_flags: + if flag is not None: + type_flags.append(flag) + if flag: + enabled_types.append(label) + + if creation_type: + normalized_type = creation_type.lower() + if normalized_type == "none": + type_flags.append(False) + else: + creation_messages = { + "all": "repositories of any type", + "public": "public repositories", + "private": "private repositories", + "internal": "internal repositories", + "selected": "repositories for selected members or teams", + } + enabled_types.append( + creation_messages.get( + normalized_type, f"{creation_type} repositories" + ) + ) + + restricted = bool(type_flags) and all(flag is False for flag in type_flags) + + if restricted: + report.status = "PASS" + report.status_extended = f"Organization {org.name} has disabled repository creation for members." + else: + report.status = "FAIL" + unique_enabled = list(dict.fromkeys(enabled_types)) + allowed_desc = _join_human_readable(unique_enabled) + if allowed_desc: + report.status_extended = f"Organization {org.name} allows members to create {allowed_desc}." + else: + report.status_extended = f"Organization {org.name} does not have enough data to confirm repository creation restrictions." + + findings.append(report) + + return findings diff --git a/prowler/providers/github/services/organization/organization_service.py b/prowler/providers/github/services/organization/organization_service.py index 484ebcd378..2f7e9bc4ea 100644 --- a/prowler/providers/github/services/organization/organization_service.py +++ b/prowler/providers/github/services/organization/organization_service.py @@ -38,8 +38,9 @@ class Organization(GithubService): org_names_to_check = set() try: - for client in getattr(self, "clients", []) or []: - if getattr(self.provider, "organizations", None): + clients = getattr(self, "clients", []) + for client in clients: + if self.provider.organizations: org_names_to_check.update(self.provider.organizations) # If repositories are specified without organizations, don't perform organization checks @@ -147,22 +148,74 @@ class Organization(GithubService): logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + repo_creation_settings = { + "members_can_create_repositories": None, + "members_can_create_public_repositories": None, + "members_can_create_private_repositories": None, + "members_can_create_internal_repositories": None, + "members_allowed_repository_creation_type": None, + } - # Base permission (default repository permission for members) - base_perm: Optional[str] = None - try: - base_perm = getattr(org, "default_repository_permission", None) - except Exception as error: - logger.error( - f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - base_perm = None + def _extract_flag(attribute: str, expected_type: type): + """Return attribute value if it matches expected type and is not a mock placeholder.""" + try: + value = getattr(org, attribute, None) + if hasattr(value, "_mock_parent"): + return None + if expected_type is bool and isinstance(value, bool): + return value + if expected_type is str and isinstance(value, str): + return value + return None + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return None + + repo_creation_settings["members_can_create_repositories"] = _extract_flag( + "members_can_create_repositories", bool + ) + repo_creation_settings["members_can_create_public_repositories"] = ( + _extract_flag("members_can_create_public_repositories", bool) + ) + repo_creation_settings["members_can_create_private_repositories"] = ( + _extract_flag("members_can_create_private_repositories", bool) + ) + repo_creation_settings["members_can_create_internal_repositories"] = ( + _extract_flag("members_can_create_internal_repositories", bool) + ) + repo_creation_settings["members_allowed_repository_creation_type"] = ( + _extract_flag("members_allowed_repository_creation_type", str) + ) + + base_permission_raw = _extract_flag("default_repository_permission", str) + base_permission = ( + base_permission_raw.lower() + if isinstance(base_permission_raw, str) + else None + ) organizations[org.id] = Org( id=org.id, name=org.login, mfa_required=require_mfa, - base_permission=base_perm, + members_can_create_repositories=repo_creation_settings[ + "members_can_create_repositories" + ], + members_can_create_public_repositories=repo_creation_settings[ + "members_can_create_public_repositories" + ], + members_can_create_private_repositories=repo_creation_settings[ + "members_can_create_private_repositories" + ], + members_can_create_internal_repositories=repo_creation_settings[ + "members_can_create_internal_repositories" + ], + members_allowed_repository_creation_type=repo_creation_settings[ + "members_allowed_repository_creation_type" + ], + base_permission=base_permission, ) @@ -172,4 +225,9 @@ class Org(BaseModel): id: int name: str mfa_required: Optional[bool] = False + members_can_create_repositories: Optional[bool] = None + members_can_create_public_repositories: Optional[bool] = None + members_can_create_private_repositories: Optional[bool] = None + members_can_create_internal_repositories: Optional[bool] = None + members_allowed_repository_creation_type: Optional[str] = None base_permission: Optional[str] = None diff --git a/tests/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited_test.py b/tests/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited_test.py new file mode 100644 index 0000000000..2bacd5dda1 --- /dev/null +++ b/tests/providers/github/services/organization/organization_repository_creation_limited/organization_repository_creation_limited_test.py @@ -0,0 +1,170 @@ +from unittest import mock + +from prowler.providers.github.services.organization.organization_service import Org +from tests.providers.github.github_fixtures import set_mocked_github_provider + + +class Test_organization_repository_creation_limited: + def test_no_organizations(self): + organization_client = mock.MagicMock + organization_client.organizations = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import ( + organization_repository_creation_limited, + ) + + check = organization_repository_creation_limited() + result = check.execute() + assert len(result) == 0 + + def test_repository_creation_disabled_globally(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=None, + members_can_create_repositories=False, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import ( + organization_repository_creation_limited, + ) + + check = organization_repository_creation_limited() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_name == org_name + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Organization {org_name} has disabled repository creation for members." + ) + + def test_repository_creation_allowed_for_members(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=None, + members_can_create_repositories=True, + members_can_create_public_repositories=True, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import ( + organization_repository_creation_limited, + ) + + check = organization_repository_creation_limited() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "public repositories" in result[0].status_extended + assert "repositories of any type" in result[0].status_extended + + def test_repository_creation_disabled_per_visibility(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=None, + members_can_create_public_repositories=False, + members_can_create_private_repositories=False, + members_can_create_internal_repositories=False, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import ( + organization_repository_creation_limited, + ) + + check = organization_repository_creation_limited() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Organization {org_name} has disabled repository creation for members." + ) + + def test_repository_creation_disabled_via_members_allowed_type(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=None, + members_allowed_repository_creation_type="none", + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import ( + organization_repository_creation_limited, + ) + + check = organization_repository_creation_limited() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Organization {org_name} has disabled repository creation for members." + ) From 7e03b423ddcf64e4e96a1852580239c94baf76fa Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:37:38 +0100 Subject: [PATCH 19/26] feat(api): add MongoDBAtlas provider to api (#9167) --- README.md | 2 +- api/CHANGELOG.md | 1 + .../migrations/0055_mongodbatlas_provider.py | 32 ++++++++ api/src/backend/api/models.py | 10 +++ api/src/backend/api/specs/v1.yaml | 80 ++++++++++++++++++- api/src/backend/api/tests/test_utils.py | 8 ++ api/src/backend/api/tests/test_views.py | 46 +++++++++-- api/src/backend/api/utils.py | 16 +++- .../api/v1/serializer_utils/providers.py | 15 ++++ api/src/backend/api/v1/serializers.py | 10 +++ api/src/backend/conftest.py | 17 +++- docs/introduction.mdx | 4 +- prowler/CHANGELOG.md | 1 + .../mongodbatlas/mongodbatlas_provider.py | 6 +- 14 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 api/src/backend/api/migrations/0055_mongodbatlas_provider.py diff --git a/README.md b/README.md index bda691722b..9791d93e4e 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ prowler dashboard | M365 | 70 | 7 | 3 | 2 | Official | UI, API, CLI | | OCI | 51 | 13 | 1 | 10 | Official | UI, API, CLI | | IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI | -| MongoDB Atlas | 10 | 3 | 0 | 0 | Official | CLI | +| MongoDB Atlas | 10 | 3 | 0 | 0 | Official | CLI, API | | LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI | | NHN | 6 | 2 | 1 | 0 | Unofficial | CLI | diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 5fcf573805..5e4720bcf9 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the **Prowler API** are documented in this file. - Support muting findings based on simple rules with custom reason [(#9051)](https://github.com/prowler-cloud/prowler/pull/9051) - Support C5 compliance framework for the GCP provider [(#9097)](https://github.com/prowler-cloud/prowler/pull/9097) - Support for Amazon Bedrock and OpenAI compatible providers in Lighthouse AI [(#8957)](https://github.com/prowler-cloud/prowler/pull/8957) +- Support for MongoDB Atlas provider [(#9167)](https://github.com/prowler-cloud/prowler/pull/9167) --- diff --git a/api/src/backend/api/migrations/0055_mongodbatlas_provider.py b/api/src/backend/api/migrations/0055_mongodbatlas_provider.py new file mode 100644 index 0000000000..beb7a3946a --- /dev/null +++ b/api/src/backend/api/migrations/0055_mongodbatlas_provider.py @@ -0,0 +1,32 @@ +# Generated by Django 5.1.13 on 2025-11-05 08:37 + +from django.db import migrations + +import api.db_utils + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0054_iac_provider"), + ] + + operations = [ + migrations.AlterField( + model_name="provider", + name="provider", + field=api.db_utils.ProviderEnumField( + choices=[ + ("aws", "AWS"), + ("azure", "Azure"), + ("gcp", "GCP"), + ("kubernetes", "Kubernetes"), + ("m365", "M365"), + ("github", "GitHub"), + ("mongodbatlas", "MongoDB Atlas"), + ("iac", "IaC"), + ("oraclecloud", "Oracle Cloud Infrastructure"), + ], + default="aws", + ), + ), + ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index 304275f137..7ca9594d5e 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -284,6 +284,7 @@ class Provider(RowLevelSecurityProtectedModel): KUBERNETES = "kubernetes", _("Kubernetes") M365 = "m365", _("M365") GITHUB = "github", _("GitHub") + MONGODBATLAS = "mongodbatlas", _("MongoDB Atlas") IAC = "iac", _("IaC") ORACLECLOUD = "oraclecloud", _("Oracle Cloud Infrastructure") @@ -381,6 +382,15 @@ class Provider(RowLevelSecurityProtectedModel): pointer="/data/attributes/uid", ) + @staticmethod + def validate_mongodbatlas_uid(value): + if not re.match(r"^[0-9a-fA-F]{24}$", value): + raise ModelValidationError( + detail="MongoDB Atlas organization ID must be a 24-character hexadecimal string.", + code="mongodbatlas-uid", + pointer="/data/attributes/uid", + ) + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) inserted_at = models.DateTimeField(auto_now_add=True, editable=False) updated_at = models.DateTimeField(auto_now=True, editable=False) diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index 6e81e5f81b..e44dba0084 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -875,6 +875,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -886,6 +887,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -900,6 +902,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -912,6 +915,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -1409,6 +1413,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -1420,6 +1425,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -1434,6 +1440,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -1446,6 +1453,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -1851,6 +1859,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -1862,6 +1871,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -1876,6 +1886,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -1888,6 +1899,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -2291,6 +2303,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -2302,6 +2315,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -2316,6 +2330,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -2328,6 +2343,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -2719,6 +2735,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -2730,6 +2747,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -2744,6 +2762,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -2756,6 +2775,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -4551,6 +4571,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -4562,6 +4583,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -4576,6 +4598,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -4588,6 +4611,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -4735,6 +4759,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -4746,6 +4771,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -4760,6 +4786,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -4772,6 +4799,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -4985,6 +5013,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -4996,6 +5025,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -5010,6 +5040,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -5022,6 +5053,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -5715,6 +5747,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -5726,6 +5759,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -5740,6 +5774,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -5752,6 +5787,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `oraclecloud` - Oracle Cloud Infrastructure explode: false style: form @@ -6404,6 +6440,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -6415,6 +6452,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -6429,6 +6467,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -6441,6 +6480,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -6776,6 +6816,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -6787,6 +6828,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -6801,6 +6843,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -6813,6 +6856,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -7049,6 +7093,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -7060,6 +7105,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -7074,6 +7120,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -7086,6 +7133,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -7328,6 +7376,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -7339,6 +7388,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -7353,6 +7403,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -7365,6 +7416,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -8170,6 +8222,7 @@ paths: - azure - gcp - github + - mongodbatlas - iac - kubernetes - m365 @@ -8181,6 +8234,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure - in: query @@ -8195,6 +8249,7 @@ paths: - azure - gcp - github + - mongodbatlas - kubernetes - m365 - oraclecloud @@ -8207,6 +8262,7 @@ paths: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `iac` - IaC * `oraclecloud` - Oracle Cloud Infrastructure explode: false @@ -14229,7 +14285,7 @@ components: type: string description: GitHub OAuth App token for authentication. required: - - oauth_app_token + - oauth_app_token - type: object title: GitHub App Credentials properties: @@ -14242,6 +14298,18 @@ components: required: - github_app_id - github_app_key + - type: object + title: MongoDB Atlas API Key + properties: + atlas_public_key: + type: string + description: MongoDB Atlas API public key. + atlas_private_key: + type: string + description: MongoDB Atlas API private key. + required: + - atlas_public_key + - atlas_private_key - type: object title: IaC Repository Credentials properties: @@ -15280,6 +15348,7 @@ components: - kubernetes - m365 - github + - mongodbatlas - oraclecloud type: string description: |- @@ -15289,6 +15358,7 @@ components: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `oraclecloud` - Oracle Cloud Infrastructure x-spec-enum-id: 7087703783970b51 uid: @@ -15402,6 +15472,7 @@ components: - kubernetes - m365 - github + - mongodbatlas - oraclecloud type: string x-spec-enum-id: 7087703783970b51 @@ -15414,12 +15485,13 @@ components: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `oraclecloud` - Oracle Cloud Infrastructure uid: type: string title: Unique identifier for the provider, set by the provider description: Unique identifier for the provider, set by the provider, - e.g. AWS account ID, Azure subscription ID, GCP project ID, etc. + e.g. AWS account ID, Azure subscription ID, GCP project ID, MongoDB Atlas organization ID, etc. maxLength: 250 minLength: 3 required: @@ -15458,6 +15530,7 @@ components: - kubernetes - m365 - github + - mongodbatlas - oraclecloud type: string x-spec-enum-id: 7087703783970b51 @@ -15470,13 +15543,14 @@ components: * `kubernetes` - Kubernetes * `m365` - M365 * `github` - GitHub + * `mongodbatlas` - MongoDB Atlas * `oraclecloud` - Oracle Cloud Infrastructure uid: type: string minLength: 3 title: Unique identifier for the provider, set by the provider description: Unique identifier for the provider, set by the provider, - e.g. AWS account ID, Azure subscription ID, GCP project ID, etc. + e.g. AWS account ID, Azure subscription ID, GCP project ID, MongoDB Atlas organization ID, etc. maxLength: 250 required: - uid diff --git a/api/src/backend/api/tests/test_utils.py b/api/src/backend/api/tests/test_utils.py index 02378eb11b..d67df93ff5 100644 --- a/api/src/backend/api/tests/test_utils.py +++ b/api/src/backend/api/tests/test_utils.py @@ -20,8 +20,10 @@ from prowler.providers.aws.aws_provider import AwsProvider from prowler.providers.aws.lib.security_hub.security_hub import SecurityHubConnection from prowler.providers.azure.azure_provider import AzureProvider from prowler.providers.gcp.gcp_provider import GcpProvider +from prowler.providers.github.github_provider import GithubProvider from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider from prowler.providers.m365.m365_provider import M365Provider +from prowler.providers.mongodbatlas.mongodbatlas_provider import MongodbatlasProvider from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider @@ -109,6 +111,8 @@ class TestReturnProwlerProvider: (Provider.ProviderChoices.AZURE.value, AzureProvider), (Provider.ProviderChoices.KUBERNETES.value, KubernetesProvider), (Provider.ProviderChoices.M365.value, M365Provider), + (Provider.ProviderChoices.GITHUB.value, GithubProvider), + (Provider.ProviderChoices.MONGODBATLAS.value, MongodbatlasProvider), (Provider.ProviderChoices.ORACLECLOUD.value, OraclecloudProvider), ], ) @@ -209,6 +213,10 @@ class TestGetProwlerProviderKwargs: Provider.ProviderChoices.ORACLECLOUD.value, {}, ), + ( + Provider.ProviderChoices.MONGODBATLAS.value, + {"atlas_organization_id": "provider_uid"}, + ), ], ) def test_get_prowler_provider_kwargs(self, provider_type, expected_extra_kwargs): diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 82db438d44..8822bd9f12 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -1153,6 +1153,11 @@ class TestProviderViewSet: "uid": "https://gitlab.com/user/project", "alias": "GitLab Repo", }, + { + "provider": "mongodbatlas", + "uid": "64b1d3c0e4b03b1234567890", + "alias": "Atlas Organization", + }, ] ), ) @@ -1329,6 +1334,24 @@ class TestProviderViewSet: "iac-uid", "uid", ), + ( + { + "provider": "mongodbatlas", + "uid": "64b1d3c0e4b03b123456789g", + "alias": "test", + }, + "mongodbatlas-uid", + "uid", + ), + ( + { + "provider": "mongodbatlas", + "uid": "1234", + "alias": "test", + }, + "mongodbatlas-uid", + "uid", + ), ] ), ) @@ -1502,22 +1525,22 @@ class TestProviderViewSet: ( "uid.icontains", "1", - 6, - ), # Updated: includes OCI provider with "1" in UID + 7, + ), ("alias", "aws_testing_1", 1), ("alias.icontains", "aws", 2), - ("inserted_at", TODAY, 7), # Updated: 7 providers now (added OCI) + ("inserted_at", TODAY, 8), ( "inserted_at.gte", "2024-01-01", - 7, - ), # Updated: 7 providers now (added OCI) + 8, + ), ("inserted_at.lte", "2024-01-01", 0), ( "updated_at.gte", "2024-01-01", - 7, - ), # Updated: 7 providers now (added OCI) + 8, + ), ("updated_at.lte", "2024-01-01", 0), ] ), @@ -2057,6 +2080,15 @@ class TestProviderSecretViewSet: "pass_phrase": "my-secure-passphrase", }, ), + # MongoDB Atlas credentials + ( + Provider.ProviderChoices.MONGODBATLAS.value, + ProviderSecret.TypeChoices.STATIC, + { + "atlas_public_key": "public-key", + "atlas_private_key": "private-key", + }, + ), ], ) def test_provider_secrets_create_valid( diff --git a/api/src/backend/api/utils.py b/api/src/backend/api/utils.py index c7db08435b..958f24b3f0 100644 --- a/api/src/backend/api/utils.py +++ b/api/src/backend/api/utils.py @@ -21,6 +21,7 @@ from prowler.providers.github.github_provider import GithubProvider from prowler.providers.iac.iac_provider import IacProvider from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider from prowler.providers.m365.m365_provider import M365Provider +from prowler.providers.mongodbatlas.mongodbatlas_provider import MongodbatlasProvider from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider @@ -70,6 +71,7 @@ def return_prowler_provider( | IacProvider | KubernetesProvider | M365Provider + | MongodbatlasProvider | OraclecloudProvider ]: """Return the Prowler provider class based on the given provider type. @@ -78,7 +80,7 @@ def return_prowler_provider( provider (Provider): The provider object containing the provider type and associated secrets. Returns: - AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OraclecloudProvider: The corresponding provider class. + AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OraclecloudProvider | MongodbatlasProvider: The corresponding provider class. Raises: ValueError: If the provider type specified in `provider.provider` is not supported. @@ -96,6 +98,8 @@ def return_prowler_provider( prowler_provider = M365Provider case Provider.ProviderChoices.GITHUB.value: prowler_provider = GithubProvider + case Provider.ProviderChoices.MONGODBATLAS.value: + prowler_provider = MongodbatlasProvider case Provider.ProviderChoices.IAC.value: prowler_provider = IacProvider case Provider.ProviderChoices.ORACLECLOUD.value: @@ -146,6 +150,11 @@ def get_prowler_provider_kwargs( prowler_provider_kwargs["oauth_app_token"] = provider.secret.secret[ "access_token" ] + elif provider.provider == Provider.ProviderChoices.MONGODBATLAS.value: + prowler_provider_kwargs = { + **prowler_provider_kwargs, + "atlas_organization_id": provider.uid, + } if mutelist_processor: mutelist_content = mutelist_processor.configuration.get("Mutelist", {}) @@ -166,6 +175,7 @@ def initialize_prowler_provider( | IacProvider | KubernetesProvider | M365Provider + | MongodbatlasProvider | OraclecloudProvider ): """Initialize a Prowler provider instance based on the given provider type. @@ -175,8 +185,8 @@ def initialize_prowler_provider( mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration. Returns: - AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OciProvider: An instance of the corresponding provider class - (`AwsProvider`, `AzureProvider`, `GcpProvider`, `GithubProvider`, `IacProvider`, `KubernetesProvider`, `M365Provider` or `OraclecloudProvider`) initialized with the + AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OraclecloudProvider | MongodbatlasProvider: An instance of the corresponding provider class + (`AwsProvider`, `AzureProvider`, `GcpProvider`, `GithubProvider`, `IacProvider`, `KubernetesProvider`, `M365Provider`, `OraclecloudProvider` or `MongodbatlasProvider`) initialized with the provider's secrets. """ prowler_provider = return_prowler_provider(provider) diff --git a/api/src/backend/api/v1/serializer_utils/providers.py b/api/src/backend/api/v1/serializer_utils/providers.py index 1965a16cb5..4ec772e02f 100644 --- a/api/src/backend/api/v1/serializer_utils/providers.py +++ b/api/src/backend/api/v1/serializer_utils/providers.py @@ -289,6 +289,21 @@ from rest_framework_json_api import serializers }, "required": ["user", "fingerprint", "tenancy", "region"], }, + { + "type": "object", + "title": "MongoDB Atlas API Key", + "properties": { + "atlas_public_key": { + "type": "string", + "description": "MongoDB Atlas API public key.", + }, + "atlas_private_key": { + "type": "string", + "description": "MongoDB Atlas API private key.", + }, + }, + "required": ["atlas_public_key", "atlas_private_key"], + }, ] } ) diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index 682d94f798..c79d305942 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -1370,6 +1370,8 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer): serializer = M365ProviderSecret(data=secret) elif provider_type == Provider.ProviderChoices.ORACLECLOUD.value: serializer = OracleCloudProviderSecret(data=secret) + elif provider_type == Provider.ProviderChoices.MONGODBATLAS.value: + serializer = MongoDBAtlasProviderSecret(data=secret) else: raise serializers.ValidationError( {"provider": f"Provider type not supported {provider_type}"} @@ -1466,6 +1468,14 @@ class GCPServiceAccountProviderSecret(serializers.Serializer): resource_name = "provider-secrets" +class MongoDBAtlasProviderSecret(serializers.Serializer): + atlas_public_key = serializers.CharField() + atlas_private_key = serializers.CharField() + + class Meta: + resource_name = "provider-secrets" + + class KubernetesProviderSecret(serializers.Serializer): kubeconfig_content = serializers.CharField() diff --git a/api/src/backend/conftest.py b/api/src/backend/conftest.py index 4e35b5c83f..d5fa21a787 100644 --- a/api/src/backend/conftest.py +++ b/api/src/backend/conftest.py @@ -506,8 +506,23 @@ def providers_fixture(tenants_fixture): alias="oci_testing", tenant_id=tenant.id, ) + provider8 = Provider.objects.create( + provider="mongodbatlas", + uid="64b1d3c0e4b03b1234567890", + alias="mongodbatlas_testing", + tenant_id=tenant.id, + ) - return provider1, provider2, provider3, provider4, provider5, provider6, provider7 + return ( + provider1, + provider2, + provider3, + provider4, + provider5, + provider6, + provider7, + provider8, + ) @pytest.fixture diff --git a/docs/introduction.mdx b/docs/introduction.mdx index aa56177351..616ba34f53 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -33,7 +33,7 @@ The supported providers right now are: | [Github](/user-guide/providers/github/getting-started-github) | Official | UI, API, CLI | | [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | UI, API, CLI | | [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | UI, API, CLI | -| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | CLI | +| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | CLI, API | | [LLM](/user-guide/providers/llm/getting-started-llm) | Official | CLI | | **NHN** | Unofficial | CLI | @@ -48,4 +48,4 @@ For more information about the checks and compliance of each provider visit [Pro Interested in contributing to Prowler? - \ No newline at end of file + diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 49265bf3c7..da6a8a336e 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - C5 compliance framework for the GCP provider [(#9097)](https://github.com/prowler-cloud/prowler/pull/9097) - `organization_repository_creation_limited` check for GitHub provider [(#8844)](https://github.com/prowler-cloud/prowler/pull/8844) - HIPAA compliance framework for the GCP provider [(#8955)](https://github.com/prowler-cloud/prowler/pull/8955) +- Add organization ID parameter for MongoDB Atlas provider [(#9167)](https://github.com/prowler-cloud/prowler/pull/9167) - Add multiple compliance improvements [(#9145)](https://github.com/prowler-cloud/prowler/pull/9145) - Added validation for invalid checks, services, and categories in `load_checks_to_execute` function [(#8971)](https://github.com/prowler-cloud/prowler/pull/8971) diff --git a/prowler/providers/mongodbatlas/mongodbatlas_provider.py b/prowler/providers/mongodbatlas/mongodbatlas_provider.py index 82e48eadff..b379e4ab9f 100644 --- a/prowler/providers/mongodbatlas/mongodbatlas_provider.py +++ b/prowler/providers/mongodbatlas/mongodbatlas_provider.py @@ -54,6 +54,7 @@ class MongodbatlasProvider(Provider): mutelist_content: dict = None, # Optional filters atlas_project_id: str = None, + atlas_organization_id: str = None, ): """ MongoDB Atlas Provider constructor @@ -67,6 +68,7 @@ class MongodbatlasProvider(Provider): mutelist_path: Path to the mutelist file mutelist_content: Mutelist content atlas_project_id: Project ID to filter + atlas_organization_id: Organization ID """ logger.info("Instantiating MongoDB Atlas Provider...") @@ -79,6 +81,7 @@ class MongodbatlasProvider(Provider): # Store filter options self._project_id = atlas_project_id + self._organization_id = atlas_organization_id # Audit Config if config_content: @@ -292,6 +295,7 @@ class MongodbatlasProvider(Provider): atlas_public_key: str = "", atlas_private_key: str = "", raise_on_exception: bool = True, + provider_id: str = None, ) -> Connection: """ Test connection to MongoDB Atlas @@ -300,7 +304,7 @@ class MongodbatlasProvider(Provider): atlas_public_key: MongoDB Atlas API public key atlas_private_key: MongoDB Atlas API private key raise_on_exception: Whether to raise exceptions - + provider_id: MongoDB Atlas project ID to validate access (added for API compatibility) Returns: Connection: Connection status """ From 7d5c4d32eef08fa6ccffdfd30a7e827a05a89417 Mon Sep 17 00:00:00 2001 From: UniCode Date: Fri, 7 Nov 2025 03:25:16 +0530 Subject: [PATCH 20/26] feat(aws): add compliance NIST CSF 2.0 (#9185) Co-authored-by: MrCloudSec --- prowler/CHANGELOG.md | 1 + prowler/compliance/aws/nist_csf_2.0_aws.json | 1612 ++++++++++++++++++ 2 files changed, 1613 insertions(+) create mode 100644 prowler/compliance/aws/nist_csf_2.0_aws.json diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index da6a8a336e..ffb83b7bb5 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add organization ID parameter for MongoDB Atlas provider [(#9167)](https://github.com/prowler-cloud/prowler/pull/9167) - Add multiple compliance improvements [(#9145)](https://github.com/prowler-cloud/prowler/pull/9145) - Added validation for invalid checks, services, and categories in `load_checks_to_execute` function [(#8971)](https://github.com/prowler-cloud/prowler/pull/8971) +- NIST CSF 2.0 compliance framework for the AWS provider [(#9185)](https://github.com/prowler-cloud/prowler/pull/9185) ### Changed - Update AWS Direct Connect service metadata to new format [(#8855)](https://github.com/prowler-cloud/prowler/pull/8855) diff --git a/prowler/compliance/aws/nist_csf_2.0_aws.json b/prowler/compliance/aws/nist_csf_2.0_aws.json new file mode 100644 index 0000000000..3430763879 --- /dev/null +++ b/prowler/compliance/aws/nist_csf_2.0_aws.json @@ -0,0 +1,1612 @@ +{ + "Framework": "NIST-CSF", + "Name": "National Institute of Standards and Technology (NIST) Cybersecurity Framework (CSF) v2.0", + "Version": "2.0", + "Provider": "AWS", + "Description": "The NIST Cybersecurity Framework (CSF) 2.0 offers a taxonomy of high-level cybersecurity outcomes that can be used by any organization regardless of its size, sector, or maturity to better understand, assess, prioritize, and communicate its cybersecurity efforts. The framework's core functions are organized into six key areas: Govern (new), Identify, Protect, Detect, Respond, and Recover. Together, these functions provide a comprehensive lifecycle approach to managing cybersecurity risk.", + "Requirements": [ + { + "Id": "oc_1", + "Name": "GV.OC-1", + "Description": "The organization's role in the supply chain is identified and communicated.", + "Attributes": [ + { + "ItemId": "oc_1", + "Section": "Govern (GV)", + "SubSection": "Organizational Context (GV.OC)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "oc_2", + "Name": "GV.OC-2", + "Description": "Legal, regulatory, and contractual requirements regarding cybersecurity — including privacy and civil liberties obligations — are understood and managed.", + "Attributes": [ + { + "ItemId": "oc_2", + "Section": "Govern (GV)", + "SubSection": "Organizational Context (GV.OC)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "oc_3", + "Name": "GV.OC-3", + "Description": "The organization's place in critical infrastructure and its industry sector is identified and communicated.", + "Attributes": [ + { + "ItemId": "oc_3", + "Section": "Govern (GV)", + "SubSection": "Organizational Context (GV.OC)", + "Service": "aws" + } + ], + "Checks": [ + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "rm_1", + "Name": "GV.RM-1", + "Description": "Organizational cybersecurity risk management strategy is established, communicated, and maintained.", + "Attributes": [ + { + "ItemId": "rm_1", + "Section": "Govern (GV)", + "SubSection": "Risk Management Strategy (GV.RM)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "rm_2", + "Name": "GV.RM-2", + "Description": "Organizational risk tolerance is determined and clearly expressed.", + "Attributes": [ + { + "ItemId": "rm_2", + "Section": "Govern (GV)", + "SubSection": "Risk Management Strategy (GV.RM)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "rm_3", + "Name": "GV.RM-3", + "Description": "The organization's determination of risk tolerance is informed by its role in critical infrastructure, sector-specific risk analysis, or organizational risk analysis.", + "Attributes": [ + { + "ItemId": "rm_3", + "Section": "Govern (GV)", + "SubSection": "Risk Management Strategy (GV.RM)", + "Service": "aws" + } + ], + "Checks": [ + "config_recorder_all_regions_enabled", + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "sc_1", + "Name": "GV.SC-1", + "Description": "Cybersecurity supply chain risk management processes are identified, established, assessed, managed, and agreed to by organizational stakeholders.", + "Attributes": [ + { + "ItemId": "sc_1", + "Section": "Govern (GV)", + "SubSection": "Cybersecurity Supply Chain Risk Management (GV.SC)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "config_recorder_all_regions_enabled", + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "sc_2", + "Name": "GV.SC-2", + "Description": "Supply chain risk management processes are established, managed, and agreed to by organizational stakeholders.", + "Attributes": [ + { + "ItemId": "sc_2", + "Section": "Govern (GV)", + "SubSection": "Cybersecurity Supply Chain Risk Management (GV.SC)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "sc_3", + "Name": "GV.SC-3", + "Description": "Suppliers and third-party partners are evaluated, assessed, and monitored using cybersecurity supply chain risk management processes.", + "Attributes": [ + { + "ItemId": "sc_3", + "Section": "Govern (GV)", + "SubSection": "Cybersecurity Supply Chain Risk Management (GV.SC)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "config_recorder_all_regions_enabled", + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "sc_4", + "Name": "GV.SC-4", + "Description": "Supply chain risk management processes are continuously improved.", + "Attributes": [ + { + "ItemId": "sc_4", + "Section": "Govern (GV)", + "SubSection": "Cybersecurity Supply Chain Risk Management (GV.SC)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "rr_1", + "Name": "GV.RR-1", + "Description": "Cybersecurity roles and responsibilities are coordinated and aligned with internal roles and external partners.", + "Attributes": [ + { + "ItemId": "rr_1", + "Section": "Govern (GV)", + "SubSection": "Roles, Responsibilities, and Authorities (GV.RR)", + "Service": "iam" + } + ], + "Checks": [ + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_inline_policy_no_administrative_privileges", + "iam_no_root_access_key", + "cloudtrail_multi_region_enabled" + ] + }, + { + "Id": "rr_2", + "Name": "GV.RR-2", + "Description": "Cybersecurity responsibilities are established and communicated.", + "Attributes": [ + { + "ItemId": "rr_2", + "Section": "Govern (GV)", + "SubSection": "Roles, Responsibilities, and Authorities (GV.RR)", + "Service": "iam" + } + ], + "Checks": [ + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_inline_policy_no_administrative_privileges", + "cloudtrail_multi_region_enabled" + ] + }, + { + "Id": "rr_3", + "Name": "GV.RR-3", + "Description": "Senior executives are accountable for cybersecurity risk.", + "Attributes": [ + { + "ItemId": "rr_3", + "Section": "Govern (GV)", + "SubSection": "Roles, Responsibilities, and Authorities (GV.RR)", + "Service": "aws" + } + ], + "Checks": [ + "iam_root_hardware_mfa_enabled", + "iam_root_mfa_enabled", + "iam_no_root_access_key", + "cloudtrail_multi_region_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "po_1", + "Name": "GV.PO-1", + "Description": "Cybersecurity policy is established, communicated, and enforced.", + "Attributes": [ + { + "ItemId": "po_1", + "Section": "Govern (GV)", + "SubSection": "Policies, Processes, and Procedures (GV.PO)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "iam_password_policy_reuse_24", + "securityhub_enabled" + ] + }, + { + "Id": "po_2", + "Name": "GV.PO-2", + "Description": "Cybersecurity policy is updated as needed to reflect changes in organizational priorities, threat landscape, or technology.", + "Attributes": [ + { + "ItemId": "po_2", + "Section": "Govern (GV)", + "SubSection": "Policies, Processes, and Procedures (GV.PO)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "config_recorder_all_regions_enabled", + "cloudwatch_log_metric_filter_policy_changes" + ] + }, + { + "Id": "po_3", + "Name": "GV.PO-3", + "Description": "Legal and regulatory requirements regarding cybersecurity, including privacy and civil liberties obligations, are understood and managed.", + "Attributes": [ + { + "ItemId": "po_3", + "Section": "Govern (GV)", + "SubSection": "Policies, Processes, and Procedures (GV.PO)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "po_4", + "Name": "GV.PO-4", + "Description": "Governance and risk management processes address cybersecurity risks.", + "Attributes": [ + { + "ItemId": "po_4", + "Section": "Govern (GV)", + "SubSection": "Policies, Processes, and Procedures (GV.PO)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "po_5", + "Name": "GV.PO-5", + "Description": "Processes for managing cybersecurity requirements for suppliers and third-party partners are established.", + "Attributes": [ + { + "ItemId": "po_5", + "Section": "Govern (GV)", + "SubSection": "Policies, Processes, and Procedures (GV.PO)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ov_1", + "Name": "GV.OV-1", + "Description": "Cybersecurity risk management strategy outcomes are reviewed to inform and adjust organizational priorities.", + "Attributes": [ + { + "ItemId": "ov_1", + "Section": "Govern (GV)", + "SubSection": "Oversight (GV.OV)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ov_2", + "Name": "GV.OV-2", + "Description": "Cybersecurity risk management performance is measured and reported.", + "Attributes": [ + { + "ItemId": "ov_2", + "Section": "Govern (GV)", + "SubSection": "Oversight (GV.OV)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "config_recorder_all_regions_enabled", + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ov_3", + "Name": "GV.OV-3", + "Description": "Cybersecurity risk management strategy and practices are reviewed and adjusted to adapt to changes in the threat landscape, technologies, or mission, business, or system environments.", + "Attributes": [ + { + "ItemId": "ov_3", + "Section": "Govern (GV)", + "SubSection": "Oversight (GV.OV)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "config_recorder_all_regions_enabled", + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ov_4", + "Name": "GV.OV-4", + "Description": "Cybersecurity risk management processes are reviewed to ensure they meet legal and regulatory requirements.", + "Attributes": [ + { + "ItemId": "ov_4", + "Section": "Govern (GV)", + "SubSection": "Oversight (GV.OV)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "config_recorder_all_regions_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "am_1", + "Name": "ID.AM-1", + "Description": "Physical devices and systems within the organization are inventoried.", + "Attributes": [ + { + "ItemId": "am_1", + "Section": "Identify (ID)", + "SubSection": "Asset Management (ID.AM)", + "Service": "aws" + } + ], + "Checks": [ + "config_recorder_all_regions_enabled", + "ec2_instance_managed_by_ssm" + ] + }, + { + "Id": "am_2", + "Name": "ID.AM-2", + "Description": "Software platforms and applications within the organization are inventoried.", + "Attributes": [ + { + "ItemId": "am_2", + "Section": "Identify (ID)", + "SubSection": "Asset Management (ID.AM)", + "Service": "aws" + } + ], + "Checks": [ + "ec2_instance_managed_by_ssm", + "ssm_managed_compliant_patching" + ] + }, + { + "Id": "am_3", + "Name": "ID.AM-3", + "Description": "Organizational communication and data flows are mapped.", + "Attributes": [ + { + "ItemId": "am_3", + "Section": "Identify (ID)", + "SubSection": "Asset Management (ID.AM)", + "Service": "aws" + } + ], + "Checks": [ + "apigateway_restapi_logging_enabled", + "cloudtrail_multi_region_enabled", + "elbv2_logging_enabled", + "elb_logging_enabled", + "redshift_cluster_audit_logging", + "s3_bucket_server_access_logging_enabled", + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "am_5", + "Name": "ID.AM-5", + "Description": "Resources (e.g., hardware, devices, data, time, personnel, and software) are prioritized based on their classification, criticality, and business value.", + "Attributes": [ + { + "ItemId": "am_5", + "Section": "Identify (ID)", + "SubSection": "Asset Management (ID.AM)", + "Service": "aws" + } + ], + "Checks": [] + }, + { + "Id": "am_6", + "Name": "ID.AM-6", + "Description": "Cybersecurity roles and responsibilities for the entire workforce and third-party stakeholders (e.g., suppliers, customers, partners) are established.", + "Attributes": [ + { + "ItemId": "am_6", + "Section": "Identify (ID)", + "SubSection": "Asset Management (ID.AM)", + "Service": "iam" + } + ], + "Checks": [] + }, + { + "Id": "be_5", + "Name": "ID.BE-5", + "Description": "Resilience requirements to support delivery of critical services are established for all operating states (e.g. under duress/attack, during recovery, normal operations)", + "Attributes": [ + { + "ItemId": "be_5", + "Section": "Identify (ID)", + "SubSection": "Business Environment (ID.BE)", + "Service": "aws" + } + ], + "Checks": [ + "elbv2_deletion_protection", + "rds_instance_backup_enabled", + "rds_instance_multi_az", + "s3_bucket_object_versioning" + ] + }, + { + "Id": "ra_1", + "Name": "ID.RA-1", + "Description": "Asset vulnerabilities are identified and documented.", + "Attributes": [ + { + "ItemId": "ra_1", + "Section": "Identify (ID)", + "SubSection": "Risk Assessment (ID.RA)", + "Service": "aws" + } + ], + "Checks": [ + "guardduty_is_enabled", + "securityhub_enabled", + "ssm_managed_compliant_patching" + ] + }, + { + "Id": "ra_2", + "Name": "ID.RA-2", + "Description": "Cyber threat intelligence is received from information sharing forums and sources.", + "Attributes": [ + { + "ItemId": "ra_2", + "Section": "Identify (ID)", + "SubSection": "Risk Assessment (ID.RA)", + "Service": "aws" + } + ], + "Checks": [ + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ra_3", + "Name": "ID.RA-3", + "Description": "Threats, both internal and external, are identified and documented.", + "Attributes": [ + { + "ItemId": "ra_3", + "Section": "Identify (ID)", + "SubSection": "Risk Assessment (ID.RA)", + "Service": "aws" + } + ], + "Checks": [ + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ra_5", + "Name": "ID.RA-5", + "Description": "Threats, vulnerabilities, likelihoods, and impacts are used to determine risk.", + "Attributes": [ + { + "ItemId": "ra_5", + "Section": "Identify (ID)", + "SubSection": "Risk Assessment (ID.RA)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_cloudwatch_logging_enabled", + "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured", + "config_recorder_all_regions_enabled", + "ec2_instance_imdsv2_enabled", + "guardduty_is_enabled", + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_sign_in_without_mfa", + "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", + "cloudwatch_log_metric_filter_policy_changes", + "cloudwatch_log_metric_filter_root_usage", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "rds_instance_enhanced_monitoring_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "id_sc_4", + "Name": "ID.SC-4", + "Description": "Suppliers and third-party partners are routinely assessed using audits, test results, or other forms of evaluations to confirm they are meeting their contractual obligations.", + "Attributes": [ + { + "ItemId": "id_sc_4", + "Section": "Identify (ID)", + "SubSection": "Supply Chain Risk Management (ID.SC)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_cloudwatch_logging_enabled", + "config_recorder_all_regions_enabled", + "ec2_instance_imdsv2_enabled", + "guardduty_is_enabled", + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_sign_in_without_mfa", + "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", + "cloudwatch_log_metric_filter_policy_changes", + "cloudwatch_log_metric_filter_root_usage", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "rds_instance_enhanced_monitoring_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ac_1", + "Name": "PR.AC-1", + "Description": "Identities and credentials are issued, managed, verified, revoked, and audited for authorized devices, users and processes.", + "Attributes": [ + { + "ItemId": "ac_1", + "Section": "Protect (PR)", + "SubSection": "Identity Management and Access Control (PR.AC)", + "Service": "aws" + } + ], + "Checks": [ + "iam_password_policy_reuse_24", + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_inline_policy_no_administrative_privileges", + "iam_no_root_access_key", + "iam_rotate_access_key_90_days", + "iam_user_accesskey_unused", + "iam_user_console_access_unused", + "secretsmanager_automatic_rotation_enabled" + ] + }, + { + "Id": "ac_3", + "Name": "PR.AC-3", + "Description": "Remote access is managed.", + "Attributes": [ + { + "ItemId": "ac_3", + "Section": "Protect (PR)", + "SubSection": "Identity Management and Access Control (PR.AC)", + "Service": "aws" + } + ], + "Checks": [ + "ec2_ebs_public_snapshot", + "ec2_instance_public_ip", + "emr_cluster_master_nodes_no_public_ip", + "iam_root_hardware_mfa_enabled", + "iam_root_mfa_enabled", + "iam_user_mfa_enabled_console_access", + "awslambda_function_not_publicly_accessible", + "awslambda_function_url_public", + "rds_instance_no_public_access", + "rds_snapshots_public_access", + "redshift_cluster_public_access", + "s3_bucket_public_access", + "s3_bucket_policy_public_write_access", + "s3_account_level_public_access_blocks", + "sagemaker_notebook_instance_without_direct_internet_access_configured", + "ec2_securitygroup_default_restrict_traffic", + "ec2_networkacl_allow_ingress_any_port", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + ] + }, + { + "Id": "ac_4", + "Name": "PR.AC-4", + "Description": "Access permissions and authorizations are managed, incorporating the principles of least privilege and separation of duties.", + "Attributes": [ + { + "ItemId": "ac_4", + "Section": "Protect (PR)", + "SubSection": "Identity Management and Access Control (PR.AC)", + "Service": "aws" + } + ], + "Checks": [ + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_inline_policy_no_administrative_privileges", + "iam_no_root_access_key", + "iam_user_accesskey_unused", + "iam_user_console_access_unused" + ] + }, + { + "Id": "ac_5", + "Name": "PR.AC-5", + "Description": "Network integrity is protected (e.g., network segregation, network segmentation).", + "Attributes": [ + { + "ItemId": "ac_5", + "Section": "Protect (PR)", + "SubSection": "Identity Management and Access Control (PR.AC)", + "Service": "aws" + } + ], + "Checks": [ + "acm_certificates_expiration_check", + "ec2_ebs_public_snapshot", + "ec2_instance_public_ip", + "emr_cluster_master_nodes_no_public_ip", + "awslambda_function_not_publicly_accessible", + "awslambda_function_url_public", + "rds_instance_no_public_access", + "rds_snapshots_public_access", + "redshift_cluster_public_access", + "s3_bucket_public_access", + "s3_bucket_policy_public_write_access", + "s3_account_level_public_access_blocks", + "sagemaker_notebook_instance_without_direct_internet_access_configured", + "ec2_securitygroup_default_restrict_traffic", + "ec2_networkacl_allow_ingress_any_port", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + ] + }, + { + "Id": "ac_6", + "Name": "PR.AC-6", + "Description": "Identities are proofed and bound to credentials and asserted in interactions.", + "Attributes": [ + { + "ItemId": "ac_6", + "Section": "Protect (PR)", + "SubSection": "Identity Management and Access Control (PR.AC)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "redshift_cluster_audit_logging", + "s3_bucket_server_access_logging_enabled" + ] + }, + { + "Id": "ac_7", + "Name": "PR.AC-7", + "Description": "Users, devices, and other assets are authenticated (e.g., single-factor, multi-factor) commensurate with the risk of the transaction (e.g., individuals' security and privacy risks and other organizational risks).", + "Attributes": [ + { + "ItemId": "ac_7", + "Section": "Protect (PR)", + "SubSection": "Identity Management and Access Control (PR.AC)", + "Service": "iam" + } + ], + "Checks": [ + "iam_root_hardware_mfa_enabled", + "iam_root_mfa_enabled", + "iam_user_mfa_enabled_console_access" + ] + }, + { + "Id": "ds_1", + "Name": "PR.DS-1", + "Description": "Data-at-rest is protected.", + "Attributes": [ + { + "ItemId": "ds_1", + "Section": "Protect (PR)", + "SubSection": "Data Security (PR.DS)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_kms_encryption_enabled", + "ec2_ebs_volume_encryption", + "efs_encryption_at_rest_enabled", + "opensearch_service_domains_encryption_at_rest_enabled", + "cloudwatch_log_group_kms_encryption_enabled", + "rds_instance_storage_encrypted", + "s3_bucket_default_encryption", + "sagemaker_notebook_instance_encryption_enabled", + "sns_topics_kms_encryption_at_rest_enabled" + ] + }, + { + "Id": "ds_2", + "Name": "PR.DS-2", + "Description": "Data-in-transit is protected.", + "Attributes": [ + { + "ItemId": "ds_2", + "Section": "Protect (PR)", + "SubSection": "Data Security (PR.DS)", + "Service": "aws" + } + ], + "Checks": [ + "acm_certificates_expiration_check", + "elb_ssl_listeners", + "opensearch_service_domains_node_to_node_encryption_enabled", + "s3_bucket_secure_transport_policy" + ] + }, + { + "Id": "ds_3", + "Name": "PR.DS-3", + "Description": "Assets are formally managed throughout removal, transfers, and disposition.", + "Attributes": [ + { + "ItemId": "ds_3", + "Section": "Protect (PR)", + "SubSection": "Data Security (PR.DS)", + "Service": "aws" + } + ], + "Checks": [ + "ec2_instance_managed_by_ssm", + "ssm_managed_compliant_patching", + "ec2_elastic_ip_unassigned" + ] + }, + { + "Id": "ds_4", + "Name": "PR.DS-4", + "Description": "Adequate capacity to ensure availability is maintained.", + "Attributes": [ + { + "ItemId": "ds_4", + "Section": "Protect (PR)", + "SubSection": "Data Security (PR.DS)", + "Service": "aws" + } + ], + "Checks": [ + "elbv2_deletion_protection", + "rds_instance_enhanced_monitoring_enabled", + "rds_instance_backup_enabled", + "rds_instance_multi_az", + "s3_bucket_object_versioning" + ] + }, + { + "Id": "ds_5", + "Name": "PR.DS-5", + "Description": "Protections against data leaks are implemented.", + "Attributes": [ + { + "ItemId": "ds_5", + "Section": "Protect (PR)", + "SubSection": "Data Security (PR.DS)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "ec2_ebs_public_snapshot", + "elbv2_logging_enabled", + "elb_logging_enabled", + "guardduty_is_enabled", + "awslambda_function_url_public", + "rds_instance_no_public_access", + "rds_snapshots_public_access", + "redshift_cluster_public_access", + "s3_bucket_server_access_logging_enabled", + "s3_bucket_public_access", + "s3_bucket_policy_public_write_access", + "s3_account_level_public_access_blocks", + "sagemaker_notebook_instance_without_direct_internet_access_configured", + "securityhub_enabled", + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "ds_6", + "Name": "PR.DS-6", + "Description": "Integrity checking mechanisms are used to verify software, firmware, and information integrity.", + "Attributes": [ + { + "ItemId": "ds_6", + "Section": "Protect (PR)", + "SubSection": "Data Security (PR.DS)", + "Service": "cloudtrail" + } + ], + "Checks": [ + "cloudtrail_log_file_validation_enabled" + ] + }, + { + "Id": "ds_7", + "Name": "PR.DS-7", + "Description": "The development and testing environment(s) are separate from the production environment.", + "Attributes": [ + { + "ItemId": "ds_7", + "Section": "Protect (PR)", + "SubSection": "Data Security (PR.DS)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_log_file_validation_enabled", + "ec2_instance_managed_by_ssm", + "ec2_instance_older_than_specific_days", + "elbv2_deletion_protection", + "ssm_managed_compliant_patching", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + ] + }, + { + "Id": "ds_8", + "Name": "PR.DS-8", + "Description": "Integrity checking mechanisms are used to verify hardware integrity.", + "Attributes": [ + { + "ItemId": "ds_8", + "Section": "Protect (PR)", + "SubSection": "Data Security (PR.DS)", + "Service": "aws" + } + ], + "Checks": [ + "ec2_instance_managed_by_ssm", + "securityhub_enabled" + ] + }, + { + "Id": "ip_1", + "Name": "PR.IP-1", + "Description": "A baseline configuration of information technology/industrial control systems is created and maintained incorporating security principles (e.g. concept of least functionality).", + "Attributes": [ + { + "ItemId": "ip_1", + "Section": "Protect (PR)", + "SubSection": "Information Protection Processes and Procedures (PR.IP)", + "Service": "aws" + } + ], + "Checks": [ + "ec2_instance_managed_by_ssm", + "ec2_instance_older_than_specific_days", + "ssm_managed_compliant_patching" + ] + }, + { + "Id": "ip_2", + "Name": "PR.IP-2", + "Description": "A System Development Life Cycle to manage systems is implemented.", + "Attributes": [ + { + "ItemId": "ip_2", + "Section": "Protect (PR)", + "SubSection": "Information Protection Processes and Procedures (PR.IP)", + "Service": "aws" + } + ], + "Checks": [ + "ec2_instance_managed_by_ssm" + ] + }, + { + "Id": "ip_3", + "Name": "PR.IP-3", + "Description": "Configuration change control processes are in place.", + "Attributes": [ + { + "ItemId": "ip_3", + "Section": "Protect (PR)", + "SubSection": "Information Protection Processes and Procedures (PR.IP)", + "Service": "elb" + } + ], + "Checks": [ + "elbv2_deletion_protection" + ] + }, + { + "Id": "ip_4", + "Name": "PR.IP-4", + "Description": "Backups of information are conducted, maintained, and tested periodically.", + "Attributes": [ + { + "ItemId": "ip_4", + "Section": "Protect (PR)", + "SubSection": "Information Protection Processes and Procedures (PR.IP)", + "Service": "aws" + } + ], + "Checks": [ + "dynamodb_tables_pitr_enabled", + "rds_instance_backup_enabled", + "s3_bucket_object_versioning" + ] + }, + { + "Id": "ip_7", + "Name": "PR.IP-7", + "Description": "Protection processes are improved.", + "Attributes": [ + { + "ItemId": "ip_7", + "Section": "Protect (PR)", + "SubSection": "Information Protection Processes and Procedures (PR.IP)", + "Service": "ec2" + } + ], + "Checks": [] + }, + { + "Id": "ip_8", + "Name": "PR.IP-8", + "Description": "Effectiveness of protection technologies is shared.", + "Attributes": [ + { + "ItemId": "ip_8", + "Section": "Protect (PR)", + "SubSection": "Information Protection Processes and Procedures (PR.IP)", + "Service": "aws" + } + ], + "Checks": [ + "ec2_ebs_public_snapshot", + "ec2_instance_public_ip", + "eks_cluster_not_publicly_accessible", + "emr_cluster_master_nodes_no_public_ip", + "awslambda_function_url_public", + "rds_instance_no_public_access", + "rds_snapshots_public_access", + "redshift_cluster_public_access", + "s3_bucket_public_access", + "s3_bucket_policy_public_write_access", + "s3_account_level_public_access_blocks", + "sagemaker_notebook_instance_without_direct_internet_access_configured" + ] + }, + { + "Id": "ip_9", + "Name": "PR.IP-9", + "Description": "Response plans (Incident Response and Business Continuity) and recovery plans (Incident Recovery and Disaster Recovery) are in place and managed.", + "Attributes": [ + { + "ItemId": "ip_9", + "Section": "Protect (PR)", + "SubSection": "Information Protection Processes and Procedures (PR.IP)", + "Service": "aws" + } + ], + "Checks": [ + "dynamodb_tables_pitr_enabled", + "efs_have_backup_enabled", + "elbv2_deletion_protection", + "rds_instance_backup_enabled", + "rds_instance_multi_az", + "redshift_cluster_automated_snapshot", + "s3_bucket_object_versioning" + ] + }, + { + "Id": "ip_12", + "Name": "PR.IP-12", + "Description": "A vulnerability management plan is developed and implemented.", + "Attributes": [ + { + "ItemId": "ip_12", + "Section": "Protect (PR)", + "SubSection": "Information Protection Processes and Procedures (PR.IP)", + "Service": "aws" + } + ], + "Checks": [ + "config_recorder_all_regions_enabled", + "ec2_instance_managed_by_ssm", + "ssm_managed_compliant_patching" + ] + }, + { + "Id": "ma_2", + "Name": "PR.MA-2", + "Description": "Remote maintenance of organizational assets is approved, logged, and performed in a manner that prevents unauthorized access.", + "Attributes": [ + { + "ItemId": "ma_2", + "Section": "Protect (PR)", + "SubSection": "Maintenance (PR.MA)", + "Service": "cloudtrail" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled" + ] + }, + { + "Id": "pt_1", + "Name": "PR.PT-1", + "Description": "Audit/log records are determined, documented, implemented, and reviewed in accordance with policy.", + "Attributes": [ + { + "ItemId": "pt_1", + "Section": "Protect (PR)", + "SubSection": "Protective Technology (PR.PT)", + "Service": "aws" + } + ], + "Checks": [ + "apigateway_restapi_logging_enabled", + "cloudtrail_multi_region_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "elbv2_logging_enabled", + "elb_logging_enabled", + "s3_bucket_server_access_logging_enabled", + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "pt_3", + "Name": "PR.PT-3", + "Description": "The principle of least functionality is incorporated by configuring systems to provide only essential capabilities.", + "Attributes": [ + { + "ItemId": "pt_3", + "Section": "Protect (PR)", + "SubSection": "Protective Technology (PR.PT)", + "Service": "aws" + } + ], + "Checks": [ + "ec2_ebs_public_snapshot", + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_inline_policy_no_administrative_privileges", + "iam_no_root_access_key", + "awslambda_function_url_public", + "rds_snapshots_public_access", + "redshift_cluster_public_access", + "s3_bucket_public_access", + "s3_bucket_policy_public_write_access", + "s3_account_level_public_access_blocks" + ] + }, + { + "Id": "pt_4", + "Name": "PR.PT-4", + "Description": "Communications and control networks are protected.", + "Attributes": [ + { + "ItemId": "pt_4", + "Section": "Protect (PR)", + "SubSection": "Protective Technology (PR.PT)", + "Service": "aws" + } + ], + "Checks": [ + "awslambda_function_not_publicly_accessible", + "rds_instance_no_public_access", + "redshift_cluster_public_access", + "ec2_networkacl_allow_ingress_any_port", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + ] + }, + { + "Id": "pt_5", + "Name": "PR.PT-5", + "Description": "Mechanisms (e.g., failsafe, load balancing, hot swap) are implemented to achieve resilience requirements in normal and adverse situations.", + "Attributes": [ + { + "ItemId": "pt_5", + "Section": "Protect (PR)", + "SubSection": "Protective Technology (PR.PT)", + "Service": "aws" + } + ], + "Checks": [ + "elbv2_deletion_protection", + "rds_instance_backup_enabled", + "rds_instance_multi_az", + "s3_bucket_object_versioning" + ] + }, + { + "Id": "ae_1", + "Name": "DE.AE-1", + "Description": "A baseline of network operations and expected data flows for users and systems is established and managed.", + "Attributes": [ + { + "ItemId": "ae_1", + "Section": "Detect (DE)", + "SubSection": "Anomalies and Events (DE.AE)", + "Service": "aws" + } + ], + "Checks": [ + "apigateway_restapi_logging_enabled", + "cloudtrail_multi_region_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "elbv2_logging_enabled", + "elb_logging_enabled", + "redshift_cluster_audit_logging", + "s3_bucket_server_access_logging_enabled", + "ec2_securitygroup_default_restrict_traffic", + "vpc_flow_logs_enabled", + "ec2_networkacl_allow_ingress_any_port", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + ] + }, + { + "Id": "ae_2", + "Name": "DE.AE-2", + "Description": "Detected events are analyzed to understand attack targets and methods.", + "Attributes": [ + { + "ItemId": "ae_2", + "Section": "Detect (DE)", + "SubSection": "Anomalies and Events (DE.AE)", + "Service": "aws" + } + ], + "Checks": [ + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ae_3", + "Name": "DE.AE-3", + "Description": "Event data are collected and correlated from multiple sources and sensors.", + "Attributes": [ + { + "ItemId": "ae_3", + "Section": "Detect (DE)", + "SubSection": "Anomalies and Events (DE.AE)", + "Service": "aws" + } + ], + "Checks": [ + "apigateway_restapi_logging_enabled", + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "elbv2_logging_enabled", + "elb_logging_enabled", + "redshift_cluster_audit_logging", + "s3_bucket_server_access_logging_enabled", + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "ae_4", + "Name": "DE.AE-4", + "Description": "Impact of events is determined.", + "Attributes": [ + { + "ItemId": "ae_4", + "Section": "Detect (DE)", + "SubSection": "Anomalies and Events (DE.AE)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "elbv2_logging_enabled", + "elb_logging_enabled", + "guardduty_is_enabled", + "guardduty_no_high_severity_findings", + "s3_bucket_server_access_logging_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "ae_5", + "Name": "DE.AE-5", + "Description": "Incident alert thresholds are established.", + "Attributes": [ + { + "ItemId": "ae_5", + "Section": "Detect (DE)", + "SubSection": "Anomalies and Events (DE.AE)", + "Service": "aws" + } + ], + "Checks": [ + "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured" + ] + }, + { + "Id": "cm_1", + "Name": "DE.CM-1", + "Description": "The network is monitored to detect potential cybersecurity events.", + "Attributes": [ + { + "ItemId": "cm_1", + "Section": "Detect (DE)", + "SubSection": "Security Continuous Monitoring (DE.CM)", + "Service": "aws" + } + ], + "Checks": [ + "apigateway_restapi_logging_enabled", + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "elbv2_logging_enabled", + "elb_logging_enabled", + "guardduty_is_enabled", + "s3_bucket_server_access_logging_enabled", + "securityhub_enabled", + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "cm_2", + "Name": "DE.CM-2", + "Description": "The physical environment is monitored to detect potential cybersecurity events.", + "Attributes": [ + { + "ItemId": "cm_2", + "Section": "Detect (DE)", + "SubSection": "Security Continuous Monitoring (DE.CM)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_cloudwatch_logging_enabled", + "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured", + "config_recorder_all_regions_enabled", + "ec2_instance_imdsv2_enabled", + "guardduty_is_enabled", + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_sign_in_without_mfa", + "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", + "cloudwatch_log_metric_filter_policy_changes", + "cloudwatch_log_metric_filter_root_usage", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "rds_instance_enhanced_monitoring_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "cm_3", + "Name": "DE.CM-3", + "Description": "Personnel activity is monitored to detect potential cybersecurity events.", + "Attributes": [ + { + "ItemId": "cm_3", + "Section": "Detect (DE)", + "SubSection": "Security Continuous Monitoring (DE.CM)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "guardduty_is_enabled", + "s3_bucket_server_access_logging_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "cm_4", + "Name": "DE.CM-4", + "Description": "Malicious code is detected.", + "Attributes": [ + { + "ItemId": "cm_4", + "Section": "Detect (DE)", + "SubSection": "Security Continuous Monitoring (DE.CM)", + "Service": "aws" + } + ], + "Checks": [ + "guardduty_is_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "cm_5", + "Name": "DE.CM-5", + "Description": "Unauthorized mobile code is detected.", + "Attributes": [ + { + "ItemId": "cm_5", + "Section": "Detect (DE)", + "SubSection": "Security Continuous Monitoring (DE.CM)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_cloudwatch_logging_enabled", + "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured", + "ec2_instance_imdsv2_enabled", + "elbv2_waf_acl_attached", + "guardduty_is_enabled", + "guardduty_no_high_severity_findings", + "securityhub_enabled" + ] + }, + { + "Id": "cm_6", + "Name": "DE.CM-6", + "Description": "External service provider activity is monitored to detect potential cybersecurity events.", + "Attributes": [ + { + "ItemId": "cm_6", + "Section": "Detect (DE)", + "SubSection": "Security Continuous Monitoring (DE.CM)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "guardduty_is_enabled", + "s3_bucket_server_access_logging_enabled", + "securityhub_enabled" + ] + }, + { + "Id": "cm_7", + "Name": "DE.CM-7", + "Description": "Monitoring for unauthorized personnel, connections, devices, and software is performed.", + "Attributes": [ + { + "ItemId": "cm_7", + "Section": "Detect (DE)", + "SubSection": "Security Continuous Monitoring (DE.CM)", + "Service": "aws" + } + ], + "Checks": [ + "apigateway_restapi_logging_enabled", + "cloudtrail_multi_region_enabled", + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_s3_dataevents_write_enabled", + "elbv2_logging_enabled", + "elb_logging_enabled", + "guardduty_is_enabled", + "s3_bucket_server_access_logging_enabled", + "securityhub_enabled", + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "dp_4", + "Name": "DE.DP-4", + "Description": "Event detection information is communicated.", + "Attributes": [ + { + "ItemId": "dp_4", + "Section": "Detect (DE)", + "SubSection": "Detection Processes (DE.DP)", + "Service": "aws" + } + ], + "Checks": [ + "cloudtrail_cloudwatch_logging_enabled", + "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured", + "ec2_instance_imdsv2_enabled", + "elbv2_waf_acl_attached", + "guardduty_is_enabled", + "guardduty_no_high_severity_findings", + "securityhub_enabled" + ] + }, + { + "Id": "dp_5", + "Name": "DE.DP-5", + "Description": "Detection processes are continuously improved.", + "Attributes": [ + { + "ItemId": "dp_5", + "Section": "Detect (DE)", + "SubSection": "Detection Processes (DE.DP)", + "Service": "ec2" + } + ], + "Checks": [ + "ec2_instance_imdsv2_enabled" + ] + }, + { + "Id": "an_2", + "Name": "RS.AN-2", + "Description": "The impact of the incident is understood.", + "Attributes": [ + { + "ItemId": "an_2", + "Section": "Respond (RS)", + "SubSection": "Analysis (RS.AN)", + "Service": "guardduty" + } + ], + "Checks": [ + "guardduty_no_high_severity_findings" + ] + }, + { + "Id": "mi_3", + "Name": "RS.MI-3", + "Description": "Newly identified vulnerabilities are mitigated or documented as accepted risks.", + "Attributes": [ + { + "ItemId": "mi_3", + "Section": "Respond (RS)", + "SubSection": "Mitigation (RS.MI)", + "Service": "guardduty" + } + ], + "Checks": [ + "guardduty_no_high_severity_findings" + ] + }, + { + "Id": "rp_1", + "Name": "RS.RP-1", + "Description": "Response plan is executed during or after an incident.", + "Attributes": [ + { + "ItemId": "rp_1", + "Section": "Respond (RS)", + "SubSection": "Response Planning (RS.RP)", + "Service": "aws" + } + ], + "Checks": [ + "dynamodb_tables_pitr_enabled", + "efs_have_backup_enabled", + "elbv2_deletion_protection", + "rds_instance_backup_enabled", + "rds_instance_multi_az", + "redshift_cluster_automated_snapshot", + "s3_bucket_object_versioning" + ] + }, + { + "Id": "rc_rp_1", + "Name": "RC.RP-1", + "Description": "Recovery plan is executed during or after a cybersecurity incident.", + "Attributes": [ + { + "ItemId": "rc_rp_1", + "Section": "Recover (RC)", + "SubSection": "Recovery Planning (RC.RP)", + "Service": "aws" + } + ], + "Checks": [ + "dynamodb_tables_pitr_enabled", + "efs_have_backup_enabled", + "elbv2_deletion_protection", + "rds_instance_backup_enabled", + "rds_instance_multi_az", + "redshift_cluster_automated_snapshot", + "s3_bucket_object_versioning" + ] + } + ] +} + From 13e486650704ef6f71f45aaab04591132f8a9e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Thu, 6 Nov 2025 22:58:59 +0100 Subject: [PATCH 21/26] chore(oraclecloud): enhance metadata for `analytics` service (#9114) Co-authored-by: Sergio Garcia --- prowler/CHANGELOG.md | 4 ++- ...s_instance_access_restricted.metadata.json | 34 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index ffb83b7bb5..eb33889546 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -37,6 +37,8 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update AWS FMS service metadata to new format [(#9005)](https://github.com/prowler-cloud/prowler/pull/9005) - Update AWS FSx service metadata to new format [(#9006)](https://github.com/prowler-cloud/prowler/pull/9006) - Update AWS Glacier service metadata to new format [(#9007)](https://github.com/prowler-cloud/prowler/pull/9007) +- Update oraclecloud analytics service metadata to new format [(#9114)](https://github.com/prowler-cloud/prowler/pull/9114) + - Update AWS CodeArtifact service metadata to new format [(#8850)](https://github.com/prowler-cloud/prowler/pull/8850) - Rename OCI provider to oraclecloud with oci alias [(#9126)](https://github.com/prowler-cloud/prowler/pull/9126) @@ -49,7 +51,7 @@ All notable changes to the **Prowler SDK** are documented in this file. --- -## [v5.13.1] (Prowler UNRELEASED) +## [v5.13.1] (Prowler v5.13.1) ### Fixed - Add `resource_name` for checks under `logging` for the GCP provider [(#9023)](https://github.com/prowler-cloud/prowler/pull/9023) diff --git a/prowler/providers/oraclecloud/services/analytics/analytics_instance_access_restricted/analytics_instance_access_restricted.metadata.json b/prowler/providers/oraclecloud/services/analytics/analytics_instance_access_restricted/analytics_instance_access_restricted.metadata.json index 17828fc2c4..b5d7bcc6ae 100644 --- a/prowler/providers/oraclecloud/services/analytics/analytics_instance_access_restricted/analytics_instance_access_restricted.metadata.json +++ b/prowler/providers/oraclecloud/services/analytics/analytics_instance_access_restricted/analytics_instance_access_restricted.metadata.json @@ -1,34 +1,38 @@ { "Provider": "oraclecloud", "CheckID": "analytics_instance_access_restricted", - "CheckTitle": "Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network", - "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS OCI Foundations Benchmark" - ], + "CheckTitle": "Oracle Analytics Cloud instance is deployed within a Virtual Cloud Network or restricts public access to allowed sources", + "CheckType": [], "ServiceName": "analytics", "SubServiceName": "", - "ResourceIdTemplate": "oci:analytics:instance", + "ResourceIdTemplate": "", "Severity": "high", "ResourceType": "AnalyticsInstance", - "Description": "Oracle Analytics Cloud access should be restricted or deployed in VCN.", - "Risk": "Not meeting this network security requirement increases risk of unauthorized access.", - "RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Network/home.htm", + "Description": "Oracle Analytics Cloud endpoints are evaluated for **network exposure**. Public endpoints must use **restricted allowlists** of specific IPs/CIDRs; presence of `0.0.0.0/0` or no allowed sources indicates unrestricted access. Instances using a **VCN/private endpoint** or public endpoints limited to specific sources align with the intended exposure model.", + "Risk": "Unrestricted OAC endpoints allow Internet-wide access to the login surface, enabling **credential stuffing** and **brute force**. Account takeover can expose **reports and data sources** (**confidentiality**), permit **dashboard/model changes** (**integrity**), and support **lateral movement** into connected systems.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.public.content.oci.oraclecloud.com/en-us/iaas/analytics-cloud/doc/public-endpoints-and-access-control-rules.html", + "https://docs.oracle.com/en/cloud/paas/analytics-cloud/acsds/connect-databases-deployed-public-ip-address.html", + "https://docs.oracle.com/en/cloud/paas/analytics-cloud/acoci/top-faqs-public-or-private-endpoint-security.html", + "https://docs.oracle.com/en/cloud/paas/analytics-cloud/acoci/manage-ingress-access-rules-public-endpoint-using-console.html", + "https://docs.oracle.com/en-us/iaas/analytics-cloud/doc/public-endpoints-and-access-control-rules.html" + ], "Remediation": { "Code": { "CLI": "", "NativeIaC": "", - "Other": "", - "Terraform": "" + "Other": "1. In OCI Console, go to Analytics & AI > Analytics Cloud and select your instance\n2. On Instance Details, under Network Access, click Edit next to Access Control\n3. Remove any 0.0.0.0/0 entry (if present)\n4. Add an access rule with the specific allowed public IP or CIDR\n5. Click Save", + "Terraform": "```hcl\nresource \"oci_analytics_analytics_instance\" \"example\" {\n compartment_id = \"\"\n name = \"\"\n feature_set = \"ENTERPRISE_ANALYTICS\"\n license_type = \"LICENSE_INCLUDED\"\n idcs_access_token = \"\"\n\n capacity {\n capacity_type = \"OLPU_COUNT\"\n capacity_value = 1\n }\n\n network_endpoint_details {\n network_endpoint_type = \"PUBLIC\"\n whitelisted_ips = [\"\"] # Critical: restrict to specific allowed CIDR; not 0.0.0.0/0\n }\n}\n```" }, "Recommendation": { - "Text": "Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network", - "Url": "https://hub.prowler.com/check/oci/analytics_instance_access_restricted" + "Text": "Prefer **private deployment in a VCN** and apply **least privilege** network access. *If public is required*, enforce **allowlists** to specific IPs/CIDRs and never include `0.0.0.0/0`. Use **private access channels/service gateways**, require **MFA/SSO**, and apply **defense in depth** (WAF, audit monitoring) to reduce exposure.", + "Url": "https://hub.prowler.com/check/analytics_instance_access_restricted" } }, "Categories": [ - "network-security" + "internet-exposed", + "trust-boundaries" ], "DependsOn": [], "RelatedTo": [], From 5d4b7445f83527c9738ce358c543ef572b450ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Fri, 7 Nov 2025 10:17:42 +0100 Subject: [PATCH 22/26] chore: fix image path in README for Prowler App (#9186) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9791d93e4e..83af8dcfb7 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Prowler includes hundreds of built-in controls to ensure compliance with standar Prowler App is a web-based application that simplifies running Prowler across your cloud provider accounts. It provides a user-friendly interface to visualize the results and streamline your security assessments. -![Prowler App](docs/products/img/overview.png) +![Prowler App](docs/images/products/overview.png) >For more details, refer to the [Prowler App Documentation](https://docs.prowler.com/projects/prowler-open-source/en/latest/#prowler-app-installation) @@ -73,7 +73,7 @@ prowler ```console prowler dashboard ``` -![Prowler Dashboard](docs/products/img/dashboard.png) +![Prowler Dashboard](docs/images/products/dashboard.png) # Prowler at a Glance > [!Tip] From a73a79f420e8dddfb183541fbb8afc81fcec5c62 Mon Sep 17 00:00:00 2001 From: Alan Buscaglia Date: Fri, 7 Nov 2025 10:49:27 +0100 Subject: [PATCH 23/26] fix: exclude docs folder from Tailwind content scanning (#9184) Co-authored-by: alejandrobailo --- ui/Dockerfile | 1 + ui/package.json | 2 +- ui/scripts/postinstall.js | 32 ++++++++++++++++++++++++++++++++ ui/tailwind.config.js | 5 +++-- 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 ui/scripts/postinstall.js diff --git a/ui/Dockerfile b/ui/Dockerfile index da920e52fe..234ed10399 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -10,6 +10,7 @@ WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +COPY scripts ./scripts RUN \ if [ -f package-lock.json ]; then npm install; \ else echo "Lockfile not found." && exit 1; \ diff --git a/ui/package.json b/ui/package.json index 8394a221d9..e89a07c1bf 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,7 +7,7 @@ "start": "next start", "start:standalone": "node .next/standalone/server.js", "deps:log": "node scripts/update-dependency-log.js", - "postinstall": "node -e \"const fs=require('fs'); if(fs.existsSync('scripts/update-dependency-log.js')) require('./scripts/update-dependency-log.js'); else console.log('skip deps:log (script missing)');\" && node scripts/setup-git-hooks.js", + "postinstall": "node scripts/postinstall.js", "typecheck": "tsc", "healthcheck": "npm run typecheck && npm run lint:check", "lint:check": "eslint . --ext .ts,.tsx -c .eslintrc.cjs", diff --git a/ui/scripts/postinstall.js b/ui/scripts/postinstall.js new file mode 100644 index 0000000000..d545707178 --- /dev/null +++ b/ui/scripts/postinstall.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +/** + * Post-install script for Prowler UI + * + * This script runs after npm install to: + * 1. Update dependency log (if the script exists) + * 2. Setup git hooks (if the script exists) + */ + +const fs = require("fs"); +const path = require("path"); + +function runScriptIfExists(scriptPath, scriptName) { + const fullPath = path.join(__dirname, scriptPath); + + if (fs.existsSync(fullPath)) { + try { + require(fullPath); + } catch (error) { + console.warn(`⚠️ Error running ${scriptName}:`, error.message); + } + } else { + console.log(`Skip ${scriptName} (script missing)`); + } +} + +// Run dependency log update +runScriptIfExists("./update-dependency-log.js", "deps:log"); + +// Run git hooks setup +runScriptIfExists("./setup-git-hooks.js", "setup-git-hooks"); diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js index fedd8da682..d9f032ada8 100644 --- a/ui/tailwind.config.js +++ b/ui/tailwind.config.js @@ -4,9 +4,10 @@ const { heroui } = require("@heroui/theme"); module.exports = { darkMode: ["class"], content: [ - "./components/**/*.{ts,jsx,tsx,mdx}", - "./app/**/*.{ts,jsx,tsx,mdx}", + "./components/**/*.{ts,jsx,tsx}", + "./app/**/*.{ts,jsx,tsx}", "./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}", + "!./docs/**/*", ], prefix: "", theme: { From f0b1c4c29e09691adcfac690ae049a3636d72474 Mon Sep 17 00:00:00 2001 From: Rakan Farhouda Date: Fri, 7 Nov 2025 15:16:55 +0300 Subject: [PATCH 24/26] =?UTF-8?q?fix(api):=20update=20unique=20constraint?= =?UTF-8?q?=20for=20Provider=20model=20to=20exclude=20soft=E2=80=A6=20(#90?= =?UTF-8?q?54)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/CHANGELOG.md | 5 + ..._provider_unique_provider_uids_and_more.py | 24 +++ api/src/backend/api/models.py | 3 +- api/src/backend/api/tests/test_views.py | 155 ++++++++++++++++++ 4 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 api/src/backend/api/migrations/0056_remove_provider_unique_provider_uids_and_more.py diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 5e4720bcf9..b9b0583c68 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -18,6 +18,11 @@ All notable changes to the **Prowler API** are documented in this file. --- +## [1.14.2] (Prowler 5.13.2) + +### Fixed +- Update unique constraint for `Provider` model to exclude soft-deleted entries, resolving duplicate errors when re-deleting providers. + ## [1.14.1] (Prowler 5.13.1) ### Fixed diff --git a/api/src/backend/api/migrations/0056_remove_provider_unique_provider_uids_and_more.py b/api/src/backend/api/migrations/0056_remove_provider_unique_provider_uids_and_more.py new file mode 100644 index 0000000000..14cbc4fa47 --- /dev/null +++ b/api/src/backend/api/migrations/0056_remove_provider_unique_provider_uids_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.13 on 2025-11-06 09:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0055_mongodbatlas_provider"), + ] + + operations = [ + migrations.RemoveConstraint( + model_name="provider", + name="unique_provider_uids", + ), + migrations.AddConstraint( + model_name="provider", + constraint=models.UniqueConstraint( + condition=models.Q(("is_deleted", False)), + fields=("tenant_id", "provider", "uid"), + name="unique_provider_uids", + ), + ), + ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index 7ca9594d5e..49dbc5f35c 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -425,7 +425,8 @@ class Provider(RowLevelSecurityProtectedModel): constraints = [ models.UniqueConstraint( - fields=("tenant_id", "provider", "uid", "is_deleted"), + fields=("tenant_id", "provider", "uid"), + condition=Q(is_deleted=False), name="unique_provider_uids", ), RowLevelSecurityConstraint( diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 8822bd9f12..7da8a095a1 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -1171,6 +1171,161 @@ class TestProviderViewSet: assert Provider.objects.get().uid == provider_json_payload["uid"] assert Provider.objects.get().alias == provider_json_payload["alias"] + @pytest.mark.parametrize( + "provider_json_payload", + ( + [ + {"provider": "aws", "uid": "111111111111", "alias": "test"}, + {"provider": "gcp", "uid": "a12322-test54321", "alias": "test"}, + { + "provider": "kubernetes", + "uid": "kubernetes-test-123456789", + "alias": "test", + }, + { + "provider": "kubernetes", + "uid": "arn:aws:eks:us-east-1:111122223333:cluster/test-cluster-long-name-123456789", + "alias": "EKS", + }, + { + "provider": "kubernetes", + "uid": "gke_aaaa-dev_europe-test1_dev-aaaa-test-cluster-long-name-123456789", + "alias": "GKE", + }, + { + "provider": "kubernetes", + "uid": "gke_project/cluster-name", + "alias": "GKE", + }, + { + "provider": "kubernetes", + "uid": "admin@k8s-demo", + "alias": "test", + }, + { + "provider": "azure", + "uid": "8851db6b-42e5-4533-aa9e-30a32d67e875", + "alias": "test", + }, + { + "provider": "m365", + "uid": "TestingPro.onmicrosoft.com", + "alias": "test", + }, + { + "provider": "m365", + "uid": "subdomain.domain.es", + "alias": "test", + }, + { + "provider": "m365", + "uid": "microsoft.net", + "alias": "test", + }, + { + "provider": "m365", + "uid": "subdomain1.subdomain2.subdomain3.subdomain4.domain.net", + "alias": "test", + }, + { + "provider": "github", + "uid": "test-user", + "alias": "test", + }, + { + "provider": "github", + "uid": "test-organization", + "alias": "GitHub Org", + }, + { + "provider": "github", + "uid": "prowler-cloud", + "alias": "Prowler", + }, + { + "provider": "github", + "uid": "microsoft", + "alias": "Microsoft", + }, + { + "provider": "github", + "uid": "a12345678901234567890123456789012345678", + "alias": "Long Username", + }, + ] + ), + ) + @patch("api.v1.views.Task.objects.get") + @patch("api.v1.views.delete_provider_task.delay") + def test_providers_soft_delete( + self, + mock_delete_task, + mock_task_get, + authenticated_client, + provider_json_payload, + tasks_fixture, + ): + # Mock the Celery task response + prowler_task = tasks_fixture[0] + task_mock = Mock() + task_mock.id = prowler_task.id + mock_delete_task.return_value = task_mock + mock_task_get.return_value = prowler_task + + # 1.Create a provider + response = authenticated_client.post( + reverse("provider-list"), data=provider_json_payload, format="json" + ) + assert response.status_code == status.HTTP_201_CREATED + assert Provider.objects.count() == 1 + provider_id = response.json()["data"]["id"] + + # 2. Soft delete the provider using the actual API endpoint + response = authenticated_client.delete( + reverse("provider-detail", kwargs={"pk": provider_id}) + ) + assert response.status_code == status.HTTP_202_ACCEPTED + assert Provider.objects.count() == 0 + assert Provider.all_objects.count() == 1 + + mock_delete_task.assert_called_once_with( + provider_id=str(provider_id), tenant_id=ANY + ) + + # 3. Create a provider with the same UID should succeed (since the old one is soft deleted) + response = authenticated_client.post( + reverse("provider-list"), data=provider_json_payload, format="json" + ) + assert response.status_code == status.HTTP_201_CREATED + assert Provider.objects.count() == 1 + assert Provider.all_objects.count() == 2 + provider_id = response.json()["data"]["id"] + + # 4. Creating another provider with the same UID should fail (duplicate) + response = authenticated_client.post( + reverse("provider-list"), data=provider_json_payload, format="json" + ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + + mock_delete_task.reset_mock() + mock_delete_task.return_value = task_mock + + # 5. Delete the second provider + response = authenticated_client.delete( + reverse("provider-detail", kwargs={"pk": provider_id}) + ) + assert response.status_code == status.HTTP_202_ACCEPTED + assert Provider.objects.count() == 0 + assert Provider.all_objects.count() == 2 + + # 6. Creating a provider with the same UID should succeed again + response = authenticated_client.post( + reverse("provider-list"), data=provider_json_payload, format="json" + ) + assert response.status_code == status.HTTP_201_CREATED + assert Provider.objects.count() == 1 + assert Provider.all_objects.count() == 3 + @pytest.mark.parametrize( "provider_json_payload, error_code, error_pointer", ( From fb9eda208eac0f7882d71fe5cb47d9250ce91fa2 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:19:37 +0100 Subject: [PATCH 25/26] fix(powershell): depth truncation and parsing error (#9181) --- prowler/CHANGELOG.md | 1 + prowler/lib/powershell/powershell.py | 23 +-- .../m365/lib/powershell/m365_powershell.py | 160 +++++++++++++----- tests/lib/powershell/powershell_test.py | 8 + .../lib/powershell/m365_powershell_test.py | 17 +- 5 files changed, 146 insertions(+), 63 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index eb33889546..7ee85b6607 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -48,6 +48,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Fixed - Check `check_name` has no `resource_name` error for GCP provider [(#9169)](https://github.com/prowler-cloud/prowler/pull/9169) +- Depth Truncation and parsing error in PowerShell queries [(#9181)](https://github.com/prowler-cloud/prowler/pull/9181) --- diff --git a/prowler/lib/powershell/powershell.py b/prowler/lib/powershell/powershell.py index 034d28fa16..8142fa8e45 100644 --- a/prowler/lib/powershell/powershell.py +++ b/prowler/lib/powershell/powershell.py @@ -220,18 +220,19 @@ class PowerShellSession: if output == "": return {} - json_match = re.search(r"(\[.*\]|\{.*\})", output, re.DOTALL) - if not json_match: - logger.error( - f"Unexpected PowerShell output: {output}\n", - ) - else: + decoder = json.JSONDecoder() + for index, character in enumerate(output): + if character not in ("{", "["): + continue try: - return json.loads(json_match.group(1)) - except json.JSONDecodeError as error: - logger.error( - f"Error parsing PowerShell output as JSON: {str(error)}\n", - ) + parsed_json, _ = decoder.raw_decode(output[index:]) + return parsed_json + except json.JSONDecodeError: + continue + + logger.error( + f"Unexpected PowerShell output: {output}\n", + ) return {} diff --git a/prowler/providers/m365/lib/powershell/m365_powershell.py b/prowler/providers/m365/lib/powershell/m365_powershell.py index ef3c72cc71..e3d729275f 100644 --- a/prowler/providers/m365/lib/powershell/m365_powershell.py +++ b/prowler/providers/m365/lib/powershell/m365_powershell.py @@ -1,4 +1,5 @@ import os +from typing import Optional from prowler.lib.logger import logger from prowler.lib.powershell.powershell import PowerShellSession @@ -11,6 +12,7 @@ from prowler.providers.m365.models import M365Credentials, M365IdentityInfo class M365PowerShell(PowerShellSession): + CONNECT_TIMEOUT = 15 """ Microsoft 365 specific PowerShell session management implementation. @@ -123,6 +125,23 @@ class M365PowerShell(PowerShellSession): '$graphToken = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $graphtokenBody | Select-Object -ExpandProperty Access_Token' ) + def _execute_connect_command( + self, command: str, timeout: Optional[int] = None + ) -> str: + """ + Execute a PowerShell connect command ensuring empty responses surface as timeouts. + + Args: + command (str): PowerShell connect command to run. + timeout (Optional[int]): Timeout in seconds for the command execution. + + Returns: + str: Command output or 'Timeout' if the command produced no output. + """ + effective_timeout = timeout or self.CONNECT_TIMEOUT + result = self.execute(command, timeout=effective_timeout) + return result or "Timeout" + def test_credentials(self, credentials: M365Credentials) -> bool: """ Test Microsoft 365 credentials by attempting to authenticate against Entra ID. @@ -141,24 +160,32 @@ class M365PowerShell(PowerShellSession): # Test Certificate Auth if credentials.certificate_content and credentials.client_id: try: - self.test_teams_certificate_connection() or self.test_exchange_certificate_connection() + logger.info("Testing Microsoft Graph Certificate connection...") + self.test_graph_certificate_connection() + logger.info("Microsoft Graph Certificate connection successful") + teams_connection_successful = self.test_teams_certificate_connection() + if not teams_connection_successful: + self.test_exchange_certificate_connection() return True except Exception as e: - logger.error(f"Exchange Online Certificate connection failed: {e}") - - else: - # Test Microsoft Graph connection - try: - logger.info("Testing Microsoft Graph connection...") - self.test_graph_connection() - logger.info("Microsoft Graph connection successful") - return True - except Exception as e: - logger.error(f"Microsoft Graph connection failed: {e}") + logger.error(f"Microsoft Graph Cer connection failed: {e}") raise M365GraphConnectionError( file=os.path.basename(__file__), original_exception=e, - message="Check your Microsoft Application credentials and ensure the app has proper permissions", + message="Check your Microsoft Application Certificate and ensure the app has proper permissions", + ) + else: + try: + logger.info("Testing Microsoft Graph Client Secret connection...") + self.test_graph_connection() + logger.info("Microsoft Graph Client Secret connection successful") + return True + except Exception as e: + logger.error(f"Microsoft Graph Client Secret connection failed: {e}") + raise M365GraphConnectionError( + file=os.path.basename(__file__), + original_exception=e, + message="Check your Microsoft Application Client Secret and ensure the app has proper permissions", ) def test_graph_connection(self) -> bool: @@ -178,6 +205,16 @@ class M365PowerShell(PowerShellSession): message=f"Failed to connect to Microsoft Graph API: {str(e)}", ) + def test_graph_certificate_connection(self) -> bool: + """Test Microsoft Graph API connection using certificate and raise exception if it fails.""" + result = self._execute_connect_command( + "Connect-Graph -Certificate $certificate -AppId $clientID -TenantId $tenantID" + ) + if "Welcome to Microsoft Graph!" not in result: + logger.error(f"Microsoft Graph Certificate connection failed: {result}") + return False + return True + def test_teams_connection(self) -> bool: """Test Microsoft Teams API connection and raise exception if it fails.""" try: @@ -195,7 +232,7 @@ class M365PowerShell(PowerShellSession): "Microsoft Teams connection failed: Please check your permissions and try again." ) return False - self.execute( + self._execute_connect_command( 'Connect-MicrosoftTeams -AccessTokens @("$graphToken","$teamsToken")' ) return True @@ -207,7 +244,7 @@ class M365PowerShell(PowerShellSession): def test_teams_certificate_connection(self) -> bool: """Test Microsoft Teams API connection using certificate and raise exception if it fails.""" - result = self.execute( + result = self._execute_connect_command( "Connect-MicrosoftTeams -Certificate $certificate -ApplicationId $clientID -TenantId $tenantID" ) if self.tenant_identity.identity_id not in result: @@ -231,8 +268,9 @@ class M365PowerShell(PowerShellSession): "Exchange Online connection failed: Please check your permissions and try again." ) return False - self.execute( - 'Connect-ExchangeOnline -AccessToken $exchangeToken.AccessToken -Organization "$tenantID"' + self._execute_connect_command( + 'Connect-ExchangeOnline -AccessToken $exchangeToken.AccessToken -Organization "$tenantID"', + timeout=self.CONNECT_TIMEOUT, ) return True except Exception as e: @@ -243,8 +281,9 @@ class M365PowerShell(PowerShellSession): def test_exchange_certificate_connection(self) -> bool: """Test Exchange Online API connection using certificate and raise exception if it fails.""" - result = self.execute( - "Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain" + result = self._execute_connect_command( + "Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain", + timeout=self.CONNECT_TIMEOUT, ) if "https://aka.ms/exov3-module" not in result: logger.error(f"Exchange Online Certificate connection failed: {result}") @@ -290,7 +329,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-CsTeamsClientConfiguration | ConvertTo-Json", json_parse=True + "Get-CsTeamsClientConfiguration | ConvertTo-Json -Depth 10", + json_parse=True, ) def get_global_meeting_policy(self) -> dict: @@ -309,7 +349,7 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-CsTeamsMeetingPolicy -Identity Global | ConvertTo-Json", + "Get-CsTeamsMeetingPolicy -Identity Global | ConvertTo-Json -Depth 10", json_parse=True, ) @@ -329,7 +369,7 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-CsTeamsMessagingPolicy -Identity Global | ConvertTo-Json", + "Get-CsTeamsMessagingPolicy -Identity Global | ConvertTo-Json -Depth 10", json_parse=True, ) @@ -349,7 +389,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-CsTenantFederationConfiguration | ConvertTo-Json", json_parse=True + "Get-CsTenantFederationConfiguration | ConvertTo-Json -Depth 10", + json_parse=True, ) def connect_exchange_online(self) -> dict: @@ -389,7 +430,7 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled | ConvertTo-Json", + "Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled | ConvertTo-Json -Depth 10", json_parse=True, ) @@ -409,7 +450,9 @@ class M365PowerShell(PowerShellSession): "Identity": "Default" } """ - return self.execute("Get-MalwareFilterPolicy | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-MalwareFilterPolicy | ConvertTo-Json -Depth 10", json_parse=True + ) def get_malware_filter_rule(self) -> dict: """ @@ -427,7 +470,9 @@ class M365PowerShell(PowerShellSession): "State": "Enabled" } """ - return self.execute("Get-MalwareFilterRule | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-MalwareFilterRule | ConvertTo-Json -Depth 10", json_parse=True + ) def get_outbound_spam_filter_policy(self) -> dict: """ @@ -448,7 +493,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-HostedOutboundSpamFilterPolicy | ConvertTo-Json", json_parse=True + "Get-HostedOutboundSpamFilterPolicy | ConvertTo-Json -Depth 10", + json_parse=True, ) def get_outbound_spam_filter_rule(self) -> dict: @@ -467,7 +513,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-HostedOutboundSpamFilterRule | ConvertTo-Json", json_parse=True + "Get-HostedOutboundSpamFilterRule | ConvertTo-Json -Depth 10", + json_parse=True, ) def get_antiphishing_policy(self) -> dict: @@ -493,7 +540,9 @@ class M365PowerShell(PowerShellSession): "IsDefault": false } """ - return self.execute("Get-AntiPhishPolicy | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-AntiPhishPolicy | ConvertTo-Json -Depth 10", json_parse=True + ) def get_antiphishing_rules(self) -> dict: """ @@ -511,7 +560,9 @@ class M365PowerShell(PowerShellSession): "State": Enabled, } """ - return self.execute("Get-AntiPhishRule | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-AntiPhishRule | ConvertTo-Json -Depth 10", json_parse=True + ) def get_organization_config(self) -> dict: """ @@ -530,7 +581,9 @@ class M365PowerShell(PowerShellSession): "AuditDisabled": false } """ - return self.execute("Get-OrganizationConfig | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-OrganizationConfig | ConvertTo-Json -Depth 10", json_parse=True + ) def get_mailbox_audit_config(self) -> dict: """ @@ -550,7 +603,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-MailboxAuditBypassAssociation | ConvertTo-Json", json_parse=True + "Get-MailboxAuditBypassAssociation | ConvertTo-Json -Depth 10", + json_parse=True, ) def get_mailbox_policy(self) -> dict: @@ -569,7 +623,9 @@ class M365PowerShell(PowerShellSession): "AdditionalStorageProvidersAvailable": True } """ - return self.execute("Get-OwaMailboxPolicy | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-OwaMailboxPolicy | ConvertTo-Json -Depth 10", json_parse=True + ) def get_external_mail_config(self) -> dict: """ @@ -587,7 +643,9 @@ class M365PowerShell(PowerShellSession): "ExternalMailTagEnabled": true } """ - return self.execute("Get-ExternalInOutlook | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-ExternalInOutlook | ConvertTo-Json -Depth 10", json_parse=True + ) def get_transport_rules(self) -> dict: """ @@ -606,7 +664,9 @@ class M365PowerShell(PowerShellSession): "SenderDomainIs": ["example.com"] } """ - return self.execute("Get-TransportRule | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-TransportRule | ConvertTo-Json -Depth 10", json_parse=True + ) def get_connection_filter_policy(self) -> dict: """ @@ -625,7 +685,7 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-HostedConnectionFilterPolicy -Identity Default | ConvertTo-Json", + "Get-HostedConnectionFilterPolicy -Identity Default | ConvertTo-Json -Depth 10", json_parse=True, ) @@ -645,7 +705,9 @@ class M365PowerShell(PowerShellSession): "Enabled": true } """ - return self.execute("Get-DkimSigningConfig | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-DkimSigningConfig | ConvertTo-Json -Depth 10", json_parse=True + ) def get_inbound_spam_filter_policy(self) -> dict: """ @@ -664,7 +726,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-HostedContentFilterPolicy | ConvertTo-Json", json_parse=True + "Get-HostedContentFilterPolicy | ConvertTo-Json -Depth 10", + json_parse=True, ) def get_inbound_spam_filter_rule(self) -> dict: @@ -684,7 +747,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-HostedContentFilterRule | ConvertTo-Json", json_parse=True + "Get-HostedContentFilterRule | ConvertTo-Json -Depth 10", + json_parse=True, ) def get_report_submission_policy(self) -> dict: @@ -715,7 +779,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-ReportSubmissionPolicy | ConvertTo-Json", json_parse=True + "Get-ReportSubmissionPolicy | ConvertTo-Json -Depth 10", + json_parse=True, ) def get_role_assignment_policies(self) -> dict: @@ -736,7 +801,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-RoleAssignmentPolicy | ConvertTo-Json", json_parse=True + "Get-RoleAssignmentPolicy | ConvertTo-Json -Depth 10", + json_parse=True, ) def get_mailbox_audit_properties(self) -> dict: @@ -801,7 +867,7 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-EXOMailbox -PropertySets Audit -ResultSize Unlimited | ConvertTo-Json", + "Get-EXOMailbox -PropertySets Audit -ResultSize Unlimited | ConvertTo-Json -Depth 10", json_parse=True, ) @@ -820,7 +886,9 @@ class M365PowerShell(PowerShellSession): "SmtpClientAuthenticationDisabled": True, } """ - return self.execute("Get-TransportConfig | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-TransportConfig | ConvertTo-Json -Depth 10", json_parse=True + ) def get_sharing_policy(self) -> dict: """ @@ -838,7 +906,9 @@ class M365PowerShell(PowerShellSession): "Enabled": true } """ - return self.execute("Get-SharingPolicy | ConvertTo-Json", json_parse=True) + return self.execute( + "Get-SharingPolicy | ConvertTo-Json -Depth 10", json_parse=True + ) def get_user_account_status(self) -> dict: """ @@ -850,7 +920,7 @@ class M365PowerShell(PowerShellSession): dict: User account status settings in JSON format. """ return self.execute( - "$dict=@{}; Get-User -ResultSize Unlimited | ForEach-Object { $dict[$_.Id] = @{ AccountDisabled = $_.AccountDisabled } }; $dict | ConvertTo-Json", + "$dict=@{}; Get-User -ResultSize Unlimited | ForEach-Object { $dict[$_.Id] = @{ AccountDisabled = $_.AccountDisabled } }; $dict | ConvertTo-Json -Depth 10", json_parse=True, ) diff --git a/tests/lib/powershell/powershell_test.py b/tests/lib/powershell/powershell_test.py index 49108e1cd2..e96740b1d9 100644 --- a/tests/lib/powershell/powershell_test.py +++ b/tests/lib/powershell/powershell_test.py @@ -216,6 +216,14 @@ class TestPowerShellSession: result = session.json_parse_output('prefix [{"key": "value"}] suffix') assert result == [{"key": "value"}] + result = session.json_parse_output( + 'INFO {context data} {"key": "value", "list": [1, 2]} extra' + ) + assert result == {"key": "value", "list": [1, 2]} + + result = session.json_parse_output('{"key": "value"} trailing {log}') + assert result == {"key": "value"} + # Test non-JSON text returns empty dict result = session.json_parse_output("just some text") assert result == {} diff --git a/tests/providers/m365/lib/powershell/m365_powershell_test.py b/tests/providers/m365/lib/powershell/m365_powershell_test.py index 6f6a7e0b95..8d37b6c095 100644 --- a/tests/providers/m365/lib/powershell/m365_powershell_test.py +++ b/tests/providers/m365/lib/powershell/m365_powershell_test.py @@ -936,7 +936,8 @@ class Testm365PowerShell: assert result is True session.execute.assert_called_once_with( - "Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain" + "Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain", + timeout=M365PowerShell.CONNECT_TIMEOUT, ) session.close() @@ -960,7 +961,8 @@ class Testm365PowerShell: assert result is False session.execute.assert_called_once_with( - "Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain" + "Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain", + timeout=M365PowerShell.CONNECT_TIMEOUT, ) session.close() @@ -979,7 +981,7 @@ class Testm365PowerShell: session = M365PowerShell(credentials, identity) # Mock successful Teams connection - the method returns bool - def mock_execute_side_effect(command): + def mock_execute_side_effect(command, *_, **__): if "Connect-MicrosoftTeams" in command: # Return result that contains the identity_id for success return "Connected successfully test_identity_id" @@ -991,7 +993,8 @@ class Testm365PowerShell: assert result is True session.execute.assert_called_once_with( - "Connect-MicrosoftTeams -Certificate $certificate -ApplicationId $clientID -TenantId $tenantID" + "Connect-MicrosoftTeams -Certificate $certificate -ApplicationId $clientID -TenantId $tenantID", + timeout=M365PowerShell.CONNECT_TIMEOUT, ) session.close() @@ -1007,7 +1010,7 @@ class Testm365PowerShell: session = M365PowerShell(credentials, identity) # Mock failed Teams connection - def mock_execute_side_effect(command, json_parse=False): + def mock_execute_side_effect(command, **kwargs): if "Connect-MicrosoftTeams" in command: raise Exception("Connection failed: Authentication error") return "" @@ -1090,7 +1093,7 @@ class Testm365PowerShell: # Mock certificate variable check and teams connection execute_calls = [] - def mock_execute_side_effect(command, json_parse=False): + def mock_execute_side_effect(command): execute_calls.append(command) if "Write-Output $certificate" in command: return "certificate_content" # Non-empty means certificate exists @@ -1123,7 +1126,7 @@ class Testm365PowerShell: # Mock certificate variable check and exchange connection execute_calls = [] - def mock_execute_side_effect(command, json_parse=False): + def mock_execute_side_effect(command): execute_calls.append(command) if "Write-Output $certificate" in command: return "certificate_content" # Non-empty means certificate exists From 66a04b55472da8dc5ab747f73e2246d1229a6ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Fri, 7 Nov 2025 16:59:40 +0100 Subject: [PATCH 26/26] feat(aws): improve `nist_csf_2.0` mapping (#9189) --- dashboard/compliance/nist_csf_2_0_aws.py | 24 + prowler/compliance/aws/nist_csf_2.0_aws.json | 1089 ++++++++++-------- 2 files changed, 653 insertions(+), 460 deletions(-) create mode 100644 dashboard/compliance/nist_csf_2_0_aws.py diff --git a/dashboard/compliance/nist_csf_2_0_aws.py b/dashboard/compliance/nist_csf_2_0_aws.py new file mode 100644 index 0000000000..07b956b104 --- /dev/null +++ b/dashboard/compliance/nist_csf_2_0_aws.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_format3 + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_DESCRIPTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ].copy() + + return get_section_containers_format3( + aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID" + ) diff --git a/prowler/compliance/aws/nist_csf_2.0_aws.json b/prowler/compliance/aws/nist_csf_2.0_aws.json index 3430763879..591e558bb1 100644 --- a/prowler/compliance/aws/nist_csf_2.0_aws.json +++ b/prowler/compliance/aws/nist_csf_2.0_aws.json @@ -17,16 +17,12 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "config_recorder_all_regions_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "oc_2", "Name": "GV.OC-2", - "Description": "Legal, regulatory, and contractual requirements regarding cybersecurity — including privacy and civil liberties obligations — are understood and managed.", + "Description": "Legal, regulatory, and contractual requirements regarding cybersecurity including privacy and civil liberties obligations are understood and managed.", "Attributes": [ { "ItemId": "oc_2", @@ -35,12 +31,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "config_recorder_all_regions_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "oc_3", @@ -54,10 +45,7 @@ "Service": "aws" } ], - "Checks": [ - "config_recorder_all_regions_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "rm_1", @@ -72,10 +60,18 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", "config_recorder_all_regions_enabled", - "guardduty_is_enabled", - "securityhub_enabled" + "config_recorder_using_aws_service_role", + "organizations_account_part_of_organizations", + "organizations_scp_check_deny_regions", + "organizations_tags_policies_enabled_and_attached", + "organizations_delegated_administrators", + "organizations_opt_out_ai_services_policy", + "trustedadvisor_premium_support_plan_subscribed", + "trustedadvisor_errors_and_warnings", + "securityhub_enabled", + "wellarchitected_workload_no_high_or_medium_risks", + "servicecatalog_portfolio_shared_within_organization_only" ] }, { @@ -90,11 +86,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "config_recorder_all_regions_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "rm_3", @@ -108,11 +100,7 @@ "Service": "aws" } ], - "Checks": [ - "config_recorder_all_regions_enabled", - "guardduty_is_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "sc_1", @@ -127,12 +115,11 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "config_recorder_all_regions_enabled", - "guardduty_is_enabled", - "securityhub_enabled" + "codeartifact_packages_external_public_publishing_disabled", + "codebuild_project_uses_allowed_github_organizations", + "codebuild_project_source_repo_url_no_sensitive_credentials", + "codebuild_project_user_controlled_buildspec", + "codebuild_project_no_secrets_in_variables" ] }, { @@ -148,9 +135,8 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "config_recorder_all_regions_enabled", - "securityhub_enabled" + "codeartifact_packages_external_public_publishing_disabled", + "codebuild_project_uses_allowed_github_organizations" ] }, { @@ -166,12 +152,10 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "config_recorder_all_regions_enabled", - "guardduty_is_enabled", - "securityhub_enabled" + "codeartifact_packages_external_public_publishing_disabled", + "codebuild_project_user_controlled_buildspec", + "codebuild_project_source_repo_url_no_sensitive_credentials", + "codebuild_project_uses_allowed_github_organizations" ] }, { @@ -186,11 +170,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "config_recorder_all_regions_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "rr_1", @@ -205,11 +185,27 @@ } ], "Checks": [ - "iam_aws_attached_policy_no_administrative_privileges", - "iam_customer_attached_policy_no_administrative_privileges", + "organizations_account_part_of_organizations", + "organizations_delegated_administrators", + "organizations_scp_check_deny_regions", + "organizations_opt_out_ai_services_policy", + "vpc_endpoint_connections_trust_boundaries", + "iam_role_administratoraccess_policy", + "iam_group_administrator_access_policy", + "iam_user_administrator_access_policy", + "iam_policy_attached_only_to_group_or_roles", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation", + "iam_inline_policy_no_full_access_to_kms", "iam_inline_policy_no_administrative_privileges", - "iam_no_root_access_key", - "cloudtrail_multi_region_enabled" + "iam_policy_no_full_access_to_cloudtrail", + "iam_policy_no_full_access_to_kms", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_administrator_access_with_mfa", + "iam_root_mfa_enabled", + "iam_root_credentials_management_enabled", + "iam_no_custom_policy_permissive_role_assumption", + "iam_aws_attached_policy_no_administrative_privileges" ] }, { @@ -225,10 +221,21 @@ } ], "Checks": [ - "iam_aws_attached_policy_no_administrative_privileges", + "iam_group_administrator_access_policy", + "iam_role_administratoraccess_policy", + "iam_user_administrator_access_policy", "iam_customer_attached_policy_no_administrative_privileges", "iam_inline_policy_no_administrative_privileges", - "cloudtrail_multi_region_enabled" + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation", + "iam_policy_attached_only_to_group_or_roles", + "iam_aws_attached_policy_no_administrative_privileges", + "organizations_account_part_of_organizations", + "organizations_delegated_administrators", + "organizations_opt_out_ai_services_policy", + "organizations_scp_check_deny_regions", + "iam_root_credentials_management_enabled", + "iam_role_cross_account_readonlyaccess_policy" ] }, { @@ -243,13 +250,7 @@ "Service": "aws" } ], - "Checks": [ - "iam_root_hardware_mfa_enabled", - "iam_root_mfa_enabled", - "iam_no_root_access_key", - "cloudtrail_multi_region_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "po_1", @@ -264,10 +265,21 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "config_recorder_all_regions_enabled", - "iam_password_policy_reuse_24", - "securityhub_enabled" + "iam_policy_attached_only_to_group_or_roles", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation", + "iam_inline_policy_no_full_access_to_cloudtrail", + "iam_inline_policy_no_full_access_to_kms", + "iam_inline_policy_no_administrative_privileges", + "iam_policy_no_full_access_to_cloudtrail", + "iam_policy_no_full_access_to_kms", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_customer_unattached_policy_no_administrative_privileges", + "iam_group_administrator_access_policy", + "iam_user_administrator_access_policy", + "iam_role_administratoraccess_policy", + "iam_aws_attached_policy_no_administrative_privileges", + "iam_no_custom_policy_permissive_role_assumption" ] }, { @@ -282,12 +294,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "config_recorder_all_regions_enabled", - "cloudwatch_log_metric_filter_policy_changes" - ] + "Checks": [] }, { "Id": "po_3", @@ -302,10 +309,19 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", + "securityhub_enabled", "config_recorder_all_regions_enabled", - "securityhub_enabled" + "organizations_account_part_of_organizations", + "organizations_opt_out_ai_services_policy", + "trustedadvisor_premium_support_plan_subscribed", + "trustedadvisor_errors_and_warnings", + "accessanalyzer_enabled", + "vpc_endpoint_connections_trust_boundaries", + "organizations_scp_check_deny_regions", + "servicecatalog_portfolio_shared_within_organization_only", + "wellarchitected_workload_no_high_or_medium_risks", + "organizations_delegated_administrators", + "organizations_tags_policies_enabled_and_attached" ] }, { @@ -321,10 +337,21 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", "config_recorder_all_regions_enabled", - "guardduty_is_enabled", - "securityhub_enabled" + "config_recorder_using_aws_service_role", + "accessanalyzer_enabled", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_multi_region_enabled_logging_management_events", + "organizations_scp_check_deny_regions", + "organizations_account_part_of_organizations", + "trustedadvisor_premium_support_plan_subscribed", + "trustedadvisor_errors_and_warnings", + "securityhub_enabled", + "vpc_flow_logs_enabled", + "iam_root_mfa_enabled", + "iam_root_credentials_management_enabled" ] }, { @@ -340,11 +367,11 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "config_recorder_all_regions_enabled", - "securityhub_enabled" + "codeartifact_packages_external_public_publishing_disabled", + "codebuild_project_uses_allowed_github_organizations", + "codebuild_project_source_repo_url_no_sensitive_credentials", + "codebuild_project_user_controlled_buildspec", + "codebuild_project_no_secrets_in_variables" ] }, { @@ -359,12 +386,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "config_recorder_all_regions_enabled", - "guardduty_is_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "ov_2", @@ -379,11 +401,13 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "config_recorder_all_regions_enabled", - "guardduty_is_enabled", - "securityhub_enabled" + "accessanalyzer_enabled_without_findings", + "wellarchitected_workload_no_high_or_medium_risks", + "securityhub_enabled", + "inspector2_active_findings_exist", + "accessanalyzer_enabled", + "guardduty_no_high_severity_findings", + "trustedadvisor_errors_and_warnings" ] }, { @@ -399,10 +423,25 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", "config_recorder_all_regions_enabled", - "guardduty_is_enabled", - "securityhub_enabled" + "config_recorder_using_aws_service_role", + "accessanalyzer_enabled", + "securityhub_enabled", + "trustedadvisor_premium_support_plan_subscribed", + "trustedadvisor_errors_and_warnings", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_threat_detection_llm_jacking", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_policy_changes", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "vpc_endpoint_connections_trust_boundaries", + "organizations_scp_check_deny_regions", + "organizations_tags_policies_enabled_and_attached", + "organizations_delegated_administrators" ] }, { @@ -417,12 +456,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "config_recorder_all_regions_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "am_1", @@ -436,10 +470,7 @@ "Service": "aws" } ], - "Checks": [ - "config_recorder_all_regions_enabled", - "ec2_instance_managed_by_ssm" - ] + "Checks": [] }, { "Id": "am_2", @@ -453,10 +484,7 @@ "Service": "aws" } ], - "Checks": [ - "ec2_instance_managed_by_ssm", - "ssm_managed_compliant_patching" - ] + "Checks": [] }, { "Id": "am_3", @@ -470,15 +498,7 @@ "Service": "aws" } ], - "Checks": [ - "apigateway_restapi_logging_enabled", - "cloudtrail_multi_region_enabled", - "elbv2_logging_enabled", - "elb_logging_enabled", - "redshift_cluster_audit_logging", - "s3_bucket_server_access_logging_enabled", - "vpc_flow_logs_enabled" - ] + "Checks": [] }, { "Id": "am_5", @@ -506,7 +526,14 @@ "Service": "iam" } ], - "Checks": [] + "Checks": [ + "organizations_account_part_of_organizations", + "organizations_delegated_administrators", + "iam_group_administrator_access_policy", + "iam_policy_attached_only_to_group_or_roles", + "iam_user_administrator_access_policy", + "iam_role_cross_account_readonlyaccess_policy" + ] }, { "Id": "be_5", @@ -521,10 +548,32 @@ } ], "Checks": [ - "elbv2_deletion_protection", - "rds_instance_backup_enabled", - "rds_instance_multi_az", - "s3_bucket_object_versioning" + "drs_job_exist", + "backup_vaults_exist", + "backup_vaults_encrypted", + "backup_reportplans_exist", + "rds_cluster_protected_by_backup_plan", + "rds_instance_protected_by_backup_plan", + "dynamodb_table_protected_by_backup_plan", + "s3_bucket_cross_region_replication", + "autoscaling_group_capacity_rebalance_enabled", + "autoscaling_group_multiple_az", + "vpc_endpoint_multi_az_enabled", + "directconnect_connection_redundancy", + "directconnect_virtual_interface_redundancy", + "vpc_subnet_different_az", + "vpc_subnet_no_public_ip_by_default", + "vpc_subnet_separate_private_public", + "elbv2_cross_zone_load_balancing_enabled", + "elb_cross_zone_load_balancing_enabled", + "efs_encryption_at_rest_enabled", + "firehose_stream_encrypted_at_rest", + "glue_ml_transform_encrypted_at_rest", + "kafka_cluster_encryption_at_rest_uses_cmk", + "kinesis_stream_encrypted_at_rest", + "opensearch_service_domains_encryption_at_rest_enabled", + "redshift_cluster_encrypted_at_rest", + "sns_topics_kms_encryption_at_rest_enabled" ] }, { @@ -540,9 +589,27 @@ } ], "Checks": [ - "guardduty_is_enabled", - "securityhub_enabled", - "ssm_managed_compliant_patching" + "acm_certificates_with_secure_key_algorithms", + "acm_certificates_expiration_check", + "awslambda_function_no_secrets_in_variables", + "awslambda_function_no_secrets_in_code", + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_threat_detection_privilege_escalation", + "ecr_repositories_not_publicly_accessible", + "rds_snapshots_public_access", + "route53_dangling_ip_subdomain_takeover", + "secretsmanager_not_publicly_accessible", + "s3_bucket_public_access", + "s3_bucket_public_write_acl", + "s3_bucket_cross_account_access", + "ssm_document_secrets", + "kms_cmk_not_deleted_unintentionally", + "kms_cmk_not_multi_region", + "glacier_vaults_policy_public_access", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation", + "ssm_documents_set_as_public", + "s3_bucket_shadow_resource_vulnerability" ] }, { @@ -557,10 +624,7 @@ "Service": "aws" } ], - "Checks": [ - "guardduty_is_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "ra_3", @@ -575,8 +639,9 @@ } ], "Checks": [ - "guardduty_is_enabled", - "securityhub_enabled" + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation" ] }, { @@ -592,26 +657,22 @@ } ], "Checks": [ - "cloudtrail_cloudwatch_logging_enabled", - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured", - "config_recorder_all_regions_enabled", - "ec2_instance_imdsv2_enabled", - "guardduty_is_enabled", - "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_unauthorized_api_calls", "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", - "cloudwatch_log_metric_filter_authentication_failures", - "cloudwatch_log_metric_filter_sign_in_without_mfa", - "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", - "cloudwatch_log_metric_filter_policy_changes", - "cloudwatch_log_metric_filter_root_usage", - "cloudwatch_log_metric_filter_security_group_changes", - "cloudwatch_log_metric_filter_unauthorized_api_calls", - "rds_instance_enhanced_monitoring_enabled", - "securityhub_enabled" + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured", + "eventbridge_bus_cross_account_access", + "eventbridge_bus_exposed", + "route53_dangling_ip_subdomain_takeover", + "vpc_endpoint_connections_trust_boundaries", + "vpc_peering_routing_tables_with_least_privilege", + "vpc_endpoint_for_ec2_enabled" ] }, { @@ -626,24 +687,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_cloudwatch_logging_enabled", - "config_recorder_all_regions_enabled", - "ec2_instance_imdsv2_enabled", - "guardduty_is_enabled", - "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", - "cloudwatch_log_metric_filter_authentication_failures", - "cloudwatch_log_metric_filter_sign_in_without_mfa", - "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", - "cloudwatch_log_metric_filter_policy_changes", - "cloudwatch_log_metric_filter_root_usage", - "cloudwatch_log_metric_filter_security_group_changes", - "cloudwatch_log_metric_filter_unauthorized_api_calls", - "rds_instance_enhanced_monitoring_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "ac_1", @@ -658,15 +702,27 @@ } ], "Checks": [ - "iam_password_policy_reuse_24", + "iam_root_mfa_enabled", + "iam_no_root_access_key", + "iam_user_console_access_unused", + "iam_user_accesskey_unused", + "iam_user_two_active_access_key", + "iam_root_credentials_management_enabled", + "accessanalyzer_enabled", + "cognito_user_pool_mfa_enabled", + "cognito_user_pool_password_policy_symbol", + "cognito_user_pool_password_policy_uppercase", + "cognito_user_pool_password_policy_lowercase", + "cognito_user_pool_password_policy_minimum_length_14", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation", + "iam_policy_attached_only_to_group_or_roles", "iam_aws_attached_policy_no_administrative_privileges", "iam_customer_attached_policy_no_administrative_privileges", - "iam_inline_policy_no_administrative_privileges", - "iam_no_root_access_key", - "iam_rotate_access_key_90_days", - "iam_user_accesskey_unused", - "iam_user_console_access_unused", - "secretsmanager_automatic_rotation_enabled" + "iam_group_administrator_access_policy", + "iam_role_administratoraccess_policy", + "iam_policy_no_full_access_to_cloudtrail", + "iam_policy_no_full_access_to_kms" ] }, { @@ -682,24 +738,27 @@ } ], "Checks": [ - "ec2_ebs_public_snapshot", - "ec2_instance_public_ip", - "emr_cluster_master_nodes_no_public_ip", - "iam_root_hardware_mfa_enabled", - "iam_root_mfa_enabled", - "iam_user_mfa_enabled_console_access", - "awslambda_function_not_publicly_accessible", - "awslambda_function_url_public", + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389", + "ec2_instance_port_ssh_exposed_to_internet", + "ec2_instance_port_rdp_exposed_to_internet", + "ec2_instance_port_telnet_exposed_to_internet", + "ec2_instance_imdsv2_enabled", + "ec2_launch_template_imdsv2_required", + "ec2_instance_managed_by_ssm", + "ssm_managed_compliant_patching", "rds_instance_no_public_access", + "dms_instance_no_public_access", + "ec2_ebs_snapshot_account_block_public_access", + "glacier_vaults_policy_public_access", "rds_snapshots_public_access", "redshift_cluster_public_access", - "s3_bucket_public_access", - "s3_bucket_policy_public_write_access", + "s3_access_point_public_access_block", "s3_account_level_public_access_blocks", - "sagemaker_notebook_instance_without_direct_internet_access_configured", - "ec2_securitygroup_default_restrict_traffic", - "ec2_networkacl_allow_ingress_any_port", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + "s3_bucket_level_public_access_block", + "s3_bucket_public_access", + "s3_multi_region_access_point_public_access_block" ] }, { @@ -715,12 +774,20 @@ } ], "Checks": [ - "iam_aws_attached_policy_no_administrative_privileges", + "iam_administrator_access_with_mfa", + "iam_group_administrator_access_policy", + "iam_user_administrator_access_policy", + "iam_role_administratoraccess_policy", + "iam_policy_attached_only_to_group_or_roles", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation", "iam_customer_attached_policy_no_administrative_privileges", - "iam_inline_policy_no_administrative_privileges", - "iam_no_root_access_key", - "iam_user_accesskey_unused", - "iam_user_console_access_unused" + "iam_customer_unattached_policy_no_administrative_privileges", + "accessanalyzer_enabled_without_findings", + "eventbridge_bus_cross_account_access", + "eventbridge_bus_exposed", + "iam_policy_no_full_access_to_cloudtrail", + "iam_policy_no_full_access_to_kms" ] }, { @@ -736,22 +803,20 @@ } ], "Checks": [ - "acm_certificates_expiration_check", - "ec2_ebs_public_snapshot", - "ec2_instance_public_ip", - "emr_cluster_master_nodes_no_public_ip", - "awslambda_function_not_publicly_accessible", - "awslambda_function_url_public", - "rds_instance_no_public_access", - "rds_snapshots_public_access", - "redshift_cluster_public_access", - "s3_bucket_public_access", - "s3_bucket_policy_public_write_access", - "s3_account_level_public_access_blocks", - "sagemaker_notebook_instance_without_direct_internet_access_configured", + "vpc_subnet_different_az", + "vpc_subnet_separate_private_public", + "vpc_endpoint_connections_trust_boundaries", + "vpc_peering_routing_tables_with_least_privilege", + "ec2_networkacl_unused", "ec2_securitygroup_default_restrict_traffic", - "ec2_networkacl_allow_ingress_any_port", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + "ec2_securitygroup_not_used", + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_mongodb_27017_27018", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_oracle_1521_2483", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_postgres_5432", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_elasticsearch_kibana_9200_9300_5601" ] }, { @@ -767,9 +832,20 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "redshift_cluster_audit_logging", - "s3_bucket_server_access_logging_enabled" + "iam_root_mfa_enabled", + "iam_no_root_access_key", + "iam_rotate_access_key_90_days", + "iam_user_two_active_access_key", + "iam_user_with_temporary_credentials", + "iam_policy_attached_only_to_group_or_roles", + "iam_group_administrator_access_policy", + "iam_role_cross_account_readonlyaccess_policy", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_customer_unattached_policy_no_administrative_privileges", + "accessanalyzer_enabled", + "cognito_user_pool_password_policy_symbol" ] }, { @@ -785,9 +861,25 @@ } ], "Checks": [ - "iam_root_hardware_mfa_enabled", "iam_root_mfa_enabled", - "iam_user_mfa_enabled_console_access" + "iam_user_mfa_enabled_console_access", + "cognito_user_pool_password_policy_symbol", + "cognito_user_pool_password_policy_number", + "cognito_user_pool_password_policy_lowercase", + "cognito_user_pool_password_policy_uppercase", + "cognito_user_pool_password_policy_minimum_length_14", + "cognito_user_pool_mfa_enabled", + "cognito_identity_pool_guest_access_disabled", + "apigatewayv2_api_authorizers_enabled", + "apigateway_restapi_authorizers_enabled", + "apigateway_restapi_public_with_authorizer", + "awslambda_function_not_publicly_accessible", + "awslambda_function_url_public", + "iam_policy_attached_only_to_group_or_roles", + "iam_inline_policy_allows_privilege_escalation", + "iam_inline_policy_no_full_access_to_cloudtrail", + "iam_policy_allows_privilege_escalation", + "iam_check_saml_providers_sts" ] }, { @@ -803,15 +895,29 @@ } ], "Checks": [ + "backup_vaults_encrypted", + "backup_recovery_point_encrypted", "cloudtrail_kms_encryption_enabled", - "ec2_ebs_volume_encryption", - "efs_encryption_at_rest_enabled", - "opensearch_service_domains_encryption_at_rest_enabled", "cloudwatch_log_group_kms_encryption_enabled", - "rds_instance_storage_encrypted", + "s3_bucket_kms_encryption", "s3_bucket_default_encryption", - "sagemaker_notebook_instance_encryption_enabled", - "sns_topics_kms_encryption_at_rest_enabled" + "efs_encryption_at_rest_enabled", + "dynamodb_tables_kms_cmk_encryption_enabled", + "rds_instance_storage_encrypted", + "rds_snapshots_encrypted", + "redshift_cluster_encrypted_at_rest", + "elasticache_redis_cluster_rest_encryption_enabled", + "neptune_cluster_storage_encrypted", + "opensearch_service_domains_encryption_at_rest_enabled", + "opensearch_service_domains_node_to_node_encryption_enabled", + "glue_data_catalogs_metadata_encryption_enabled", + "glue_data_catalogs_connection_passwords_encryption_enabled", + "glue_etl_jobs_amazon_s3_encryption_enabled", + "glue_etl_jobs_cloudwatch_logs_encryption_enabled", + "glue_etl_jobs_job_bookmark_encryption_enabled", + "kinesis_stream_encrypted_at_rest", + "firehose_stream_encrypted_at_rest", + "storagegateway_fileshare_encryption_enabled" ] }, { @@ -827,10 +933,18 @@ } ], "Checks": [ - "acm_certificates_expiration_check", - "elb_ssl_listeners", - "opensearch_service_domains_node_to_node_encryption_enabled", - "s3_bucket_secure_transport_policy" + "elasticache_redis_cluster_in_transit_encryption_enabled", + "cloudfront_distributions_origin_traffic_encrypted", + "cloudfront_distributions_https_enabled", + "s3_bucket_secure_transport_policy", + "rds_instance_transport_encrypted", + "transfer_server_in_transit_encryption_enabled", + "kafka_cluster_in_transit_encryption_enabled", + "kafka_connector_in_transit_encryption_enabled", + "dms_endpoint_ssl_enabled", + "dms_endpoint_redis_in_transit_encryption_enabled", + "opensearch_service_domains_https_communications_enforced", + "opensearch_service_domains_node_to_node_encryption_enabled" ] }, { @@ -846,9 +960,23 @@ } ], "Checks": [ - "ec2_instance_managed_by_ssm", - "ssm_managed_compliant_patching", - "ec2_elastic_ip_unassigned" + "dynamodb_table_deletion_protection_enabled", + "eks_cluster_deletion_protection_enabled", + "rds_cluster_deletion_protection", + "elbv2_deletion_protection", + "cloudtrail_bucket_requires_mfa_delete", + "kms_cmk_not_deleted_unintentionally", + "dlm_ebs_snapshot_lifecycle_policy_exists", + "backup_vaults_encrypted", + "backup_vaults_exist", + "backup_reportplans_exist", + "backup_recovery_point_encrypted", + "rds_instance_backup_enabled", + "rds_cluster_protected_by_backup_plan", + "ec2_ebs_volume_protected_by_backup_plan", + "s3_bucket_lifecycle_enabled", + "s3_bucket_default_encryption", + "s3_bucket_kms_encryption" ] }, { @@ -864,11 +992,28 @@ } ], "Checks": [ - "elbv2_deletion_protection", - "rds_instance_enhanced_monitoring_enabled", - "rds_instance_backup_enabled", + "dynamodb_table_deletion_protection_enabled", + "dynamodb_table_protected_by_backup_plan", + "eks_cluster_deletion_protection_enabled", "rds_instance_multi_az", - "s3_bucket_object_versioning" + "rds_instance_backup_enabled", + "rds_cluster_deletion_protection", + "rds_instance_deletion_protection", + "redshift_cluster_multi_az_enabled", + "dynamodb_table_autoscaling_enabled", + "autoscaling_group_capacity_rebalance_enabled", + "autoscaling_group_multiple_az", + "elb_cross_zone_load_balancing_enabled", + "elbv2_cross_zone_load_balancing_enabled", + "elb_is_in_multiple_az", + "directconnect_connection_redundancy", + "s3_bucket_cross_region_replication", + "s3_bucket_lifecycle_enabled", + "backup_plans_exist", + "backup_recovery_point_encrypted", + "backup_reportplans_exist", + "rds_cluster_protected_by_backup_plan", + "rds_instance_protected_by_backup_plan" ] }, { @@ -884,24 +1029,26 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "ec2_ebs_public_snapshot", - "elbv2_logging_enabled", - "elb_logging_enabled", - "guardduty_is_enabled", - "awslambda_function_url_public", - "rds_instance_no_public_access", - "rds_snapshots_public_access", - "redshift_cluster_public_access", - "s3_bucket_server_access_logging_enabled", "s3_bucket_public_access", - "s3_bucket_policy_public_write_access", - "s3_account_level_public_access_blocks", - "sagemaker_notebook_instance_without_direct_internet_access_configured", - "securityhub_enabled", - "vpc_flow_logs_enabled" + "s3_bucket_public_write_acl", + "s3_bucket_public_list_acl", + "s3_bucket_cross_account_access", + "s3_bucket_kms_encryption", + "s3_bucket_acl_prohibited", + "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", + "cloudtrail_kms_encryption_enabled", + "cloudwatch_log_group_not_publicly_accessible", + "cloudwatch_log_group_kms_encryption_enabled", + "secretsmanager_not_publicly_accessible", + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "ec2_ebs_snapshot_account_block_public_access", + "ec2_ebs_public_snapshot", + "rds_snapshots_public_access", + "ec2_ami_public", + "ssm_document_secrets", + "ec2_instance_secrets_user_data", + "awslambda_function_no_secrets_in_variables", + "awslambda_function_no_secrets_in_code" ] }, { @@ -932,14 +1079,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_log_file_validation_enabled", - "ec2_instance_managed_by_ssm", - "ec2_instance_older_than_specific_days", - "elbv2_deletion_protection", - "ssm_managed_compliant_patching", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" - ] + "Checks": [] }, { "Id": "ds_8", @@ -953,10 +1093,7 @@ "Service": "aws" } ], - "Checks": [ - "ec2_instance_managed_by_ssm", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "ip_1", @@ -971,9 +1108,18 @@ } ], "Checks": [ - "ec2_instance_managed_by_ssm", - "ec2_instance_older_than_specific_days", - "ssm_managed_compliant_patching" + "ec2_securitygroup_default_restrict_traffic", + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "ec2_networkacl_unused", + "ec2_securitygroup_not_used", + "ec2_instance_public_ip", + "iam_root_mfa_enabled", + "iam_administrator_access_with_mfa", + "iam_policy_attached_only_to_group_or_roles", + "iam_inline_policy_allows_privilege_escalation", + "iam_policy_allows_privilege_escalation", + "s3_bucket_public_access", + "s3_bucket_cross_account_access" ] }, { @@ -989,7 +1135,15 @@ } ], "Checks": [ - "ec2_instance_managed_by_ssm" + "codepipeline_project_repo_private", + "codebuild_project_not_publicly_accessible", + "codebuild_project_logging_enabled", + "codebuild_project_s3_logs_encrypted", + "codebuild_project_uses_allowed_github_organizations", + "codebuild_project_source_repo_url_no_sensitive_credentials", + "codebuild_project_no_secrets_in_variables", + "codebuild_project_user_controlled_buildspec", + "codebuild_project_older_90_days" ] }, { @@ -1004,9 +1158,7 @@ "Service": "elb" } ], - "Checks": [ - "elbv2_deletion_protection" - ] + "Checks": [] }, { "Id": "ip_4", @@ -1021,9 +1173,21 @@ } ], "Checks": [ - "dynamodb_tables_pitr_enabled", + "backup_vaults_exist", + "backup_vaults_encrypted", + "backup_recovery_point_encrypted", + "backup_reportplans_exist", + "backup_plans_exist", + "documentdb_cluster_backup_enabled", + "dynamodb_table_protected_by_backup_plan", + "ec2_ebs_volume_protected_by_backup_plan", + "efs_have_backup_enabled", + "elasticache_redis_cluster_backup_enabled", + "fsx_file_system_copy_tags_to_backups_enabled", + "neptune_cluster_backup_enabled", + "rds_cluster_protected_by_backup_plan", "rds_instance_backup_enabled", - "s3_bucket_object_versioning" + "rds_instance_protected_by_backup_plan" ] }, { @@ -1038,7 +1202,21 @@ "Service": "ec2" } ], - "Checks": [] + "Checks": [ + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_alarm_actions_alarm_state_configured", + "guardduty_is_enabled", + "guardduty_ec2_malware_protection_enabled", + "guardduty_eks_runtime_monitoring_enabled", + "inspector2_is_enabled", + "inspector2_active_findings_exist", + "secretsmanager_automatic_rotation_enabled", + "secretsmanager_secret_rotated_periodically" + ] }, { "Id": "ip_8", @@ -1053,18 +1231,15 @@ } ], "Checks": [ - "ec2_ebs_public_snapshot", - "ec2_instance_public_ip", - "eks_cluster_not_publicly_accessible", - "emr_cluster_master_nodes_no_public_ip", - "awslambda_function_url_public", - "rds_instance_no_public_access", - "rds_snapshots_public_access", - "redshift_cluster_public_access", - "s3_bucket_public_access", - "s3_bucket_policy_public_write_access", - "s3_account_level_public_access_blocks", - "sagemaker_notebook_instance_without_direct_internet_access_configured" + "cloudtrail_cloudwatch_logging_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_alarm_actions_enabled", + "cloudwatch_alarm_actions_alarm_state_configured", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_root_usage", + "cloudwatch_log_metric_filter_authentication_failures" ] }, { @@ -1080,13 +1255,8 @@ } ], "Checks": [ - "dynamodb_tables_pitr_enabled", - "efs_have_backup_enabled", - "elbv2_deletion_protection", - "rds_instance_backup_enabled", - "rds_instance_multi_az", - "redshift_cluster_automated_snapshot", - "s3_bucket_object_versioning" + "ssmincidents_enabled_with_plans", + "drs_job_exist" ] }, { @@ -1102,9 +1272,9 @@ } ], "Checks": [ - "config_recorder_all_regions_enabled", - "ec2_instance_managed_by_ssm", - "ssm_managed_compliant_patching" + "inspector2_is_enabled", + "guardduty_is_enabled", + "inspector2_active_findings_exist" ] }, { @@ -1119,9 +1289,7 @@ "Service": "cloudtrail" } ], - "Checks": [ - "cloudtrail_multi_region_enabled" - ] + "Checks": [] }, { "Id": "pt_1", @@ -1136,13 +1304,22 @@ } ], "Checks": [ - "apigateway_restapi_logging_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "elbv2_logging_enabled", - "elb_logging_enabled", - "s3_bucket_server_access_logging_enabled", - "vpc_flow_logs_enabled" + "cloudtrail_kms_encryption_enabled", + "cloudtrail_log_file_validation_enabled", + "cloudtrail_logs_s3_bucket_access_logging_enabled", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_threat_detection_llm_jacking", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", + "vpc_flow_logs_enabled", + "config_recorder_all_regions_enabled", + "config_recorder_using_aws_service_role" ] }, { @@ -1157,19 +1334,7 @@ "Service": "aws" } ], - "Checks": [ - "ec2_ebs_public_snapshot", - "iam_aws_attached_policy_no_administrative_privileges", - "iam_customer_attached_policy_no_administrative_privileges", - "iam_inline_policy_no_administrative_privileges", - "iam_no_root_access_key", - "awslambda_function_url_public", - "rds_snapshots_public_access", - "redshift_cluster_public_access", - "s3_bucket_public_access", - "s3_bucket_policy_public_write_access", - "s3_account_level_public_access_blocks" - ] + "Checks": [] }, { "Id": "pt_4", @@ -1184,11 +1349,15 @@ } ], "Checks": [ - "awslambda_function_not_publicly_accessible", - "rds_instance_no_public_access", - "redshift_cluster_public_access", - "ec2_networkacl_allow_ingress_any_port", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + "cloudfront_distributions_https_enabled", + "cloudfront_distributions_origin_traffic_encrypted", + "vpc_flow_logs_enabled", + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "apigateway_restapi_public_with_authorizer", + "apigateway_restapi_authorizers_enabled", + "s3_bucket_secure_transport_policy", + "s3_bucket_kms_encryption", + "cloudtrail_kms_encryption_enabled" ] }, { @@ -1204,10 +1373,19 @@ } ], "Checks": [ - "elbv2_deletion_protection", - "rds_instance_backup_enabled", - "rds_instance_multi_az", - "s3_bucket_object_versioning" + "eks_cluster_deletion_protection_enabled", + "rds_cluster_deletion_protection", + "rds_instance_deletion_protection", + "dynamodb_table_deletion_protection_enabled", + "directconnect_connection_redundancy", + "vpc_endpoint_multi_az_enabled", + "elbv2_cross_zone_load_balancing_enabled", + "elb_cross_zone_load_balancing_enabled", + "autoscaling_group_capacity_rebalance_enabled", + "autoscaling_group_multiple_az", + "vpc_vpn_connection_tunnels_up", + "cloudfront_distributions_multiple_origin_failover_configured", + "s3_bucket_cross_region_replication" ] }, { @@ -1223,17 +1401,19 @@ } ], "Checks": [ - "apigateway_restapi_logging_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "elbv2_logging_enabled", - "elb_logging_enabled", - "redshift_cluster_audit_logging", - "s3_bucket_server_access_logging_enabled", - "ec2_securitygroup_default_restrict_traffic", "vpc_flow_logs_enabled", - "ec2_networkacl_allow_ingress_any_port", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + "cloudtrail_cloudwatch_logging_enabled", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_threat_detection_llm_jacking", + "vpc_endpoint_connections_trust_boundaries", + "vpc_endpoint_for_ec2_enabled", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_unauthorized_api_calls" ] }, { @@ -1249,8 +1429,21 @@ } ], "Checks": [ - "guardduty_is_enabled", - "securityhub_enabled" + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_log_metric_filter_root_usage", + "cloudwatch_log_metric_filter_policy_changes", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk" ] }, { @@ -1266,16 +1459,16 @@ } ], "Checks": [ - "apigateway_restapi_logging_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_log_metric_filter_aws_organizations_changes", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_threat_detection_llm_jacking", "cloudtrail_cloudwatch_logging_enabled", - "elbv2_logging_enabled", - "elb_logging_enabled", - "redshift_cluster_audit_logging", - "s3_bucket_server_access_logging_enabled", - "vpc_flow_logs_enabled" + "cloudtrail_multi_region_enabled_logging_management_events" ] }, { @@ -1290,17 +1483,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "elbv2_logging_enabled", - "elb_logging_enabled", - "guardduty_is_enabled", - "guardduty_no_high_severity_findings", - "s3_bucket_server_access_logging_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "ae_5", @@ -1314,12 +1497,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured" - ] + "Checks": [] }, { "Id": "cm_1", @@ -1334,16 +1512,24 @@ } ], "Checks": [ - "apigateway_restapi_logging_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "elbv2_logging_enabled", - "elb_logging_enabled", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_insights_exist", + "cloudtrail_cloudwatch_logging_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured", + "cloudwatch_changes_to_network_acls_alarm_configured", + "vpc_flow_logs_enabled", "guardduty_is_enabled", - "s3_bucket_server_access_logging_enabled", - "securityhub_enabled", - "vpc_flow_logs_enabled" + "inspector2_is_enabled", + "accessanalyzer_enabled_without_findings" ] }, { @@ -1358,28 +1544,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_cloudwatch_logging_enabled", - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured", - "config_recorder_all_regions_enabled", - "ec2_instance_imdsv2_enabled", - "guardduty_is_enabled", - "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", - "cloudwatch_log_metric_filter_authentication_failures", - "cloudwatch_log_metric_filter_sign_in_without_mfa", - "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", - "cloudwatch_log_metric_filter_policy_changes", - "cloudwatch_log_metric_filter_root_usage", - "cloudwatch_log_metric_filter_security_group_changes", - "cloudwatch_log_metric_filter_unauthorized_api_calls", - "rds_instance_enhanced_monitoring_enabled", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "cm_3", @@ -1394,12 +1559,14 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "guardduty_is_enabled", - "s3_bucket_server_access_logging_enabled", - "securityhub_enabled" + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudtrail_cloudwatch_logging_enabled", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled" ] }, { @@ -1415,8 +1582,8 @@ } ], "Checks": [ - "guardduty_is_enabled", - "securityhub_enabled" + "codebuild_project_source_repo_url_no_sensitive_credentials", + "codebuild_project_no_secrets_in_variables" ] }, { @@ -1431,18 +1598,7 @@ "Service": "aws" } ], - "Checks": [ - "cloudtrail_cloudwatch_logging_enabled", - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured", - "ec2_instance_imdsv2_enabled", - "elbv2_waf_acl_attached", - "guardduty_is_enabled", - "guardduty_no_high_severity_findings", - "securityhub_enabled" - ] + "Checks": [] }, { "Id": "cm_6", @@ -1457,12 +1613,9 @@ } ], "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "guardduty_is_enabled", - "s3_bucket_server_access_logging_enabled", - "securityhub_enabled" + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation" ] }, { @@ -1478,16 +1631,26 @@ } ], "Checks": [ - "apigateway_restapi_logging_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "elbv2_logging_enabled", - "elb_logging_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_llm_jacking", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_root_usage", + "cloudwatch_log_metric_filter_aws_organizations_changes", + "cloudwatch_log_metric_filter_policy_changes", + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", "guardduty_is_enabled", - "s3_bucket_server_access_logging_enabled", - "securityhub_enabled", - "vpc_flow_logs_enabled" + "guardduty_ec2_malware_protection_enabled", + "guardduty_s3_protection_enabled", + "guardduty_rds_protection_enabled", + "guardduty_lambda_protection_enabled", + "guardduty_eks_runtime_monitoring_enabled" ] }, { @@ -1503,16 +1666,27 @@ } ], "Checks": [ - "cloudtrail_cloudwatch_logging_enabled", - "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudtrail_threat_detection_llm_jacking", + "cloudtrail_threat_detection_enumeration", + "cloudtrail_threat_detection_privilege_escalation", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudtrail_insights_exist", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_alarm_actions_alarm_state_configured", + "cloudwatch_alarm_actions_enabled", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_root_usage", "cloudwatch_changes_to_network_gateways_alarm_configured", "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured", - "ec2_instance_imdsv2_enabled", - "elbv2_waf_acl_attached", - "guardduty_is_enabled", - "guardduty_no_high_severity_findings", - "securityhub_enabled" + "cloudwatch_changes_to_network_acls_alarm_configured", + "eventbridge_bus_cross_account_access", + "eventbridge_bus_exposed", + "route53_dangling_ip_subdomain_takeover", + "s3_bucket_event_notifications_enabled", + "sns_subscription_not_using_http_endpoints" ] }, { @@ -1527,9 +1701,7 @@ "Service": "ec2" } ], - "Checks": [ - "ec2_instance_imdsv2_enabled" - ] + "Checks": [] }, { "Id": "an_2", @@ -1543,9 +1715,7 @@ "Service": "guardduty" } ], - "Checks": [ - "guardduty_no_high_severity_findings" - ] + "Checks": [] }, { "Id": "mi_3", @@ -1559,9 +1729,7 @@ "Service": "guardduty" } ], - "Checks": [ - "guardduty_no_high_severity_findings" - ] + "Checks": [] }, { "Id": "rp_1", @@ -1576,13 +1744,7 @@ } ], "Checks": [ - "dynamodb_tables_pitr_enabled", - "efs_have_backup_enabled", - "elbv2_deletion_protection", - "rds_instance_backup_enabled", - "rds_instance_multi_az", - "redshift_cluster_automated_snapshot", - "s3_bucket_object_versioning" + "ssmincidents_enabled_with_plans" ] }, { @@ -1598,15 +1760,22 @@ } ], "Checks": [ - "dynamodb_tables_pitr_enabled", + "backup_vaults_exist", + "backup_vaults_encrypted", + "backup_recovery_point_encrypted", + "backup_reportplans_exist", + "backup_plans_exist", + "documentdb_cluster_backup_enabled", + "dynamodb_table_protected_by_backup_plan", + "ec2_ebs_volume_protected_by_backup_plan", "efs_have_backup_enabled", - "elbv2_deletion_protection", + "elasticache_redis_cluster_backup_enabled", + "fsx_file_system_copy_tags_to_backups_enabled", + "neptune_cluster_backup_enabled", + "rds_cluster_protected_by_backup_plan", "rds_instance_backup_enabled", - "rds_instance_multi_az", - "redshift_cluster_automated_snapshot", - "s3_bucket_object_versioning" + "rds_instance_protected_by_backup_plan" ] } ] } -