mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-21 20:18:06 +02:00
feat(dynamic): add PartiallyConfirmed status for finer-grained sink-reachability categorization, update dynamic verification, telemetry, and reporting systems
This commit is contained in:
parent
635b213825
commit
c0501884ae
23 changed files with 658 additions and 142 deletions
|
|
@ -3,7 +3,12 @@ export type Confidence = 'Low' | 'Medium' | 'High';
|
|||
export type FlowStepKind = 'source' | 'assignment' | 'call' | 'phi' | 'sink';
|
||||
|
||||
// Dynamic verification types (from src/evidence.rs VerifyStatus / VerifyResult)
|
||||
export type VerifyStatus = 'Confirmed' | 'NotConfirmed' | 'Inconclusive' | 'Unsupported';
|
||||
export type VerifyStatus =
|
||||
| 'Confirmed'
|
||||
| 'PartiallyConfirmed'
|
||||
| 'NotConfirmed'
|
||||
| 'Inconclusive'
|
||||
| 'Unsupported';
|
||||
|
||||
export interface AttemptSummary {
|
||||
payload_label: string;
|
||||
|
|
@ -29,6 +34,7 @@ export interface VerifyResult {
|
|||
export interface DynamicVerificationSummary {
|
||||
total: number;
|
||||
confirmed: number;
|
||||
partially_confirmed: number;
|
||||
not_confirmed: number;
|
||||
inconclusive: number;
|
||||
unsupported: number;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { VerifyResult, VerifyStatus } from '../api/types';
|
|||
|
||||
const STATUS_LABELS: Record<VerifyStatus, string> = {
|
||||
Confirmed: 'Confirmed',
|
||||
PartiallyConfirmed: 'Partially confirmed',
|
||||
NotConfirmed: 'Not confirmed',
|
||||
Inconclusive: 'Inconclusive',
|
||||
Unsupported: 'Unsupported',
|
||||
|
|
@ -15,6 +16,10 @@ function verdictTooltip(verdict: VerifyResult): string {
|
|||
return triggered_payload
|
||||
? `Confirmed via payload: ${triggered_payload}`
|
||||
: 'Dynamically confirmed exploitable';
|
||||
case 'PartiallyConfirmed':
|
||||
return detail
|
||||
? `Partially confirmed (sink reached): ${detail}`
|
||||
: 'Partially confirmed: sink reached but exploit chain did not complete';
|
||||
case 'NotConfirmed':
|
||||
return (verdict.attempts?.length ?? 0) > 0
|
||||
? `Not confirmed after ${verdict.attempts?.length ?? 0} payload attempt(s)`
|
||||
|
|
|
|||
|
|
@ -244,13 +244,14 @@ export function ScannerQualityPanel({
|
|||
const dynamic = quality.dynamic_verification ?? {
|
||||
total: 0,
|
||||
confirmed: 0,
|
||||
partially_confirmed: 0,
|
||||
not_confirmed: 0,
|
||||
inconclusive: 0,
|
||||
unsupported: 0,
|
||||
};
|
||||
const dynamicDetail =
|
||||
dynamic.total > 0
|
||||
? `${dynamic.total.toLocaleString()} verdicts · ${dynamic.not_confirmed.toLocaleString()} not confirmed · ${dynamic.inconclusive.toLocaleString()} inconclusive · ${dynamic.unsupported.toLocaleString()} unsupported`
|
||||
? `${dynamic.total.toLocaleString()} verdicts · ${dynamic.partially_confirmed.toLocaleString()} partially confirmed · ${dynamic.not_confirmed.toLocaleString()} not confirmed · ${dynamic.inconclusive.toLocaleString()} inconclusive · ${dynamic.unsupported.toLocaleString()} unsupported`
|
||||
: 'no dynamic verdicts in latest scan';
|
||||
|
||||
const rows: Array<{
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ function formatTriageState(state: string): string {
|
|||
|
||||
function formatVerificationStatus(status: string): string {
|
||||
if (status === 'NotConfirmed') return 'Not confirmed';
|
||||
if (status === 'PartiallyConfirmed') return 'Partially confirmed';
|
||||
return status || 'Unverified';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2668,6 +2668,10 @@ tr.selected td {
|
|||
background: var(--success-bg);
|
||||
color: var(--success);
|
||||
}
|
||||
.badge-dyn-partiallyconfirmed {
|
||||
background: var(--conf-medium-bg);
|
||||
color: var(--conf-medium);
|
||||
}
|
||||
.badge-dyn-notconfirmed {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,19 @@ describe('DynamicVerdictSection', () => {
|
|||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders PartiallyConfirmed badge', () => {
|
||||
render(
|
||||
<DynamicVerdictSection
|
||||
verdict={makeVerdict('PartiallyConfirmed', {
|
||||
detail: 'sink reached but exploit chain did not complete',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('verdict-badge-partiallyconfirmed'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not crash when the API omits an empty attempts array', () => {
|
||||
render(
|
||||
<DynamicVerdictSection
|
||||
|
|
@ -82,6 +95,7 @@ describe('DynamicVerdictSection', () => {
|
|||
unmount();
|
||||
|
||||
for (const status of [
|
||||
'PartiallyConfirmed',
|
||||
'NotConfirmed',
|
||||
'Unsupported',
|
||||
'Inconclusive',
|
||||
|
|
|
|||
|
|
@ -35,6 +35,21 @@ describe('VerdictBadge', () => {
|
|||
expect(badge.textContent).toContain('🔥');
|
||||
});
|
||||
|
||||
it('renders PartiallyConfirmed badge with amber class and no flame', () => {
|
||||
render(
|
||||
<VerdictBadge
|
||||
verdict={makeVerdict('PartiallyConfirmed', {
|
||||
detail: 'sink-reachability probe fired but the oracle marker was not observed',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
const badge = screen.getByTestId('verdict-badge-partiallyconfirmed');
|
||||
expect(badge).toBeInTheDocument();
|
||||
expect(badge.className).toContain('badge-dyn-partiallyconfirmed');
|
||||
expect(badge.textContent).not.toContain('🔥');
|
||||
expect(badge.getAttribute('title')).toContain('sink reached');
|
||||
});
|
||||
|
||||
it('renders NotConfirmed badge with correct class', () => {
|
||||
render(<VerdictBadge verdict={makeVerdict('NotConfirmed')} />);
|
||||
const badge = screen.getByTestId('verdict-badge-notconfirmed');
|
||||
|
|
@ -107,9 +122,10 @@ describe('VerdictBadge', () => {
|
|||
expect(badge.textContent?.replace('🔥 ', '')).toBe('C');
|
||||
});
|
||||
|
||||
it('renders all four VerifyStatus variants without crashing', () => {
|
||||
it('renders all five VerifyStatus variants without crashing', () => {
|
||||
const statuses: VerifyResult['status'][] = [
|
||||
'Confirmed',
|
||||
'PartiallyConfirmed',
|
||||
'NotConfirmed',
|
||||
'Unsupported',
|
||||
'Inconclusive',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue