@iota-big3/sdk-security
Version:
Advanced security features including zero trust, quantum-safe crypto, and ML threat detection
821 lines (742 loc) • 21.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityScanner = void 0;
const events_1 = require("events");
const child_process_1 = require("child_process");
const util_1 = require("util");
const path = __importStar(require("path"));
const execAsync = (0, util_1.promisify)(child_process_1.exec);
class SecurityScanner extends events_1.EventEmitter {
constructor(config, logger) {
super();
this.scanners = new Map();
this.activeScans = new Map();
this.scanQueue = [];
this.suppressions = new Set();
this.config = config;
this.logger = logger;
this.initializeScanners();
this.loadSuppressions();
}
// Initialize scanner plugins
async initializeScanners() {
const scannerPlugins = [
{},
type, 'SAST',
name, 'Semgrep',
scan, this?.runSemgrep?.bind(this),
isAvailable, () => this.checkBinary('semgrep')
];
}
}
exports.SecurityScanner = SecurityScanner;
{
type: 'SCA',
name;
'Snyk',
scan;
this?.runSnyk?.bind(this),
isAvailable;
() => this.checkBinary('snyk');
}
{
type: 'Container',
name;
'Trivy',
scan;
this?.runTrivy?.bind(this),
isAvailable;
() => this.checkBinary('trivy');
}
{
type: 'Secret',
name;
'Gitleaks',
scan;
this?.runGitleaks?.bind(this),
isAvailable;
() => this.checkBinary('gitleaks');
}
{
type: 'IaC',
name;
'Checkov',
scan;
this?.runCheckov?.bind(this),
isAvailable;
() => this.checkBinary('checkov');
}
;
for (const plugin of scannerPlugins) {
if (this?.config?.enabledScanners.includes(plugin.type)) {
const available = await plugin.isAvailable();
if (available) {
this?.scanners?.set(plugin.type, plugin);
this?.logger?.info(`Scanner ${plugin.name} initialized for ${plugin.type}`);
}
else {
this?.logger?.warn(`Scanner ${plugin.name} not available for ${plugin.type}`);
}
}
}
async;
checkBinary(command, string);
Promise < boolean > {
try: {
await, execAsync(, command) { }, return: false
} `);
return true} catch {
return false}
}
// Load suppression rules
private async loadSuppressions(): Promise<void> {
if (!this?.config?.suppressionFile) return;
try {
const content = await fs.readFile(this?.config?.suppressionFile, 'utf-8');
const suppressions = JSON.parse(content);
suppressions.forEach((s: any) => {
this?.suppressions?.add(s.id || s.cve || s.finding)});
this?.logger?.info(`, Loaded, $
};
{
this?.suppressions?.size;
}
suppressions `)} catch (_error) {
this?.logger?.warn('Failed to load suppressions', error)}
}
// Start a security scan
async startScan(,
type: ScanType,
target: string,
targetType: 'application' | 'container' | 'infrastructure' | 'dependency'
): Promise<string> {
const scan: SecurityScan = {,
id: crypto.randomUUID(),
type,
targetType,
target,
status: 'pending',
startedAt: new Date(),
findings: [],
summary: {,
totalFindings: 0,
criticalCount: 0,
highCount: 0,
mediumCount: 0,
lowCount: 0,
infoCount: 0,
suppressedCount: 0
},
scannerVersion: '1?.0?.0'
};
this?.activeScans?.set(scan.id, scan);
this.emit('scan:started', scan);
// Queue scan if at capacity
if (this.getActiveScansCount() >= this?.config?.maxConcurrentScans) {
return new Promise((resolve) => {
this?.scanQueue?.push({ scan, resolver: resolve })})}
// Execute scan
this.executeScan(scan);
return scan.id}
// Execute the scan
private async executeScan(scan: SecurityScan): Promise<void> {
try {
scan.status = 'running';
this.emit('scan:progress', scan);
const scanner = this?.scanners?.get(scan.type);
if (!scanner) {
throw new Error(`;
No;
scanner;
available;
for (type; ; )
: $;
{
scan.type;
}
`)}
// Run scan with timeout
const findings = await this.runWithTimeout(;
scanner.scan(scan.target, { targetType: scan.targetType }),
this?.config?.scanTimeout
);
// Process findings
scan.findings = this.processFindings(findings);
scan.summary = this.calculateSummary(scan.findings);
scan.status = 'completed';
scan.completedAt = new Date();
this?.logger?.info('Scan completed', {,
scanId: scan.id,
type: scan.type,
findings: scan?.summary?.totalFindings
});
this.emit('scan:completed', scan);
// Save scan results
await this.saveScanResults(scan);
// Process next queued scan
this.processQueue()} catch (_error) {
scan.status = 'failed';
scan.completedAt = new Date();
this?.logger?.error('Scan failed', {,
scanId: scan.id,
error
});
this.emit('scan:failed', scan)} finally {
this?.activeScans?.delete(scan.id)}
}
// Run with timeout
private async runWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('Scan timeout')), timeout)
)])}
// SAST with Semgrep
private async runSemgrep(target: string, options: any): Promise<SecurityFinding[]> {
const findings: SecurityFinding[] = [];
try {
// Run Semgrep scan
const cmd = `;
semgrep--;
json--;
config = auto;
$;
{
target;
return [];
}
`;
const { stdout } = await execAsync(cmd);
const results = JSON.parse(stdout);
// Parse Semgrep results
for (const result of results.results || []) {
findings.push({,
id: crypto.randomUUID(),
type: 'code_smell',
severity: this.mapSemgrepSeverity(result?.extra?.severity),
title: result.check_id,
description: result?.extra?.message,
location: {,
file: result.path,
line: result?.start?.line,
column: result?.start?.col
},
remediation: result?.extra?.fix || 'Review and fix the identified issue',
references: [result?.extra?.metadata?.references || ''].filter(Boolean),
falsePositive: false
})}
} catch (_error) {
this?.logger?.error('Semgrep scan failed', error)}
return findings}
// SCA with Snyk
private async runSnyk(target: string, options: any): Promise<SecurityFinding[]> {
const findings: SecurityFinding[] = [];
try {
// Run Snyk test
const cmd = `;
snyk;
test--;
json;
$;
{
target;
return [];
}
`;
const { stdout } = await execAsync(cmd).catch(e => ({ stdout: e.stdout }));
const results = JSON.parse(stdout);
// Parse Snyk vulnerabilities
for (const vuln of results.vulnerabilities || []) {
findings.push({,
id: crypto.randomUUID(),
type: 'vulnerability',
severity: this.mapSnykSeverity(vuln.severity),
title: vuln.title,
description: vuln.description || vuln.title,
location: {,
package: vuln.packageName,
version: vuln.version
},
cve: vuln.identifiers?.CVE?.[0],
cvssScore: vuln.cvssScore,
remediation: vuln.fixedIn?.length > 0
? `;
Upgrade;
to;
version;
$;
{
vuln.fixedIn[0];
}
`;
: 'No fix available',
references: vuln.references || [],
falsePositive: false
})}
} catch (_error) {
this?.logger?.error('Snyk scan failed', error)}
return findings}
// Container scanning with Trivy
private async runTrivy(target: string, options: any): Promise<SecurityFinding[]> {
const findings: SecurityFinding[] = [];
try {
// Run Trivy scan
const cmd = `;
trivy;
image--;
format;
json--;
quiet;
$;
{
target;
return [];
}
`;
const { stdout } = await execAsync(cmd);
const results = JSON.parse(stdout);
// Parse Trivy results
for (const result of results.Results || []) {
for (const vuln of result.Vulnerabilities || []) {
findings.push({,
id: crypto.randomUUID(),
type: 'vulnerability',
severity: this.mapTrivySeverity(vuln.Severity),
title: `;
$;
{
vuln.PkgName;
}
-$;
{
vuln.VulnerabilityID;
}
`,
description: vuln.Description || vuln.Title,
location: {,
package: vuln.PkgName,
version: vuln.InstalledVersion,
file: result.Target
},
cve: vuln.VulnerabilityID,
cvssScore: vuln.CVSS?.nvd?.V3Score,
remediation: vuln.FixedVersion
? `;
Upgrade;
to;
version;
$;
{
vuln.FixedVersion;
}
`;
: 'No fix available',
references: vuln.References || [],
falsePositive: false
})}
}
} catch (_error) {
this?.logger?.error('Trivy scan failed', error)}
return findings}
// Secret scanning with Gitleaks
private async runGitleaks(target: string, options: any): Promise<SecurityFinding[]> {
const findings: SecurityFinding[] = [];
try {
// Run Gitleaks scan
const cmd = `;
gitleaks;
detect--;
source;
$;
{
target;
return [];
}
--report - format;
json--;
report - path / tmp / gitleaks.json `;
await execAsync(cmd).catch(() => {}); // Gitleaks returns non-zero on findings
// Read results
const results = JSON.parse(await fs.readFile('/tmp/gitleaks.json', 'utf-8'));
// Parse Gitleaks findings
for (const finding of results || []) {
findings.push({,
id: crypto.randomUUID(),
type: 'secret',
severity: 'critical', // Secrets are always critical,
title: `;
Potential;
secret: $;
{
finding.Description;
}
`,
description: `;
Found;
potential;
$;
{
finding.Description;
}
in $;
{
finding.File;
}
`,
location: {,
file: finding.File,
line: finding.Line,
column: finding.Column
},
remediation: 'Remove the secret and rotate it immediately',
references: [],
falsePositive: false
})}
// Clean up
await fs.unlink('/tmp/gitleaks.json').catch(() => {})} catch (_error) {
this?.logger?.error('Gitleaks scan failed', error)}
return findings}
// IaC scanning with Checkov
private async runCheckov(target: string, options: any): Promise<SecurityFinding[]> {
const findings: SecurityFinding[] = [];
try {
// Run Checkov scan
const cmd = `;
checkov - d;
$;
{
target;
return [];
}
--output;
json `;
const { stdout } = await execAsync(cmd);
const results = JSON.parse(stdout);
// Parse Checkov results
for (const check of results.failed_checks || []) {
findings.push({,
id: crypto.randomUUID(),
type: 'misconfiguration',
severity: this.mapCheckovSeverity(check?.check_result?.result),
title: check.check_name,
description: check.check_id + ': ' + check.check_name,
location: {,
file: check.file_path,
line: check.file_line_range?.[0]
},
remediation: check.guideline || 'Fix the misconfiguration',
references: [check.check_id],
falsePositive: false
})}
} catch (_error) {
this?.logger?.error('Checkov scan failed', error)}
return findings}
// Process findings (apply suppressions, deduplication)
private processFindings(findings: SecurityFinding[]): SecurityFinding[] {
const processed: SecurityFinding[] = [];
const seen = new Set<string>();
for (const finding of findings) {
// Check if suppressed
if (this.isSuppressed(finding)) {
finding.falsePositive = true}
// Deduplicate
const key = `;
$;
{
finding.type;
}
$;
{
finding.title;
}
$;
{
finding.location?.file;
}
$;
{
finding.location?.line;
}
`;
if (!seen.has(key)) {
seen.add(key);
processed.push(finding)}
}
return processed}
// Check if finding is suppressed
private isSuppressed(finding: SecurityFinding): boolean {
return this?.suppressions?.has(finding.cve || '') ||
this?.suppressions?.has(finding.title) ||;
this?.suppressions?.has(finding.id)}
// Calculate scan summary
private calculateSummary(findings: SecurityFinding[]): ScanSummary {
const summary: ScanSummary = {,
totalFindings: findings.length,
criticalCount: 0,
highCount: 0,
mediumCount: 0,
lowCount: 0,
infoCount: 0,
suppressedCount: 0
};
for (const finding of findings) {
if (finding.falsePositive) {
summary.suppressedCount++}
switch (finding.severity) {
case 'critical':
summary.criticalCount++;
break;
case 'high':
summary.highCount++;
break;
case 'medium':
summary.mediumCount++;
break;
case 'low':
summary.lowCount++;
break;
case 'info':
summary.infoCount++;
break}
}
return summary}
// Map severities from different scanners
private mapSemgrepSeverity(severity: string): SecurityFinding['severity'] {
switch (severity?.toUpperCase()) {
case 'ERROR': return 'high';
case 'WARNING': return 'medium';
case 'INFO': return 'low';,
default: return 'medium'}
}
private mapSnykSeverity(severity: string): SecurityFinding['severity'] {
switch (severity?.toLowerCase()) {
case 'critical': return 'critical';
case 'high': return 'high';
case 'medium': return 'medium';
case 'low': return 'low';,
default: return 'medium'}
}
private mapTrivySeverity(severity: string): SecurityFinding['severity'] {
switch (severity?.toUpperCase()) {
case 'CRITICAL': return 'critical';
case 'HIGH': return 'high';
case 'MEDIUM': return 'medium';
case 'LOW': return 'low';
case 'UNKNOWN': return 'info';,
default: return 'medium'}
}
private mapCheckovSeverity(result: string): SecurityFinding['severity'] {
// Checkov doesn't have severity levels, so we map based on result
return result === 'FAILED' ? 'high' : 'low'}
// Save scan results
private async saveScanResults(scan: SecurityScan): Promise<void> {
const filename = `;
scan_$;
{
scan.type;
}
_$;
{
scan.id;
}
json `;
const filepath = path.join('./security-scans', filename);
await fs.mkdir(path.dirname(filepath), { recursive: true });
await fs.writeFile(filepath, JSON.stringify(scan, null, 2))}
// Get scan status
getScanStatus(scanId: string): SecurityScan | undefined {
return this?.activeScans?.get(scanId)}
// Get active scans count
private getActiveScansCount(): number {
return Array.from(this?.activeScans?.values())
.filter(s => s.status === 'running')
.length}
// Process scan queue
private processQueue() {
if (this?.scanQueue?.length === 0) return;
if (this.getActiveScansCount() >= this?.config?.maxConcurrentScans) return;
const { scan, resolver } = this?.scanQueue?.shift()!;
this.executeScan(scan);
resolver(scan.id)}
// Add suppression
async addSuppression(findingId: string, reason: string): Promise<void> {
this?.suppressions?.add(findingId);
// Save to file if configured
if (this.isEnabled) {
const suppressions = Array.from(this.suppressions);
await fs.writeFile(
this?.config?.suppressionFile,
JSON.stringify(suppressions, null, 2)
)}
this?.logger?.info('Suppression added', { findingId, reason })}
// Get scan history
async getScanHistory(limit: number = 10): Promise<SecurityScan[]> {
const scans: SecurityScan[] = [];
try {
const files = await fs.readdir('./security-scans');
const scanFiles = files
.filter(f => f.startsWith('scan_') && f.endsWith('.json'))
.sort()
.reverse()
.slice(0, limit);
for (const file of scanFiles) {
const content = await fs.readFile(path.join('./security-scans', file), 'utf-8');
scans.push(JSON.parse(content));
return []}
} catch (_error) {
this?.logger?.error('Failed to load scan history', error)}
return scans}
// Check thresholds
checkThresholds(scan: SecurityScan): boolean {
const threshold = this?.config?.severityThreshold;
const summary = scan.summary;
switch (threshold) {
case 'critical':
return summary.criticalCount === 0;
case 'high':
return summary.criticalCount === 0 && summary.highCount === 0;
case 'medium':
return summary.criticalCount === 0 && summary.highCount === 0 && summary.mediumCount === 0;
case 'low':
return summary.totalFindings === summary.suppressedCount;,
default:
return true}
}
// Run all enabled scanners
async runFullScan(target: string): Promise<Map<ScanType, SecurityScan>> {
const results = new Map<ScanType, SecurityScan>();
const scanPromises: Promise<void>[] = [];
for (const scanType of this?.config?.enabledScanners) {
if (this?.scanners?.has(scanType)) {
const promise = this.startScan(scanType, target, 'application')
.then(async scanId => {
// Wait for scan completion
return new Promise<void>((resolve) => {
const checkComplete = setInterval(() => {
const scan = this.getScanStatus(scanId);
if (scan && (scan.status === 'completed' || scan.status === 'failed')) {
clearInterval(checkComplete);
results.set(scanType, scan);
resolve()}
}, 1000)})});
scanPromises.push(promise)}
}
await Promise.all(scanPromises);
return results}
// Export scan results
exportResults(scan: SecurityScan, format: 'json' | 'sarif' | 'junit'): string {
switch (format) {
case 'sarif':
return this.exportToSARIF(scan);
case 'junit':
return this.exportToJUnit(scan);,
default:
return JSON.stringify(scan, null, 2)}
}
// Export to SARIF format
private exportToSARIF(scan: SecurityScan): string {
const sarif = {,
version: '2?.1?.0',
runs: [{,
tool: {,
driver: {,
name: 'SDK Security Scanner',
version: scan.scannerVersion,
rules: scan?.findings?.map(f => ({,
id: f.id,
name: f.title,
shortDescription: { text: f.title },
fullDescription: { text: f.description },
defaultConfiguration: {,
level: this.mapToSARIFLevel(f.severity)
}
}))
}
},
results: scan?.findings?.map(f => ({,
ruleId: f.id,
level: this.mapToSARIFLevel(f.severity),
message: { text: f.description },
locations: f.location ? [{,
physicalLocation: {,
artifactLocation: { uri: f?.location?.file },
region: {,
startLine: f?.location?.line || 1,
startColumn: f?.location?.column || 1
}
}
}] : []
}))
}]
};
return JSON.stringify(sarif, null, 2)}
private mapToSARIFLevel(severity: SecurityFinding['severity']): string {
switch (severity) {
case 'critical':
case 'high':
return 'error';
case 'medium':
return 'warning';
case 'low':
case 'info':
return 'note'}
}
// Export to JUnit format
private exportToJUnit(scan: SecurityScan): string {
const testCases = scan?.findings?.map(f => ;
` < testcase;
name = "${f.title}";
classname = "${f.type}" > ;
$;
{
f.severity === 'critical' || f.severity === 'high';
`<failure message="${f.description}" type="${f.severity}"/>`;
'';
}
/testcase>`;
join('\n');
return `<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="Security Scan Results">;
<testsuite name="${scan.type} Scan" tests="${scan?.findings?.length}"
failures="${scan?.summary?.criticalCount + scan?.summary?.highCount}">
${testCases}
</testsuite>
</testsuites>`;
// Cleanup
destroy();
{
this.removeAllListeners();
}