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

@@ -24,7 +24,10 @@ from app.models.technique import Technique
from app.models.test_template import TestTemplate
from app.models.threat_actor import ThreatActorTechnique
from app.services.scoring_service import calculate_technique_score
from app.services.compliance_import_service import import_nist_800_53_mappings
from app.services.compliance_import_service import (
import_nist_800_53_mappings,
import_cis_controls_v8_mappings,
)
router = APIRouter(prefix="/compliance", tags=["compliance"])
@@ -378,3 +381,13 @@ def import_nist(
"""Import NIST 800-53 Rev 5 mappings (admin only)."""
result = import_nist_800_53_mappings(db)
return result
@router.post("/import/cis-controls-v8")
def import_cis(
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""Import CIS Controls v8 mappings (admin only)."""
result = import_cis_controls_v8_mappings(db)
return result

View File

@@ -40,7 +40,7 @@ def _get_sync_handler(source_name: str):
"caldera": ("app.services.caldera_import_service", "sync"),
"elastic_rules": ("app.services.elastic_import_service", "sync"),
"mitre_cti": ("app.services.threat_actor_import_service", "sync"),
# d3fend added in later phases
"d3fend": ("app.services.d3fend_import_service", "sync"),
}
if source_name not in handlers:

View File

@@ -55,13 +55,16 @@ def list_templates(
severity: Optional[str] = Query(None, description="Filter by severity (low, medium, high, critical)"),
mitre_technique_id: Optional[str] = Query(None, description="Filter by MITRE technique ID"),
search: Optional[str] = Query(None, description="Search in name and description"),
is_active: Optional[bool] = Query(None, description="Filter by active status (true/false). Omit to return all."),
offset: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Return a paginated, filterable list of test templates."""
query = db.query(TestTemplate).filter(TestTemplate.is_active == True) # noqa: E712
query = db.query(TestTemplate)
if is_active is not None:
query = query.filter(TestTemplate.is_active == is_active) # noqa: E712
if source:
query = query.filter(TestTemplate.source == source)
@@ -137,6 +140,41 @@ def template_stats(
}
# ---------------------------------------------------------------------------
# PATCH /test-templates/bulk-activate — activate/deactivate all (admin)
# ---------------------------------------------------------------------------
@router.patch("/bulk-activate")
def bulk_activate_templates(
activate: bool = Query(True, description="True to activate all, False to deactivate all"),
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""Set all templates to active or inactive. Admin only."""
count = (
db.query(TestTemplate)
.filter(TestTemplate.is_active != activate)
.update({TestTemplate.is_active: activate})
)
db.commit()
log_action(
db,
user_id=current_user.id,
action="bulk_activate_templates" if activate else "bulk_deactivate_templates",
entity_type="test_template",
entity_id=None,
details={"affected": count, "is_active": activate},
)
return {
"detail": f"{'Activated' if activate else 'Deactivated'} {count} templates",
"affected": count,
"is_active": activate,
}
# ---------------------------------------------------------------------------
# GET /test-templates/by-technique/{mitre_id}
# ---------------------------------------------------------------------------