Skip to content

Commit ac12a1f

Browse files
authored
Merge pull request #190 from mitre-attack/detection_strategies_fix
Detection strategies fix
2 parents 49f7d1d + 873ae19 commit ac12a1f

40 files changed

Lines changed: 2056 additions & 326 deletions

.flake8

Lines changed: 0 additions & 13 deletions
This file was deleted.

.github/workflows/lint-publish.yml

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,60 @@
11
name: Lint, test, and publish
22

3-
on: [push]
3+
on: push
44

55
jobs:
6-
lint-test:
6+
lint-format:
77
runs-on: ubuntu-latest
88
steps:
99
- uses: actions/checkout@v4
1010

11+
- name: Install poetry
12+
run: pipx install poetry
13+
1114
- name: Set up Python 3.11
1215
uses: actions/setup-python@v5
1316
with:
1417
python-version: "3.11"
18+
cache: "poetry"
1519

1620
- name: Install dependencies
17-
run: |
18-
python -m pip install --upgrade pip
19-
pip install -r requirements-dev.txt
20-
pip install .
21+
run: poetry install --with=dev
2122

22-
- name: Lint with flake8
23-
run: flake8 mitreattack/ --count --exit-zero --statistics
23+
- name: Lint with ruff
24+
run: poetry run ruff check --exit-zero --output-format github
2425

25-
# should turn these back on once they take less than 10 minutes to run
26-
# - name: Run pytest
27-
# run: |
28-
# cd tests
29-
# pytest --cov=mitreattack --cov-report html
26+
- name: Check ruff format
27+
run: poetry run ruff format --check
3028

3129
publish:
32-
needs: lint-test
30+
needs: lint-format
3331
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
3432
runs-on: ubuntu-latest
35-
# required for PyPI Trusted Publisher setup
36-
# https://docs.pypi.org/trusted-publishers/using-a-publisher/
3733
environment: release
34+
# this permission is required for PyPI Trusted Publisher setup
35+
# https://docs.pypi.org/trusted-publishers/using-a-publisher/
3836
permissions:
3937
id-token: write
4038
steps:
4139
- uses: actions/checkout@v4
4240

41+
- name: Install poetry
42+
run: pipx install poetry
43+
4344
- name: Set up Python 3.11
4445
uses: actions/setup-python@v5
4546
with:
4647
python-version: "3.11"
48+
cache: "poetry"
4749

4850
- name: Install dependencies
49-
run: |
50-
python -m pip install --upgrade pip
51-
pip install -r requirements-dev.txt
52-
pip install .
51+
run: poetry install --with=dev
5352

5453
- name: Run pytest
55-
run: |
56-
cd tests
57-
pytest --cov=mitreattack --cov-report html
58-
cd ..
54+
run: poetry run pytest --cov=mitreattack
5955

6056
- name: Build package
61-
run: python setup.py sdist bdist_wheel
57+
run: poetry build
6258

6359
- name: Publish package
64-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
6560
uses: pypa/gh-action-pypi-publish@release/v1

.readthedocs.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
version: 2
22

33
build:
4-
os: ubuntu-22.04
4+
os: ubuntu-24.04
55
tools:
6-
python: "3.10"
6+
python: "3.11"
7+
8+
sphinx:
9+
configuration: docs/conf.py
710

811
python:
912
install:

docs/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
# -- Project information -----------------------------------------------------
1111
project = "mitreattack-python"
12-
copyright = "2022, The MITRE Corporation"
13-
version = "2.0.0"
14-
release = "2.0.0"
12+
copyright = "2025, The MITRE Corporation"
13+
version = "5.0.0"
14+
release = "5.0.0"
1515

1616
# -- General configuration ---------------------------------------------------
1717
extensions = [

examples/get_all_matrices.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def main():
66

77
matrices = mitre_attack_data.get_matrices(remove_revoked_deprecated=True)
88

9-
print(f"Retrieved {len(matrices)} ATT&CK matrices: { ', '.join([m.name for m in matrices]) }")
9+
print(f"Retrieved {len(matrices)} ATT&CK matrices: {', '.join([m.name for m in matrices])}")
1010

1111

1212
if __name__ == "__main__":

examples/get_objects_by_content.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def main():
88
techniques = mitre_attack_data.get_objects_by_content("LSASS", "attack-pattern", remove_revoked_deprecated=True)
99
print(f"There are {len(techniques)} techniques where 'LSASS' appears in the description.")
1010

11-
# retrieve all objects by the content of their description
11+
# retrieve all objects by the content of their description
1212
objects = mitre_attack_data.get_objects_by_content("LSASS", None, remove_revoked_deprecated=True)
1313
print(f"There are a total of {len(objects)} objects where 'LSASS' appears in the description.")
1414

mitreattack/attackToExcel/attackToExcel.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import argparse
44
import os
5-
from typing import Dict, List
5+
from typing import Dict, List, Optional
66

77
import pandas as pd
88
import requests
@@ -16,7 +16,9 @@
1616
SUB_CHARACTERS = ["\\", "/"]
1717

1818

19-
def get_stix_data(domain: str, version: str = None, remote: str = None, stix_file: str = None) -> MemoryStore:
19+
def get_stix_data(
20+
domain: str, version: Optional[str] = None, remote: Optional[str] = None, stix_file: Optional[str] = None
21+
) -> MemoryStore:
2022
"""Download the ATT&CK STIX data for the given domain and version from MITRE/CTI (or just domain if a remote workbench is specified).
2123
2224
Parameters
@@ -105,12 +107,12 @@ def build_dataframes(src: MemoryStore, domain: str) -> Dict:
105107
"mitigations": stixToDf.mitigationsToDf(src),
106108
"matrices": stixToDf.matricesToDf(src, domain),
107109
"relationships": stixToDf.relationshipsToDf(src),
108-
"datasources": stixToDf.datasourcesToDf(src)
110+
"datasources": stixToDf.datasourcesToDf(src),
109111
}
110112
return df
111113

112114

113-
def write_excel(dataframes: Dict, domain: str, version: str = None, output_dir: str = ".") -> List:
115+
def write_excel(dataframes: Dict, domain: str, version: Optional[str] = None, output_dir: str = ".") -> List:
114116
"""Given a set of dataframes from build_dataframes, write the ATT&CK dataset to output directory.
115117
116118
Parameters
@@ -262,11 +264,11 @@ def write_excel(dataframes: Dict, domain: str, version: str = None, output_dir:
262264

263265
def export(
264266
domain: str = "enterprise-attack",
265-
version: str = None,
267+
version: Optional[str] = None,
266268
output_dir: str = ".",
267-
remote: str = None,
268-
stix_file: str = None,
269-
mem_store: MemoryStore = None,
269+
remote: Optional[str] = None,
270+
stix_file: Optional[str] = None,
271+
mem_store: Optional[MemoryStore] = None,
270272
):
271273
"""Download ATT&CK data from MITRE/CTI and convert it to Excel spreadsheets.
272274
@@ -298,6 +300,8 @@ def export(
298300
------
299301
TypeError
300302
Raised when missing exactly one of `remote`, `stix_file`, or `mem_store`.
303+
ValueError
304+
Raised when `mem_store` fails to load.
301305
"""
302306
if (
303307
(remote and stix_file and mem_store)
@@ -312,6 +316,9 @@ def export(
312316
if get_stix_from_github or remote or stix_file:
313317
mem_store = get_stix_data(domain=domain, version=version, remote=remote, stix_file=stix_file)
314318

319+
if mem_store is None:
320+
raise ValueError("`mem_store` is empty - this should not be possible!")
321+
315322
logger.info(f"************ Exporting {domain} to Excel ************")
316323

317324
# build dataframes

mitreattack/attackToExcel/stixToDf.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from mitreattack.constants import MITRE_ATTACK_ID_SOURCE_NAMES, PLATFORMS_LOOKUP
1515
from mitreattack.stix20 import MitreAttackData
1616

17+
1718
def remove_revoked_deprecated(stix_objects):
1819
"""Remove any revoked or deprecated objects from queries made to the data source."""
1920
# Note we use .get() because the property may not be present in the JSON data. The default is False
@@ -132,6 +133,8 @@ def techniquesToDf(src, domain):
132133
if subtechnique:
133134
subtechnique_of = all_sub_techniques.query([Filter("source_ref", "=", technique["id"])])[0]
134135
parent = src.get(subtechnique_of["target_ref"])
136+
else:
137+
parent = None
135138

136139
# base STIX properties
137140
row = parseBaseStix(technique)
@@ -164,7 +167,7 @@ def techniquesToDf(src, domain):
164167
# domain specific fields -- enterprise
165168
if domain == "enterprise-attack":
166169
row["is sub-technique"] = subtechnique
167-
if subtechnique:
170+
if subtechnique and parent is not None:
168171
row["name"] = f"{parent['name']}: {technique['name']}"
169172
row["sub-technique of"] = parent["external_references"][0]["external_id"]
170173

mitreattack/collections/collection_to_index.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ def generate_index(name, description, root_url, files=None, folders=None, sets=N
7575
)
7676
cleaned_bundles.append(potentially_valid_bundle)
7777
else:
78-
print(f"cannot use bundle {potentially_valid_bundle.id} due to lack of collection object")
78+
print(f"cannot use bundle {potentially_valid_bundle['id']} due to lack of collection object")
7979
else:
80-
print(f"cannot use bundle {potentially_valid_bundle.id} due to lack of collection object")
80+
print(f"cannot use bundle {potentially_valid_bundle['id']} due to lack of collection object")
8181

82-
index_created = None
83-
index_modified = None
82+
index_created = isoparse("9999-12-31T23:59:59.999Z")
83+
index_modified = isoparse("9999-12-31T23:59:59.999Z")
8484
collections = {} # STIX ID -> collection object
8585

8686
if files:

0 commit comments

Comments
 (0)