Refactor /results/* + /api/results/* to put type before target
This commit is contained in:
@@ -316,32 +316,32 @@ class LitterBoxClient:
|
||||
def get_results(self, target: str, analysis_type: str) -> Dict:
|
||||
"""Get results for a specific analysis type."""
|
||||
self._validate_analysis_type(analysis_type, ['static', 'dynamic', 'info'])
|
||||
response = self._make_request('GET', f'/results/{target}/{analysis_type}')
|
||||
response = self._make_request('GET', f'/results/{analysis_type}/{target}')
|
||||
return response.json()
|
||||
|
||||
def get_file_info(self, target: str) -> Dict:
|
||||
"""Get file information via API endpoint."""
|
||||
response = self._make_request('GET', f'/api/results/{target}/info')
|
||||
response = self._make_request('GET', f'/api/results/info/{target}')
|
||||
return response.json()
|
||||
|
||||
def get_static_results(self, target: str) -> Dict:
|
||||
"""Get static analysis results via API endpoint."""
|
||||
response = self._make_request('GET', f'/api/results/{target}/static')
|
||||
response = self._make_request('GET', f'/api/results/static/{target}')
|
||||
return response.json()
|
||||
|
||||
def get_dynamic_results(self, target: str) -> Dict:
|
||||
"""Get dynamic analysis results via API endpoint."""
|
||||
response = self._make_request('GET', f'/api/results/{target}/dynamic')
|
||||
response = self._make_request('GET', f'/api/results/dynamic/{target}')
|
||||
return response.json()
|
||||
|
||||
def get_holygrail_results(self, target: str) -> Dict:
|
||||
"""Get HolyGrail/BYOVD analysis results via API endpoint."""
|
||||
response = self._make_request('GET', f'/api/results/{target}/holygrail')
|
||||
response = self._make_request('GET', f'/api/results/holygrail/{target}')
|
||||
return response.json()
|
||||
|
||||
def get_risk_assessment(self, target: str) -> Dict:
|
||||
"""Get the computed detection assessment (score, level, triggering indicators) for a target."""
|
||||
response = self._make_request('GET', f'/api/results/{target}/risk')
|
||||
response = self._make_request('GET', f'/api/results/risk/{target}')
|
||||
return response.json()
|
||||
|
||||
def get_files_summary(self) -> Dict:
|
||||
|
||||
@@ -16,7 +16,7 @@ def _deps():
|
||||
return current_app.extensions['litterbox']
|
||||
|
||||
|
||||
@api_bp.route('/api/results/<target>/static', methods=['GET'])
|
||||
@api_bp.route('/api/results/static/<target>', methods=['GET'])
|
||||
@error_handler
|
||||
def api_static_results(target):
|
||||
app = current_app
|
||||
@@ -36,7 +36,7 @@ def api_static_results(target):
|
||||
return jsonify(json.load(f))
|
||||
|
||||
|
||||
@api_bp.route('/api/results/<target>/dynamic', methods=['GET'])
|
||||
@api_bp.route('/api/results/dynamic/<target>', methods=['GET'])
|
||||
@error_handler
|
||||
def api_dynamic_results(target):
|
||||
app = current_app
|
||||
@@ -61,7 +61,7 @@ def api_dynamic_results(target):
|
||||
return jsonify(json.load(f))
|
||||
|
||||
|
||||
@api_bp.route('/api/results/<target>/info', methods=['GET'])
|
||||
@api_bp.route('/api/results/info/<target>', methods=['GET'])
|
||||
@error_handler
|
||||
def api_file_info(target):
|
||||
app = current_app
|
||||
@@ -81,7 +81,7 @@ def api_file_info(target):
|
||||
return jsonify(json.load(f))
|
||||
|
||||
|
||||
@api_bp.route('/api/results/<target>/holygrail', methods=['GET'])
|
||||
@api_bp.route('/api/results/holygrail/<target>', methods=['GET'])
|
||||
@error_handler
|
||||
def api_byovd_info(target):
|
||||
app = current_app
|
||||
@@ -185,7 +185,7 @@ def api_edr_agents_status():
|
||||
return jsonify({"agents": results})
|
||||
|
||||
|
||||
@api_bp.route('/api/results/<target>/edr/<profile>', methods=['GET'])
|
||||
@api_bp.route('/api/results/edr/<profile>/<target>', methods=['GET'])
|
||||
@error_handler
|
||||
def api_edr_results(target, profile):
|
||||
"""Read the saved findings for a specific EDR profile run on `target`."""
|
||||
@@ -203,7 +203,7 @@ def api_edr_results(target, profile):
|
||||
return jsonify(json.load(f))
|
||||
|
||||
|
||||
@api_bp.route('/api/results/<target>/edr', methods=['GET'])
|
||||
@api_bp.route('/api/results/edr/<target>', methods=['GET'])
|
||||
@error_handler
|
||||
def api_edr_index(target):
|
||||
"""List which EDR profiles have saved results for `target`."""
|
||||
@@ -220,7 +220,7 @@ def api_edr_index(target):
|
||||
return jsonify({'profiles': sorted(profiles)})
|
||||
|
||||
|
||||
@api_bp.route('/api/results/<target>/risk', methods=['GET'])
|
||||
@api_bp.route('/api/results/risk/<target>', methods=['GET'])
|
||||
@error_handler
|
||||
def api_risk_assessment(target):
|
||||
"""Return the computed detection assessment (score, level, triggering indicators) for a target."""
|
||||
|
||||
@@ -15,7 +15,7 @@ def _deps():
|
||||
return current_app.extensions['litterbox']
|
||||
|
||||
|
||||
@results_bp.route('/results/<target>/<analysis_type>', methods=['GET'])
|
||||
@results_bp.route('/results/<analysis_type>/<target>', methods=['GET'])
|
||||
@error_handler
|
||||
def get_analysis_results(target, analysis_type):
|
||||
app = current_app
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# app/services/rendering.py
|
||||
"""Result-page render helpers for the /results/<target>/<type> endpoint."""
|
||||
"""Result-page render helpers for the /results/<type>/<target> endpoint."""
|
||||
import os
|
||||
|
||||
from flask import current_app, render_template
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// app/static/js/byovd/core.js
|
||||
// /results/<hash>/byovd page entry: fetches BYOVD analysis, renders verdict,
|
||||
// /results/byovd/<hash> page entry: fetches BYOVD analysis, renders verdict,
|
||||
// details, imports, YARA matches, and Win10/11 mitigation status.
|
||||
|
||||
import { PERF_CONFIG, ElementCache, DOMUtils, AnimationSystem } from './utils.js';
|
||||
@@ -60,7 +60,7 @@ class ByovdApp {
|
||||
// Parse path efficiently
|
||||
const pathSegments = window.location.pathname.split('/').filter(Boolean);
|
||||
|
||||
// Check for results pattern: /results/{hash}/byovd
|
||||
// Check for results pattern: /results/byovd/{hash}
|
||||
const resultsIndex = pathSegments.indexOf('results');
|
||||
if (resultsIndex !== -1 && pathSegments[resultsIndex + 1]) {
|
||||
return pathSegments[resultsIndex + 1];
|
||||
@@ -167,7 +167,7 @@ class ByovdApp {
|
||||
|
||||
console.log('[BYOVD] Fetching data from API...');
|
||||
const data = await this.apiClient.fetch(
|
||||
`/api/results/${encodeURIComponent(this.driverHash)}/holygrail`,
|
||||
`/api/results/holygrail/${encodeURIComponent(this.driverHash)}`,
|
||||
{ forceRefresh }
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// app/static/js/results/core.js
|
||||
// Entry point for the /results/<target>/<analysis_type> page.
|
||||
// Entry point for the /results/<analysis_type>/<target> page.
|
||||
// Wires up TabManager, PayloadManager, AnalysisTypeHandler, ModalHandler
|
||||
// and the AnalysisCore poll loop.
|
||||
|
||||
@@ -215,7 +215,7 @@ window.startHolyGrailScan = function() {
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
// Redirect to results page
|
||||
window.location.href = `/results/${fileHash}/byovd`;
|
||||
window.location.href = `/results/byovd/${fileHash}`;
|
||||
} else {
|
||||
// Handle error and restore button
|
||||
console.error('HolyGrail analysis failed:', data.error || data.message);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//
|
||||
// On the initial POST response we render whatever Phase 1 produced. If
|
||||
// the status is `polling_alerts`, we kick off a foreground poll of GET
|
||||
// /api/results/<hash>/edr/<profile> so the alerts pane and summary chip
|
||||
// /api/results/edr/<profile>/<hash> so the alerts pane and summary chip
|
||||
// reflect Phase 2 progress in real time. The block-vs-clean-exec
|
||||
// distinction is carried in `summary.blocked_by_av` — Phase 2 doesn't
|
||||
// fork its status on it because the polling itself is purely between
|
||||
@@ -595,7 +595,7 @@ function schedulePoll(profile) {
|
||||
if (!hash || !profile) return;
|
||||
_pollTimer = setTimeout(async () => {
|
||||
try {
|
||||
const resp = await fetch(`/api/results/${encodeURIComponent(hash)}/edr/${encodeURIComponent(profile)}`, {
|
||||
const resp = await fetch(`/api/results/edr/${encodeURIComponent(profile)}/${encodeURIComponent(hash)}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!resp.ok) {
|
||||
|
||||
@@ -443,15 +443,15 @@ function getDriverAnalysisStatus(driver) {
|
||||
|
||||
// Action functions
|
||||
function viewFile(md5) {
|
||||
window.location.href = `/results/${md5}/info`;
|
||||
window.location.href = `/results/info/${md5}`;
|
||||
}
|
||||
|
||||
function viewDriver(md5) {
|
||||
window.location.href = `/results/${md5}/byovd`;
|
||||
window.location.href = `/results/byovd/${md5}`;
|
||||
}
|
||||
|
||||
function viewProcess(pid) {
|
||||
window.location.href = `/results/${pid}/dynamic`;
|
||||
window.location.href = `/results/dynamic/${pid}`;
|
||||
}
|
||||
|
||||
function showFileDeleteWarning(md5) {
|
||||
|
||||
@@ -641,7 +641,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
showToast('HolyGrail analysis completed successfully', 'success');
|
||||
// Redirect to results page
|
||||
setTimeout(() => {
|
||||
window.location.href = `/results/${currentFileHash}/byovd`;
|
||||
window.location.href = `/results/byovd/${currentFileHash}`;
|
||||
}, 1000);
|
||||
} else {
|
||||
// Handle error
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function apiDelete(url, options = {}) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Cached GET — useful for relatively-static endpoints (e.g. /api/results/<hash>/info).
|
||||
// Cached GET — useful for relatively-static endpoints (e.g. /api/results/info/<hash>).
|
||||
// Returns the cached promise on repeat calls; pass {forceRefresh:true} to bypass.
|
||||
const _cache = new Map();
|
||||
export async function apiGetCached(url, { forceRefresh = false, ...options } = {}) {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
Back to Summary
|
||||
</a>
|
||||
{% if file_info %}
|
||||
<a href="/results/{{ file_info.md5 }}/static" class="lb-btn" style="padding: 2px 10px; font-size: 12px;" rel="noopener">
|
||||
<a href="/results/static/{{ file_info.md5 }}" class="lb-btn" style="padding: 2px 10px; font-size: 12px;" rel="noopener">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Static Analysis
|
||||
</a>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="lb-panel-hdr">
|
||||
<span class="lb-glyph">▸</span>Dynamic Analysis Summary
|
||||
{% if file_info %}
|
||||
<button onclick="window.location.href='/results/{{ file_info.md5 }}/info'" class="lb-btn lb-btn-ghost" style="margin-left: auto; padding: 2px 10px; font-size: 12px;" aria-label="Navigate back to file information">
|
||||
<button onclick="window.location.href='/results/info/{{ file_info.md5 }}'" class="lb-btn lb-btn-ghost" style="margin-left: auto; padding: 2px 10px; font-size: 12px;" aria-label="Navigate back to file information">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 19l-7-7 7-7"/></svg>
|
||||
Back to File Info
|
||||
</button>
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 19l-7-7 7-7"/></svg>
|
||||
Back
|
||||
</button>
|
||||
<button onclick="window.location.href='/results/{{ file_info.md5 }}/static'" class="lb-btn lb-btn-primary" style="padding: 2px 10px; font-size: 12px;">
|
||||
<button onclick="window.location.href='/results/static/{{ file_info.md5 }}'" class="lb-btn lb-btn-primary" style="padding: 2px 10px; font-size: 12px;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Static Analysis
|
||||
</button>
|
||||
<button onclick="window.location.href='/results/{{ file_info.md5 }}/dynamic'" class="lb-btn" style="padding: 2px 10px; font-size: 12px; color: var(--lb-sev-medium); border-color: var(--lb-sev-medium);" title="Dynamic analysis executes the payload">
|
||||
<button onclick="window.location.href='/results/dynamic/{{ file_info.md5 }}'" class="lb-btn" style="padding: 2px 10px; font-size: 12px; color: var(--lb-sev-medium); border-color: var(--lb-sev-medium);" title="Dynamic analysis executes the payload">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Dynamic Analysis
|
||||
</button>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="lb-panel">
|
||||
<div class="lb-panel-hdr">
|
||||
<span class="lb-glyph">▸</span>Static Analysis Summary
|
||||
<button onclick="window.location.href='/results/{{ file_info.md5 }}/info'" class="lb-btn lb-btn-ghost" style="margin-left: auto; padding: 2px 10px; font-size: 12px;" aria-label="Navigate back to file information">
|
||||
<button onclick="window.location.href='/results/info/{{ file_info.md5 }}'" class="lb-btn lb-btn-ghost" style="margin-left: auto; padding: 2px 10px; font-size: 12px;" aria-label="Navigate back to file information">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 19l-7-7 7-7"/></svg>
|
||||
Back to File Info
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user