secmanifest 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +308 -0
- package/package.json +54 -0
- package/src/cli.ts +138 -0
- package/src/commands/audit.ts +249 -0
- package/src/commands/fix.ts +88 -0
- package/src/commands/watch.ts +40 -0
- package/src/core/fixer.ts +89 -0
- package/src/core/html-report.ts +259 -0
- package/src/core/notify.ts +153 -0
- package/src/core/package-manager.ts +85 -0
- package/src/core/project-analyzer.ts +84 -0
- package/src/core/reporter.ts +170 -0
- package/src/i18n/index.ts +256 -0
- package/src/scanners/backdoors.ts +192 -0
- package/src/scanners/binaries.ts +102 -0
- package/src/scanners/bundle-size.ts +114 -0
- package/src/scanners/duplicates.ts +116 -0
- package/src/scanners/integrity.ts +108 -0
- package/src/scanners/licenses.ts +111 -0
- package/src/scanners/lockfile-drift.ts +182 -0
- package/src/scanners/malware.ts +148 -0
- package/src/scanners/metadata.ts +148 -0
- package/src/scanners/node-version.ts +71 -0
- package/src/scanners/obfuscation.ts +151 -0
- package/src/scanners/outdated.ts +76 -0
- package/src/scanners/secrets.ts +224 -0
- package/src/scanners/socket-dev.ts +140 -0
- package/src/scanners/transitive.ts +97 -0
- package/src/scanners/vulnerabilities.ts +63 -0
- package/src/utils/cache.ts +59 -0
- package/src/utils/http.ts +134 -0
- package/src/utils/registry.ts +170 -0
- package/src/utils/types.ts +67 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
import { t } from "../i18n/index.js";
|
|
4
|
+
import type { AuditReport, Finding, Severity } from "../utils/types.js";
|
|
5
|
+
|
|
6
|
+
const SEVERITY_COLORS: Record<Severity, (text: string) => string> = {
|
|
7
|
+
critical: (t: string) => chalk.red.bold(t),
|
|
8
|
+
high: (t: string) => chalk.hex("#FF6600").bold(t),
|
|
9
|
+
medium: (t: string) => chalk.yellow(t),
|
|
10
|
+
low: (t: string) => chalk.cyan(t),
|
|
11
|
+
info: (t: string) => chalk.gray(t),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const SEVERITY_ICONS: Record<Severity, string> = {
|
|
15
|
+
critical: "[!!!]",
|
|
16
|
+
high: "[!!]",
|
|
17
|
+
medium: "[!]",
|
|
18
|
+
low: "*",
|
|
19
|
+
info: "[i]",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CATEGORY_LABELS: Record<string, string> = {
|
|
23
|
+
vulnerability: "Vuln",
|
|
24
|
+
malware: "Malware",
|
|
25
|
+
backdoor: "Backdoor",
|
|
26
|
+
drift: "Drift",
|
|
27
|
+
secrets: "Secrets",
|
|
28
|
+
license: "License",
|
|
29
|
+
metadata: "Metadata",
|
|
30
|
+
obfuscation: "Obfuscation",
|
|
31
|
+
"node-version": "Node.js",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function getScoreColor(score: number): (text: string) => string {
|
|
35
|
+
if (score >= 80) return (t: string) => chalk.red.bold(t);
|
|
36
|
+
if (score >= 60) return (t: string) => chalk.hex("#FF6600").bold(t);
|
|
37
|
+
if (score >= 40) return (t: string) => chalk.yellow(t);
|
|
38
|
+
if (score >= 20) return (t: string) => chalk.cyan(t);
|
|
39
|
+
return (t: string) => chalk.green.bold(t);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function renderBanner(): void {
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(chalk.bold.cyan(" ╔══════════════════════════════════════╗"));
|
|
45
|
+
console.log(chalk.bold.cyan(" ║") + chalk.bold.white(` ${t("report.banner")} `) + chalk.bold.cyan("║"));
|
|
46
|
+
console.log(chalk.bold.cyan(" ╚══════════════════════════════════════╝"));
|
|
47
|
+
console.log();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderSummary(report: AuditReport): void {
|
|
51
|
+
const totalCritical = report.findings.filter((f) => f.severity === "critical").length;
|
|
52
|
+
const totalHigh = report.findings.filter((f) => f.severity === "high").length;
|
|
53
|
+
const totalMedium = report.findings.filter((f) => f.severity === "medium").length;
|
|
54
|
+
const totalLow = report.findings.filter((f) => f.severity === "low").length;
|
|
55
|
+
|
|
56
|
+
console.log(chalk.bold(` ${t("report.summary")}`));
|
|
57
|
+
console.log(chalk.gray(" ─────────────────────────────────────"));
|
|
58
|
+
console.log(` ${t("report.project")} ${chalk.bold(report.projectName)}`);
|
|
59
|
+
console.log(` ${t("report.directory")} ${report.projectPath}`);
|
|
60
|
+
console.log(` ${t("report.package_manager")} ${report.packageManager.name ?? "None"} ${report.packageManager.version ? `v${report.packageManager.version}` : ""}`);
|
|
61
|
+
console.log(` ${t("report.packages")} ${chalk.bold(String(report.totalPackages))}`);
|
|
62
|
+
console.log(` ${t("report.findings")} ${chalk.bold(String(report.findings.length))}`);
|
|
63
|
+
console.log(` ${t("report.duration")} ${(report.duration / 1000).toFixed(2)}s`);
|
|
64
|
+
console.log();
|
|
65
|
+
|
|
66
|
+
if (report.findings.length === 0) {
|
|
67
|
+
console.log(chalk.green.bold(` ✓ ${t("report.no_issues")}`));
|
|
68
|
+
console.log();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(chalk.bold(` ${t("report.severity_breakdown")}`));
|
|
73
|
+
if (totalCritical > 0) console.log(` ${SEVERITY_COLORS.critical(`${SEVERITY_ICONS.critical} ${t("report.severity_critical")}: ${totalCritical}`)}`);
|
|
74
|
+
if (totalHigh > 0) console.log(` ${SEVERITY_COLORS.high(`${SEVERITY_ICONS.high} ${t("report.severity_high")}: ${totalHigh}`)}`);
|
|
75
|
+
if (totalMedium > 0) console.log(` ${SEVERITY_COLORS.medium(`${SEVERITY_ICONS.medium} ${t("report.severity_medium")}: ${totalMedium}`)}`);
|
|
76
|
+
if (totalLow > 0) console.log(` ${SEVERITY_COLORS.low(`${SEVERITY_ICONS.low} ${t("report.severity_low")}: ${totalLow}`)}`);
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderScore(score: number): void {
|
|
81
|
+
const colorFn = getScoreColor(score);
|
|
82
|
+
const barLength = 30;
|
|
83
|
+
const filledLength = Math.round((score / 100) * barLength);
|
|
84
|
+
const bar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);
|
|
85
|
+
|
|
86
|
+
console.log(chalk.bold(` ${t("report.score")}`));
|
|
87
|
+
console.log(` [${colorFn(bar)}] ${colorFn(String(score))}/100`);
|
|
88
|
+
console.log();
|
|
89
|
+
|
|
90
|
+
if (score >= 80) console.log(chalk.red.bold(` ⚠ ${t("report.risk_high")}`));
|
|
91
|
+
else if (score >= 60) console.log(chalk.hex("#FF6600").bold(` ⚠ ${t("report.risk_significant")}`));
|
|
92
|
+
else if (score >= 40) console.log(chalk.yellow(` ℹ ${t("report.risk_moderate")}`));
|
|
93
|
+
else if (score >= 20) console.log(chalk.cyan(` ℹ ${t("report.risk_low")}`));
|
|
94
|
+
else console.log(chalk.green.bold(` ✓ ${t("report.risk_good")}`));
|
|
95
|
+
console.log();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function renderFindingsTable(findings: Finding[]): void {
|
|
99
|
+
if (findings.length === 0) return;
|
|
100
|
+
|
|
101
|
+
console.log(chalk.bold(` ${t("report.detailed_findings")}`));
|
|
102
|
+
console.log();
|
|
103
|
+
|
|
104
|
+
const sorted = [...findings].sort((a, b) => {
|
|
105
|
+
const order: Record<Severity, number> = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
106
|
+
return order[a.severity] - order[b.severity];
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const table = new Table({
|
|
110
|
+
head: [chalk.bold("Severity"), chalk.bold("Category"), chalk.bold("Title"), chalk.bold("Package")],
|
|
111
|
+
colWidths: [14, 14, 45, 30],
|
|
112
|
+
style: { head: [], border: [] },
|
|
113
|
+
wordWrap: true,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
for (const finding of sorted) {
|
|
117
|
+
const colorFn = SEVERITY_COLORS[finding.severity];
|
|
118
|
+
table.push([
|
|
119
|
+
colorFn(`${SEVERITY_ICONS[finding.severity]} ${finding.severity}`),
|
|
120
|
+
CATEGORY_LABELS[finding.category] ?? finding.category,
|
|
121
|
+
finding.title,
|
|
122
|
+
finding.package ?? "-",
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(table.toString());
|
|
127
|
+
console.log();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function renderDetails(findings: Finding[]): void {
|
|
131
|
+
const detailed = findings.filter((f) => f.severity === "critical" || f.severity === "high");
|
|
132
|
+
if (detailed.length === 0) return;
|
|
133
|
+
|
|
134
|
+
console.log(chalk.bold(` ${t("report.critical_high_details")}`));
|
|
135
|
+
console.log(chalk.gray(" ─────────────────────────────────────"));
|
|
136
|
+
|
|
137
|
+
for (const finding of detailed) {
|
|
138
|
+
const colorFn = SEVERITY_COLORS[finding.severity];
|
|
139
|
+
console.log();
|
|
140
|
+
console.log(colorFn(` [${finding.severity.toUpperCase()}]`) + ` ${chalk.bold(finding.title)}`);
|
|
141
|
+
console.log(` ${finding.description}`);
|
|
142
|
+
if (finding.package) console.log(` Package: ${chalk.cyan(finding.package)}${finding.version ? `@${finding.version}` : ""}`);
|
|
143
|
+
if (finding.cve) console.log(` CVE: ${chalk.yellow(finding.cve)}`);
|
|
144
|
+
if (finding.recommendation) console.log(` Action: ${chalk.green(finding.recommendation)}`);
|
|
145
|
+
}
|
|
146
|
+
console.log();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function renderReport(report: AuditReport): void {
|
|
150
|
+
renderBanner();
|
|
151
|
+
renderSummary(report);
|
|
152
|
+
renderScore(report.score);
|
|
153
|
+
renderFindingsTable(report.findings);
|
|
154
|
+
renderDetails(report.findings);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function calculateScore(findings: Finding[]): number {
|
|
158
|
+
if (findings.length === 0) return 0;
|
|
159
|
+
let score = 0;
|
|
160
|
+
for (const finding of findings) {
|
|
161
|
+
switch (finding.severity) {
|
|
162
|
+
case "critical": score += 25; break;
|
|
163
|
+
case "high": score += 15; break;
|
|
164
|
+
case "medium": score += 8; break;
|
|
165
|
+
case "low": score += 3; break;
|
|
166
|
+
case "info": score += 1; break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return Math.min(100, score);
|
|
170
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
export type Locale = "en" | "es";
|
|
2
|
+
|
|
3
|
+
const translations: Record<Locale, Record<string, string>> = {
|
|
4
|
+
en: {
|
|
5
|
+
// CLI
|
|
6
|
+
"cli.name": "secmanifest",
|
|
7
|
+
"cli.description": "Deep security auditing tool for JavaScript/TypeScript projects",
|
|
8
|
+
|
|
9
|
+
// Audit
|
|
10
|
+
"audit.detecting_pm": "Detecting package manager...",
|
|
11
|
+
"audit.veto_npm": "[VETO] npm is blocked for security reasons.",
|
|
12
|
+
"audit.veto_npm_reason": "npm has a history of vulnerabilities in dependency script execution.",
|
|
13
|
+
"audit.veto_npm_required": "pnpm, bun, or yarn are required.",
|
|
14
|
+
"audit.no_pm_warning": "[WARNING] No secure package manager detected.",
|
|
15
|
+
"audit.no_pm_reason": "It is recommended to install pnpm, bun, or yarn for better security.",
|
|
16
|
+
"audit.continue_anyway": "Do you want to continue anyway?",
|
|
17
|
+
"audit.cancelled": "Operation cancelled by user.",
|
|
18
|
+
"audit.pm_detected": "Package manager detected:",
|
|
19
|
+
"audit.analyzing": "Analyzing project...",
|
|
20
|
+
"audit.project": "Project:",
|
|
21
|
+
"audit.packages_found": "Packages found:",
|
|
22
|
+
"audit.scanners_running": "Running security scanners...",
|
|
23
|
+
"audit.errors_during_scan": "Errors during scan:",
|
|
24
|
+
|
|
25
|
+
// Scanners
|
|
26
|
+
"scanner.malware": "Malware & typosquatting",
|
|
27
|
+
"scanner.backdoors": "Backdoors in manifests",
|
|
28
|
+
"scanner.drift": "Lockfile drift",
|
|
29
|
+
"scanner.secrets": "Secrets & credentials",
|
|
30
|
+
"scanner.licenses": "Restricted licenses",
|
|
31
|
+
"scanner.metadata": "Suspicious metadata",
|
|
32
|
+
"scanner.obfuscation": "Obfuscated code",
|
|
33
|
+
"scanner.node_version": "Node.js EOL",
|
|
34
|
+
"scanner.socket_dev": "Socket.dev (supply chain)",
|
|
35
|
+
"scanner.transitive": "Transitive dependencies",
|
|
36
|
+
"scanner.binaries": "Suspicious binaries",
|
|
37
|
+
"scanner.duplicates": "Duplicate dependencies",
|
|
38
|
+
"scanner.bundle_size": "Bundle size",
|
|
39
|
+
"scanner.integrity": "Package integrity",
|
|
40
|
+
"scanner.sonatype": "Sonatype OSS Index (vulnerabilities)",
|
|
41
|
+
"scanner.outdated": "Outdated packages",
|
|
42
|
+
|
|
43
|
+
// Reporter
|
|
44
|
+
"report.banner": "SECMANIFEST - Security Audit",
|
|
45
|
+
"report.summary": "Project Summary",
|
|
46
|
+
"report.project": "Project:",
|
|
47
|
+
"report.directory": "Directory:",
|
|
48
|
+
"report.package_manager": "Package Manager:",
|
|
49
|
+
"report.packages": "Packages:",
|
|
50
|
+
"report.findings": "Findings:",
|
|
51
|
+
"report.duration": "Duration:",
|
|
52
|
+
"report.severity_breakdown": "Severity Breakdown:",
|
|
53
|
+
"report.severity_critical": "Critical",
|
|
54
|
+
"report.severity_high": "High",
|
|
55
|
+
"report.severity_medium": "Medium",
|
|
56
|
+
"report.severity_low": "Low",
|
|
57
|
+
"report.score": "Risk Score:",
|
|
58
|
+
"report.no_issues": "No security issues found.",
|
|
59
|
+
"report.risk_high": "HIGH RISK PROJECT - Immediate action recommended",
|
|
60
|
+
"report.risk_significant": "SIGNIFICANT RISKS - Review findings",
|
|
61
|
+
"report.risk_moderate": "MODERATE RISKS - Review recommended",
|
|
62
|
+
"report.risk_low": "LOW RISKS - Preventive maintenance",
|
|
63
|
+
"report.risk_good": "GOOD STATUS - No critical issues",
|
|
64
|
+
"report.detailed_findings": "Detailed Findings:",
|
|
65
|
+
"report.critical_high_details": "Critical/High Finding Details:",
|
|
66
|
+
|
|
67
|
+
// Fix
|
|
68
|
+
"fix.searching_safe": "Searching safe version for",
|
|
69
|
+
"fix.unsafe_version": "Unsafe version:",
|
|
70
|
+
"fix.safe_version": "Safe version:",
|
|
71
|
+
"fix.uninstalling": "Uninstalling",
|
|
72
|
+
"fix.installing": "Installing",
|
|
73
|
+
"fix.installed_successfully": "installed successfully",
|
|
74
|
+
"fix.install_error": "Error installing",
|
|
75
|
+
"fix.restoring": "Restoring original version...",
|
|
76
|
+
"fix.results": "Auto-Fix Results:",
|
|
77
|
+
"fix.packages_fixed": "package(s) fixed:",
|
|
78
|
+
"fix.packages_failed": "package(s) failed:",
|
|
79
|
+
"fix.no_packages": "No packages need fixing.",
|
|
80
|
+
"fix.packages_to_fix": "Packages to fix:",
|
|
81
|
+
"fix.analyzing_packages": "Analyzing packages for auto-fix...",
|
|
82
|
+
"fix.dry_run": "[DRY RUN] No changes made. Run without --dry-run to apply.",
|
|
83
|
+
"fix.run_audit": "Run \"secmanifest audit\" to verify.",
|
|
84
|
+
|
|
85
|
+
// Watch
|
|
86
|
+
"watch.title": "SecManifest Watch Mode",
|
|
87
|
+
"watch.directory": "Directory:",
|
|
88
|
+
"watch.interval": "Interval:",
|
|
89
|
+
"watch.stopping": "Watch mode stopped.",
|
|
90
|
+
"watch.next_scan": "Next scan in",
|
|
91
|
+
"watch.press_ctrlc": "Press Ctrl+C to stop",
|
|
92
|
+
|
|
93
|
+
// Config
|
|
94
|
+
"config.saved": "Configuration saved.",
|
|
95
|
+
"config.cache_cleared": "Cache cleared.",
|
|
96
|
+
|
|
97
|
+
// HTML Report
|
|
98
|
+
"html.title": "SecManifest Security Audit",
|
|
99
|
+
"html.packages": "Packages",
|
|
100
|
+
"html.critical": "Critical",
|
|
101
|
+
"html.high": "High",
|
|
102
|
+
"html.medium": "Medium",
|
|
103
|
+
"html.low": "Low",
|
|
104
|
+
"html.risk_score": "Risk Score",
|
|
105
|
+
"html.findings": "Findings",
|
|
106
|
+
"html.no_findings": "No security issues found.",
|
|
107
|
+
"html.generated_by": "Generated by SecManifest",
|
|
108
|
+
|
|
109
|
+
// Errors
|
|
110
|
+
"error.dir_not_found": "Directory not found:",
|
|
111
|
+
"error.no_package_json": "No package.json found in:",
|
|
112
|
+
"error.analyze_failed": "Error analyzing project:",
|
|
113
|
+
"error.fatal": "Fatal error:",
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
es: {
|
|
117
|
+
// CLI
|
|
118
|
+
"cli.name": "secmanifest",
|
|
119
|
+
"cli.description": "Herramienta de auditoria de seguridad profunda para proyectos JavaScript/TypeScript",
|
|
120
|
+
|
|
121
|
+
// Audit
|
|
122
|
+
"audit.detecting_pm": "Detectando gestor de paquetes...",
|
|
123
|
+
"audit.veto_npm": "[VETO] npm esta bloqueado por razones de seguridad.",
|
|
124
|
+
"audit.veto_npm_reason": "npm tiene historial de vulnerabilidades en la ejecucion de scripts de dependencias.",
|
|
125
|
+
"audit.veto_npm_required": "Se requiere pnpm, bun o yarn.",
|
|
126
|
+
"audit.no_pm_warning": "[AVISO] No se detecto ningún gestor de paquetes seguro.",
|
|
127
|
+
"audit.no_pm_reason": "Se recomienda instalar pnpm, bun o yarn para mejor seguridad.",
|
|
128
|
+
"audit.continue_anyway": "Desea continuar de todos modos?",
|
|
129
|
+
"audit.cancelled": "Operacion cancelada por el usuario.",
|
|
130
|
+
"audit.pm_detected": "Gestor detectado:",
|
|
131
|
+
"audit.analyzing": "Analizando proyecto...",
|
|
132
|
+
"audit.project": "Proyecto:",
|
|
133
|
+
"audit.packages_found": "Paquetes encontrados:",
|
|
134
|
+
"audit.scanners_running": "Ejecutando escaners de seguridad...",
|
|
135
|
+
"audit.errors_during_scan": "Errores durante el escaneo:",
|
|
136
|
+
|
|
137
|
+
// Scanners
|
|
138
|
+
"scanner.malware": "Malware y typosquatting",
|
|
139
|
+
"scanner.backdoors": "Backdoors en manifiestos",
|
|
140
|
+
"scanner.drift": "Drift en lockfile",
|
|
141
|
+
"scanner.secrets": "Secrets y credenciales",
|
|
142
|
+
"scanner.licenses": "Licencias restrictivas",
|
|
143
|
+
"scanner.metadata": "Metadatos sospechosos",
|
|
144
|
+
"scanner.obfuscation": "Codigo ofuscado",
|
|
145
|
+
"scanner.node_version": "Node.js EOL",
|
|
146
|
+
"scanner.socket_dev": "Socket.dev (supply chain)",
|
|
147
|
+
"scanner.transitive": "Dependencias transitivas",
|
|
148
|
+
"scanner.binaries": "Binarios sospechosos",
|
|
149
|
+
"scanner.duplicates": "Dependencias duplicadas",
|
|
150
|
+
"scanner.bundle_size": "Bundle size",
|
|
151
|
+
"scanner.integrity": "Integridad de paquetes",
|
|
152
|
+
"scanner.sonatype": "Sonatype OSS Index (vulnerabilidades)",
|
|
153
|
+
"scanner.outdated": "Paquetes desactualizados",
|
|
154
|
+
|
|
155
|
+
// Reporter
|
|
156
|
+
"report.banner": "SECMANIFEST - Auditoria de Seguridad",
|
|
157
|
+
"report.summary": "Resumen del Proyecto",
|
|
158
|
+
"report.project": "Proyecto:",
|
|
159
|
+
"report.directory": "Directorio:",
|
|
160
|
+
"report.package_manager": "Gestor de Paquetes:",
|
|
161
|
+
"report.packages": "Paquetes:",
|
|
162
|
+
"report.findings": "Hallazgos:",
|
|
163
|
+
"report.duration": "Tiempo:",
|
|
164
|
+
"report.severity_breakdown": "Desglose por Severidad:",
|
|
165
|
+
"report.severity_critical": "Critico",
|
|
166
|
+
"report.severity_high": "Alto",
|
|
167
|
+
"report.severity_medium": "Medio",
|
|
168
|
+
"report.severity_low": "Bajo",
|
|
169
|
+
"report.score": "Score de Riesgo:",
|
|
170
|
+
"report.no_issues": "No se encontraron problemas de seguridad.",
|
|
171
|
+
"report.risk_high": "PROYECTO DE ALTO RIESGO - Se recomienda accion inmediata",
|
|
172
|
+
"report.risk_significant": "PROYECTO CON RIESGOS SIGNIFICATIVOS - Revisar hallazgos",
|
|
173
|
+
"report.risk_moderate": "PROYECTO CON RIESGOS MODERADOS - Revision recomendada",
|
|
174
|
+
"report.risk_low": "PROYECTO CON RIESGOS BAJOS - Mantenimiento preventivo",
|
|
175
|
+
"report.risk_good": "PROYECTO EN BUEN ESTADO - Sin problemas criticos",
|
|
176
|
+
"report.detailed_findings": "Hallazgos Detallados:",
|
|
177
|
+
"report.critical_high_details": "Detalles de Hallazgos Criticos/Altos:",
|
|
178
|
+
|
|
179
|
+
// Fix
|
|
180
|
+
"fix.searching_safe": "Buscando version segura para",
|
|
181
|
+
"fix.unsafe_version": "Version insegura:",
|
|
182
|
+
"fix.safe_version": "Version segura:",
|
|
183
|
+
"fix.uninstalling": "Desinstalando",
|
|
184
|
+
"fix.installing": "Instalando",
|
|
185
|
+
"fix.installed_successfully": "instalado exitosamente",
|
|
186
|
+
"fix.install_error": "Error al instalar",
|
|
187
|
+
"fix.restoring": "Intentando restaurar version original...",
|
|
188
|
+
"fix.results": "Resultados del Auto-Fix:",
|
|
189
|
+
"fix.packages_fixed": "paquete(s) arreglado(s):",
|
|
190
|
+
"fix.packages_failed": "paquete(s) fallaron:",
|
|
191
|
+
"fix.no_packages": "No se encontraron paquetes que necesiten arreglo.",
|
|
192
|
+
"fix.packages_to_fix": "Paquetes a arreglar:",
|
|
193
|
+
"fix.analyzing_packages": "Analizando paquetes para auto-fix...",
|
|
194
|
+
"fix.dry_run": "[DRY RUN] No se realizo ningun cambio. Usa sin --dry-run para aplicar.",
|
|
195
|
+
"fix.run_audit": "Ejecuta \"secmanifest audit\" para verificar.",
|
|
196
|
+
|
|
197
|
+
// Watch
|
|
198
|
+
"watch.title": "Modo Watch de SecManifest",
|
|
199
|
+
"watch.directory": "Directorio:",
|
|
200
|
+
"watch.interval": "Intervalo:",
|
|
201
|
+
"watch.stopping": "Watch mode detenido.",
|
|
202
|
+
"watch.next_scan": "Proximo scan en",
|
|
203
|
+
"watch.press_ctrlc": "Presiona Ctrl+C para detener",
|
|
204
|
+
|
|
205
|
+
// Config
|
|
206
|
+
"config.saved": "Configuracion guardada.",
|
|
207
|
+
"config.cache_cleared": "Cache limpiado.",
|
|
208
|
+
|
|
209
|
+
// HTML Report
|
|
210
|
+
"html.title": "Auditoria de Seguridad SecManifest",
|
|
211
|
+
"html.packages": "Paquetes",
|
|
212
|
+
"html.critical": "Criticos",
|
|
213
|
+
"html.high": "Altos",
|
|
214
|
+
"html.medium": "Medios",
|
|
215
|
+
"html.low": "Bajos",
|
|
216
|
+
"html.risk_score": "Score de Riesgo",
|
|
217
|
+
"html.findings": "Hallazgos",
|
|
218
|
+
"html.no_findings": "No se encontraron problemas de seguridad.",
|
|
219
|
+
"html.generated_by": "Generado por SecManifest",
|
|
220
|
+
|
|
221
|
+
// Errors
|
|
222
|
+
"error.dir_not_found": "Directorio no encontrado:",
|
|
223
|
+
"error.no_package_json": "No se encontro package.json en:",
|
|
224
|
+
"error.analyze_failed": "Error al analizar proyecto:",
|
|
225
|
+
"error.fatal": "Error fatal:",
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
let currentLocale: Locale = "en";
|
|
230
|
+
|
|
231
|
+
function detectSystemLocale(): Locale {
|
|
232
|
+
const env = process.env.LC_ALL ?? process.env.LC_MESSAGES ?? process.env.LANG ?? process.env.LANGUAGE ?? "";
|
|
233
|
+
const lang = env.toLowerCase().split(".")[0]?.split("-")[0] ?? "";
|
|
234
|
+
if (lang === "es" || lang.startsWith("es")) return "es";
|
|
235
|
+
return "en";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function setLocale(locale: Locale): void {
|
|
239
|
+
currentLocale = locale;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function getLocale(): Locale {
|
|
243
|
+
return currentLocale;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function t(key: string): string {
|
|
247
|
+
return translations[currentLocale]?.[key] ?? translations.en[key] ?? key;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function initI18n(preferredLocale?: string): void {
|
|
251
|
+
if (preferredLocale === "es" || preferredLocale === "en") {
|
|
252
|
+
currentLocale = preferredLocale;
|
|
253
|
+
} else {
|
|
254
|
+
currentLocale = detectSystemLocale();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { Finding, ScanResult, ProjectInfo } from "../utils/types.js";
|
|
2
|
+
|
|
3
|
+
const DANGEROUS_SCRIPTS = [
|
|
4
|
+
"preinstall",
|
|
5
|
+
"postinstall",
|
|
6
|
+
"install",
|
|
7
|
+
"prepare",
|
|
8
|
+
"prepublish",
|
|
9
|
+
"preuninstall",
|
|
10
|
+
"postuninstall",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const SUSPICIOUS_SCRIPT_PATTERNS = [
|
|
14
|
+
/curl\s+.*\|\s*(?:bash|sh|node)/i,
|
|
15
|
+
/wget\s+.*\|\s*(?:bash|sh|node)/i,
|
|
16
|
+
/eval\s*\(/i,
|
|
17
|
+
/exec\s*\(/i,
|
|
18
|
+
/require\s*\(\s*['"]child_process['"]\s*\)/i,
|
|
19
|
+
/spawn\s*\(/i,
|
|
20
|
+
/execSync\s*\(/i,
|
|
21
|
+
/process\.exit\s*\(/i,
|
|
22
|
+
/fs\.writeFile/i,
|
|
23
|
+
/fs\.unlink/i,
|
|
24
|
+
/rimraf\s/i,
|
|
25
|
+
/rm\s+-rf?\s/i,
|
|
26
|
+
/chmod\s+777/i,
|
|
27
|
+
/\/etc\/passwd/i,
|
|
28
|
+
/\/etc\/shadow/i,
|
|
29
|
+
/base64\s+(?:--decode|[-d])/i,
|
|
30
|
+
/atob\s*\(/i,
|
|
31
|
+
/fromCharCode/i,
|
|
32
|
+
/\\x[0-9a-f]{2}/i,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const GIT_DEP_PATTERN = /^git(?:hub|lab)?[:/]/;
|
|
36
|
+
const GIT_URL_PATTERN = /^https?:\/\/.*\.git$/;
|
|
37
|
+
|
|
38
|
+
const OPEN_VERSION_RANGES = ["*", ">=0.0.0", ">=0"];
|
|
39
|
+
const LATEST_VERSION = "latest";
|
|
40
|
+
|
|
41
|
+
export function scanBackdoors(projectInfo: ProjectInfo): ScanResult {
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
const findings: Finding[] = [];
|
|
44
|
+
const errors: Error[] = [];
|
|
45
|
+
|
|
46
|
+
analyzeSuspiciousScripts(projectInfo, findings);
|
|
47
|
+
|
|
48
|
+
analyzeGitDependencies(projectInfo, findings);
|
|
49
|
+
|
|
50
|
+
analyzeVersionRanges(projectInfo, findings);
|
|
51
|
+
|
|
52
|
+
analyzeLifecycleHooks(projectInfo, findings);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
findings,
|
|
56
|
+
errors,
|
|
57
|
+
duration: Date.now() - startTime,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function analyzeSuspiciousScripts(
|
|
62
|
+
projectInfo: ProjectInfo,
|
|
63
|
+
findings: Finding[],
|
|
64
|
+
): void {
|
|
65
|
+
for (const [scriptName, scriptValue] of Object.entries(
|
|
66
|
+
projectInfo.scripts,
|
|
67
|
+
)) {
|
|
68
|
+
for (const pattern of SUSPICIOUS_SCRIPT_PATTERNS) {
|
|
69
|
+
if (pattern.test(scriptValue)) {
|
|
70
|
+
findings.push({
|
|
71
|
+
id: `backdoor-script-${scriptName}-${pattern.source.slice(0, 20)}`,
|
|
72
|
+
severity:
|
|
73
|
+
DANGEROUS_SCRIPTS.includes(scriptName) ? "critical" : "high",
|
|
74
|
+
category: "backdoor",
|
|
75
|
+
title: `Script sospechoso en "${scriptName}"`,
|
|
76
|
+
description: `El script "${scriptName}" contiene patron potencialmente malicioso: ${scriptValue.slice(0, 100)}`,
|
|
77
|
+
recommendation: `Revisar manualmente el script "${scriptName}" en package.json`,
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function analyzeGitDependencies(
|
|
86
|
+
projectInfo: ProjectInfo,
|
|
87
|
+
findings: Finding[],
|
|
88
|
+
): void {
|
|
89
|
+
const allDeps = {
|
|
90
|
+
...projectInfo.dependencies,
|
|
91
|
+
...projectInfo.devDependencies,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
for (const [name, version] of Object.entries(allDeps)) {
|
|
95
|
+
if (GIT_DEP_PATTERN.test(version) || GIT_URL_PATTERN.test(version)) {
|
|
96
|
+
findings.push({
|
|
97
|
+
id: `backdoor-git-dep-${name}`,
|
|
98
|
+
severity: "high",
|
|
99
|
+
category: "backdoor",
|
|
100
|
+
title: `Dependencia Git no verificada: ${name}`,
|
|
101
|
+
description: `${name} apunta a un repositorio Git externo (${version}). Las dependencias Git pueden cambiar sin previo aviso.`,
|
|
102
|
+
package: name,
|
|
103
|
+
version,
|
|
104
|
+
recommendation: `Fijar a una version especifica (tag o commit hash) o usar un paquete del registry.`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function analyzeVersionRanges(
|
|
111
|
+
projectInfo: ProjectInfo,
|
|
112
|
+
findings: Finding[],
|
|
113
|
+
): void {
|
|
114
|
+
const allDeps = {
|
|
115
|
+
...projectInfo.dependencies,
|
|
116
|
+
...projectInfo.devDependencies,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
for (const [name, version] of Object.entries(allDeps)) {
|
|
120
|
+
if (version === LATEST_VERSION) {
|
|
121
|
+
findings.push({
|
|
122
|
+
id: `backdoor-latest-version-${name}`,
|
|
123
|
+
severity: "high",
|
|
124
|
+
category: "backdoor",
|
|
125
|
+
title: `Dependencia con "latest": ${name}`,
|
|
126
|
+
description: `${name} usa "latest" como version. Esto es un vector de ataque de cadena de suministro - un paquete malicioso podria ser publicado como "latest" y ser instalado automaticamente.`,
|
|
127
|
+
package: name,
|
|
128
|
+
version,
|
|
129
|
+
recommendation: `Fijar a una version especifica (ej: ^1.2.3). Puedes usar "secmanifest fix" para arreglarlo automaticamente.`,
|
|
130
|
+
});
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (OPEN_VERSION_RANGES.includes(version)) {
|
|
135
|
+
findings.push({
|
|
136
|
+
id: `backdoor-open-version-${name}`,
|
|
137
|
+
severity: "medium",
|
|
138
|
+
category: "backdoor",
|
|
139
|
+
title: `Rango de version abierto: ${name}`,
|
|
140
|
+
description: `${name} usa el rango "${version}" que permite cualquier version, incluyendo potencialmente versiones comprometidas.`,
|
|
141
|
+
package: name,
|
|
142
|
+
version,
|
|
143
|
+
recommendation: `Fijar a un rango especifico (ej: ^1.2.3) o version exacta (1.2.3).`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function analyzeLifecycleHooks(
|
|
150
|
+
projectInfo: ProjectInfo,
|
|
151
|
+
findings: Finding[],
|
|
152
|
+
): void {
|
|
153
|
+
const dangerousHooks = DANGEROUS_SCRIPTS.filter(
|
|
154
|
+
(hook) => hook in projectInfo.scripts,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
for (const hook of dangerousHooks) {
|
|
158
|
+
const scriptValue = projectInfo.scripts[hook];
|
|
159
|
+
if (!scriptValue) continue;
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
scriptValue.includes("&&") ||
|
|
163
|
+
scriptValue.includes(";") ||
|
|
164
|
+
scriptValue.includes("|")
|
|
165
|
+
) {
|
|
166
|
+
findings.push({
|
|
167
|
+
id: `backdoor-complex-hook-${hook}`,
|
|
168
|
+
severity: "high",
|
|
169
|
+
category: "backdoor",
|
|
170
|
+
title: `Hook de ciclo de vida complejo: ${hook}`,
|
|
171
|
+
description: `El hook "${hook}" contiene multiples comandos encadenados: ${scriptValue.slice(0, 100)}. Esto puede usarse para ejecutar codigo malicioso.`,
|
|
172
|
+
recommendation: `Auditar y simplificar el hook "${hook}". Ejecutar solo comandos necesarios.`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
scriptValue.includes("node ") ||
|
|
178
|
+
scriptValue.includes("npx ") ||
|
|
179
|
+
scriptValue.includes("bun ")
|
|
180
|
+
) {
|
|
181
|
+
findings.push({
|
|
182
|
+
id: `backdoor-exec-hook-${hook}`,
|
|
183
|
+
severity: "medium",
|
|
184
|
+
category: "backdoor",
|
|
185
|
+
title: `Hook ejecuta scripts: ${hook}`,
|
|
186
|
+
description: `El hook "${hook}" ejecuta un script o binario: ${scriptValue.slice(0, 100)}`,
|
|
187
|
+
package: hook,
|
|
188
|
+
recommendation: `Verificar que el script ejecutado es seguro y necesario.`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|