From 8751615faae5249fe3c0d70a9f4afee31a83c43d Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:27:47 +0200 Subject: [PATCH] feat: MittreAtack compliance detailed view (#8002) --- ui/CHANGELOG.md | 2 + .../compliance/[compliancetitle]/page.tsx | 8 +- .../client-accordion-content.tsx | 2 +- .../client-accordion-wrapper.tsx | 24 +- ui/components/compliance/compliance-card.tsx | 9 +- .../compliance-charts/bar-chart.tsx | 4 +- .../aws-well-architected-details.tsx | 2 +- .../compliance-custom-details/cis-details.tsx | 2 +- .../mitre-details.tsx | 111 ++++++++ .../threat-details.tsx | 4 +- .../compliance-header/scan-selector.tsx | 9 +- ui/lib/compliance/commons.ts | 32 +++ ui/lib/compliance/iso.tsx | 60 ++--- ui/lib/compliance/mitre.tsx | 252 ++++++++++++++++++ ui/types/compliance.ts | 16 +- 15 files changed, 466 insertions(+), 71 deletions(-) create mode 100644 ui/components/compliance/compliance-custom-details/mitre-details.tsx create mode 100644 ui/lib/compliance/mitre.tsx diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 6a384b133c..4d5dd03148 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -21,6 +21,8 @@ All notable changes to the **Prowler UI** are documented in this file. - Add compliance detail view: AWS Well-Architected Framework [(#7925)](https://github.com/prowler-cloud/prowler/pull/7925) - Add compliance detail view: KISA [(#7965)](https://github.com/prowler-cloud/prowler/pull/7965) - Add compliance detail view: ProwlerThreatScore [(#7966)](https://github.com/prowler-cloud/prowler/pull/7966) +- Add compliance detail view: Generic (rest of the compliances) [(#7990)](https://github.com/prowler-cloud/prowler/pull/7990) +- Add compliance detail view: MITRE ATTACK [(#8002)](https://github.com/prowler-cloud/prowler/pull/8002) - Improve `Scan ID` filter by adding more context and enhancing the UI/UX. [(#7979)](https://github.com/prowler-cloud/prowler/pull/7979) ### 🔄 Changed diff --git a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx index f75c344039..71e7706285 100644 --- a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx +++ b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx @@ -22,10 +22,7 @@ import { } from "@/components/compliance"; import { getComplianceIcon } from "@/components/icons/compliance/IconCompliance"; import { ContentLayout } from "@/components/ui"; -import { - calculateCategoryHeatmapData, - getComplianceMapper, -} from "@/lib/compliance/commons"; +import { getComplianceMapper } from "@/lib/compliance/commons"; import { ScanProps } from "@/types"; import { Framework, RequirementsTotals } from "@/types/compliance"; @@ -239,7 +236,7 @@ const SSRComplianceContent = async ({ ); // Calculate category heatmap data - const categoryHeatmapData = calculateCategoryHeatmapData(data); + const categoryHeatmapData = mapper.calculateCategoryHeatmapData(data); const totalRequirements: RequirementsTotals = data.reduce( (acc: RequirementsTotals, framework: Framework) => ({ @@ -271,6 +268,7 @@ const SSRComplianceContent = async ({ diff --git a/ui/components/compliance/compliance-accordion/client-accordion-content.tsx b/ui/components/compliance/compliance-accordion/client-accordion-content.tsx index 5769e13259..23bedb0459 100644 --- a/ui/components/compliance/compliance-accordion/client-accordion-content.tsx +++ b/ui/components/compliance/compliance-accordion/client-accordion-content.tsx @@ -173,7 +173,7 @@ export const ClientAccordionContent = ({ return (
- ⚠️ There are no findings for this regions + ⚠️ There are no findings for these regions
); }; diff --git a/ui/components/compliance/compliance-accordion/client-accordion-wrapper.tsx b/ui/components/compliance/compliance-accordion/client-accordion-wrapper.tsx index a47952612b..6288cf9171 100644 --- a/ui/components/compliance/compliance-accordion/client-accordion-wrapper.tsx +++ b/ui/components/compliance/compliance-accordion/client-accordion-wrapper.tsx @@ -8,9 +8,11 @@ import { CustomButton } from "@/components/ui/custom"; export const ClientAccordionWrapper = ({ items, defaultExpandedKeys, + hideExpandButton = false, }: { items: AccordionItemProps[]; defaultExpandedKeys: string[]; + hideExpandButton?: boolean; }) => { const [selectedKeys, setSelectedKeys] = useState(defaultExpandedKeys); @@ -56,16 +58,18 @@ export const ClientAccordionWrapper = ({ return (
-
- - {isExpanded ? "Collapse all" : "Expand all"} - -
+ {!hideExpandButton && ( +
+ + {isExpanded ? "Collapse all" : "Expand all"} + +
+ )} = ({ return "success"; }; - const isPressable = !id.includes("mitre"); - const navigateToDetail = () => { - // We will unlock this while developing the rest of complainces. - if (!isPressable) { - return; - } - const formattedTitleForUrl = encodeURIComponent(title); const path = `/compliance/${formattedTitleForUrl}`; const params = new URLSearchParams(); @@ -103,7 +96,7 @@ export const ComplianceCard: React.FC = ({ fullWidth isHoverable shadow="sm" - isPressable={isPressable} + isPressable onPress={navigateToDetail} > diff --git a/ui/components/compliance/compliance-charts/bar-chart.tsx b/ui/components/compliance/compliance-charts/bar-chart.tsx index 4e5fe95995..3afd02ee5d 100644 --- a/ui/components/compliance/compliance-charts/bar-chart.tsx +++ b/ui/components/compliance/compliance-charts/bar-chart.tsx @@ -101,9 +101,7 @@ export const BarChart = ({ sections }: FailedSectionsListProps) => {
{title}
-

- There are no failed sections -

+

There are no failed sections

); diff --git a/ui/components/compliance/compliance-custom-details/aws-well-architected-details.tsx b/ui/components/compliance/compliance-custom-details/aws-well-architected-details.tsx index b0069433ee..9317c61485 100644 --- a/ui/components/compliance/compliance-custom-details/aws-well-architected-details.tsx +++ b/ui/components/compliance/compliance-custom-details/aws-well-architected-details.tsx @@ -78,7 +78,7 @@ export const AWSWellArchitectedCustomDetails = ({ - {requirement.implementation_guidance_url} + {requirement.implementation_guidance_url as string} )} diff --git a/ui/components/compliance/compliance-custom-details/cis-details.tsx b/ui/components/compliance/compliance-custom-details/cis-details.tsx index 6977c7d101..f804f8e7d0 100644 --- a/ui/components/compliance/compliance-custom-details/cis-details.tsx +++ b/ui/components/compliance/compliance-custom-details/cis-details.tsx @@ -17,7 +17,7 @@ interface CISDetailsProps { export const CISCustomDetails = ({ requirement }: CISDetailsProps) => { const processReferences = ( - references: string | number | string[] | undefined, + references: string | number | string[] | object[] | undefined, ): string[] => { if (typeof references !== "string") return []; diff --git a/ui/components/compliance/compliance-custom-details/mitre-details.tsx b/ui/components/compliance/compliance-custom-details/mitre-details.tsx new file mode 100644 index 0000000000..71eca0d370 --- /dev/null +++ b/ui/components/compliance/compliance-custom-details/mitre-details.tsx @@ -0,0 +1,111 @@ +import { Requirement } from "@/types/compliance"; + +import { + ComplianceBadge, + ComplianceBadgeContainer, + ComplianceChipContainer, + ComplianceDetailContainer, + ComplianceDetailSection, + ComplianceDetailText, + ComplianceLink, +} from "./shared-components"; + +export const MITRECustomDetails = ({ + requirement, +}: { + requirement: Requirement; +}) => { + const cloudServices = requirement.cloud_services as + | Array<{ + service: string; + category: string; + value: string; + comment: string; + }> + | undefined; + + return ( + + {requirement.description && ( + + {requirement.description} + + )} + + + {requirement.technique_id && ( + + )} + + + + + + + {requirement.subtechniques && + Array.isArray(requirement.subtechniques) && + requirement.subtechniques.length > 0 && ( + + )} + + {requirement.technique_url && ( + + + {requirement.technique_url as string} + + + )} + + {cloudServices && cloudServices.length > 0 && ( + +
+ {cloudServices.map((service, index) => ( +
+
+ + + +
+ {service.comment && ( +
+
+ Details +
+ + {service.comment} + +
+ )} +
+ ))} +
+
+ )} +
+ ); +}; diff --git a/ui/components/compliance/compliance-custom-details/threat-details.tsx b/ui/components/compliance/compliance-custom-details/threat-details.tsx index 4f2f5e87da..1cf127bf84 100644 --- a/ui/components/compliance/compliance-custom-details/threat-details.tsx +++ b/ui/components/compliance/compliance-custom-details/threat-details.tsx @@ -24,7 +24,7 @@ export const ThreatCustomDetails = ({ {requirement.attributeDescription && ( - {requirement.attributeDescription} + {requirement.attributeDescription as string} )} @@ -59,7 +59,7 @@ export const ThreatCustomDetails = ({ {requirement.additionalInformation && ( - {requirement.additionalInformation} + {requirement.additionalInformation as string} )} diff --git a/ui/components/compliance/compliance-header/scan-selector.tsx b/ui/components/compliance/compliance-header/scan-selector.tsx index 390bc669bc..4739633cc6 100644 --- a/ui/components/compliance/compliance-header/scan-selector.tsx +++ b/ui/components/compliance/compliance-header/scan-selector.tsx @@ -31,9 +31,12 @@ export const ScanSelector = ({ size="lg" labelPlacement="outside" selectedKeys={new Set([selectedScanId])} - onSelectionChange={(keys) => - onSelectionChange(Array.from(keys)[0] as string) - } + onSelectionChange={(keys) => { + const newSelectedId = Array.from(keys)[0] as string; + if (newSelectedId && newSelectedId !== selectedScanId) { + onSelectionChange(newSelectedId); + } + }} renderValue={() => { const selectedItem = scans.find((item) => item.id === selectedScanId); return selectedItem ? ( diff --git a/ui/lib/compliance/commons.ts b/ui/lib/compliance/commons.ts index 286d6e6e78..9befd489c7 100644 --- a/ui/lib/compliance/commons.ts +++ b/ui/lib/compliance/commons.ts @@ -6,6 +6,7 @@ import { ENSCustomDetails } from "@/components/compliance/compliance-custom-deta import { GenericCustomDetails } from "@/components/compliance/compliance-custom-details/generic-details"; import { ISOCustomDetails } from "@/components/compliance/compliance-custom-details/iso-details"; import { KISACustomDetails } from "@/components/compliance/compliance-custom-details/kisa-details"; +import { MITRECustomDetails } from "@/components/compliance/compliance-custom-details/mitre-details"; import { ThreatCustomDetails } from "@/components/compliance/compliance-custom-details/threat-details"; import { AccordionItemProps } from "@/components/ui/accordion/Accordion"; import { @@ -41,6 +42,12 @@ import { mapComplianceData as mapKISAComplianceData, toAccordionItems as toKISAAccordionItems, } from "./kisa"; +import { + calculateCategoryHeatmapData as calculateMITRECategoryHeatmapData, + getTopFailedSections as getMITRETopFailedSections, + mapComplianceData as mapMITREComplianceData, + toAccordionItems as toMITREAccordionItems, +} from "./mitre"; import { mapComplianceData as mapThetaComplianceData, toAccordionItems as toThetaAccordionItems, @@ -57,6 +64,7 @@ export interface ComplianceMapper { scanId: string | undefined, ) => AccordionItemProps[]; getTopFailedSections: (mappedData: Framework[]) => FailedSection[]; + calculateCategoryHeatmapData: (complianceData: Framework[]) => CategoryData[]; getDetailsComponent: (requirement: Requirement) => React.ReactNode; } @@ -103,6 +111,8 @@ const complianceMappers: Record = { mapComplianceData: mapENSComplianceData, toAccordionItems: toENSAccordionItems, getTopFailedSections, + calculateCategoryHeatmapData: (data: Framework[]) => + calculateCategoryHeatmapData(data), getDetailsComponent: (requirement: Requirement) => React.createElement(ENSCustomDetails, { requirement }), }, @@ -110,6 +120,8 @@ const complianceMappers: Record = { mapComplianceData: mapISOComplianceData, toAccordionItems: toISOAccordionItems, getTopFailedSections, + calculateCategoryHeatmapData: (data: Framework[]) => + calculateCategoryHeatmapData(data), getDetailsComponent: (requirement: Requirement) => React.createElement(ISOCustomDetails, { requirement }), }, @@ -117,6 +129,8 @@ const complianceMappers: Record = { mapComplianceData: mapCISComplianceData, toAccordionItems: toCISAccordionItems, getTopFailedSections, + calculateCategoryHeatmapData: (data: Framework[]) => + calculateCategoryHeatmapData(data), getDetailsComponent: (requirement: Requirement) => React.createElement(CISCustomDetails, { requirement }), }, @@ -124,6 +138,8 @@ const complianceMappers: Record = { mapComplianceData: mapAWSWellArchitectedComplianceData, toAccordionItems: toAWSWellArchitectedAccordionItems, getTopFailedSections, + calculateCategoryHeatmapData: (data: Framework[]) => + calculateCategoryHeatmapData(data), getDetailsComponent: (requirement: Requirement) => React.createElement(AWSWellArchitectedCustomDetails, { requirement }), }, @@ -131,6 +147,8 @@ const complianceMappers: Record = { mapComplianceData: mapAWSWellArchitectedComplianceData, toAccordionItems: toAWSWellArchitectedAccordionItems, getTopFailedSections, + calculateCategoryHeatmapData: (data: Framework[]) => + calculateCategoryHeatmapData(data), getDetailsComponent: (requirement: Requirement) => React.createElement(AWSWellArchitectedCustomDetails, { requirement }), }, @@ -138,13 +156,25 @@ const complianceMappers: Record = { mapComplianceData: mapKISAComplianceData, toAccordionItems: toKISAAccordionItems, getTopFailedSections, + calculateCategoryHeatmapData: (data: Framework[]) => + calculateCategoryHeatmapData(data), getDetailsComponent: (requirement: Requirement) => React.createElement(KISACustomDetails, { requirement }), }, + "MITRE-ATTACK": { + mapComplianceData: mapMITREComplianceData, + toAccordionItems: toMITREAccordionItems, + getTopFailedSections: getMITRETopFailedSections, + calculateCategoryHeatmapData: calculateMITRECategoryHeatmapData, + getDetailsComponent: (requirement: Requirement) => + React.createElement(MITRECustomDetails, { requirement }), + }, ProwlerThreatScore: { mapComplianceData: mapThetaComplianceData, toAccordionItems: toThetaAccordionItems, getTopFailedSections, + calculateCategoryHeatmapData: (complianceData: Framework[]) => + calculateCategoryHeatmapData(complianceData), getDetailsComponent: (requirement: Requirement) => React.createElement(ThreatCustomDetails, { requirement }), }, @@ -155,6 +185,8 @@ const defaultMapper: ComplianceMapper = { mapComplianceData: mapGenericComplianceData, toAccordionItems: toGenericAccordionItems, getTopFailedSections, + calculateCategoryHeatmapData: (data: Framework[]) => + calculateCategoryHeatmapData(data), getDetailsComponent: (requirement: Requirement) => React.createElement(GenericCustomDetails, { requirement }), }; diff --git a/ui/lib/compliance/iso.tsx b/ui/lib/compliance/iso.tsx index 8fd2e7ae7e..4fdfd0fc31 100644 --- a/ui/lib/compliance/iso.tsx +++ b/ui/lib/compliance/iso.tsx @@ -101,6 +101,7 @@ export const mapComplianceData = ( manual: finalStatus === "MANUAL" ? 1 : 0, objetive_name: objetiveName, check_summary: checkSummary, + control_label: controlLabel, }; control.requirements.push(requirement); @@ -152,6 +153,10 @@ export const toAccordionItems = ( ): AccordionItemProps[] => { return data.flatMap((framework) => framework.categories.map((category) => { + const allRequirements = category.controls.flatMap( + (control) => control.requirements, + ); + return { key: `${framework.name}-${category.name}`, title: ( @@ -164,46 +169,29 @@ export const toAccordionItems = ( /> ), content: "", - items: category.controls.map((control, i: number) => { + items: allRequirements.map((requirement, j: number) => { + const itemKey = `${framework.name}-${category.name}-req-${j}`; + return { - key: `${framework.name}-${category.name}-control-${i}`, + key: itemKey, title: ( - ), - content: "", - items: control.requirements.map((requirement, j: number) => { - const itemKey = `${framework.name}-${category.name}-control-${i}-req-${j}`; - - return { - key: itemKey, - title: ( - - ), - content: ( - - ), - items: [], - }; - }), - isDisabled: - control.pass === 0 && control.fail === 0 && control.manual === 0, + content: ( + + ), + items: [], }; }), }; diff --git a/ui/lib/compliance/mitre.tsx b/ui/lib/compliance/mitre.tsx new file mode 100644 index 0000000000..480cb8c967 --- /dev/null +++ b/ui/lib/compliance/mitre.tsx @@ -0,0 +1,252 @@ +import { ClientAccordionContent } from "@/components/compliance/compliance-accordion/client-accordion-content"; +import { ComplianceAccordionRequirementTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-requeriment-title"; +import { AccordionItemProps } from "@/components/ui/accordion/Accordion"; +import { FindingStatus } from "@/components/ui/table/status-finding-badge"; +import { + AttributesData, + CategoryData, + FailedSection, + Framework, + MITREAttributesMetadata, + Requirement, + RequirementItemData, + RequirementsData, + RequirementStatus, +} from "@/types/compliance"; + +export const mapComplianceData = ( + attributesData: AttributesData, + requirementsData: RequirementsData, +): Framework[] => { + const attributes = attributesData?.data || []; + const requirements = requirementsData?.data || []; + + // Create a map for quick lookup of requirements by id + const requirementsMap = new Map(); + requirements.forEach((req: RequirementItemData) => { + requirementsMap.set(req.id, req); + }); + + const frameworks: Framework[] = []; + + // Process attributes and merge with requirements data + for (const attributeItem of attributes) { + const id = attributeItem.id; + const metadataArray = attributeItem.attributes?.attributes + ?.metadata as unknown as MITREAttributesMetadata[]; + + if (!metadataArray || metadataArray.length === 0) continue; + + // Get corresponding requirement data + const requirementData = requirementsMap.get(id); + if (!requirementData) continue; + + const frameworkName = attributeItem.attributes.framework; + const techniqueName = attributeItem.attributes.name || id; + const description = attributeItem.attributes.description; + const status = requirementData.attributes.status || ""; + const checks = attributeItem.attributes.attributes.check_ids || []; + const tactics = attributeItem.attributes.tactics || []; + const subtechniques = attributeItem.attributes.subtechniques || []; + const platforms = attributeItem.attributes.platforms || []; + const techniqueUrl = attributeItem.attributes.technique_url || ""; + const requirementName = `${id} - ${techniqueName}`; + + // Find or create framework + let framework = frameworks.find((f) => f.name === frameworkName); + if (!framework) { + framework = { + name: frameworkName, + pass: 0, + fail: 0, + manual: 0, + categories: [], + }; + frameworks.push(framework); + } + + // Create requirement directly (flat structure - no categories) + const finalStatus: RequirementStatus = status as RequirementStatus; + const requirement: Requirement = { + name: requirementName, + description: description, + status: finalStatus, + check_ids: checks, + pass: finalStatus === "PASS" ? 1 : 0, + fail: finalStatus === "FAIL" ? 1 : 0, + manual: finalStatus === "MANUAL" ? 1 : 0, + // MITRE specific fields + technique_id: id, + technique_name: techniqueName, + tactics: tactics, + subtechniques: subtechniques, + platforms: platforms, + technique_url: techniqueUrl, + cloud_services: metadataArray.map((m) => { + // Dynamically find the service field (AWSService, GCPService, AzureService, etc.) + const serviceKey = Object.keys(m).find((key) => + key.toLowerCase().includes("service"), + ); + const serviceName = serviceKey ? m[serviceKey] : "Unknown Service"; + + return { + service: serviceName, + category: m.Category, + value: m.Value, + comment: m.Comment, + }; + }), + }; + + // Add requirement directly to framework (store in a special property) + (framework as any).requirements = (framework as any).requirements || []; + (framework as any).requirements.push(requirement); + + // Update framework counters + if (requirement.status === "MANUAL") { + framework.manual++; + } else if (requirement.status === "PASS") { + framework.pass++; + } else if (requirement.status === "FAIL") { + framework.fail++; + } + } + + return frameworks; +}; + +export const toAccordionItems = ( + data: Framework[], + scanId: string | undefined, +): AccordionItemProps[] => { + return data.flatMap((framework) => { + const requirements = (framework as any).requirements || []; + + return requirements.map((requirement: Requirement, i: number) => { + const itemKey = `${framework.name}-req-${i}`; + + return { + key: itemKey, + title: ( + + ), + content: ( + + ), + items: [], + }; + }); + }); +}; + +// Custom function for MITRE to get top failed sections grouped by tactics +export const getTopFailedSections = ( + mappedData: Framework[], +): FailedSection[] => { + const failedSectionMap = new Map(); + + mappedData.forEach((framework) => { + const requirements = (framework as any).requirements || []; + + requirements.forEach((requirement: Requirement) => { + if (requirement.status === "FAIL") { + const tactics = (requirement.tactics as string[]) || []; + + tactics.forEach((tactic) => { + if (!failedSectionMap.has(tactic)) { + failedSectionMap.set(tactic, { total: 0, types: {} }); + } + + const sectionData = failedSectionMap.get(tactic); + sectionData.total += 1; + + const type = "Fails"; + sectionData.types[type] = (sectionData.types[type] || 0) + 1; + }); + } + }); + }); + + // Convert in descending order and slice top 5 + return Array.from(failedSectionMap.entries()) + .map(([name, data]) => ({ name, ...data })) + .sort((a, b) => b.total - a.total) + .slice(0, 5); // Top 5 +}; + +// Custom function for MITRE to calculate category heatmap data grouped by tactics +export const calculateCategoryHeatmapData = ( + complianceData: Framework[], +): CategoryData[] => { + if (!complianceData?.length) { + return []; + } + + try { + const tacticMap = new Map< + string, + { pass: number; fail: number; manual: number } + >(); + + // Aggregate data by tactics + complianceData.forEach((framework) => { + const requirements = (framework as any).requirements || []; + + requirements.forEach((requirement: Requirement) => { + const tactics = (requirement.tactics as string[]) || []; + + tactics.forEach((tactic) => { + const existing = tacticMap.get(tactic) || { + pass: 0, + fail: 0, + manual: 0, + }; + + tacticMap.set(tactic, { + pass: existing.pass + requirement.pass, + fail: existing.fail + requirement.fail, + manual: existing.manual + requirement.manual, + }); + }); + }); + }); + + const categoryData: CategoryData[] = Array.from(tacticMap.entries()).map( + ([name, stats]) => { + const totalRequirements = stats.pass + stats.fail + stats.manual; + const failurePercentage = + totalRequirements > 0 + ? Math.round((stats.fail / totalRequirements) * 100) + : 0; + + return { + name, + failurePercentage, + totalRequirements, + failedRequirements: stats.fail, + }; + }, + ); + + const filteredData = categoryData + .filter((category) => category.totalRequirements > 0) + .sort((a, b) => b.failurePercentage - a.failurePercentage) + .slice(0, 9); // Show top 9 tactics + + return filteredData; + } catch (error) { + console.error("Error calculating MITRE category heatmap data:", error); + return []; + } +}; diff --git a/ui/types/compliance.ts b/ui/types/compliance.ts index 04c39877a8..bbe0e7ce18 100644 --- a/ui/types/compliance.ts +++ b/ui/types/compliance.ts @@ -27,7 +27,7 @@ export interface Requirement { check_ids: string[]; // This is to allow any key to be added to the requirement object // because each compliance has different keys - [key: string]: string | string[] | number | undefined; + [key: string]: string | string[] | number | object[] | undefined; } export interface Control { @@ -133,6 +133,14 @@ export interface KISAAttributesMetadata { NonComplianceCases: string[]; } +export interface MITREAttributesMetadata { + // Dynamic cloud service field - could be AWSService, GCPService, AzureService, etc. + [key: string]: string; + Category: string; // "Protect", "Detect", "Respond" + Value: string; // "Minimal", "Partial", "Significant" + Comment: string; +} + export interface GenericAttributesMetadata { ItemId: string; Section: string; @@ -150,6 +158,11 @@ export interface AttributesItemData { framework: string; version: string; description: string; + // MITRE specific fields + tactics?: string[]; + subtechniques?: string[]; + platforms?: string[]; + technique_url?: string; attributes: { metadata: | ENSAttributesMetadata[] @@ -158,6 +171,7 @@ export interface AttributesItemData { | AWSWellArchitectedAttributesMetadata[] | ThreatAttributesMetadata[] | KISAAttributesMetadata[] + | MITREAttributesMetadata[] | GenericAttributesMetadata[]; check_ids: string[]; };