fix: D3FEND expandable cards, System page cleanup, and multi-source improvements

- Make D3FEND defense cards clickable with expandable details and external link
- Fix D3FEND URLs to use PascalCase technique names matching the ontology
- Remove duplicate Import Atomic Red Team from System page (use Data Sources)
- Add bulk Activate All / Deactivate All buttons with confirmation modal
- Fix template admin list to show both active and inactive templates
- Add PATCH /test-templates/bulk-activate backend endpoint
- Auto-seed data sources on container startup via entrypoint.sh
- Fix SigmaHQ, CALDERA, GTFOBins import issues
- Register D3FEND sync handler in data sources router
- Add CIS Controls v8 compliance framework import
- Expand Test Catalog source filters (CALDERA, LOLBAS, GTFOBins)
- Campaign Generate from Threat Actor now opens actor selector modal
- Add coverage snapshot creation button to Comparison page
- Update README with accurate data source and feature documentation
This commit is contained in:
2026-02-10 13:22:23 +01:00
parent 8032b67fab
commit c2e9c687f4
19 changed files with 778 additions and 197 deletions

View File

@@ -44,12 +44,12 @@ logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
CALDERA_ZIP_URL = (
"https://github.com/mitre/caldera"
"https://github.com/mitre/stockpile"
"/archive/refs/heads/master.zip"
)
_DOWNLOAD_TIMEOUT = 300
_ZIP_ROOT_PREFIX = "caldera-master"
_ZIP_ROOT_PREFIX = "stockpile-master"
# ---------------------------------------------------------------------------
@@ -144,10 +144,17 @@ def _parse_abilities(abilities_dir: Path) -> list[dict]:
logger.debug("Failed to parse %s: %s", yaml_path, exc)
continue
# Stockpile YAML files may contain YAML lists of abilities
# (e.g. [- id: ..., - id: ...]) or single-document dicts.
# Flatten everything into individual ability dicts.
abilities: list[dict] = []
for data in data_list:
if not isinstance(data, dict):
continue
if isinstance(data, dict):
abilities.append(data)
elif isinstance(data, list):
abilities.extend(d for d in data if isinstance(d, dict))
for data in abilities:
ability_id = data.get("id", "")
if not ability_id:
continue
@@ -193,7 +200,7 @@ def _parse_abilities(abilities_dir: Path) -> list[dict]:
"tool_suggested": executor_str,
"attack_procedure": commands[:4000] if commands else None,
"atomic_test_id": f"caldera:{ability_id}",
"source_url": f"https://github.com/mitre/caldera/tree/master/data/abilities/{tactic}",
"source_url": f"https://github.com/mitre/stockpile/tree/master/data/abilities/{tactic}",
})
logger.info("Parsed %d CALDERA abilities total", len(results))