@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 +62 -69
- package/package.json +1 -1
- package/src/collector.js +34 -5
- package/audit-axios/axios-1.13.6.tgz +0 -0
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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!
|
|
70
|
-
console.log(chalk.yellow.bold(`\nTo activate IMMEDIATELY, run
|
|
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
|
|
85
|
-
function npm {
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
104
|
-
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
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
|
|
127
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
rl.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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 -
|
|
167
|
+
console.log(chalk.blue.bold('\n🛡️ Zift - Universal Security Scanner\n'));
|
|
187
168
|
console.log('Usage:');
|
|
188
|
-
console.log(' zift setup
|
|
189
|
-
console.log(' zift install <pkg>
|
|
190
|
-
console.log('
|
|
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
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;
|
|
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
|