Overview
Perform security vulnerability scans on Docker images using integrated scanners (Grype or Trivy). Results are saved to the database and can be retrieved for historical analysis.
Endpoint
POST /api/images/scan?env={environmentId}
Query Parameters
Environment ID containing the image to scan. Optional for local environments.
Request Body
Image name or ID to scan:
nginx:latest
ubuntu:22.04
sha256:abc123...
Force specific scanner: grype or trivy. If not specified, uses environment scanner settings.
Authentication
Requires images:inspect permission for the specified environment.
Returns job ID for progress tracking:
{
"jobId": "550e8400-e29b-41d4-a716-446655440000"
}
For synchronous requests with Accept: application/json, returns scan results directly.
Progress Events
Scanning Stage
{
"stage": "scanning",
"message": "Running Grype scanner...",
"progress": 0
}
Analyzing Stage
{
"stage": "analyzing",
"message": "Analyzing image layers",
"progress": 45
}
Processing Stage
{
"stage": "processing",
"message": "Processing vulnerability data",
"progress": 75
}
Complete
{
"stage": "complete",
"message": "Scan complete - found 12 vulnerabilities",
"progress": 100,
"result": {
"imageId": "sha256:abc123...",
"imageName": "nginx:latest",
"scanner": "grype",
"scannedAt": "2024-03-04T10:30:00Z",
"scanDuration": 8500,
"summary": {
"critical": 0,
"high": 2,
"medium": 5,
"low": 5,
"negligible": 0,
"unknown": 0
},
"vulnerabilities": [
{
"id": "CVE-2024-1234",
"severity": "high",
"package": "openssl",
"version": "1.1.1",
"fixedVersion": "1.1.1w",
"description": "Buffer overflow vulnerability in OpenSSL",
"urls": [
"https://nvd.nist.gov/vuln/detail/CVE-2024-1234"
]
}
]
},
"results": [
// Array of all scanner results (if multiple scanners configured)
]
}
Error
{
"stage": "error",
"message": "Scan failed: Scanner not available",
"error": "Scanner not available"
}
Scan Result Schema
Image identifier (SHA256 hash)
Human-readable image name with tag
Scanner used: grype or trivy
ISO 8601 timestamp when scan completed
Scan duration in milliseconds
Vulnerability count by severity:
critical: Critical severity (CVSS 9.0-10.0)
high: High severity (CVSS 7.0-8.9)
medium: Medium severity (CVSS 4.0-6.9)
low: Low severity (CVSS 0.1-3.9)
negligible: Negligible/informational
unknown: Unknown severity
Array of vulnerability objects
Vulnerability Object
CVE identifier or vendor-specific ID
Severity level: critical, high, medium, low, negligible, unknown
Installed package version
Version containing the fix (if available)
Vulnerability description
Reference URLs for more information
Implementation
export const POST: RequestHandler = async ({ request, url, cookies }) => {
const auth = await authorize(cookies);
const envId = url.searchParams.get('env') ? parseInt(url.searchParams.get('env')!) : undefined;
// Permission check (Scanning is an inspect operation)
if (auth.authEnabled && !await auth.can('images', 'inspect', envId)) {
return json({ error: 'Permission denied' }, { status: 403 });
}
const { imageName, scanner: forceScannerType } = await request.json();
return createJobResponse(async (send) => {
const sendProgress = (progress: ScanProgress) => {
send('progress', progress);
};
try {
const results = await scanImage(imageName, envId, sendProgress, forceScannerType);
// Save results to database
for (const result of results) {
await saveVulnerabilityScan(scanResultToDbFormat(result, envId));
}
const completeProgress: ScanProgress = {
stage: 'complete',
message: `Scan complete - found ${results.reduce((sum, r) => sum + r.vulnerabilities.length, 0)} vulnerabilities`,
progress: 100,
result: results[0],
results: results
};
send('result', completeProgress);
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
send('result', {
stage: 'error',
message: `Scan failed: ${errorMsg}`,
error: errorMsg
});
}
}, request);
};
Retrieve Cached Results
Get the latest scan results for an image without running a new scan:
GET /api/images/scan?image={imageName}&env={environmentId}&scanner={scannerType}
Query Parameters
Filter by scanner: grype or trivy
Response
{
"found": true,
"result": {
"imageId": "sha256:abc123...",
"imageName": "nginx:latest",
"scanner": "grype",
"scannedAt": "2024-03-04T10:30:00Z",
"summary": { /* ... */ },
"vulnerabilities": [ /* ... */ ]
}
}
If no cached results exist:
Usage Examples
Scan with Default Scanner
curl -X POST 'https://dockhand.example.com/api/images/scan?env=1' \
-H 'Content-Type: application/json' \
-H 'Cookie: session=...' \
-d '{
"imageName": "nginx:latest"
}'
Force Specific Scanner
curl -X POST 'https://dockhand.example.com/api/images/scan?env=1' \
-H 'Content-Type: application/json' \
-d '{
"imageName": "ubuntu:22.04",
"scanner": "trivy"
}'
Get Cached Results
curl -X GET 'https://dockhand.example.com/api/images/scan?image=nginx:latest&env=1&scanner=grype' \
-H 'Cookie: session=...'
Stream Progress (JavaScript)
const response = await fetch('/api/images/scan?env=1', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ imageName: 'nginx:latest' })
});
const { jobId } = await response.json();
const eventSource = new EventSource(`/api/jobs/${jobId}`);
eventSource.addEventListener('progress', (e) => {
const progress = JSON.parse(e.data);
console.log(`[${progress.stage}] ${progress.message} - ${progress.progress}%`);
});
eventSource.addEventListener('result', (e) => {
const result = JSON.parse(e.data);
if (result.stage === 'complete') {
console.log('Vulnerabilities found:', result.result.vulnerabilities.length);
console.log('Summary:', result.result.summary);
}
eventSource.close();
});
Filter Critical Vulnerabilities
const response = await fetch('/api/images/scan?image=nginx:latest&env=1');
const data = await response.json();
if (data.found) {
const critical = data.result.vulnerabilities.filter(
vuln => vuln.severity === 'critical'
);
console.log(`Found ${critical.length} critical vulnerabilities`);
}
Supported Scanners
Grype
Fast, accurate vulnerability scanner by Anchore:
- Language-specific package scanning
- OS package scanning
- Database automatically updated
- Supports offline scanning
Trivy
Comprehensive security scanner by Aqua Security:
- Vulnerability scanning
- Misconfiguration detection
- Secret detection
- License scanning
Error Responses
Missing required parameter{ "error": "Image name is required" }
Permission denied{ "error": "Permission denied" }
Scan failed{ "error": "Failed to get scan results" }
Scan-on-Pull Integration
Scans are automatically triggered when pulling images (if configured):
const { scanner } = await getScannerSettings(envId);
if (scanner !== 'none') {
const results = await scanImage(image, envId, (progress) => {
sendData({ status: 'scan-progress', ...progress });
});
}
Notes
- Results are automatically saved to database
- Multiple scanners can be configured simultaneously
- Scan duration typically ranges from 5-30 seconds depending on image size
- Progress events provide real-time feedback
- Cached results can be retrieved without re-scanning
- Scanner selection follows environment configuration
- Scan-on-pull integration is automatic when scanner is configured