@7nsane/zift 1.1.0 → 1.2.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/bin/zift.js CHANGED
@@ -12,22 +12,30 @@ async function main() {
12
12
  let target = '.';
13
13
  let format = 'text';
14
14
  let isInstallMode = false;
15
+ let installer = 'npm';
15
16
 
17
+ // 1. Setup Command
16
18
  if (args[0] === 'setup') {
17
19
  await runSetup();
18
20
  return;
19
21
  }
20
22
 
21
- if (args[0] === 'install' || args[0] === 'i') {
23
+ // 2. Detection for bun/pnpm usage
24
+ if (args.includes('--bun')) installer = 'bun';
25
+ if (args.includes('--pnpm')) installer = 'pnpm';
26
+
27
+ // 3. Installation Verbs
28
+ if (args[0] === 'install' || args[0] === 'i' || args[0] === 'add') {
22
29
  isInstallMode = true;
23
30
  target = args.find((a, i) => i > 0 && !a.startsWith('-')) || '.';
24
31
  }
25
32
 
26
33
  if (args.includes('--zift')) {
27
34
  isInstallMode = true;
28
- target = args.find(a => !a.startsWith('-') && !['install', 'i', 'npm'].includes(a)) || '.';
35
+ target = args.find(a => !a.startsWith('-') && !['install', 'i', 'add', 'npm', 'bun', 'pnpm'].includes(a)) || '.';
29
36
  }
30
37
 
38
+ // 4. Flags
31
39
  for (let i = 0; i < args.length; i++) {
32
40
  if (args[i] === '--format' && args[i + 1]) {
33
41
  format = args[i + 1];
@@ -35,26 +43,28 @@ async function main() {
35
43
  }
36
44
  }
37
45
 
46
+ // 5. No Args? Show Help
38
47
  if (args.length === 0) {
39
48
  showHelp();
40
49
  return;
41
50
  }
42
51
 
52
+ // 6. Execution
43
53
  const isLocal = fs.existsSync(target) && fs.lstatSync(target).isDirectory();
44
54
 
45
55
  if (isLocal) {
46
56
  await runLocalScan(target, format);
47
57
  } else {
48
- await runRemoteAudit(target, format, isInstallMode);
58
+ await runRemoteAudit(target, format, installer);
49
59
  }
50
60
  }
51
61
 
52
62
  async function runSetup() {
53
63
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
54
- console.log(chalk.blue.bold('\n🛡️ Zift Secure Alias Setup'));
55
- console.log(chalk.gray('Configure `npm install --zift` for automatic security audits.\n'));
64
+ console.log(chalk.blue.bold('\n🛡️ Zift Secure Alias Setup (Universal)'));
65
+ console.log(chalk.gray('Configure secure wrappers for npm, bun, and pnpm.\n'));
56
66
 
57
- const question = chalk.white('Add the Zift secure wrapper to your shell profile? (y/n): ');
67
+ const question = chalk.white('Add secure wrappers to your shell profile? (y/n): ');
58
68
 
59
69
  rl.question(question, (answer) => {
60
70
  rl.close();
@@ -66,74 +76,47 @@ async function runSetup() {
66
76
  } else {
67
77
  reloadCmd = setupUnix();
68
78
  }
69
- console.log(chalk.green('\n✅ Setup complete! Profile updated.'));
70
- console.log(chalk.yellow.bold(`\nTo activate IMMEDIATELY, run this command:`));
71
- console.log(chalk.cyan.inverse(` ${reloadCmd} \n`));
72
- console.log(chalk.gray('Alternatively, simply restart your terminal.'));
79
+ console.log(chalk.green('\n✅ Setup complete! All package managers are now secured.'));
80
+ console.log(chalk.yellow.bold(`\nTo activate IMMEDIATELY, run: `) + chalk.cyan.inverse(` ${reloadCmd} `));
73
81
  } catch (e) {
74
82
  console.error(chalk.red('\n❌ Setup failed: ') + e.message);
75
83
  }
76
- } else {
77
- console.log(chalk.yellow('\nSetup cancelled.'));
78
84
  }
79
85
  });
80
86
  }
81
87
 
82
88
  function setupWindows() {
83
89
  const psFunction = `
84
- # Zift Secure Alias
85
- function npm {
86
- if ($args -contains "--zift") {
87
- $pkg = $args | Where-Object { $_ -ne "install" -and $_ -ne "i" -and $_ -ne "--zift" } | Select-Object -First 1
88
- Write-Host "\n🛡️ Zift: Intercepting installation for audit...\n" -ForegroundColor Green
89
- npx @7nsane/zift@latest install $pkg
90
- } else {
91
- & (Get-Command npm.cmd).Definition @args
92
- }
93
- }
90
+ # Zift Secure Wrappers
91
+ function npm { if ($args -contains "--zift") { npx @7nsane/zift@latest install ($args | Where-Object { $_ -ne "install" -and $_ -ne "i" -and $_ -ne "--zift" } | Select-Object -First 1) } else { & (Get-Command npm.cmd).Definition @args } }
92
+ function bun { if ($args -contains "--zift") { npx @7nsane/zift@latest install ($args | Where-Object { $_ -ne "add" -and $_ -ne "install" -and $_ -ne "--zift" } | Select-Object -First 1) --bun } else { & (Get-Command bun.exe).Definition @args } }
93
+ function pnpm { if ($args -contains "--zift") { npx @7nsane/zift@latest install ($args | Where-Object { $_ -ne "add" -and $_ -ne "install" -and $_ -ne "i" -and $_ -ne "--zift" } | Select-Object -First 1) --pnpm } else { & (Get-Command pnpm.cmd).Definition @args } }
94
94
  `;
95
95
  const profilePath = cp.execSync('powershell -NoProfile -Command "echo $PROFILE"').toString().trim();
96
- const profileDir = path.dirname(profilePath);
97
- if (!fs.existsSync(profileDir)) fs.mkdirSync(profileDir, { recursive: true });
98
96
  fs.appendFileSync(profilePath, psFunction);
99
97
  return '. $PROFILE';
100
98
  }
101
99
 
102
100
  function setupUnix() {
103
- const bashFunction = `
104
- # Zift Secure Alias
105
- npm() {
101
+ const wrapperPattern = (cmd, aliasCmd) => `
102
+ ${cmd}() {
106
103
  if [[ "$*" == *"--zift"* ]]; then
107
- pkg=$(echo "$@" | sed 's/install//g; s/ i //g; s/--zift//g' | xargs)
108
- npx @7nsane/zift@latest install $pkg
104
+ pkg=$(echo "$@" | sed 's/install//g; s/add//g; s/ i //g; s/--zift//g' | xargs)
105
+ npx @7nsane/zift@latest install $pkg --${cmd}
109
106
  else
110
- command npm "$@"
107
+ command ${cmd} "$@"
111
108
  fi
112
109
  }
113
110
  `;
111
+ const shellFunctions = wrapperPattern('npm') + wrapperPattern('bun') + wrapperPattern('pnpm');
114
112
  const home = os.homedir();
115
113
  const profiles = [path.join(home, '.bashrc'), path.join(home, '.zshrc')];
116
- let reloadTarget = '~/.zshrc';
117
- profiles.forEach(p => {
118
- if (fs.existsSync(p)) {
119
- fs.appendFileSync(p, bashFunction);
120
- if (p.endsWith('.bashrc')) reloadTarget = '~/.bashrc';
121
- }
122
- });
123
- return `source ${reloadTarget}`;
114
+ profiles.forEach(p => { if (fs.existsSync(p)) fs.appendFileSync(p, shellFunctions); });
115
+ return 'source ~/.zshrc # or ~/.bashrc';
124
116
  }
125
117
 
126
- async function runLocalScan(targetDir, format) {
127
- const scanner = new PackageScanner(targetDir);
128
- if (format === 'text') console.log(chalk.blue(`\n🔍 Scanning local directory: ${path.resolve(targetDir)}`));
129
- try {
130
- const findings = await scanner.scan();
131
- handleFindings(findings, format, targetDir);
132
- } catch (err) { handleError(err, format); }
133
- }
134
-
135
- async function runRemoteAudit(packageName, format, installOnSuccess) {
136
- if (format === 'text') console.log(chalk.blue(`\n🌍 Remote Audit: Pre-scanning package '${packageName}'...`));
118
+ async function runRemoteAudit(packageName, format, installer) {
119
+ if (format === 'text') console.log(chalk.blue(`\n🌍 Remote Audit [via ${installer}]: Pre-scanning '${packageName}'...`));
137
120
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zift-audit-'));
138
121
  try {
139
122
  cp.execSync(`npm pack ${packageName}`, { cwd: tmpDir, stdio: 'ignore' });
@@ -144,22 +127,20 @@ async function runRemoteAudit(packageName, format, installOnSuccess) {
144
127
  const findings = await scanner.scan();
145
128
  handleFindings(findings, format, scanPath, true);
146
129
 
147
- if (format === 'text') {
148
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
149
- const promptText = findings.length > 0
150
- ? chalk.yellow(`\n⚠️ Suspicious patterns found. Still install '${packageName}'? (y/n): `)
151
- : chalk.blue(`\nAudit passed. Proceed with installation of '${packageName}'? (y/n): `);
152
-
153
- rl.question(promptText, (answer) => {
154
- rl.close();
155
- if (['y', 'yes'].includes(answer.toLowerCase())) {
156
- console.log(chalk.blue(`\n📦 Installing ${packageName}...`));
157
- cp.execSync(`npm install ${packageName}`, { stdio: 'inherit' });
158
- console.log(chalk.green(`\n✅ ${packageName} installed successfully.`));
159
- }
160
- cleanupAndExit(tmpDir, 0);
161
- });
162
- } else { cleanupAndExit(tmpDir, 0); }
130
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
131
+ const promptText = findings.length > 0
132
+ ? chalk.yellow(`\n⚠️ Suspicious patterns found. Still install '${packageName}' with ${installer}? (y/n): `)
133
+ : chalk.blue(`\nAudit passed. Proceed with installation of '${packageName}' via ${installer}? (y/n): `);
134
+
135
+ rl.question(promptText, (answer) => {
136
+ rl.close();
137
+ if (['y', 'yes'].includes(answer.toLowerCase())) {
138
+ console.log(chalk.blue(`\n📦 Running '${installer} install ${packageName}'...`));
139
+ const installCmd = installer === 'bun' ? `bun add ${packageName}` : installer === 'pnpm' ? `pnpm add ${packageName}` : `npm install ${packageName}`;
140
+ try { cp.execSync(installCmd, { stdio: 'inherit' }); } catch (err) { }
141
+ }
142
+ cleanupAndExit(tmpDir, 0);
143
+ });
163
144
  } catch (err) { cleanupAndExit(tmpDir, 1); }
164
145
  }
165
146
 
@@ -183,11 +164,11 @@ function handleFindings(findings, format, targetDir, skipExit = false) {
183
164
  }
184
165
 
185
166
  function showHelp() {
186
- console.log(chalk.blue.bold('\n🛡️ Zift - The Elite Security Scanner\n'));
167
+ console.log(chalk.blue.bold('\n🛡️ Zift - Universal Security Scanner\n'));
187
168
  console.log('Usage:');
188
- console.log(' zift setup Configure secure npm wrapper');
189
- console.log(' zift install <pkg> Audit and install package');
190
- console.log(' zift . Scan local directory');
169
+ console.log(' zift setup Secure npm, bun, and pnpm');
170
+ console.log(' zift install <pkg> Scan and install package');
171
+ console.log(' --bun / --pnpm Use a specific installer');
191
172
  }
192
173
 
193
174
  function cleanupAndExit(dir, code) {
@@ -200,4 +181,16 @@ function handleError(err, format) {
200
181
  process.exit(1);
201
182
  }
202
183
 
184
+ function getSummary(findings) {
185
+ const s = { Critical: 0, High: 0, Medium: 0, Low: 0 };
186
+ findings.forEach(f => s[f.classification]++);
187
+ return s;
188
+ }
189
+
190
+ function printSummary(findings) {
191
+ const s = getSummary(findings);
192
+ console.log(chalk.bold('Severity Summary:'));
193
+ console.log(chalk.red(` Critical: ${s.Critical}\n High: ${s.High}`));
194
+ }
195
+
203
196
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7nsane/zift",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A high-performance, deterministic security scanner for npm packages.",
5
5
  "main": "src/scanner.js",
6
6
  "bin": {
package/src/collector.js CHANGED
@@ -48,7 +48,6 @@ class ASTCollector {
48
48
  CallExpression: (node) => {
49
49
  const calleeCode = this.getSourceCode(node.callee);
50
50
 
51
- // Detect eval / Function
52
51
  if (calleeCode === 'eval' || calleeCode === 'Function') {
53
52
  this.facts.DYNAMIC_EXECUTION.push({
54
53
  file: filePath,
@@ -57,7 +56,6 @@ class ASTCollector {
57
56
  });
58
57
  }
59
58
 
60
- // Detect Sinks
61
59
  if (this.isNetworkSink(calleeCode)) {
62
60
  this.facts.NETWORK_SINK.push({
63
61
  file: filePath,
@@ -66,7 +64,6 @@ class ASTCollector {
66
64
  });
67
65
  }
68
66
 
69
- // Detect Sources
70
67
  if (this.isSensitiveFileRead(calleeCode, node)) {
71
68
  this.facts.FILE_READ_SENSITIVE.push({
72
69
  file: filePath,
@@ -77,11 +74,10 @@ class ASTCollector {
77
74
  },
78
75
  MemberExpression: (node) => {
79
76
  const objectCode = this.getSourceCode(node.object);
80
- // Detect process.env
81
77
  if (objectCode === 'process.env' || objectCode === 'process["env"]' || objectCode === "process['env']") {
82
78
  const property = node.property.name || (node.property.type === 'Literal' ? node.property.value : null);
83
79
  const whitelist = ['NODE_ENV', 'TIMING', 'DEBUG', 'VERBOSE', 'CI', 'APPDATA', 'HOME', 'USERPROFILE', 'PATH', 'PWD'];
84
- if (whitelist.includes(property)) return; // Whitelist common env check
80
+ if (whitelist.includes(property)) return;
85
81
 
86
82
  this.facts.ENV_READ.push({
87
83
  file: filePath,
@@ -100,6 +96,39 @@ class ASTCollector {
100
96
  line: node.loc.start.line
101
97
  });
102
98
  }
99
+ },
100
+ AssignmentExpression: (node) => {
101
+ if (node.left.type === 'MemberExpression' && node.right.type === 'Identifier') {
102
+ // Track property assignments: obj.prop = taintedVar
103
+ const from = this.getSourceCode(node.right);
104
+ const to = this.getSourceCode(node.left);
105
+ this.flows.push({
106
+ fromVar: from,
107
+ toVar: to,
108
+ file: filePath,
109
+ line: node.loc.start.line
110
+ });
111
+ }
112
+ },
113
+ ObjectExpression: (node, state, ancestors) => {
114
+ // Track object literal property assignments: const x = { p: process.env }
115
+ const parent = ancestors[ancestors.length - 2];
116
+ if (parent && parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
117
+ const objName = parent.id.name;
118
+ node.properties.forEach(prop => {
119
+ if (prop.value.type === 'MemberExpression') {
120
+ const valCode = this.getSourceCode(prop.value);
121
+ if (valCode.includes('process.env')) {
122
+ this.flows.push({
123
+ fromVar: valCode,
124
+ toVar: `${objName}.${this.getSourceCode(prop.key)}`,
125
+ file: filePath,
126
+ line: prop.loc.start.line
127
+ });
128
+ }
129
+ }
130
+ });
131
+ }
103
132
  }
104
133
  });
105
134
 
Binary file