@7nsane/zift 1.0.3 â 1.0.4
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/bin/zift.js +49 -75
- package/package.json +1 -1
package/bin/zift.js
CHANGED
|
@@ -11,32 +11,44 @@ async function main() {
|
|
|
11
11
|
const args = process.argv.slice(2);
|
|
12
12
|
let target = '.';
|
|
13
13
|
let format = 'text';
|
|
14
|
+
let isInstallMode = false;
|
|
14
15
|
|
|
15
|
-
//
|
|
16
|
+
// Verb detection: zift install <pkg> or zift i <pkg>
|
|
17
|
+
if (args[0] === 'install' || args[0] === 'i') {
|
|
18
|
+
isInstallMode = true;
|
|
19
|
+
target = args[1] || '.';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Flag detection: zift <pkg> --zift (for future-proofing/aliases)
|
|
23
|
+
if (args.includes('--zift')) {
|
|
24
|
+
isInstallMode = true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Basic arg parsing for other flags
|
|
16
28
|
for (let i = 0; i < args.length; i++) {
|
|
17
29
|
if (args[i] === '--format' && args[i + 1]) {
|
|
18
30
|
format = args[i + 1];
|
|
19
31
|
i++;
|
|
20
|
-
} else if (!args[i].startsWith('-')) {
|
|
32
|
+
} else if (!args[i].startsWith('-') && !['install', 'i'].includes(args[i])) {
|
|
21
33
|
target = args[i];
|
|
22
34
|
}
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
// Determine if target is local
|
|
37
|
+
// Determine if target is a local folder
|
|
26
38
|
const isLocal = fs.existsSync(target) && fs.lstatSync(target).isDirectory();
|
|
27
39
|
|
|
28
40
|
if (isLocal) {
|
|
29
41
|
await runLocalScan(target, format);
|
|
30
42
|
} else {
|
|
31
43
|
// Treat as remote package name
|
|
32
|
-
await runRemoteAudit(target, format);
|
|
44
|
+
await runRemoteAudit(target, format, isInstallMode);
|
|
33
45
|
}
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
async function runLocalScan(targetDir, format) {
|
|
37
49
|
const scanner = new PackageScanner(targetDir);
|
|
38
50
|
if (format === 'text') {
|
|
39
|
-
|
|
51
|
+
console.log(chalk.blue(`\nđ Scanning local directory: ${path.resolve(targetDir)}`));
|
|
40
52
|
}
|
|
41
53
|
|
|
42
54
|
try {
|
|
@@ -47,120 +59,90 @@ async function runLocalScan(targetDir, format) {
|
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
async function runRemoteAudit(packageName, format) {
|
|
62
|
+
async function runRemoteAudit(packageName, format, forceInstall = false) {
|
|
51
63
|
if (format === 'text') {
|
|
52
|
-
console.log(chalk.blue(`\nđ Remote Audit:
|
|
64
|
+
console.log(chalk.blue(`\nđ Remote Audit: Pre-scanning package '${packageName}'...`));
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zift-audit-'));
|
|
56
68
|
|
|
57
69
|
try {
|
|
58
|
-
// Download tarball
|
|
59
70
|
cp.execSync(`npm pack ${packageName}`, { cwd: tmpDir, stdio: 'ignore' });
|
|
60
71
|
const tarball = fs.readdirSync(tmpDir).find(f => f.endsWith('.tgz'));
|
|
61
|
-
|
|
62
|
-
// Extract
|
|
63
72
|
cp.execSync(`tar -xzf ${tarball}`, { cwd: tmpDir });
|
|
64
|
-
const scanPath = path.join(tmpDir, 'package');
|
|
65
73
|
|
|
74
|
+
const scanPath = path.join(tmpDir, 'package');
|
|
66
75
|
const scanner = new PackageScanner(scanPath);
|
|
67
76
|
const findings = await scanner.scan();
|
|
68
77
|
|
|
69
|
-
// Custom reporting for remote audit
|
|
70
78
|
if (format === 'text') {
|
|
71
|
-
console.log(chalk.green(`â
Pre-scan of '${packageName}' complete.`));
|
|
72
79
|
const s = getSummary(findings);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
s.
|
|
76
|
-
|
|
80
|
+
const statusText = s.Critical > 0 ? chalk.red.bold('CRITICAL RISK') :
|
|
81
|
+
s.High > 0 ? chalk.red('HIGH RISK') :
|
|
82
|
+
s.Medium > 0 ? chalk.yellow('WARNING') : chalk.green('SECURE');
|
|
83
|
+
|
|
84
|
+
console.log(chalk.green(`â
Audit of '${packageName}' complete. Status: ${statusText}`));
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
handleFindings(findings, format, scanPath, true);
|
|
80
88
|
|
|
81
|
-
// Interactive Prompt
|
|
82
89
|
if (format === 'text') {
|
|
83
90
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
84
|
-
const
|
|
85
|
-
? chalk.yellow(`\nâ ď¸ Suspicious patterns found. Still install '${packageName}'? (
|
|
86
|
-
: chalk.blue(`\nProceed with installation of '${packageName}'? (
|
|
91
|
+
const promptText = findings.length > 0
|
|
92
|
+
? chalk.yellow(`\nâ ď¸ Suspicious patterns found. Still install '${packageName}'? (y/n): `)
|
|
93
|
+
: chalk.blue(`\nProceed with installation of '${packageName}'? (y/n): `);
|
|
87
94
|
|
|
88
|
-
rl.question(
|
|
95
|
+
rl.question(promptText, (answer) => {
|
|
89
96
|
rl.close();
|
|
90
|
-
|
|
97
|
+
const confirmed = ['y', 'yes'].includes(answer.toLowerCase());
|
|
98
|
+
|
|
99
|
+
if (confirmed) {
|
|
91
100
|
console.log(chalk.blue(`\nđŚ Installing ${packageName}...`));
|
|
92
101
|
try {
|
|
93
102
|
cp.execSync(`npm install ${packageName}`, { stdio: 'inherit' });
|
|
94
103
|
console.log(chalk.green(`\nâ
${packageName} installed successfully.`));
|
|
95
|
-
} catch (e) {
|
|
96
|
-
console.error(chalk.red(`\nâ Installation failed.`));
|
|
97
|
-
}
|
|
98
|
-
cleanupAndExit(tmpDir, 0);
|
|
104
|
+
} catch (e) { console.error(chalk.red(`\nâ Installation failed.`)); }
|
|
99
105
|
} else {
|
|
100
106
|
console.log(chalk.red(`\nâ Installation aborted by user.`));
|
|
101
|
-
cleanupAndExit(tmpDir, 0);
|
|
102
107
|
}
|
|
108
|
+
cleanupAndExit(tmpDir, 0);
|
|
103
109
|
});
|
|
104
110
|
} else {
|
|
105
111
|
cleanupAndExit(tmpDir, 0);
|
|
106
112
|
}
|
|
107
|
-
|
|
108
113
|
} catch (err) {
|
|
109
|
-
console.error(chalk.red(`\nâ
|
|
114
|
+
console.error(chalk.red(`\nâ Audit failed: Ensure '${packageName}' is a valid npm package.`));
|
|
110
115
|
cleanupAndExit(tmpDir, 1);
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
function handleFindings(findings, format, targetDir, skipExit = false) {
|
|
115
120
|
if (format === 'json') {
|
|
116
|
-
|
|
117
|
-
target: targetDir,
|
|
118
|
-
timestamp: new Date().toISOString(),
|
|
119
|
-
findings: findings,
|
|
120
|
-
summary: getSummary(findings)
|
|
121
|
-
}, null, 2));
|
|
121
|
+
process.stdout.write(JSON.stringify({ target: targetDir, findings, summary: getSummary(findings) }, null, 2));
|
|
122
122
|
if (!skipExit) process.exit(findings.some(f => f.score >= 90) ? 1 : 0);
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
if (findings.length === 0) {
|
|
127
127
|
if (!skipExit) {
|
|
128
|
-
console.log(chalk.green('\nâ
No suspicious patterns detected. All modules
|
|
128
|
+
console.log(chalk.green('\nâ
No suspicious patterns detected. All modules safe.'));
|
|
129
129
|
process.exit(0);
|
|
130
130
|
}
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
console.log(chalk.yellow(`\nâ ď¸
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
console.log(theme(`[${finding.classification}] ${finding.id} ${finding.name} (Risk Score: ${finding.score})`));
|
|
141
|
-
console.log(chalk.gray(`Description: ${finding.description}`));
|
|
142
|
-
|
|
143
|
-
finding.triggers.forEach(t => {
|
|
144
|
-
console.log(chalk.white(` - ${t.type} in ${t.file}:${t.line} [${t.context}]`));
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
if (finding.isLifecycle) {
|
|
148
|
-
console.log(chalk.magenta(` Context: Multiplier applied due to execution in lifecycle script.`));
|
|
149
|
-
}
|
|
134
|
+
console.log(chalk.yellow(`\nâ ď¸ Suspicious patterns found:\n`));
|
|
135
|
+
findings.forEach(f => {
|
|
136
|
+
const color = { 'Critical': chalk.red.bold, 'High': chalk.red, 'Medium': chalk.yellow, 'Low': chalk.blue }[f.classification];
|
|
137
|
+
console.log(color(`[${f.classification}] ${f.id} ${f.name} (Score: ${f.score})`));
|
|
138
|
+
f.triggers.forEach(t => console.log(chalk.white(` - ${t.type} in ${t.file}:${t.line} [${t.context}]`)));
|
|
150
139
|
console.log('');
|
|
151
140
|
});
|
|
152
141
|
|
|
153
142
|
printSummary(findings);
|
|
154
143
|
|
|
155
144
|
if (!skipExit) {
|
|
156
|
-
|
|
157
|
-
if (highestScore >= 90) {
|
|
158
|
-
console.log(chalk.red.bold(`\nâ FAILED SAFETY CHECK: Critical risk detected (Score: ${highestScore})\n`));
|
|
159
|
-
process.exit(1);
|
|
160
|
-
} else {
|
|
161
|
-
console.log(chalk.green(`\nâ Safety check completed with minor warnings.\n`));
|
|
162
|
-
process.exit(0);
|
|
163
|
-
}
|
|
145
|
+
process.exit(findings[0].score >= 90 ? 1 : 0);
|
|
164
146
|
}
|
|
165
147
|
}
|
|
166
148
|
|
|
@@ -170,29 +152,21 @@ function cleanupAndExit(dir, code) {
|
|
|
170
152
|
}
|
|
171
153
|
|
|
172
154
|
function handleError(err, format) {
|
|
173
|
-
if (format === 'json') {
|
|
174
|
-
|
|
175
|
-
} else {
|
|
176
|
-
console.error(chalk.red(`\nâ Fatal Error: ${err.message}`));
|
|
177
|
-
}
|
|
155
|
+
if (format === 'json') console.error(JSON.stringify({ error: err.message }));
|
|
156
|
+
else console.error(chalk.red(`\nâ Fatal Error: ${err.message}`));
|
|
178
157
|
process.exit(1);
|
|
179
158
|
}
|
|
180
159
|
|
|
181
160
|
function getSummary(findings) {
|
|
182
|
-
const
|
|
183
|
-
findings.forEach(f =>
|
|
184
|
-
|
|
185
|
-
summary[f.classification]++;
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
return summary;
|
|
161
|
+
const s = { Critical: 0, High: 0, Medium: 0, Low: 0 };
|
|
162
|
+
findings.forEach(f => s[f.classification]++);
|
|
163
|
+
return s;
|
|
189
164
|
}
|
|
190
165
|
|
|
191
166
|
function printSummary(findings) {
|
|
192
167
|
const s = getSummary(findings);
|
|
193
168
|
console.log(chalk.bold('Severity Summary:'));
|
|
194
|
-
console.log(chalk.red(` Critical: ${s.Critical}`));
|
|
195
|
-
console.log(chalk.red(` High: ${s.High}`));
|
|
169
|
+
console.log(chalk.red(` Critical: ${s.Critical}\n High: ${s.High}`));
|
|
196
170
|
console.log(chalk.yellow(` Medium: ${s.Medium}`));
|
|
197
171
|
console.log(chalk.blue(` Low: ${s.Low}`));
|
|
198
172
|
}
|