mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-03 13:18:56 +00:00
115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
/**
|
|
* Shared utilities for container and environment auto-update tasks.
|
|
*/
|
|
|
|
import type { VulnerabilityCriteria } from '../../db';
|
|
import type { VulnerabilitySeverity } from '../../scanner';
|
|
|
|
/**
|
|
* Parse image name and tag from a full image reference.
|
|
* Handles various formats:
|
|
* - nginx → ["nginx", "latest"]
|
|
* - nginx:1.25 → ["nginx", "1.25"]
|
|
* - registry.example.com:5000/myimage:v1 → ["registry.example.com:5000/myimage", "v1"]
|
|
* - nginx:latest-dockhand-pending → ["nginx", "latest-dockhand-pending"]
|
|
*/
|
|
export function parseImageNameAndTag(imageName: string): [string, string] {
|
|
// Handle digest-based images (return as-is with empty tag)
|
|
if (imageName.includes('@sha256:')) {
|
|
return [imageName, ''];
|
|
}
|
|
|
|
// Find the last colon that's part of the tag (not part of registry port)
|
|
const lastColon = imageName.lastIndexOf(':');
|
|
if (lastColon === -1) {
|
|
return [imageName, 'latest'];
|
|
}
|
|
|
|
// Check if this colon is part of a registry port
|
|
// Registry ports appear before a slash: registry:5000/image
|
|
const afterColon = imageName.substring(lastColon + 1);
|
|
if (afterColon.includes('/')) {
|
|
// The colon is part of the registry, not the tag
|
|
return [imageName, 'latest'];
|
|
}
|
|
|
|
// The colon separates repo from tag
|
|
return [imageName.substring(0, lastColon), afterColon];
|
|
}
|
|
|
|
/**
|
|
* Determine if an update should be blocked based on vulnerability criteria.
|
|
*/
|
|
export function shouldBlockUpdate(
|
|
criteria: VulnerabilityCriteria,
|
|
newScanSummary: VulnerabilitySeverity,
|
|
currentScanSummary?: VulnerabilitySeverity
|
|
): { blocked: boolean; reason: string } {
|
|
const totalVulns = newScanSummary.critical + newScanSummary.high + newScanSummary.medium + newScanSummary.low;
|
|
|
|
switch (criteria) {
|
|
case 'any':
|
|
if (totalVulns > 0) {
|
|
return {
|
|
blocked: true,
|
|
reason: `Found ${totalVulns} vulnerabilities (${newScanSummary.critical} critical, ${newScanSummary.high} high, ${newScanSummary.medium} medium, ${newScanSummary.low} low)`
|
|
};
|
|
}
|
|
break;
|
|
case 'critical_high':
|
|
if (newScanSummary.critical > 0 || newScanSummary.high > 0) {
|
|
return {
|
|
blocked: true,
|
|
reason: `Found ${newScanSummary.critical} critical and ${newScanSummary.high} high severity vulnerabilities`
|
|
};
|
|
}
|
|
break;
|
|
case 'critical':
|
|
if (newScanSummary.critical > 0) {
|
|
return {
|
|
blocked: true,
|
|
reason: `Found ${newScanSummary.critical} critical vulnerabilities`
|
|
};
|
|
}
|
|
break;
|
|
case 'more_than_current':
|
|
if (currentScanSummary) {
|
|
const currentTotal = currentScanSummary.critical + currentScanSummary.high + currentScanSummary.medium + currentScanSummary.low;
|
|
if (totalVulns > currentTotal) {
|
|
return {
|
|
blocked: true,
|
|
reason: `New image has ${totalVulns} vulnerabilities vs ${currentTotal} in current image`
|
|
};
|
|
}
|
|
}
|
|
break;
|
|
case 'never':
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return { blocked: false, reason: '' };
|
|
}
|
|
|
|
/**
|
|
* Check if a container is the Dockhand application itself.
|
|
* Used to prevent Dockhand from updating its own container.
|
|
*/
|
|
export function isDockhandContainer(imageName: string): boolean {
|
|
return imageName.toLowerCase().includes('fnsys/dockhand');
|
|
}
|
|
|
|
/**
|
|
* Combine multiple scan summaries by taking the maximum of each severity level.
|
|
*/
|
|
export function combineScanSummaries(results: { summary: VulnerabilitySeverity }[]): VulnerabilitySeverity {
|
|
return results.reduce((acc, result) => ({
|
|
critical: Math.max(acc.critical, result.summary.critical),
|
|
high: Math.max(acc.high, result.summary.high),
|
|
medium: Math.max(acc.medium, result.summary.medium),
|
|
low: Math.max(acc.low, result.summary.low),
|
|
negligible: Math.max(acc.negligible, result.summary.negligible),
|
|
unknown: Math.max(acc.unknown, result.summary.unknown)
|
|
}), { critical: 0, high: 0, medium: 0, low: 0, negligible: 0, unknown: 0 });
|
|
}
|