@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.
Files changed (2) hide show
  1. package/bin/zift.js +49 -75
  2. 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
- // Basic arg parsing
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 path or remote package
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
- process.stdout.write(chalk.blue(`\n🔍 Scanning local directory at ${path.resolve(targetDir)}...\n`));
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: Attempting to pre-scan package '${packageName}'...`));
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
- console.log(chalk.bold('Risk Profile: ') +
74
- (s.Critical > 0 ? chalk.red.bold('CRITICAL') :
75
- s.High > 0 ? chalk.red('HIGH') :
76
- s.Medium > 0 ? chalk.yellow('MEDIUM') : chalk.green('SECURE')));
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 question = findings.length > 0
85
- ? chalk.yellow(`\n⚠️ Suspicious patterns found. Still install '${packageName}'? (yes/no): `)
86
- : chalk.blue(`\nProceed with installation of '${packageName}'? (yes/no): `);
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(question, (answer) => {
95
+ rl.question(promptText, (answer) => {
89
96
  rl.close();
90
- if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {
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❌ Remote Audit failed: Ensure '${packageName}' exists on npm.`));
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
- console.log(JSON.stringify({
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 within safety thresholds.'));
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⚠️ Found ${findings.length} suspicious patterns.\n`));
135
-
136
- findings.forEach(finding => {
137
- const colorMap = { 'Critical': chalk.red.bold, 'High': chalk.red, 'Medium': chalk.yellow, 'Low': chalk.blue };
138
- const theme = colorMap[finding.classification] || chalk.white;
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
- const highestScore = findings.length > 0 ? findings[0].score : 0;
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
- console.error(JSON.stringify({ error: err.message }));
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 summary = { Critical: 0, High: 0, Medium: 0, Low: 0 };
183
- findings.forEach(f => {
184
- if (summary[f.classification] !== undefined) {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7nsane/zift",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A high-performance, deterministic security scanner for npm packages.",
5
5
  "main": "src/scanner.js",
6
6
  "bin": {