@7nsane/zift 1.0.3 → 1.0.5
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/README.md +5 -4
- package/bin/zift.js +132 -109
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,14 +19,15 @@ npm install -g @7nsane/zift
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
+
# NEW: Secure Installer Mode (Scan + Install)
|
|
23
|
+
@7nsane/zift install <package-name>
|
|
24
|
+
@7nsane/zift i <package-name>
|
|
25
|
+
|
|
22
26
|
# Scan current directory
|
|
23
27
|
@7nsane/zift .
|
|
24
28
|
|
|
25
|
-
# Scan a specific
|
|
29
|
+
# Scan a specific folder
|
|
26
30
|
@7nsane/zift ./node_modules/example-pkg
|
|
27
|
-
|
|
28
|
-
# Output result in JSON format for CI/CD pipelines
|
|
29
|
-
@7nsane/zift . --format json
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
## Rule Transparency
|
package/bin/zift.js
CHANGED
|
@@ -11,190 +11,213 @@ 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
|
+
// 1. Setup Command
|
|
17
|
+
if (args[0] === 'setup') {
|
|
18
|
+
await runSetup();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 2. Installation Verbs
|
|
23
|
+
if (args[0] === 'install' || args[0] === 'i') {
|
|
24
|
+
isInstallMode = true;
|
|
25
|
+
target = args.find((a, i) => i > 0 && !a.startsWith('-')) || '.';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (args.includes('--zift')) {
|
|
29
|
+
isInstallMode = true;
|
|
30
|
+
target = args.find(a => !a.startsWith('-') && !['install', 'i'].includes(a)) || '.';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Flags
|
|
16
34
|
for (let i = 0; i < args.length; i++) {
|
|
17
35
|
if (args[i] === '--format' && args[i + 1]) {
|
|
18
36
|
format = args[i + 1];
|
|
19
37
|
i++;
|
|
20
|
-
} else if (!args[i].startsWith('-')) {
|
|
21
|
-
target = args[i];
|
|
22
38
|
}
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
//
|
|
41
|
+
// 4. No Args? Show Help or offer Setup
|
|
42
|
+
if (args.length === 0) {
|
|
43
|
+
showHelp();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 5. Execution
|
|
26
48
|
const isLocal = fs.existsSync(target) && fs.lstatSync(target).isDirectory();
|
|
27
49
|
|
|
28
50
|
if (isLocal) {
|
|
29
51
|
await runLocalScan(target, format);
|
|
30
52
|
} else {
|
|
31
|
-
|
|
32
|
-
await runRemoteAudit(target, format);
|
|
53
|
+
await runRemoteAudit(target, format, isInstallMode);
|
|
33
54
|
}
|
|
34
55
|
}
|
|
35
56
|
|
|
57
|
+
async function runSetup() {
|
|
58
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
59
|
+
console.log(chalk.blue.bold('\n🛡️ Zift Secure Alias Setup'));
|
|
60
|
+
console.log(chalk.gray('This will allow you to use `npm install <pkg> --zift` for secure audits.\n'));
|
|
61
|
+
|
|
62
|
+
const question = chalk.white('Would you like to add the Zift secure alias to your shell profile? (y/n): ');
|
|
63
|
+
|
|
64
|
+
rl.question(question, (answer) => {
|
|
65
|
+
rl.close();
|
|
66
|
+
if (['y', 'yes'].includes(answer.toLowerCase())) {
|
|
67
|
+
try {
|
|
68
|
+
if (os.platform() === 'win32') {
|
|
69
|
+
setupWindows();
|
|
70
|
+
} else {
|
|
71
|
+
setupUnix();
|
|
72
|
+
}
|
|
73
|
+
console.log(chalk.green('\n✅ Setup complete! Please RESTART your terminal to use the new command.'));
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.error(chalk.red('\n❌ Setup failed: ') + e.message);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
console.log(chalk.yellow('\nSetup cancelled. You can always run `zift setup` later.'));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function setupWindows() {
|
|
84
|
+
// Define PowerShell function
|
|
85
|
+
const psFunction = `
|
|
86
|
+
# Zift Secure Alias
|
|
87
|
+
function npm-secure {
|
|
88
|
+
if ($args -contains "--zift") {
|
|
89
|
+
$pkg = $args | Where-Object { $_ -ne "install" -and $_ -ne "i" -and $_ -ne "--zift" } | Select-Object -First 1
|
|
90
|
+
npx @7nsane/zift install $pkg
|
|
91
|
+
} else {
|
|
92
|
+
npm.cmd @args
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!(Test-Path alias:npm)) { Set-Alias npm npm-secure -Force -Scope Global }
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
// Get PS Profile path
|
|
99
|
+
const profilePath = cp.execSync('powershell -NoProfile -Command "echo $PROFILE"').toString().trim();
|
|
100
|
+
const profileDir = path.dirname(profilePath);
|
|
101
|
+
|
|
102
|
+
if (!fs.existsSync(profileDir)) fs.mkdirSync(profileDir, { recursive: true });
|
|
103
|
+
fs.appendFileSync(profilePath, psFunction);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function setupUnix() {
|
|
107
|
+
const bashFunction = `
|
|
108
|
+
# Zift Secure Alias
|
|
109
|
+
npm() {
|
|
110
|
+
if [[ "$*" == *"--zift"* ]]; then
|
|
111
|
+
pkg=$(echo "$@" | sed 's/install//g; s/ i //g; s/--zift//g' | xargs)
|
|
112
|
+
npx @7nsane/zift install $pkg
|
|
113
|
+
else
|
|
114
|
+
command npm "$@"
|
|
115
|
+
fi
|
|
116
|
+
}
|
|
117
|
+
`;
|
|
118
|
+
const home = os.homedir();
|
|
119
|
+
const profiles = [path.join(home, '.bashrc'), path.join(home, '.zshrc')];
|
|
120
|
+
|
|
121
|
+
profiles.forEach(p => {
|
|
122
|
+
if (fs.existsSync(p)) {
|
|
123
|
+
fs.appendFileSync(p, bashFunction);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
36
128
|
async function runLocalScan(targetDir, format) {
|
|
37
129
|
const scanner = new PackageScanner(targetDir);
|
|
38
|
-
if (format === 'text') {
|
|
39
|
-
process.stdout.write(chalk.blue(`\n🔍 Scanning local directory at ${path.resolve(targetDir)}...\n`));
|
|
40
|
-
}
|
|
130
|
+
if (format === 'text') console.log(chalk.blue(`\n🔍 Scanning local directory: ${path.resolve(targetDir)}`));
|
|
41
131
|
|
|
42
132
|
try {
|
|
43
133
|
const findings = await scanner.scan();
|
|
44
134
|
handleFindings(findings, format, targetDir);
|
|
45
|
-
} catch (err) {
|
|
46
|
-
handleError(err, format);
|
|
47
|
-
}
|
|
135
|
+
} catch (err) { handleError(err, format); }
|
|
48
136
|
}
|
|
49
137
|
|
|
50
|
-
async function runRemoteAudit(packageName, format) {
|
|
51
|
-
if (format === 'text') {
|
|
52
|
-
console.log(chalk.blue(`\n🌍 Remote Audit: Attempting to pre-scan package '${packageName}'...`));
|
|
53
|
-
}
|
|
54
|
-
|
|
138
|
+
async function runRemoteAudit(packageName, format, installOnSuccess) {
|
|
139
|
+
if (format === 'text') console.log(chalk.blue(`\n🌍 Remote Audit: Pre-scanning package '${packageName}'...`));
|
|
55
140
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zift-audit-'));
|
|
56
141
|
|
|
57
142
|
try {
|
|
58
|
-
// Download tarball
|
|
59
143
|
cp.execSync(`npm pack ${packageName}`, { cwd: tmpDir, stdio: 'ignore' });
|
|
60
144
|
const tarball = fs.readdirSync(tmpDir).find(f => f.endsWith('.tgz'));
|
|
61
|
-
|
|
62
|
-
// Extract
|
|
63
145
|
cp.execSync(`tar -xzf ${tarball}`, { cwd: tmpDir });
|
|
64
|
-
const scanPath = path.join(tmpDir, 'package');
|
|
65
146
|
|
|
147
|
+
const scanPath = path.join(tmpDir, 'package');
|
|
66
148
|
const scanner = new PackageScanner(scanPath);
|
|
67
149
|
const findings = await scanner.scan();
|
|
68
|
-
|
|
69
|
-
// Custom reporting for remote audit
|
|
70
|
-
if (format === 'text') {
|
|
71
|
-
console.log(chalk.green(`✅ Pre-scan of '${packageName}' complete.`));
|
|
72
|
-
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')));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
150
|
handleFindings(findings, format, scanPath, true);
|
|
80
151
|
|
|
81
|
-
// Interactive Prompt
|
|
82
152
|
if (format === 'text') {
|
|
83
153
|
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(`\
|
|
154
|
+
const promptText = findings.length > 0
|
|
155
|
+
? chalk.yellow(`\n⚠️ Suspicious patterns found. Still install '${packageName}'? (y/n): `)
|
|
156
|
+
: chalk.blue(`\nAudit passed. Proceed with installation of '${packageName}'? (y/n): `);
|
|
87
157
|
|
|
88
|
-
rl.question(
|
|
158
|
+
rl.question(promptText, (answer) => {
|
|
89
159
|
rl.close();
|
|
90
|
-
if (
|
|
160
|
+
if (['y', 'yes'].includes(answer.toLowerCase())) {
|
|
91
161
|
console.log(chalk.blue(`\n📦 Installing ${packageName}...`));
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.log(chalk.green(`\n✅ ${packageName} installed successfully.`));
|
|
95
|
-
} catch (e) {
|
|
96
|
-
console.error(chalk.red(`\n❌ Installation failed.`));
|
|
97
|
-
}
|
|
98
|
-
cleanupAndExit(tmpDir, 0);
|
|
99
|
-
} else {
|
|
100
|
-
console.log(chalk.red(`\n❌ Installation aborted by user.`));
|
|
101
|
-
cleanupAndExit(tmpDir, 0);
|
|
162
|
+
cp.execSync(`npm install ${packageName}`, { stdio: 'inherit' });
|
|
163
|
+
console.log(chalk.green(`\n✅ ${packageName} installed successfully.`));
|
|
102
164
|
}
|
|
165
|
+
cleanupAndExit(tmpDir, 0);
|
|
103
166
|
});
|
|
104
|
-
} else {
|
|
105
|
-
cleanupAndExit(tmpDir, 0);
|
|
106
|
-
}
|
|
107
|
-
|
|
167
|
+
} else { cleanupAndExit(tmpDir, 0); }
|
|
108
168
|
} catch (err) {
|
|
109
|
-
console.error(chalk.red(`\n❌ Remote Audit failed: Ensure '${packageName}' exists on npm.`));
|
|
110
169
|
cleanupAndExit(tmpDir, 1);
|
|
111
170
|
}
|
|
112
171
|
}
|
|
113
172
|
|
|
114
173
|
function handleFindings(findings, format, targetDir, skipExit = false) {
|
|
115
174
|
if (format === 'json') {
|
|
116
|
-
|
|
117
|
-
target: targetDir,
|
|
118
|
-
timestamp: new Date().toISOString(),
|
|
119
|
-
findings: findings,
|
|
120
|
-
summary: getSummary(findings)
|
|
121
|
-
}, null, 2));
|
|
175
|
+
process.stdout.write(JSON.stringify({ target: targetDir, findings, summary: getSummary(findings) }, null, 2));
|
|
122
176
|
if (!skipExit) process.exit(findings.some(f => f.score >= 90) ? 1 : 0);
|
|
123
177
|
return;
|
|
124
178
|
}
|
|
125
|
-
|
|
126
179
|
if (findings.length === 0) {
|
|
127
|
-
if (!skipExit) {
|
|
128
|
-
console.log(chalk.green('\n✅ No suspicious patterns detected. All modules within safety thresholds.'));
|
|
129
|
-
process.exit(0);
|
|
130
|
-
}
|
|
180
|
+
if (!skipExit) { console.log(chalk.green('\n✅ No suspicious patterns detected. All modules safe.')); process.exit(0); }
|
|
131
181
|
return;
|
|
132
182
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
}
|
|
183
|
+
findings.forEach(f => {
|
|
184
|
+
const color = { 'Critical': chalk.red.bold, 'High': chalk.red, 'Medium': chalk.yellow, 'Low': chalk.blue }[f.classification];
|
|
185
|
+
console.log(color(`[${f.classification}] ${f.id} ${f.name} (Score: ${f.score})`));
|
|
186
|
+
f.triggers.forEach(t => console.log(chalk.white(` - ${t.type} in ${t.file}:${t.line} [${t.context}]`)));
|
|
150
187
|
console.log('');
|
|
151
188
|
});
|
|
152
|
-
|
|
153
189
|
printSummary(findings);
|
|
190
|
+
if (!skipExit) process.exit(findings[0].score >= 90 ? 1 : 0);
|
|
191
|
+
}
|
|
154
192
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console.log(chalk.green(`\n✔ Safety check completed with minor warnings.\n`));
|
|
162
|
-
process.exit(0);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
193
|
+
function showHelp() {
|
|
194
|
+
console.log(chalk.blue.bold('\n🛡️ Zift - The Elite Security Scanner\n'));
|
|
195
|
+
console.log('Usage:');
|
|
196
|
+
console.log(' zift setup Configure secure npm aliases');
|
|
197
|
+
console.log(' zift install <pkg> Scan and prompt before installing');
|
|
198
|
+
console.log(' zift . Scan current directory');
|
|
165
199
|
}
|
|
166
200
|
|
|
167
201
|
function cleanupAndExit(dir, code) {
|
|
168
202
|
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
|
|
169
|
-
|
|
203
|
+
process.exit(code);
|
|
170
204
|
}
|
|
171
205
|
|
|
172
206
|
function handleError(err, format) {
|
|
173
|
-
|
|
174
|
-
console.error(JSON.stringify({ error: err.message }));
|
|
175
|
-
} else {
|
|
176
|
-
console.error(chalk.red(`\n❌ Fatal Error: ${err.message}`));
|
|
177
|
-
}
|
|
207
|
+
console.error(chalk.red(`\n❌ Error: ${err.message}`));
|
|
178
208
|
process.exit(1);
|
|
179
209
|
}
|
|
180
210
|
|
|
181
211
|
function getSummary(findings) {
|
|
182
|
-
const
|
|
183
|
-
findings.forEach(f =>
|
|
184
|
-
|
|
185
|
-
summary[f.classification]++;
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
return summary;
|
|
212
|
+
const s = { Critical: 0, High: 0, Medium: 0, Low: 0 };
|
|
213
|
+
findings.forEach(f => s[f.classification]++);
|
|
214
|
+
return s;
|
|
189
215
|
}
|
|
190
216
|
|
|
191
217
|
function printSummary(findings) {
|
|
192
218
|
const s = getSummary(findings);
|
|
193
219
|
console.log(chalk.bold('Severity Summary:'));
|
|
194
|
-
console.log(chalk.red(` Critical: ${s.Critical}`));
|
|
195
|
-
console.log(chalk.red(` High: ${s.High}`));
|
|
196
|
-
console.log(chalk.yellow(` Medium: ${s.Medium}`));
|
|
197
|
-
console.log(chalk.blue(` Low: ${s.Low}`));
|
|
220
|
+
console.log(chalk.red(` Critical: ${s.Critical}\n High: ${s.High}`));
|
|
198
221
|
}
|
|
199
222
|
|
|
200
223
|
main();
|