@refentse/gatekeeper-cli 1.0.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/README.md +0 -0
- package/index.js +65 -0
- package/package.json +28 -0
- package/src/scanner.js +71 -0
package/README.md
ADDED
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { checkPackage } from './src/scanner.js';
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.version('1.0.0')
|
|
11
|
+
.description('npm-gatekeeper: Zero-Trust Package Management');
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command('install <package>')
|
|
15
|
+
.description('Intercepts and safely installs an npm package')
|
|
16
|
+
.action(async (packageName) => {
|
|
17
|
+
|
|
18
|
+
// 1. Run Gatekeeper Heuristics
|
|
19
|
+
const scanResult = await checkPackage(packageName);
|
|
20
|
+
|
|
21
|
+
// 2. The Interception Logic
|
|
22
|
+
if (scanResult.hardBlock) {
|
|
23
|
+
console.log(chalk.bgRed.white.bold('\n š« HARD BLOCK ACTIVATED. Installation aborted to protect your environment. '));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!scanResult.isSafe) {
|
|
28
|
+
const answers = await inquirer.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: 'confirm',
|
|
31
|
+
name: 'override',
|
|
32
|
+
message: chalk.red.bold('Threats detected. Do you want to OVERRIDE and install anyway?'),
|
|
33
|
+
default: false
|
|
34
|
+
}
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
if (!answers.override) {
|
|
38
|
+
console.log(chalk.red.bold('\nš« Installation blocked by Gatekeeper. Your machine is safe.'));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. Pass-Through Execution
|
|
44
|
+
console.log(chalk.cyan(`\nš Handing off to native npm to install ${packageName}...\n`));
|
|
45
|
+
|
|
46
|
+
// THE FIX: Explicitly call cmd.exe on Windows to avoid the shell: true warning
|
|
47
|
+
const isWindows = process.platform === 'win32';
|
|
48
|
+
const command = isWindows ? 'cmd.exe' : 'npm';
|
|
49
|
+
const args = isWindows ? ['/c', 'npm', 'install', packageName] : ['install', packageName];
|
|
50
|
+
|
|
51
|
+
const child = spawn(command, args, {
|
|
52
|
+
stdio: 'inherit',
|
|
53
|
+
shell: false // Kept false for maximum security
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
child.on('close', (code) => {
|
|
57
|
+
if (code === 0) {
|
|
58
|
+
console.log(chalk.green(`\nš gatekeeper: ${packageName} installed successfully.`));
|
|
59
|
+
} else {
|
|
60
|
+
console.log(chalk.red(`\nā native npm exited with error code ${code}`));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@refentse/gatekeeper-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zero-Trust Package Management",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"gatekeeper": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"security",
|
|
15
|
+
"zero-trust",
|
|
16
|
+
"npm",
|
|
17
|
+
"malware",
|
|
18
|
+
"osv",
|
|
19
|
+
"devsecops"
|
|
20
|
+
],
|
|
21
|
+
"author": "Quincy Refentse Moeng",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"chalk": "^5.6.2",
|
|
25
|
+
"commander": "^14.0.3",
|
|
26
|
+
"inquirer": "^13.3.2"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/scanner.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export async function checkPackage(packageName) {
|
|
4
|
+
console.log(chalk.blue(`\nš Gatekeeper is inspecting metadata for: ${packageName}...`));
|
|
5
|
+
|
|
6
|
+
let isSafe = true;
|
|
7
|
+
let hardBlock = false;
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}`);
|
|
11
|
+
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
throw new Error(`Registry returned status ${response.status}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const fullMetadata = await response.json();
|
|
17
|
+
const latestVersion = fullMetadata['dist-tags'].latest;
|
|
18
|
+
const latestData = fullMetadata.versions[latestVersion];
|
|
19
|
+
const scripts = latestData.scripts || {};
|
|
20
|
+
|
|
21
|
+
// š”ļø CHECK 1: Google OSV Vulnerability Database (HARD BLOCK)
|
|
22
|
+
console.log(chalk.gray(` Checking Google OSV database for known vulnerabilities...`));
|
|
23
|
+
const osvResponse = await fetch('https://api.osv.dev/v1/query', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
version: latestVersion,
|
|
28
|
+
package: { name: packageName, ecosystem: "npm" }
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (osvResponse.ok) {
|
|
33
|
+
const osvData = await osvResponse.json();
|
|
34
|
+
if (osvData.vulns && osvData.vulns.length > 0) {
|
|
35
|
+
console.log(chalk.red.bold(`\nšØ CRITICAL: KNOWN MALWARE DETECTED IN OSV DATABASE šØ`));
|
|
36
|
+
console.log(chalk.red(` Package: ${packageName}@${latestVersion}`));
|
|
37
|
+
console.log(chalk.red(` Vulnerabilities found: ${osvData.vulns.length}`));
|
|
38
|
+
console.log(chalk.red(` This payload will be hard-blocked. No override permitted.`));
|
|
39
|
+
return { isSafe: false, hardBlock: true };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// šØ CHECK 2: Malicious Lifecycle Scripts
|
|
44
|
+
if (scripts.postinstall || scripts.preinstall || scripts.install) {
|
|
45
|
+
console.log(chalk.red.bold(`\nā ļø WARNING: ${packageName} contains hidden installation scripts.`));
|
|
46
|
+
console.log(chalk.yellow(` Script found: ${scripts.postinstall || scripts.preinstall || scripts.install}`));
|
|
47
|
+
isSafe = false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// š CHECK 3: Package Age
|
|
51
|
+
const publishTimeString = fullMetadata.time[latestVersion];
|
|
52
|
+
const publishTime = new Date(publishTimeString || Date.now());
|
|
53
|
+
const ageInHours = (new Date() - publishTime) / (1000 * 60 * 60);
|
|
54
|
+
|
|
55
|
+
if (ageInHours < 48) {
|
|
56
|
+
console.log(chalk.yellow(`\nā ļø NEW PACKAGE ALERT: Version ${latestVersion} was published only ${Math.round(ageInHours)} hours ago.`));
|
|
57
|
+
isSafe = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (isSafe) {
|
|
61
|
+
console.log(chalk.green(`ā
${packageName}@${latestVersion} passed heuristics scan. No obvious threats detected.`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { isSafe, hardBlock };
|
|
65
|
+
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(chalk.red(`\nā Could not fetch package data for '${packageName}'.`));
|
|
68
|
+
console.error(chalk.gray(` Error details: ${error.message}`));
|
|
69
|
+
return { isSafe: false, hardBlock: false };
|
|
70
|
+
}
|
|
71
|
+
}
|