@deepv-code/safe-npm 0.1.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/README.zh-CN.md +121 -0
  4. package/dist/cli/args-parser.d.ts +11 -0
  5. package/dist/cli/args-parser.js +36 -0
  6. package/dist/cli/check.d.ts +5 -0
  7. package/dist/cli/check.js +126 -0
  8. package/dist/cli/proxy.d.ts +1 -0
  9. package/dist/cli/proxy.js +4 -0
  10. package/dist/data/popular-packages.d.ts +9 -0
  11. package/dist/data/popular-packages.js +83 -0
  12. package/dist/i18n/en.d.ts +43 -0
  13. package/dist/i18n/en.js +50 -0
  14. package/dist/i18n/index.d.ts +5 -0
  15. package/dist/i18n/index.js +11 -0
  16. package/dist/i18n/zh.d.ts +2 -0
  17. package/dist/i18n/zh.js +50 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +77 -0
  20. package/dist/scanner/code-analyzer.d.ts +2 -0
  21. package/dist/scanner/code-analyzer.js +130 -0
  22. package/dist/scanner/index.d.ts +3 -0
  23. package/dist/scanner/index.js +163 -0
  24. package/dist/scanner/patterns/exfiltration.d.ts +7 -0
  25. package/dist/scanner/patterns/exfiltration.js +49 -0
  26. package/dist/scanner/patterns/miner.d.ts +5 -0
  27. package/dist/scanner/patterns/miner.js +32 -0
  28. package/dist/scanner/patterns/obfuscation.d.ts +15 -0
  29. package/dist/scanner/patterns/obfuscation.js +110 -0
  30. package/dist/scanner/types.d.ts +26 -0
  31. package/dist/scanner/types.js +1 -0
  32. package/dist/scanner/typosquatting.d.ts +3 -0
  33. package/dist/scanner/typosquatting.js +126 -0
  34. package/dist/scanner/virustotal.d.ts +7 -0
  35. package/dist/scanner/virustotal.js +249 -0
  36. package/dist/scanner/vulnerability.d.ts +2 -0
  37. package/dist/scanner/vulnerability.js +42 -0
  38. package/dist/tui/App.d.ts +2 -0
  39. package/dist/tui/App.js +67 -0
  40. package/dist/tui/index.d.ts +1 -0
  41. package/dist/tui/index.js +6 -0
  42. package/dist/tui/screens/CheckScreen.d.ts +7 -0
  43. package/dist/tui/screens/CheckScreen.js +92 -0
  44. package/dist/tui/screens/PopularScreen.d.ts +7 -0
  45. package/dist/tui/screens/PopularScreen.js +39 -0
  46. package/dist/tui/screens/SettingsScreen.d.ts +6 -0
  47. package/dist/tui/screens/SettingsScreen.js +64 -0
  48. package/dist/utils/config.d.ts +16 -0
  49. package/dist/utils/config.js +69 -0
  50. package/dist/utils/npm-package.d.ts +38 -0
  51. package/dist/utils/npm-package.js +191 -0
  52. package/dist/utils/npm-runner.d.ts +7 -0
  53. package/dist/utils/npm-runner.js +56 -0
  54. package/package.json +48 -0
@@ -0,0 +1,50 @@
1
+ export const zh = {
2
+ // General
3
+ appName: 'safe-npm',
4
+ version: '版本',
5
+ // Risk levels
6
+ riskFatal: '致命',
7
+ riskHigh: '高危',
8
+ riskWarning: '警告',
9
+ riskSafe: '安全',
10
+ // Detection messages
11
+ virusDetected: '检测到已知恶意软件',
12
+ typosquatDetected: '检测到仿冒包 - 这可能是假冒包',
13
+ suspiciousCode: '检测到可疑代码模式',
14
+ cveFound: '发现已知漏洞',
15
+ minerDetected: '检测到挖矿脚本特征',
16
+ obfuscatedCode: '检测到代码混淆 - 可能隐藏恶意行为',
17
+ dangerousScript: '检测到危险的安装脚本',
18
+ blacklisted: '此包在已知恶意包名单中',
19
+ // Actions
20
+ blocked: '已阻止安装',
21
+ useForce: '如确认安全,可使用 --force 强制安装',
22
+ cannotBypass: '此威胁无法绕过',
23
+ continuing: '继续安装...',
24
+ // Scanner
25
+ scanning: '正在扫描包',
26
+ scanComplete: '扫描完成',
27
+ downloadingPackage: '正在下载包进行分析',
28
+ analyzingCode: '正在分析代码模式',
29
+ checkingVirustotal: '正在检查 VirusTotal 数据库',
30
+ checkingBlacklist: '正在检查已知恶意包名单',
31
+ // Errors
32
+ networkError: '网络错误 - 回退到本地缓存',
33
+ offlineMode: '离线模式运行中',
34
+ vtNoApiKey: '未配置 VirusTotal API 密钥 - 跳过在线扫描',
35
+ // TUI
36
+ tuiWelcome: '欢迎使用 safe-npm',
37
+ tuiPopular: '热门包',
38
+ tuiCheck: '检测包',
39
+ tuiSettings: '设置',
40
+ tuiQuit: '退出',
41
+ tuiLanguage: '语言',
42
+ // Suggestions
43
+ suggestionTitle: '💡 建议:您是否想安装官方正版包',
44
+ suggestionPrompt: '您是否要安装',
45
+ installingCorrect: '🚀 正在安装正确的包',
46
+ packageNotFound: '在注册表中未找到此包',
47
+ didYouMean: '您是指',
48
+ checkName: '请检查包名',
49
+ registryCheckFail: '包不存在',
50
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from './cli/args-parser.js';
3
+ import { proxyToNpm } from './cli/proxy.js';
4
+ import { checkPackages } from './cli/check.js';
5
+ import { startTui } from './tui/index.js';
6
+ import { hasConfigFile, saveConfig } from './utils/config.js';
7
+ import * as readline from 'readline';
8
+ async function promptLanguage() {
9
+ return new Promise((resolve) => {
10
+ const rl = readline.createInterface({
11
+ input: process.stdin,
12
+ output: process.stdout,
13
+ });
14
+ console.log('\nWelcome to safe-npm! / 欢迎使用 safe-npm!');
15
+ console.log('Please select your language / 请选择语言:');
16
+ console.log('1. English');
17
+ console.log('2. 中文 (Chinese)');
18
+ rl.question('Select [1/2] (default: 2): ', (answer) => {
19
+ if (answer.trim() === '1') {
20
+ saveConfig({ language: 'en' });
21
+ console.log('Language set to English.\n');
22
+ }
23
+ else {
24
+ // Default to Chinese as per request context or generic 'other'
25
+ saveConfig({ language: 'zh' });
26
+ console.log('语言已设置为中文。\n');
27
+ }
28
+ rl.close();
29
+ resolve();
30
+ });
31
+ });
32
+ }
33
+ async function main() {
34
+ const args = process.argv.slice(2);
35
+ // First run check
36
+ if (!hasConfigFile()) {
37
+ await promptLanguage();
38
+ }
39
+ if (args.length === 0) {
40
+ // Show help or start TUI
41
+ console.log('safe-npm - A security-focused npm wrapper');
42
+ console.log('');
43
+ console.log('Usage:');
44
+ console.log(' safe-npm <npm-command> Proxy npm commands with security checks');
45
+ console.log(' safe-npm check <pkg> Check package without installing');
46
+ console.log(' safe-npm tui Open interactive TUI');
47
+ console.log('');
48
+ process.exit(0);
49
+ }
50
+ const parsed = parseArgs(args);
51
+ if (parsed.isTui) {
52
+ startTui();
53
+ // Don't exit - Ink handles the process
54
+ return;
55
+ }
56
+ if (parsed.isCheck) {
57
+ await checkPackages(parsed.packages);
58
+ process.exit(0);
59
+ }
60
+ if (parsed.isInstall && parsed.packages.length > 0) {
61
+ // Security scan before install
62
+ const safe = await checkPackages(parsed.packages, {
63
+ isForce: parsed.isForce,
64
+ install: true,
65
+ });
66
+ if (!safe) {
67
+ process.exit(1);
68
+ }
69
+ }
70
+ // Proxy to npm
71
+ const exitCode = await proxyToNpm(args);
72
+ process.exit(exitCode);
73
+ }
74
+ main().catch((err) => {
75
+ console.error('Error:', err.message);
76
+ process.exit(1);
77
+ });
@@ -0,0 +1,2 @@
1
+ import type { ScanIssue } from './types.js';
2
+ export declare function scanCodePatterns(packageName: string): Promise<ScanIssue[]>;
@@ -0,0 +1,130 @@
1
+ import { checkMinerPatterns } from './patterns/miner.js';
2
+ import { checkExfiltrationPatterns } from './patterns/exfiltration.js';
3
+ import { checkObfuscationPatterns } from './patterns/obfuscation.js';
4
+ import { t } from '../i18n/index.js';
5
+ import { extractAndScanPackage, getPackageInfo } from '../utils/npm-package.js';
6
+ // Dangerous postinstall script patterns
7
+ const dangerousScriptPatterns = [
8
+ // Direct command execution
9
+ /curl\s+.*\|\s*(bash|sh)/i,
10
+ /wget\s+.*\|\s*(bash|sh)/i,
11
+ /powershell\s+-.*downloadstring/i,
12
+ /powershell\s+.*iex/i,
13
+ // Reverse shells
14
+ /bash\s+-i\s+>&\s*\/dev\/tcp/i,
15
+ /nc\s+-e\s+\/bin\/(ba)?sh/i,
16
+ /python.*socket.*connect/i,
17
+ // Suspicious node execution
18
+ /node\s+-e\s+['"].*require.*child_process/i,
19
+ /node\s+-e\s+['"].*eval\s*\(/i,
20
+ // Environment variable exfiltration
21
+ /curl.*\$\{?npm_config/i,
22
+ /curl.*process\.env/i,
23
+ ];
24
+ /**
25
+ * Scan package.json scripts for dangerous patterns
26
+ */
27
+ function scanScripts(scripts) {
28
+ const issues = [];
29
+ const dangerousScriptNames = ['preinstall', 'install', 'postinstall', 'preuninstall', 'postuninstall'];
30
+ for (const [name, script] of Object.entries(scripts)) {
31
+ // Check if it's a lifecycle script
32
+ const isLifecycleScript = dangerousScriptNames.includes(name);
33
+ for (const pattern of dangerousScriptPatterns) {
34
+ if (pattern.test(script)) {
35
+ issues.push({
36
+ type: 'suspicious_code',
37
+ severity: isLifecycleScript ? 'high' : 'warning',
38
+ message: t('suspiciousCode'),
39
+ details: `Dangerous pattern in "${name}" script: ${pattern.source}`,
40
+ });
41
+ break;
42
+ }
43
+ }
44
+ // Check for obfuscated commands in scripts
45
+ if (isLifecycleScript && script.length > 500) {
46
+ // Very long script might be hiding something
47
+ issues.push({
48
+ type: 'suspicious_code',
49
+ severity: 'warning',
50
+ message: t('suspiciousCode'),
51
+ details: `Unusually long "${name}" script (${script.length} chars) - manual review recommended`,
52
+ });
53
+ }
54
+ }
55
+ return issues;
56
+ }
57
+ /**
58
+ * Scan JavaScript file content for malicious patterns
59
+ */
60
+ function scanFileContent(filePath, content) {
61
+ const issues = [];
62
+ // Check for miner patterns
63
+ const minerResult = checkMinerPatterns(content);
64
+ if (minerResult.matched) {
65
+ issues.push({
66
+ type: 'miner',
67
+ severity: 'high',
68
+ message: t('minerDetected'),
69
+ details: `Pattern matched in ${filePath}: ${minerResult.pattern}`,
70
+ });
71
+ }
72
+ // Check for data exfiltration patterns
73
+ const exfilFindings = checkExfiltrationPatterns(content);
74
+ for (const finding of exfilFindings) {
75
+ issues.push({
76
+ type: 'suspicious_code',
77
+ severity: 'high',
78
+ message: t('suspiciousCode'),
79
+ details: `${finding} (in ${filePath})`,
80
+ });
81
+ }
82
+ // Check for obfuscation patterns
83
+ const obfuscationResult = checkObfuscationPatterns(content);
84
+ if (obfuscationResult.matched) {
85
+ issues.push({
86
+ type: 'suspicious_code',
87
+ severity: 'warning',
88
+ message: t('suspiciousCode'),
89
+ details: `Code obfuscation detected in ${filePath}: ${obfuscationResult.pattern}`,
90
+ });
91
+ }
92
+ return issues;
93
+ }
94
+ export async function scanCodePatterns(packageName) {
95
+ const issues = [];
96
+ // First, try to get basic package info without downloading
97
+ const packageInfo = await getPackageInfo(packageName);
98
+ if (!packageInfo) {
99
+ // Can't fetch package info, skip code analysis
100
+ return issues;
101
+ }
102
+ // Quick check on package.json scripts first
103
+ if (Object.keys(packageInfo.scripts).length > 0) {
104
+ const scriptIssues = scanScripts(packageInfo.scripts);
105
+ issues.push(...scriptIssues);
106
+ }
107
+ // Check if package has lifecycle scripts that warrant deeper analysis
108
+ const hasLifecycleScripts = ['preinstall', 'install', 'postinstall'].some(s => s in packageInfo.scripts);
109
+ // If there are lifecycle scripts or already found issues, do deep scan
110
+ if (hasLifecycleScripts || issues.length > 0) {
111
+ const packageFiles = await extractAndScanPackage(packageName);
112
+ if (packageFiles) {
113
+ // Scan all collected JS files
114
+ for (const file of packageFiles.allJsFiles) {
115
+ const fileIssues = scanFileContent(file.path, file.content);
116
+ issues.push(...fileIssues);
117
+ }
118
+ // Scan entry file if not already scanned
119
+ if (packageFiles.entryContent) {
120
+ const entryPath = packageFiles.entryFile || 'index.js';
121
+ const alreadyScanned = packageFiles.allJsFiles.some(f => f.path === entryPath);
122
+ if (!alreadyScanned) {
123
+ const entryIssues = scanFileContent(entryPath, packageFiles.entryContent);
124
+ issues.push(...entryIssues);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return issues;
130
+ }
@@ -0,0 +1,3 @@
1
+ import type { ScanResult, ScanOptions, ProgressCallback } from './types.js';
2
+ export declare function scanPackage(packageName: string, options?: ScanOptions, onProgress?: ProgressCallback): Promise<ScanResult>;
3
+ export type { ScanResult, ScanOptions, ScanIssue, RiskLevel, ProgressCallback } from './types.js';
@@ -0,0 +1,163 @@
1
+ import { scanVirusTotal } from './virustotal.js';
2
+ import { scanTyposquatting, findClosestPopularPackage } from './typosquatting.js';
3
+ import { scanCodePatterns } from './code-analyzer.js';
4
+ import { scanVulnerabilities } from './vulnerability.js';
5
+ import { checkPackageExists } from '../utils/npm-package.js';
6
+ import { t } from '../i18n/index.js';
7
+ // Built-in trusted packages
8
+ const TRUSTED_PACKAGES = new Set([
9
+ 'deepv-code'
10
+ ]);
11
+ export async function scanPackage(packageName, options = {}, onProgress) {
12
+ // Check whitelist first
13
+ if (TRUSTED_PACKAGES.has(packageName)) {
14
+ onProgress?.('Checking whitelist...', 1, 1);
15
+ return {
16
+ packageName,
17
+ riskLevel: 'safe',
18
+ issues: [],
19
+ checks: [
20
+ { name: 'Whitelist Check', status: 'pass', description: 'Package is in the built-in trusted list' },
21
+ { name: 'VirusTotal Scan', status: 'pass', description: 'Implicitly passed (Trusted)' },
22
+ { name: 'Typosquatting Check', status: 'pass', description: 'Implicitly passed (Trusted)' },
23
+ { name: 'Code Analysis', status: 'pass', description: 'Implicitly passed (Trusted)' },
24
+ { name: 'Vulnerability Check', status: 'pass', description: 'Implicitly passed (Trusted)' }
25
+ ],
26
+ canBypass: true,
27
+ };
28
+ }
29
+ // Check if package exists in registry
30
+ onProgress?.('Checking registry...', 0, 1);
31
+ const exists = await checkPackageExists(packageName);
32
+ if (!exists) {
33
+ const suggestion = findClosestPopularPackage(packageName);
34
+ return {
35
+ packageName,
36
+ riskLevel: 'fatal', // Treat as fatal to stop installation flow
37
+ issues: [{
38
+ type: 'typosquat',
39
+ severity: 'fatal',
40
+ message: t('packageNotFound'),
41
+ details: suggestion ? `${t('didYouMean')} "${suggestion}"?` : t('checkName')
42
+ }],
43
+ checks: [
44
+ { name: 'Registry Check', status: 'fail', description: t('registryCheckFail') }
45
+ ],
46
+ canBypass: false,
47
+ suggestedPackage: suggestion || undefined
48
+ };
49
+ }
50
+ const issues = [];
51
+ const checks = [];
52
+ // Helper to wrap tasks with progress
53
+ let completedTasks = 0;
54
+ const totalTasks = 4; // VT, Typo, Code, Vuln
55
+ const wrapTask = async (name, task) => {
56
+ onProgress?.(`Starting ${name}...`, completedTasks, totalTasks);
57
+ try {
58
+ const res = await task;
59
+ completedTasks++;
60
+ onProgress?.(`Finished ${name}`, completedTasks, totalTasks);
61
+ return res;
62
+ }
63
+ catch (err) {
64
+ completedTasks++;
65
+ onProgress?.(`Error in ${name}`, completedTasks, totalTasks);
66
+ throw err;
67
+ }
68
+ };
69
+ // Run all scans in parallel
70
+ const [virusResult, typoResult, codeResult, vulnResult] = await Promise.all([
71
+ wrapTask('VirusTotal', options.skipVirustotal ? Promise.resolve(null) : scanVirusTotal(packageName, options).catch(err => {
72
+ console.error('VT Error:', err);
73
+ return null;
74
+ })),
75
+ wrapTask('Typosquatting Check', scanTyposquatting(packageName).catch(err => {
76
+ console.error('Typo Error:', err);
77
+ return [];
78
+ })),
79
+ wrapTask('Code Analysis', scanCodePatterns(packageName).catch(err => {
80
+ console.error('Code Error:', err);
81
+ return [];
82
+ })),
83
+ wrapTask('Vulnerability Check', scanVulnerabilities(packageName).catch(err => {
84
+ console.error('Vuln Error:', err);
85
+ return [];
86
+ })),
87
+ ]);
88
+ // Process VirusTotal
89
+ if (options.skipVirustotal) {
90
+ checks.push({ name: 'VirusTotal Scan', status: 'skipped', description: 'Skipped by user option' });
91
+ }
92
+ else if (virusResult) {
93
+ // If it's the new object structure
94
+ const vtIssues = 'issues' in virusResult ? virusResult.issues : virusResult;
95
+ const vtInfo = 'info' in virusResult ? virusResult.info : 'Scan complete';
96
+ if (vtIssues && vtIssues.length > 0) {
97
+ checks.push({ name: 'VirusTotal Scan', status: 'fail', description: vtInfo });
98
+ issues.push(...vtIssues);
99
+ }
100
+ else {
101
+ checks.push({ name: 'VirusTotal Scan', status: 'pass', description: vtInfo });
102
+ }
103
+ }
104
+ else {
105
+ // Should not happen if catch returns null, but just in case
106
+ checks.push({ name: 'VirusTotal Scan', status: 'error', description: 'Scan failed to execute' });
107
+ }
108
+ // Process Typosquatting
109
+ if (typoResult && typoResult.length > 0) {
110
+ checks.push({ name: 'Typosquatting Check', status: 'fail', description: 'Suspicious package name detected' });
111
+ issues.push(...typoResult);
112
+ }
113
+ else {
114
+ checks.push({ name: 'Typosquatting Check', status: 'pass', description: 'Package name looks safe' });
115
+ }
116
+ // Process Code Patterns
117
+ if (codeResult && codeResult.length > 0) {
118
+ checks.push({ name: 'Static Code Analysis', status: 'fail', description: 'Suspicious code patterns detected' });
119
+ issues.push(...codeResult);
120
+ }
121
+ else {
122
+ checks.push({ name: 'Static Code Analysis', status: 'pass', description: 'No malicious code patterns found' });
123
+ }
124
+ // Process Vulnerabilities
125
+ if (vulnResult && vulnResult.length > 0) {
126
+ checks.push({ name: 'Vulnerability Database', status: 'fail', description: `Found ${vulnResult.length} known vulnerabilities` });
127
+ issues.push(...vulnResult);
128
+ }
129
+ else {
130
+ checks.push({ name: 'Vulnerability Database', status: 'pass', description: 'No known vulnerabilities (CVEs)' });
131
+ }
132
+ // Determine overall risk level
133
+ let riskLevel = 'safe';
134
+ let canBypass = true;
135
+ let suggestedPackage;
136
+ for (const issue of issues) {
137
+ if (issue.severity === 'fatal') {
138
+ riskLevel = 'fatal';
139
+ canBypass = false;
140
+ // Try to extract suggestion from typosquat details
141
+ if (issue.type === 'typosquat' && issue.details?.includes('"')) {
142
+ const match = issue.details.match(/"([^"]+)"/);
143
+ if (match)
144
+ suggestedPackage = match[1];
145
+ }
146
+ break;
147
+ }
148
+ if (issue.severity === 'high') {
149
+ riskLevel = 'high';
150
+ }
151
+ if (issue.severity === 'warning' && riskLevel === 'safe') {
152
+ riskLevel = 'warning';
153
+ }
154
+ }
155
+ return {
156
+ packageName,
157
+ riskLevel,
158
+ issues,
159
+ checks,
160
+ canBypass,
161
+ suggestedPackage,
162
+ };
163
+ }
@@ -0,0 +1,7 @@
1
+ export declare const exfiltrationPatterns: {
2
+ pattern: RegExp;
3
+ context: RegExp;
4
+ message: string;
5
+ }[];
6
+ export declare const suspiciousNetworkPatterns: RegExp[];
7
+ export declare function checkExfiltrationPatterns(code: string): string[];
@@ -0,0 +1,49 @@
1
+ export const exfiltrationPatterns = [
2
+ // Environment variable access + network
3
+ {
4
+ pattern: /process\.env\b/,
5
+ context: /(fetch|axios|http|https|request|got)\s*\(/i,
6
+ message: 'Environment variables accessed with network request',
7
+ },
8
+ // SSH key access
9
+ {
10
+ pattern: /\.ssh[\/\\]/,
11
+ context: /(readFile|readFileSync|createReadStream)/,
12
+ message: 'Accessing SSH directory',
13
+ },
14
+ // npmrc access
15
+ {
16
+ pattern: /\.npmrc/,
17
+ context: /(readFile|readFileSync|createReadStream)/,
18
+ message: 'Accessing .npmrc file',
19
+ },
20
+ // Base64 encoding of sensitive data
21
+ {
22
+ pattern: /Buffer\.from\(.*process\.env/,
23
+ context: /toString\s*\(\s*['"]base64['"]\s*\)/,
24
+ message: 'Base64 encoding environment data',
25
+ },
26
+ ];
27
+ export const suspiciousNetworkPatterns = [
28
+ // Direct IP connections
29
+ /https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
30
+ // Suspicious TLDs
31
+ /https?:\/\/[^\/]+\.(tk|ml|ga|cf|gq)\//,
32
+ // Hex/base64 encoded URLs
33
+ /atob\s*\(\s*['"][A-Za-z0-9+\/=]+['"]\s*\)/,
34
+ /Buffer\.from\s*\(\s*['"][A-Za-z0-9+\/=]+['"]\s*,\s*['"]base64['"]\s*\)/,
35
+ ];
36
+ export function checkExfiltrationPatterns(code) {
37
+ const findings = [];
38
+ for (const { pattern, context, message } of exfiltrationPatterns) {
39
+ if (pattern.test(code) && context.test(code)) {
40
+ findings.push(message);
41
+ }
42
+ }
43
+ for (const pattern of suspiciousNetworkPatterns) {
44
+ if (pattern.test(code)) {
45
+ findings.push(`Suspicious network pattern: ${pattern.source}`);
46
+ }
47
+ }
48
+ return findings;
49
+ }
@@ -0,0 +1,5 @@
1
+ export declare const minerPatterns: RegExp[];
2
+ export declare function checkMinerPatterns(code: string): {
3
+ matched: boolean;
4
+ pattern?: string;
5
+ };
@@ -0,0 +1,32 @@
1
+ export const minerPatterns = [
2
+ // Known miner libraries/functions
3
+ /cryptonight/i,
4
+ /coinhive/i,
5
+ /coin-?hive/i,
6
+ /minero/i,
7
+ /deepMiner/i,
8
+ /coinimp/i,
9
+ /crypto-?loot/i,
10
+ /webminer/i,
11
+ /mineralt/i,
12
+ // Mining pool connections
13
+ /stratum\+tcp:\/\//i,
14
+ /pool\..*\.(com|net|org):\d+/i,
15
+ /xmr\.pool/i,
16
+ /monero.*pool/i,
17
+ // WebAssembly mining patterns
18
+ /cryptonight.*wasm/i,
19
+ /cn-?lite/i,
20
+ // CPU/GPU mining indicators
21
+ /startMining/i,
22
+ /miner\.start/i,
23
+ /CryptoNoter/i,
24
+ ];
25
+ export function checkMinerPatterns(code) {
26
+ for (const pattern of minerPatterns) {
27
+ if (pattern.test(code)) {
28
+ return { matched: true, pattern: pattern.source };
29
+ }
30
+ }
31
+ return { matched: false };
32
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Patterns for detecting code obfuscation
3
+ * Obfuscated code is often used to hide malicious behavior
4
+ */
5
+ export declare const obfuscationPatterns: {
6
+ pattern: RegExp;
7
+ name: string;
8
+ }[];
9
+ /**
10
+ * Analyze code for obfuscation patterns
11
+ */
12
+ export declare function checkObfuscationPatterns(code: string): {
13
+ matched: boolean;
14
+ pattern?: string;
15
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Patterns for detecting code obfuscation
3
+ * Obfuscated code is often used to hide malicious behavior
4
+ */
5
+ export const obfuscationPatterns = [
6
+ // Hex encoded strings (common in obfuscated code)
7
+ {
8
+ pattern: /\\x[0-9a-f]{2}(?:\\x[0-9a-f]{2}){10,}/i,
9
+ name: 'Hex-encoded string sequence',
10
+ },
11
+ // Unicode escape sequences (excessive use)
12
+ {
13
+ pattern: /\\u[0-9a-f]{4}(?:\\u[0-9a-f]{4}){10,}/i,
14
+ name: 'Unicode escape sequence',
15
+ },
16
+ // Base64 with eval
17
+ {
18
+ pattern: /eval\s*\(\s*(?:atob|Buffer\.from)\s*\(/i,
19
+ name: 'eval() with Base64 decode',
20
+ },
21
+ // Long strings without spaces (typical of encoded payloads)
22
+ {
23
+ pattern: /['"][A-Za-z0-9+\/=]{200,}['"]/,
24
+ name: 'Long Base64-like string',
25
+ },
26
+ // Function constructor with string (code execution)
27
+ {
28
+ pattern: /new\s+Function\s*\(\s*['"].*['"]\s*\)/,
29
+ name: 'new Function() with string code',
30
+ },
31
+ // Common obfuscator patterns
32
+ {
33
+ pattern: /_0x[a-f0-9]{4,}/i,
34
+ name: 'JavaScript obfuscator variable pattern',
35
+ },
36
+ // Array-based string obfuscation
37
+ {
38
+ pattern: /\[['"][^'"]{1,20}['"](?:\s*,\s*['"][^'"]{1,20}['"]\s*){20,}\]/,
39
+ name: 'Array-based string obfuscation',
40
+ },
41
+ // Bitwise operations for string decoding
42
+ {
43
+ pattern: /String\.fromCharCode\s*\(\s*(?:\d+\s*[\^&|]\s*)+\d+\s*\)/,
44
+ name: 'Bitwise string decoding',
45
+ },
46
+ // JSFuck patterns
47
+ {
48
+ pattern: /\[\]\[(!\[\]\+\[\])/,
49
+ name: 'JSFuck obfuscation',
50
+ },
51
+ // eval with string concatenation/manipulation
52
+ {
53
+ pattern: /eval\s*\(\s*(?:\w+\s*\+\s*)+\w+\s*\)/,
54
+ name: 'eval() with string concatenation',
55
+ },
56
+ // setTimeout/setInterval with string
57
+ {
58
+ pattern: /set(?:Timeout|Interval)\s*\(\s*['"][^'"]+['"]/,
59
+ name: 'setTimeout/setInterval with string code',
60
+ },
61
+ // Char code array to string
62
+ {
63
+ pattern: /String\.fromCharCode\.apply\s*\(\s*null\s*,/,
64
+ name: 'Char code array conversion',
65
+ },
66
+ ];
67
+ // Threshold for considering code as obfuscated
68
+ const OBFUSCATION_THRESHOLD = 0.3; // 30% of lines look obfuscated
69
+ /**
70
+ * Analyze code for obfuscation patterns
71
+ */
72
+ export function checkObfuscationPatterns(code) {
73
+ // Check for specific obfuscation patterns
74
+ for (const { pattern, name } of obfuscationPatterns) {
75
+ if (pattern.test(code)) {
76
+ return { matched: true, pattern: name };
77
+ }
78
+ }
79
+ // Heuristic: check for high entropy (many short variable names)
80
+ const lines = code.split('\n').filter(line => line.trim().length > 0);
81
+ if (lines.length < 10) {
82
+ return { matched: false };
83
+ }
84
+ // Count lines with suspicious patterns
85
+ let suspiciousLines = 0;
86
+ for (const line of lines) {
87
+ // Very long line without spaces
88
+ if (line.length > 500 && line.split(/\s+/).length < 10) {
89
+ suspiciousLines++;
90
+ continue;
91
+ }
92
+ // Many short variable names (a, b, c, _0x...)
93
+ const shortVarMatches = line.match(/\b[a-z_$][a-z0-9_$]?\b/gi);
94
+ if (shortVarMatches && shortVarMatches.length > 20) {
95
+ suspiciousLines++;
96
+ continue;
97
+ }
98
+ // Excessive bracket/parenthesis nesting
99
+ const brackets = (line.match(/[\[\]\(\)\{\}]/g) || []).length;
100
+ if (brackets > 50) {
101
+ suspiciousLines++;
102
+ continue;
103
+ }
104
+ }
105
+ const obfuscationRatio = suspiciousLines / lines.length;
106
+ if (obfuscationRatio > OBFUSCATION_THRESHOLD) {
107
+ return { matched: true, pattern: `Heuristic: ${Math.round(obfuscationRatio * 100)}% of code appears obfuscated` };
108
+ }
109
+ return { matched: false };
110
+ }