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 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 scan", "1000")
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.0.0",
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": "./bin/cli.js"
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"
@@ -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 1000)
12
- * @param {number} options.concurrency - Parallel repo scans (default 5)
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 || 5;
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(`Found ${repos.length} repositories in ${org}`);
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
- // ── 2. Search each repo for relevant files ──
108
+ // ── 3. Scan each JS repo for package.json ──
77
109
  const results = [];
78
110
  let processed = 0;
79
111
 
80
- const tasks = repos.slice(0, maxRepos).map((repo) =>
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 === repos.length) {
122
+ if (processed % 10 === 0 || processed === targets.length) {
91
123
  logger.info(
92
- `Progress: ${processed}/${repos.length} repos scanned (${results.length} files found)`,
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 ${repos.length} repos`,
132
+ `GitHub scan complete: ${results.length} files from ${targets.length} JS repos`,
101
133
  );
102
134
 
103
135
  return results;
@@ -177,5 +177,5 @@ export const WEB_PROBE_PATHS = [
177
177
  // GitHub API defaults
178
178
  export const GITHUB_DEFAULTS = {
179
179
  perPage: 100,
180
- maxRepos: 1000,
180
+ maxRepos: 5000,
181
181
  };