This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/breakpilot-compliance-sdk/packages/cli/src/commands/scan.ts
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

229 lines
6.6 KiB
TypeScript

/**
* Scan command - Run security scans
*/
import { Command } from 'commander'
import * as path from 'path'
interface ScanOptions {
tools?: string
output?: string
format?: string
severity?: string
}
export const scanCommand = new Command('scan')
.description('Run security scans on your codebase')
.argument('[path]', 'Path to scan', '.')
.option('-t, --tools <tools>', 'Comma-separated list of tools (gitleaks,semgrep,bandit,trivy,grype,syft)', 'all')
.option('-o, --output <path>', 'Output file path')
.option('-f, --format <format>', 'Output format (json, sarif, table)', 'table')
.option('-s, --severity <level>', 'Minimum severity to report (low, medium, high, critical)', 'low')
.action(async (scanPath: string, options: ScanOptions) => {
const chalk = (await import('chalk')).default
const ora = (await import('ora')).default
console.log(chalk.bold.blue('\n🔒 BreakPilot Security Scanner\n'))
const targetPath = path.resolve(scanPath)
console.log(chalk.gray(`Scanning: ${targetPath}\n`))
const tools = options.tools === 'all'
? ['gitleaks', 'semgrep', 'bandit', 'trivy', 'grype', 'syft']
: options.tools!.split(',').map(t => t.trim())
const results: ScanResult[] = []
for (const tool of tools) {
const spinner = ora(`Running ${tool}...`).start()
try {
// Simulate running the tool
await sleep(1500)
const toolResult = simulateToolResult(tool)
results.push(toolResult)
if (toolResult.findings.length > 0) {
spinner.warn(`${tool}: ${toolResult.findings.length} findings`)
} else {
spinner.succeed(`${tool}: No issues found`)
}
} catch (error) {
spinner.fail(`${tool}: Failed`)
}
}
// Summary
console.log(chalk.bold('\n📊 Scan Summary\n'))
const allFindings = results.flatMap(r => r.findings)
const bySeverity = {
critical: allFindings.filter(f => f.severity === 'CRITICAL'),
high: allFindings.filter(f => f.severity === 'HIGH'),
medium: allFindings.filter(f => f.severity === 'MEDIUM'),
low: allFindings.filter(f => f.severity === 'LOW'),
}
if (bySeverity.critical.length > 0) {
console.log(chalk.red(` 🔴 Critical: ${bySeverity.critical.length}`))
}
if (bySeverity.high.length > 0) {
console.log(chalk.red(` 🟠 High: ${bySeverity.high.length}`))
}
if (bySeverity.medium.length > 0) {
console.log(chalk.yellow(` 🟡 Medium: ${bySeverity.medium.length}`))
}
if (bySeverity.low.length > 0) {
console.log(chalk.gray(` 🟢 Low: ${bySeverity.low.length}`))
}
if (allFindings.length === 0) {
console.log(chalk.green(' ✅ No security issues found!'))
} else {
console.log(chalk.gray(`\n Total: ${allFindings.length} findings`))
// Show top findings
if (options.format === 'table') {
console.log(chalk.bold('\n📋 Top Findings\n'))
const topFindings = allFindings
.sort((a, b) => severityOrder(b.severity) - severityOrder(a.severity))
.slice(0, 10)
for (const finding of topFindings) {
const severityColor = getSeverityColor(finding.severity)
console.log(
` ${severityColor(finding.severity.padEnd(8))} ` +
`${chalk.gray(finding.tool.padEnd(10))} ` +
`${finding.title}`
)
if (finding.file) {
console.log(chalk.gray(` └─ ${finding.file}:${finding.line || '?'}`))
}
}
if (allFindings.length > 10) {
console.log(chalk.gray(`\n ... and ${allFindings.length - 10} more findings`))
}
}
}
// Write output if requested
if (options.output) {
const fs = await import('fs')
const output = {
scanDate: new Date().toISOString(),
targetPath,
tools,
summary: {
total: allFindings.length,
critical: bySeverity.critical.length,
high: bySeverity.high.length,
medium: bySeverity.medium.length,
low: bySeverity.low.length,
},
findings: allFindings,
}
fs.writeFileSync(options.output, JSON.stringify(output, null, 2))
console.log(chalk.gray(`\nResults written to: ${options.output}`))
}
// Exit with error if critical findings
if (bySeverity.critical.length > 0 || bySeverity.high.length > 0) {
process.exit(1)
}
})
interface Finding {
id: string
tool: string
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'
title: string
description?: string
file?: string
line?: number
recommendation?: string
}
interface ScanResult {
tool: string
findings: Finding[]
}
function simulateToolResult(tool: string): ScanResult {
// Simulate some findings for demonstration
const findings: Finding[] = []
switch (tool) {
case 'gitleaks':
// Secrets detection - usually finds nothing in clean repos
break
case 'semgrep':
findings.push({
id: 'semgrep-1',
tool: 'semgrep',
severity: 'MEDIUM',
title: 'Potential SQL injection',
description: 'User input used in SQL query without parameterization',
file: 'src/db/queries.ts',
line: 42,
recommendation: 'Use parameterized queries instead of string concatenation',
})
break
case 'bandit':
// Python security - skip if not a Python project
break
case 'trivy':
findings.push({
id: 'trivy-1',
tool: 'trivy',
severity: 'HIGH',
title: 'CVE-2024-1234 in lodash@4.17.20',
description: 'Prototype pollution vulnerability',
recommendation: 'Upgrade to lodash@4.17.21 or higher',
})
break
case 'grype':
findings.push({
id: 'grype-1',
tool: 'grype',
severity: 'LOW',
title: 'Outdated dependency: axios@0.21.0',
recommendation: 'Update to latest version',
})
break
case 'syft':
// SBOM generation - no findings, just metadata
break
}
return { tool, findings }
}
function severityOrder(severity: string): number {
switch (severity) {
case 'CRITICAL': return 4
case 'HIGH': return 3
case 'MEDIUM': return 2
case 'LOW': return 1
default: return 0
}
}
function getSeverityColor(severity: string): (text: string) => string {
const chalk = require('chalk')
switch (severity) {
case 'CRITICAL': return chalk.red.bold
case 'HIGH': return chalk.red
case 'MEDIUM': return chalk.yellow
case 'LOW': return chalk.gray
default: return chalk.white
}
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}