From 5a6b5c2764ae41ce3567fa4a14d94169c6154fd5 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 28 Apr 2026 12:01:43 -0400 Subject: [PATCH 1/9] hacky stix2.1 switch --- contentctl/enrichments/attack_enrichment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contentctl/enrichments/attack_enrichment.py b/contentctl/enrichments/attack_enrichment.py index da7dfafa..c37c59c6 100644 --- a/contentctl/enrichments/attack_enrichment.py +++ b/contentctl/enrichments/attack_enrichment.py @@ -132,9 +132,10 @@ def get_attack_lookup( f"Please ensure that the {input_path} directory " "has been git cloned correctly." ) + enterprise_19_path = enterprise_path / "enterprise-attack-19.0.json" lift = attack_client( local_paths={ - "enterprise": str(enterprise_path), + "enterprise": str(enterprise_19_path), "mobile": str(mobile_path), "ics": str(ics_path), } From fa47b8118f55225fa3888a3c6af3a8c92038e7c1 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 28 Apr 2026 12:06:49 -0400 Subject: [PATCH 2/9] Adding mappings --- contentctl/objects/constants.py | 2 ++ contentctl/objects/mitre_attack_enrichment.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/contentctl/objects/constants.py b/contentctl/objects/constants.py index 1e6f3bed..bc508134 100644 --- a/contentctl/objects/constants.py +++ b/contentctl/objects/constants.py @@ -16,6 +16,8 @@ "Command And Control": "Command and Control", "Exfiltration": "Actions on Objectives", "Impact": "Actions on Objectives", + "Stealth": "Exploitation", + "Defense Impairment": "Exploitation", } SES_CONTEXT_MAPPING = { diff --git a/contentctl/objects/mitre_attack_enrichment.py b/contentctl/objects/mitre_attack_enrichment.py index 7d3e72f7..8f1c4c7a 100644 --- a/contentctl/objects/mitre_attack_enrichment.py +++ b/contentctl/objects/mitre_attack_enrichment.py @@ -24,6 +24,8 @@ class MitreTactics(StrEnum): COMMAND_AND_CONTROL = "Command And Control" EXFILTRATION = "Exfiltration" IMPACT = "Impact" + STEALTH = "Stealth" + DEFENSE_IMPAIRMENT = "Defense Impairment" class AttackGroupMatrix(StrEnum): From c1d2ebfa7aa6556fb0fb5c5b08ad0d62fc3a1460 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 28 Apr 2026 12:13:11 -0400 Subject: [PATCH 3/9] Tweaking MITRE download for ESCU --- .github/workflows/test_against_escu.yml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test_against_escu.yml b/.github/workflows/test_against_escu.yml index 25cae4c8..52d4dd08 100644 --- a/.github/workflows/test_against_escu.yml +++ b/.github/workflows/test_against_escu.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: python_version: ["3.11", "3.12", "3.13"] - + operating_system: ["ubuntu-24.04", "macos-15"] # Do not test against ESCU until known character encoding issue is resolved # operating_system: ["ubuntu-20.04", "ubuntu-22.04", "macos-latest", "macos-14", "windows-2022"] @@ -32,7 +32,7 @@ jobs: # Checkout the develop (default) branch of security_content - name: Checkout repo uses: actions/checkout@v5 - with: + with: path: security_content repository: splunk/security_content @@ -42,30 +42,26 @@ jobs: with: python-version: ${{ matrix.python_version }} architecture: "x64" - + - name: Install Poetry - run: - python -m pip install poetry + run: python -m pip install poetry - name: Install contentctl and activate the shell run: | poetry install --no-interaction - - - name: Clone the AtomicRedTeam Repo and the Mitre/CTI repos for testing enrichments + - name: Clone the AtomicRedTeam Repo and the Mitre/CTI repos for testing + enrichments run: | cd security_content git clone --single-branch https://github.com/redcanaryco/atomic-red-team external_repos/atomic-red-team - git clone --single-branch https://github.com/mitre/cti external_repos/cti + git clone --depth=1 --single-branch --branch="master" https://github.com/mitre-attack/attack-stix-data external_repos/cti - - # We do not separately run validate and build + # We do not separately run validate and build # since a build ALSO performs a validate - name: Run contentctl build run: | cd security_content poetry run contentctl build --enrichments - # Do not run a test - it will take far too long! # Do not upload any artifacts - From 231e95486814041145bff3915101b5ae183715b3 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 28 Apr 2026 12:53:36 -0400 Subject: [PATCH 4/9] Move version to Constants, other tooling should take it as config --- contentctl/enrichments/attack_enrichment.py | 18 ++++++++++++------ contentctl/objects/constants.py | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/contentctl/enrichments/attack_enrichment.py b/contentctl/enrichments/attack_enrichment.py index c37c59c6..5a3d0d36 100644 --- a/contentctl/enrichments/attack_enrichment.py +++ b/contentctl/enrichments/attack_enrichment.py @@ -10,6 +10,7 @@ from contentctl.objects.annotated_types import MITRE_ATTACK_ID_TYPE from contentctl.objects.config import validate +from contentctl.objects.constants import MITRE_ATTACK_VERSION from contentctl.objects.mitre_attack_enrichment import ( MitreAttackEnrichment, MitreTactics, @@ -121,8 +122,13 @@ def get_attack_lookup( flush=True, ) enterprise_path = input_path / "enterprise-attack" - mobile_path = input_path / "ics-attack" - ics_path = input_path / "mobile-attack" + enterprise_file = ( + enterprise_path / f"enterprise-attack-{MITRE_ATTACK_VERSION}.json" + ) + mobile_path = input_path / "mobile-attack" + mobile_file = mobile_path / f"mobile-attack-{MITRE_ATTACK_VERSION}.json" + ics_path = input_path / "ics-attack" + ics_file = ics_path / f"ics-attack-{MITRE_ATTACK_VERSION}.json" if not ( enterprise_path.is_dir() and mobile_path.is_dir() and ics_path.is_dir() ): @@ -132,12 +138,12 @@ def get_attack_lookup( f"Please ensure that the {input_path} directory " "has been git cloned correctly." ) - enterprise_19_path = enterprise_path / "enterprise-attack-19.0.json" + lift = attack_client( local_paths={ - "enterprise": str(enterprise_19_path), - "mobile": str(mobile_path), - "ics": str(ics_path), + "enterprise": str(enterprise_file), + "mobile": str(mobile_file), + "ics": str(ics_file), } ) diff --git a/contentctl/objects/constants.py b/contentctl/objects/constants.py index bc508134..5b2d5536 100644 --- a/contentctl/objects/constants.py +++ b/contentctl/objects/constants.py @@ -150,3 +150,5 @@ DEPRECATED_TEMPLATE = "**WARNING**, this {content_type} has been marked **DEPRECATED** by the Splunk Threat Research Team. This means that it will no longer be maintained or supported. If you have any questions feel free to email us at: research@splunk.com. {description}" EXPERIMENTAL_TEMPLATE = "**WARNING**, this {content_type} is marked **EXPERIMENTAL** by the Splunk Threat Research Team. This means that the {content_type} has been manually tested but we do not have the associated attack data to perform automated testing or cannot share this attack dataset due to its sensitive nature. If you have any questions feel free to email us at: research@splunk.com. {description}" + +MITRE_ATTACK_VERSION = "19.0" From bf4702367abf976efac3ad555b083ac0baee78d6 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 28 Apr 2026 13:14:16 -0400 Subject: [PATCH 5/9] Version bump to v5.6.0 because of STIX2.0 to STIX2.1 migration --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f035e4cb..00de409e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "contentctl" -version = "5.5.16" +version = "5.6.0" description = "Splunk Content Control Tool" authors = ["STRT "] From cc24e0ed93ef4f0784cda1345f2870f2a21e3da5 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 28 Apr 2026 14:12:01 -0400 Subject: [PATCH 6/9] Temporarily switching ref --- .github/workflows/test_against_escu.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_against_escu.yml b/.github/workflows/test_against_escu.yml index 52d4dd08..2c38a374 100644 --- a/.github/workflows/test_against_escu.yml +++ b/.github/workflows/test_against_escu.yml @@ -35,6 +35,7 @@ jobs: with: path: security_content repository: splunk/security_content + ref: mitre_updates #Install the given version of Python we will test against - name: Install Required Python Version From 022f5f52b415af46a0b2ba1bb5d7e1b856e13e3d Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 28 Apr 2026 14:16:12 -0400 Subject: [PATCH 7/9] Revert temp workflow change --- .github/workflows/test_against_escu.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test_against_escu.yml b/.github/workflows/test_against_escu.yml index 2c38a374..52d4dd08 100644 --- a/.github/workflows/test_against_escu.yml +++ b/.github/workflows/test_against_escu.yml @@ -35,7 +35,6 @@ jobs: with: path: security_content repository: splunk/security_content - ref: mitre_updates #Install the given version of Python we will test against - name: Install Required Python Version From ac5851bd86a4ea64c817aa58542b32fa3846d86e Mon Sep 17 00:00:00 2001 From: Eric McGinnis Date: Tue, 28 Apr 2026 14:55:37 -0700 Subject: [PATCH 8/9] Minor update to guidance on how to check out missing repos when --enrichments argument is passed to contentctl build --- contentctl/objects/config.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/contentctl/objects/config.py b/contentctl/objects/config.py index 6d785cb0..1c64d588 100644 --- a/contentctl/objects/config.py +++ b/contentctl/objects/config.py @@ -491,12 +491,12 @@ def ensureEnrichmentReposPresent(self) -> Self: missing_repos: list[str] = [] if not self.atomic_red_team_repo_path.is_dir(): missing_repos.append( - f"https://github.com/redcanaryco/atomic-red-team {self.atomic_red_team_repo_path}" + f"--single-branch https://github.com/redcanaryco/atomic-red-team {self.atomic_red_team_repo_path}" ) if not self.mitre_cti_repo_path.is_dir(): missing_repos.append( - f"https://github.com/mitre/cti {self.mitre_cti_repo_path}" + f"""--depth=1 --single-branch --branch="master" https://github.com/mitre-attack/attack-stix-data {self.mitre_cti_repo_path}""" ) if len(missing_repos) > 0: @@ -506,10 +506,7 @@ def ensureEnrichmentReposPresent(self) -> Self: "Please check them out using the following commands:" ] msg_list.extend( - [ - f"git clone --single-branch {repo_string}" - for repo_string in missing_repos - ] + [f"git clone {repo_string}" for repo_string in missing_repos] ) msg = "\n\t".join(msg_list) raise FileNotFoundError(msg) From 71424b4e07170e8abac1fa1e2d3c6faab5ddd719 Mon Sep 17 00:00:00 2001 From: Eric McGinnis Date: Tue, 28 Apr 2026 15:02:26 -0700 Subject: [PATCH 9/9] Bump mitre attack version in mitre attack nav output Co-authored-by: Copilot --- contentctl/output/attack_nav_output.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contentctl/output/attack_nav_output.py b/contentctl/output/attack_nav_output.py index 872cead9..625fbab0 100644 --- a/contentctl/output/attack_nav_output.py +++ b/contentctl/output/attack_nav_output.py @@ -4,6 +4,8 @@ from datetime import datetime from typing import Any, TypedDict +from contentctl.objects.constants import MITRE_ATTACK_VERSION + # Third-party imports from contentctl.objects.detection import Detection @@ -88,7 +90,7 @@ def writeObjects( layer: LayerData = { "name": self.layer_name, "versions": { - "attack": "17", # Update as needed + "attack": MITRE_ATTACK_VERSION, "navigator": "5.1.0", "layer": "4.5", },