npxconfuse 1.0.0 → 1.1.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/cli.js +3 -1
- package/package.json +3 -3
- package/src/sources/github.js +41 -9
- package/src/utils/constants.js +1 -1
package/bin/cli.js
CHANGED
|
@@ -85,8 +85,9 @@ program
|
|
|
85
85
|
program
|
|
86
86
|
.command("github <org>")
|
|
87
87
|
.description("Scan a GitHub organization for npx confusion vulnerabilities")
|
|
88
|
-
.option("--max-repos <number>", "Maximum repos to
|
|
88
|
+
.option("--max-repos <number>", "Maximum repos to fetch", "5000")
|
|
89
89
|
.option("--github-enterprise <url>", "GitHub Enterprise base URL")
|
|
90
|
+
.option("--all", "Scan ALL repos regardless of language")
|
|
90
91
|
.action(async (org, cmdOpts) => {
|
|
91
92
|
const opts = { ...program.opts(), ...cmdOpts };
|
|
92
93
|
logger.banner();
|
|
@@ -97,6 +98,7 @@ program
|
|
|
97
98
|
const files = await scanGitHub(org, {
|
|
98
99
|
maxRepos: parseInt(opts.maxRepos, 10),
|
|
99
100
|
githubEnterprise: opts.githubEnterprise,
|
|
101
|
+
allLanguages: opts.all || false,
|
|
100
102
|
});
|
|
101
103
|
spinner.succeed(`Found ${files.length} files from GitHub`);
|
|
102
104
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "npxconfuse",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Detect npx confusion vulnerabilities — find unclaimed npm package names in your codebase, GitHub orgs, and web domains before attackers do.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"npxconfuse": "
|
|
7
|
+
"npxconfuse": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./src/analyzer.js",
|
|
10
10
|
"files": [
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
33
|
-
"url": "https://github.com/cybershaykh/npxconfuse"
|
|
33
|
+
"url": "git+https://github.com/cybershaykh/npxconfuse.git"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|
package/src/sources/github.js
CHANGED
|
@@ -3,14 +3,21 @@ import pLimit from "p-limit";
|
|
|
3
3
|
import logger from "../utils/logger.js";
|
|
4
4
|
import { GITHUB_DEFAULTS } from "../utils/constants.js";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Languages that indicate a JavaScript/Node.js project.
|
|
8
|
+
*/
|
|
9
|
+
const JS_LANGUAGES = new Set(["JavaScript", "TypeScript"]);
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Scan a GitHub organization for package.json manifests.
|
|
13
|
+
* Automatically filters repos to JavaScript/TypeScript projects.
|
|
8
14
|
*
|
|
9
15
|
* @param {string} org - GitHub organization name
|
|
10
16
|
* @param {object} options
|
|
11
|
-
* @param {number} options.maxRepos - Max repos to scan (default
|
|
12
|
-
* @param {number} options.concurrency - Parallel repo scans (default
|
|
17
|
+
* @param {number} options.maxRepos - Max repos to scan (default 5000)
|
|
18
|
+
* @param {number} options.concurrency - Parallel repo scans (default 10)
|
|
13
19
|
* @param {string} options.githubEnterprise - Base URL for GHE
|
|
20
|
+
* @param {boolean} options.allLanguages - Scan ALL repos regardless of language
|
|
14
21
|
* @returns {Promise<Array<{filepath: string, content: string, type: string}>>}
|
|
15
22
|
*/
|
|
16
23
|
export async function scanGitHub(org, options = {}) {
|
|
@@ -30,7 +37,8 @@ export async function scanGitHub(org, options = {}) {
|
|
|
30
37
|
|
|
31
38
|
const octokit = new Octokit(octokitOpts);
|
|
32
39
|
const maxRepos = options.maxRepos || GITHUB_DEFAULTS.maxRepos;
|
|
33
|
-
const concurrency = options.concurrency ||
|
|
40
|
+
const concurrency = options.concurrency || 10;
|
|
41
|
+
const allLanguages = options.allLanguages || false;
|
|
34
42
|
const limit = pLimit(concurrency);
|
|
35
43
|
|
|
36
44
|
logger.info(`Scanning GitHub organization: ${org}`);
|
|
@@ -71,13 +79,37 @@ export async function scanGitHub(org, options = {}) {
|
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
logger.info(`
|
|
82
|
+
logger.info(`Fetched ${repos.length} total repositories in ${org}`);
|
|
83
|
+
|
|
84
|
+
// ── 2. Filter to JavaScript/TypeScript repos ──
|
|
85
|
+
let targets;
|
|
86
|
+
if (allLanguages) {
|
|
87
|
+
targets = repos;
|
|
88
|
+
} else {
|
|
89
|
+
targets = repos.filter((repo) => JS_LANGUAGES.has(repo.language));
|
|
90
|
+
const skipped = repos.length - targets.length;
|
|
91
|
+
const langBreakdown = {};
|
|
92
|
+
for (const r of repos) {
|
|
93
|
+
const lang = r.language || "Unknown";
|
|
94
|
+
langBreakdown[lang] = (langBreakdown[lang] || 0) + 1;
|
|
95
|
+
}
|
|
96
|
+
logger.info(
|
|
97
|
+
`Filtered to ${targets.length} JavaScript/TypeScript repos ` +
|
|
98
|
+
`(skipped ${skipped} non-JS repos)`,
|
|
99
|
+
);
|
|
100
|
+
logger.debug(`Language breakdown: ${JSON.stringify(langBreakdown)}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (targets.length === 0) {
|
|
104
|
+
logger.warn("No JavaScript/TypeScript repos found in this org.");
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
75
107
|
|
|
76
|
-
// ──
|
|
108
|
+
// ── 3. Scan each JS repo for package.json ──
|
|
77
109
|
const results = [];
|
|
78
110
|
let processed = 0;
|
|
79
111
|
|
|
80
|
-
const tasks =
|
|
112
|
+
const tasks = targets.map((repo) =>
|
|
81
113
|
limit(async () => {
|
|
82
114
|
try {
|
|
83
115
|
const repoFiles = await scanRepo(octokit, org, repo.name);
|
|
@@ -87,9 +119,9 @@ export async function scanGitHub(org, options = {}) {
|
|
|
87
119
|
}
|
|
88
120
|
|
|
89
121
|
processed++;
|
|
90
|
-
if (processed % 10 === 0 || processed ===
|
|
122
|
+
if (processed % 10 === 0 || processed === targets.length) {
|
|
91
123
|
logger.info(
|
|
92
|
-
`Progress: ${processed}/${
|
|
124
|
+
`Progress: ${processed}/${targets.length} JS repos scanned (${results.length} files found)`,
|
|
93
125
|
);
|
|
94
126
|
}
|
|
95
127
|
}),
|
|
@@ -97,7 +129,7 @@ export async function scanGitHub(org, options = {}) {
|
|
|
97
129
|
|
|
98
130
|
await Promise.all(tasks);
|
|
99
131
|
logger.success(
|
|
100
|
-
`GitHub scan complete: ${results.length} files from ${
|
|
132
|
+
`GitHub scan complete: ${results.length} files from ${targets.length} JS repos`,
|
|
101
133
|
);
|
|
102
134
|
|
|
103
135
|
return results;
|
package/src/utils/constants.js
CHANGED