Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .flake8

This file was deleted.

47 changes: 21 additions & 26 deletions .github/workflows/lint-publish.yml
Original file line number Diff line number Diff line change
@@ -1,65 +1,60 @@
name: Lint, test, and publish

on: [push]
on: push

jobs:
lint-test:
lint-format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install poetry
run: pipx install poetry

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "poetry"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
pip install .
run: poetry install --with=dev

- name: Lint with flake8
run: flake8 mitreattack/ --count --exit-zero --statistics
- name: Lint with ruff
run: poetry run ruff check --exit-zero --output-format github

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

publish:
needs: lint-test
needs: lint-format
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
runs-on: ubuntu-latest
# required for PyPI Trusted Publisher setup
# https://docs.pypi.org/trusted-publishers/using-a-publisher/
environment: release
# this permission is required for PyPI Trusted Publisher setup
# https://docs.pypi.org/trusted-publishers/using-a-publisher/
permissions:
id-token: write
steps:
- uses: actions/checkout@v4

- name: Install poetry
run: pipx install poetry

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "poetry"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
pip install .
run: poetry install --with=dev

- name: Run pytest
run: |
cd tests
pytest --cov=mitreattack --cov-report html
cd ..
run: poetry run pytest --cov=mitreattack

- name: Build package
run: python setup.py sdist bdist_wheel
run: poetry build

- name: Publish package
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
7 changes: 5 additions & 2 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
version: 2

build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "3.10"
python: "3.11"

sphinx:
configuration: docs/conf.py

python:
install:
Expand Down
6 changes: 3 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Configuration file for the Sphinx documentation builder.

Check failure on line 1 in docs/conf.py

View workflow job for this annotation

GitHub Actions / lint-format

Ruff (D100)

docs/conf.py:1:1: D100 Missing docstring in public module
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

Expand All @@ -9,9 +9,9 @@

# -- Project information -----------------------------------------------------
project = "mitreattack-python"
copyright = "2022, The MITRE Corporation"
version = "2.0.0"
release = "2.0.0"
copyright = "2025, The MITRE Corporation"
version = "5.0.0"
release = "5.0.0"

# -- General configuration ---------------------------------------------------
extensions = [
Expand Down
2 changes: 1 addition & 1 deletion examples/get_all_matrices.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def main():

matrices = mitre_attack_data.get_matrices(remove_revoked_deprecated=True)

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


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion examples/get_objects_by_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def main():
techniques = mitre_attack_data.get_objects_by_content("LSASS", "attack-pattern", remove_revoked_deprecated=True)
print(f"There are {len(techniques)} techniques where 'LSASS' appears in the description.")

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

Expand Down
23 changes: 15 additions & 8 deletions mitreattack/attackToExcel/attackToExcel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import argparse
import os
from typing import Dict, List
from typing import Dict, List, Optional

import pandas as pd
import requests
Expand All @@ -16,7 +16,9 @@
SUB_CHARACTERS = ["\\", "/"]


def get_stix_data(domain: str, version: str = None, remote: str = None, stix_file: str = None) -> MemoryStore:
def get_stix_data(
domain: str, version: Optional[str] = None, remote: Optional[str] = None, stix_file: Optional[str] = None
) -> MemoryStore:
"""Download the ATT&CK STIX data for the given domain and version from MITRE/CTI (or just domain if a remote workbench is specified).

Parameters
Expand Down Expand Up @@ -105,12 +107,12 @@ def build_dataframes(src: MemoryStore, domain: str) -> Dict:
"mitigations": stixToDf.mitigationsToDf(src),
"matrices": stixToDf.matricesToDf(src, domain),
"relationships": stixToDf.relationshipsToDf(src),
"datasources": stixToDf.datasourcesToDf(src)
"datasources": stixToDf.datasourcesToDf(src),
}
return df


def write_excel(dataframes: Dict, domain: str, version: str = None, output_dir: str = ".") -> List:
def write_excel(dataframes: Dict, domain: str, version: Optional[str] = None, output_dir: str = ".") -> List:
"""Given a set of dataframes from build_dataframes, write the ATT&CK dataset to output directory.

Parameters
Expand Down Expand Up @@ -262,11 +264,11 @@ def write_excel(dataframes: Dict, domain: str, version: str = None, output_dir:

def export(
domain: str = "enterprise-attack",
version: str = None,
version: Optional[str] = None,
output_dir: str = ".",
remote: str = None,
stix_file: str = None,
mem_store: MemoryStore = None,
remote: Optional[str] = None,
stix_file: Optional[str] = None,
mem_store: Optional[MemoryStore] = None,
):
"""Download ATT&CK data from MITRE/CTI and convert it to Excel spreadsheets.

Expand Down Expand Up @@ -298,6 +300,8 @@ def export(
------
TypeError
Raised when missing exactly one of `remote`, `stix_file`, or `mem_store`.
ValueError
Raised when `mem_store` fails to load.
"""
if (
(remote and stix_file and mem_store)
Expand All @@ -312,6 +316,9 @@ def export(
if get_stix_from_github or remote or stix_file:
mem_store = get_stix_data(domain=domain, version=version, remote=remote, stix_file=stix_file)

if mem_store is None:
raise ValueError("`mem_store` is empty - this should not be possible!")

logger.info(f"************ Exporting {domain} to Excel ************")

# build dataframes
Expand Down
5 changes: 4 additions & 1 deletion mitreattack/attackToExcel/stixToDf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from mitreattack.constants import MITRE_ATTACK_ID_SOURCE_NAMES, PLATFORMS_LOOKUP
from mitreattack.stix20 import MitreAttackData


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

# base STIX properties
row = parseBaseStix(technique)
Expand Down Expand Up @@ -164,7 +167,7 @@ def techniquesToDf(src, domain):
# domain specific fields -- enterprise
if domain == "enterprise-attack":
row["is sub-technique"] = subtechnique
if subtechnique:
if subtechnique and parent is not None:
row["name"] = f"{parent['name']}: {technique['name']}"
row["sub-technique of"] = parent["external_references"][0]["external_id"]

Expand Down
8 changes: 4 additions & 4 deletions mitreattack/collections/collection_to_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ def generate_index(name, description, root_url, files=None, folders=None, sets=N
)
cleaned_bundles.append(potentially_valid_bundle)
else:
print(f"cannot use bundle {potentially_valid_bundle.id} due to lack of collection object")
print(f"cannot use bundle {potentially_valid_bundle['id']} due to lack of collection object")
else:
print(f"cannot use bundle {potentially_valid_bundle.id} due to lack of collection object")
print(f"cannot use bundle {potentially_valid_bundle['id']} due to lack of collection object")

index_created = None
index_modified = None
index_created = isoparse("9999-12-31T23:59:59.999Z")
index_modified = isoparse("9999-12-31T23:59:59.999Z")
Comment thread
jondricek marked this conversation as resolved.
collections = {} # STIX ID -> collection object

if files:
Expand Down
Loading
Loading