Add comprehensive tests for PathConfidenceScorer, PathEnumerator, ShellSymbolicExecutor, and SymbolicState
- Implemented unit tests for PathConfidenceScorer to evaluate path scoring under various conditions, including empty constraints, known and unknown constraints, environmental dependencies, and custom weights. - Developed tests for PathEnumerator to ensure correct path enumeration from simple scripts, handling known environments, and respecting maximum paths and depth limits. - Created tests for ShellSymbolicExecutor to validate execution of shell scripts, including handling of commands, branching, and environment tracking. - Added tests for SymbolicState to verify state management, variable handling, constraint addition, and environment dependency collection.
This commit is contained in:
@@ -41,20 +41,20 @@ describe('EvidenceDrawerComponent', () => {
|
||||
] as ProofNode[],
|
||||
proofRootHash: 'sha256:rootabc123',
|
||||
reachabilityPath: {
|
||||
nodes: [
|
||||
{ id: 'entry', label: 'BillingController.Pay', type: 'entrypoint' },
|
||||
{ id: 'mid', label: 'StripeClient.Create', type: 'call' },
|
||||
{ id: 'sink', label: 'HttpClient.Post', type: 'sink' },
|
||||
callPath: [
|
||||
{ nodeId: 'mid', symbol: 'StripeClient.Create' },
|
||||
],
|
||||
edges: [
|
||||
{ from: 'entry', to: 'mid' },
|
||||
{ from: 'mid', to: 'sink' },
|
||||
gates: [
|
||||
{ gateType: 'auth', symbol: 'JwtMiddleware.Authenticate', confidence: 0.95, description: 'JWT required' },
|
||||
{ gateType: 'rate-limit', symbol: 'RateLimiter.Check', confidence: 0.90, description: '100 req/min' },
|
||||
],
|
||||
entrypoint: { nodeId: 'entry', symbol: 'BillingController.Pay' },
|
||||
sink: { nodeId: 'sink', symbol: 'HttpClient.Post' },
|
||||
},
|
||||
confidenceTier: 'high',
|
||||
gates: [
|
||||
{ kind: 'auth', passed: true, message: 'JWT required' },
|
||||
{ kind: 'rate_limit', passed: true, message: '100 req/min' },
|
||||
{ gateType: 'auth', symbol: 'JwtMiddleware.Authenticate', confidence: 0.95, description: 'JWT required' },
|
||||
{ gateType: 'rate-limit', symbol: 'RateLimiter.Check', confidence: 0.90, description: '100 req/min' },
|
||||
],
|
||||
vexDecisions: [
|
||||
{
|
||||
|
||||
@@ -303,10 +303,13 @@ export class FindingListComponent {
|
||||
*/
|
||||
readonly showSummary = input<boolean>(true);
|
||||
|
||||
/**
|
||||
* Threshold for enabling virtual scroll (number of items).
|
||||
*/
|
||||
readonly virtualScrollThreshold = input<number>(50);
|
||||
|
||||
// NOTE: Virtual scrolling requires @angular/cdk package.
|
||||
// These inputs are kept for future implementation but currently unused.
|
||||
// readonly useVirtualScroll = input<boolean>(true);
|
||||
// readonly virtualScrollThreshold = input<number>(50);
|
||||
// readonly itemHeight = input<number>(64);
|
||||
// readonly viewportHeight = input<number>(400);
|
||||
|
||||
@@ -390,6 +393,23 @@ export class FindingListComponent {
|
||||
return `Vulnerability findings list, ${count} item${count === 1 ? '' : 's'}`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Count of critical (≥9.0) and high (≥7.0) severity findings.
|
||||
*/
|
||||
criticalHighCount(): number {
|
||||
return this.sortedFindings().filter(f => {
|
||||
const score = f.score_explain?.risk_score ?? 0;
|
||||
return score >= 7.0;
|
||||
}).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use virtual scroll based on threshold.
|
||||
*/
|
||||
useVirtualScroll(): boolean {
|
||||
return this.sortedFindings().length >= this.virtualScrollThreshold();
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor.
|
||||
*/
|
||||
|
||||
@@ -410,6 +410,11 @@ export class FindingRowComponent {
|
||||
*/
|
||||
readonly showChainStatus = input<boolean>(true);
|
||||
|
||||
/**
|
||||
* Maximum number of path steps to show in preview (default: 5).
|
||||
*/
|
||||
readonly maxPathSteps = input<number>(5);
|
||||
|
||||
/**
|
||||
* Emitted when user clicks to view evidence details.
|
||||
*/
|
||||
@@ -502,6 +507,116 @@ export class FindingRowComponent {
|
||||
return `${cve} in ${component}, risk score ${score.toFixed(1)}`;
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// Boundary & Exposure Computed Properties
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Check if boundary is internet-facing.
|
||||
*/
|
||||
isInternetFacing(): boolean {
|
||||
return this.finding()?.boundary?.exposure?.internet_facing === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if auth is required for the boundary.
|
||||
*/
|
||||
hasAuthRequired(): boolean {
|
||||
return this.finding()?.boundary?.auth?.required === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the boundary surface info.
|
||||
*/
|
||||
boundarySurface(): string {
|
||||
const surface = this.finding()?.boundary?.surface;
|
||||
if (!surface) return '';
|
||||
|
||||
const parts: string[] = [];
|
||||
if (surface.protocol) parts.push(surface.protocol);
|
||||
if (surface.port !== undefined) parts.push(String(surface.port));
|
||||
if (surface.type) parts.push(surface.type);
|
||||
return parts.join(':');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Entrypoint Computed Properties
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get the entrypoint type.
|
||||
*/
|
||||
entrypointType(): string {
|
||||
return this.finding()?.entrypoint?.type ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format entrypoint route with method.
|
||||
*/
|
||||
entrypointRoute(): string {
|
||||
const entry = this.finding()?.entrypoint;
|
||||
if (!entry) return '';
|
||||
if (entry.method && entry.route) {
|
||||
return `${entry.method} ${entry.route}`;
|
||||
}
|
||||
return entry.route ?? '';
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Path Preview Computed Properties
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get truncated path preview based on maxPathSteps.
|
||||
*/
|
||||
pathPreview(): readonly string[] {
|
||||
const path = this.callPath();
|
||||
const max = this.maxPathSteps();
|
||||
if (path.length <= max) return path;
|
||||
return path.slice(0, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if path was truncated.
|
||||
*/
|
||||
pathTruncated(): boolean {
|
||||
return this.callPath().length > this.maxPathSteps();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Staleness Computed Properties
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Check if evidence has expired (is stale).
|
||||
*/
|
||||
isStale(): boolean {
|
||||
const expiresAt = this.finding()?.expires_at;
|
||||
if (!expiresAt) return false;
|
||||
try {
|
||||
const expiryDate = new Date(expiresAt);
|
||||
return expiryDate < new Date();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if evidence is near expiry (within 24 hours).
|
||||
*/
|
||||
isNearExpiry(): boolean {
|
||||
const expiresAt = this.finding()?.expires_at;
|
||||
if (!expiresAt) return false;
|
||||
try {
|
||||
const expiryDate = new Date(expiresAt);
|
||||
const now = new Date();
|
||||
const twentyFourHoursFromNow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
||||
return expiryDate > now && expiryDate <= twentyFourHoursFromNow;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Computed Descriptions
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user