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>
This commit is contained in:
228
breakpilot-compliance-sdk/packages/cli/src/commands/scan.ts
Normal file
228
breakpilot-compliance-sdk/packages/cli/src/commands/scan.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
Reference in New Issue
Block a user