Refactor /results/* + /api/results/* to put type before target

This commit is contained in:
BlackSnufkin
2026-04-30 01:43:22 -07:00
parent 966d14104c
commit 4857d157f1
14 changed files with 32 additions and 32 deletions
+6 -6
View File
@@ -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:
+7 -7
View File
@@ -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."""
+1 -1
View File
@@ -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 -1
View File
@@ -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
+3 -3
View File
@@ -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 }
);
+2 -2
View File
@@ -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);
+2 -2
View File
@@ -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) {
+3 -3
View File
@@ -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) {
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 } = {}) {
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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>