mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-26 23:49:59 -07:00
Expose severity to display
See https://github.com/EFForg/rayhunter/issues/334 Severity levels low, medium, high are now exposed to the UI in form of dotted, dashed and solid lines. The line on the UI represents the highest-so-far severity seen. Originally this was intended to be represented by Yellow/Orange/Red, but this would mean yet another divergence for colorblind mode. This is colorblind-friendly by default (I think...) As part of this, simplify EventType so that it becomes a flat "level" enum without nested variants. There is also a new debug endpoint that allows one to overwrite the display level directly for testing.
This commit is contained in:
committed by
Cooper Quintin
parent
6927da49b4
commit
781d11ed72
@@ -1,43 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { AnalysisRowType, EventType, parse_finished_report, Severity } from './analysis.svelte';
|
||||
import { AnalysisRowType, parse_finished_report } from './analysis.svelte';
|
||||
import { type NewlineDeliminatedJson } from './ndjson';
|
||||
|
||||
const SAMPLE_V1_REPORT_NDJSON: NewlineDeliminatedJson = [
|
||||
{
|
||||
analyzers: [
|
||||
{
|
||||
name: 'Analyzer 1',
|
||||
description: 'A first analyzer',
|
||||
},
|
||||
{
|
||||
name: 'Analyzer 2',
|
||||
description: 'A second analyzer',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
timestamp: '2024-10-08T13:25:43.011689003-07:00',
|
||||
skipped_message_reasons: ['The reason why the message was skipped'],
|
||||
analysis: [],
|
||||
},
|
||||
{
|
||||
timestamp: '2024-10-08T13:25:43.480872496-07:00',
|
||||
skipped_message_reasons: [],
|
||||
analysis: [
|
||||
{
|
||||
timestamp: '2024-08-19T03:33:54.318Z',
|
||||
events: [
|
||||
null,
|
||||
{
|
||||
event_type: { type: 'QualitativeWarning', severity: 'Low' },
|
||||
message: 'Something nasty happened',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const SAMPLE_V2_REPORT_NDJSON: NewlineDeliminatedJson = [
|
||||
{
|
||||
analyzers: [
|
||||
@@ -62,7 +26,7 @@ const SAMPLE_V2_REPORT_NDJSON: NewlineDeliminatedJson = [
|
||||
events: [
|
||||
null,
|
||||
{
|
||||
event_type: { type: 'QualitativeWarning', severity: 'Low' },
|
||||
event_type: 'Low',
|
||||
message: 'Something nasty happened',
|
||||
},
|
||||
],
|
||||
@@ -70,40 +34,6 @@ const SAMPLE_V2_REPORT_NDJSON: NewlineDeliminatedJson = [
|
||||
];
|
||||
|
||||
describe('analysis report parsing', () => {
|
||||
it('parses v1 example analysis', () => {
|
||||
const report = parse_finished_report(SAMPLE_V1_REPORT_NDJSON);
|
||||
expect(report.metadata.report_version).toEqual(1);
|
||||
expect(report.metadata.analyzers).toEqual([
|
||||
{
|
||||
name: 'Analyzer 1',
|
||||
description: 'A first analyzer',
|
||||
version: 0,
|
||||
},
|
||||
{
|
||||
name: 'Analyzer 2',
|
||||
description: 'A second analyzer',
|
||||
version: 0,
|
||||
},
|
||||
]);
|
||||
expect(report.rows).toHaveLength(2);
|
||||
expect(report.rows[0].type).toBe(AnalysisRowType.Skipped);
|
||||
if (report.rows[1].type === AnalysisRowType.Analysis) {
|
||||
const row = report.rows[1];
|
||||
expect(row.events).toHaveLength(2);
|
||||
expect(row.events[0]).toBeNull();
|
||||
const event = row.events[1];
|
||||
const expected_timestamp = new Date('2024-08-19T03:33:54.318Z');
|
||||
expect(row.packet_timestamp.getTime()).toEqual(expected_timestamp.getTime());
|
||||
if (event !== null && event.type === EventType.Warning) {
|
||||
expect(event.severity).toEqual(Severity.Low);
|
||||
} else {
|
||||
throw 'wrong event type';
|
||||
}
|
||||
} else {
|
||||
throw 'wrong row type';
|
||||
}
|
||||
});
|
||||
|
||||
it('parses v2 example analysis', () => {
|
||||
const report = parse_finished_report(SAMPLE_V2_REPORT_NDJSON);
|
||||
expect(report.metadata.report_version).toEqual(2);
|
||||
@@ -128,11 +58,7 @@ describe('analysis report parsing', () => {
|
||||
const event = row.events[1];
|
||||
const expected_timestamp = new Date('2024-08-19T03:33:54.318Z');
|
||||
expect(row.packet_timestamp.getTime()).toEqual(expected_timestamp.getTime());
|
||||
if (event !== null && event.type === EventType.Warning) {
|
||||
expect(event.severity).toEqual(Severity.Low);
|
||||
} else {
|
||||
throw 'wrong event type';
|
||||
}
|
||||
expect(event!.event_type).toEqual('Low');
|
||||
} else {
|
||||
throw 'wrong row type';
|
||||
}
|
||||
|
||||
@@ -21,17 +21,7 @@ export class ReportMetadata {
|
||||
constructor(ndjson: any) {
|
||||
this.analyzers = ndjson.analyzers;
|
||||
this.rayhunter = ndjson.rayhunter;
|
||||
if (ndjson.report_version === undefined) {
|
||||
this.report_version = 1;
|
||||
// we consider our legacy (unversioned) heuristics to be v0 --
|
||||
// this'll let us clearly differentiate some known false-positive
|
||||
// results from the pre-versioned era from v1 heuristics
|
||||
this.analyzers.forEach((analyzer) => {
|
||||
analyzer.version = 0;
|
||||
});
|
||||
} else {
|
||||
this.report_version = ndjson.report_version;
|
||||
}
|
||||
this.report_version = ndjson.report_version || 2; // Default to v2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,77 +54,22 @@ export type PacketAnalysis = {
|
||||
events: Event[];
|
||||
};
|
||||
|
||||
export type Event = QualitativeWarning | InformationalEvent | null;
|
||||
export enum EventType {
|
||||
Informational,
|
||||
Warning,
|
||||
}
|
||||
export type EventType = 'Informational' | 'Low' | 'Medium' | 'High';
|
||||
|
||||
export type QualitativeWarning = {
|
||||
type: EventType.Warning;
|
||||
severity: Severity;
|
||||
export type Event = {
|
||||
event_type: EventType;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export enum Severity {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
export type InformationalEvent = {
|
||||
type: EventType.Informational;
|
||||
message: string;
|
||||
};
|
||||
} | null;
|
||||
|
||||
function get_event(event_json: any): Event {
|
||||
if (event_json.event_type.type === 'Informational') {
|
||||
return {
|
||||
type: EventType.Informational,
|
||||
message: event_json.message,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: EventType.Warning,
|
||||
severity:
|
||||
event_json.event_type.severity === 'High'
|
||||
? Severity.High
|
||||
: event_json.event_type.severity === 'Medium'
|
||||
? Severity.Medium
|
||||
: Severity.Low,
|
||||
message: event_json.message,
|
||||
};
|
||||
if (!['Informational', 'Low', 'Medium', 'High'].includes(event_json.event_type)) {
|
||||
throw `Invalid/unhandled event type: ${event_json.event_type}`;
|
||||
}
|
||||
|
||||
return event_json;
|
||||
}
|
||||
|
||||
function get_v1_rows(row_jsons: any[]): AnalysisRow[] {
|
||||
const rows: AnalysisRow[] = [];
|
||||
for (const row_json of row_jsons) {
|
||||
for (const reason of row_json.skipped_message_reasons) {
|
||||
rows.push({
|
||||
type: AnalysisRowType.Skipped,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
for (const analysis_json of row_json.analysis) {
|
||||
const events: Event[] = analysis_json.events.map((event_json: any): Event | null => {
|
||||
if (event_json === null) {
|
||||
return null;
|
||||
} else {
|
||||
return get_event(event_json);
|
||||
}
|
||||
});
|
||||
rows.push({
|
||||
type: AnalysisRowType.Analysis,
|
||||
packet_timestamp: new Date(analysis_json.timestamp),
|
||||
events,
|
||||
});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function get_v2_rows(row_jsons: any[]): AnalysisRow[] {
|
||||
function get_rows(row_jsons: any[]): AnalysisRow[] {
|
||||
const rows: AnalysisRow[] = [];
|
||||
for (const row_json of row_jsons) {
|
||||
if (row_json.skipped_message_reason) {
|
||||
@@ -170,7 +105,7 @@ function get_report_stats(rows: AnalysisRow[]): ReportStatistics {
|
||||
} else {
|
||||
for (const event of row.events) {
|
||||
if (event !== null) {
|
||||
if (event.type === EventType.Informational) {
|
||||
if (event.event_type === 'Informational') {
|
||||
num_informational_logs++;
|
||||
} else {
|
||||
num_warnings++;
|
||||
@@ -188,12 +123,7 @@ function get_report_stats(rows: AnalysisRow[]): ReportStatistics {
|
||||
|
||||
export function parse_finished_report(report_json: NewlineDeliminatedJson): AnalysisReport {
|
||||
const metadata = new ReportMetadata(report_json[0]);
|
||||
let rows;
|
||||
if (metadata.report_version === 1) {
|
||||
rows = get_v1_rows(report_json.slice(1));
|
||||
} else {
|
||||
rows = get_v2_rows(report_json.slice(1));
|
||||
}
|
||||
const rows = get_rows(report_json.slice(1));
|
||||
const statistics = get_report_stats(rows);
|
||||
return {
|
||||
statistics,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { AnalysisRowType, EventType, type AnalysisReport } from '$lib/analysis.svelte';
|
||||
import { AnalysisRowType, type AnalysisReport } from '$lib/analysis.svelte';
|
||||
let {
|
||||
report,
|
||||
}: {
|
||||
@@ -50,30 +50,19 @@
|
||||
{#each row.events as event, analyzerIndex}
|
||||
{#if event !== null}
|
||||
{@const analyzer = analyzers[analyzerIndex]}
|
||||
{@const event_type_class = {
|
||||
Informational: '',
|
||||
Low: 'bg-red-200',
|
||||
Medium: 'bg-red-400',
|
||||
High: 'bg-red-600',
|
||||
}[event.event_type]}
|
||||
<tr class="even:bg-gray-200 odd:bg-white">
|
||||
{#if event.type === EventType.Warning}
|
||||
{@const severity = ['Low', 'Medium', 'High'][
|
||||
event.severity
|
||||
]}
|
||||
{@const severity_class = [
|
||||
'bg-red-200',
|
||||
'bg-red-400',
|
||||
'bg-red-600',
|
||||
][event.severity]}
|
||||
<td class="p-2">{date_formatter.format(parsed_date)}</td
|
||||
>
|
||||
<td class="p-2">{analyzer.name} v{analyzer.version}</td>
|
||||
<td class="p-2">{event.message}</td>
|
||||
<td class="p-2 {severity_class} text-center"
|
||||
>{severity}</td
|
||||
>
|
||||
{:else if event.type === EventType.Informational}
|
||||
<td class="p-2">{date_formatter.format(parsed_date)}</td
|
||||
>
|
||||
<td class="p-2">{analyzer.name} v{analyzer.version}</td>
|
||||
<td class="p-2">{event.message}</td>
|
||||
<td class="p-2">Info</td>
|
||||
{/if}
|
||||
<td class="p-2">{date_formatter.format(parsed_date)}</td>
|
||||
<td class="p-2">{analyzer.name} v{analyzer.version}</td>
|
||||
<td class="p-2">{event.message}</td>
|
||||
<td class="p-2 {event_type_class} text-center"
|
||||
>{event.event_type}</td
|
||||
>
|
||||
</tr>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
Reference in New Issue
Block a user