diff --git a/app/services/summary.py b/app/services/summary.py index 1c21bfb..13dfd9d 100644 --- a/app/services/summary.py +++ b/app/services/summary.py @@ -116,18 +116,51 @@ def process_file_summary(item, item_path, file_based_summary, logger): dynamic_results = json_helpers.load_json_file(dynamic_path) logger.debug(f"Loaded dynamic analysis results for item: {item}") + # Discover every EDR profile run for this sample. Stored as + # `edr__results.json`. The calculate_risk helper + # accepts a {profile_name: findings} mapping. + edr_results = {} + edr_prefix, edr_suffix = 'edr_', '_results.json' + for entry in os.listdir(item_path): + if entry.startswith(edr_prefix) and entry.endswith(edr_suffix): + profile_name = entry[len(edr_prefix):-len(edr_suffix)] + loaded = json_helpers.load_json_file(os.path.join(item_path, entry)) + if loaded: + edr_results[profile_name] = loaded + has_static_analysis = os.path.exists(static_path) has_dynamic_analysis = os.path.exists(dynamic_path) + has_edr_analysis = bool(edr_results) risk_score, risk_factors = risk_analyzer.calculate_risk( analysis_type='file', file_info=file_info, static_results=static_results, dynamic_results=dynamic_results, + edr_results=edr_results or None, ) risk_level = risk_analyzer.get_risk_level(risk_score) + # Collapse the per-profile EDR runs into a small list the UI can + # show in the status column without loading the full JSON. Each + # entry has just the headline: profile, alert count, killed/AV + # flags, status string. + edr_runs = [] + if not is_driver: + for profile_name, edr in (edr_results or {}).items(): + summary = (edr or {}).get('summary') or {} + exec_block = (edr or {}).get('execution') or {} + edr_runs.append({ + 'profile': profile_name, + 'display_name': edr.get('display_name') or profile_name, + 'status': edr.get('status'), + 'total_alerts': summary.get('total_alerts') or len(edr.get('alerts') or []), + 'high_severity_alerts': summary.get('high_severity_alerts'), + 'blocked_by_av': summary.get('blocked_by_av'), + 'killed_by_edr': exec_block.get('killed_by_edr'), + }) + file_based_summary[item] = { 'md5': file_info.get('md5', 'unknown'), 'sha256': file_info.get('sha256', 'unknown'), @@ -139,6 +172,8 @@ def process_file_summary(item, item_path, file_based_summary, logger): 'detection_risk': file_info.get('entropy_analysis', {}).get('detection_risk', 'Unknown'), 'has_static_analysis': has_static_analysis, 'has_dynamic_analysis': has_dynamic_analysis, + 'has_edr_analysis': has_edr_analysis if not is_driver else False, + 'edr_runs': edr_runs, 'risk_assessment': { 'score': risk_score, 'level': risk_level, diff --git a/app/static/js/summary.js b/app/static/js/summary.js index 2d87207..9344ada 100644 --- a/app/static/js/summary.js +++ b/app/static/js/summary.js @@ -160,8 +160,8 @@ function renderFiles() { const statusCell = row.querySelector('[data-field="fileAnalysisStatus"]'); const status = getAnalysisStatus(file); - statusCell.className = `px-2 py-1 text-sm rounded-lg ${status.class}`; - statusCell.textContent = status.text; + statusCell.className = ''; + statusCell.innerHTML = renderFileStatusCell(file, status); const viewButton = row.querySelector('[data-action="view"]'); const deleteButton = row.querySelector('[data-action="delete"]'); @@ -428,6 +428,38 @@ function getAnalysisStatus(file) { }; } +/** + * Build the Status cell for one file row. Two parts: + * - the static-vs-dynamic completion badge (Complete / Partial / No Results) + * - an EDR sub-line listing each profile run with alert counts and + * block/kill flags. Only rendered when the file has been dispatched + * to at least one EDR profile. + */ +function renderFileStatusCell(file, status) { + const main = `${status.text}`; + if (!file.has_edr_analysis || !Array.isArray(file.edr_runs) || !file.edr_runs.length) { + return main; + } + const badges = file.edr_runs.map(r => { + const alerts = r.total_alerts || 0; + const high = r.high_severity_alerts || 0; + const blocked = !!r.blocked_by_av; + const killed = !!r.killed_by_edr; + const tone = + blocked || killed || high > 0 ? 'critical' : + alerts > 0 ? 'medium' : + 'info'; + const detail = ( + blocked ? `${r.display_name}: blocked` : + killed ? `${r.display_name}: killed (${alerts})` : + alerts > 0 ? `${r.display_name}: ${alerts}` : + `${r.display_name}: clean` + ); + return `${detail}`; + }).join(''); + return `${main}
${badges}
`; +} + function getDriverAnalysisStatus(driver) { if (driver.has_static_analysis) { return {