muaddib-scanner 2.7.0 → 2.7.4
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/package.json +5 -3
- package/src/ioc/scraper.js +7 -1
- package/src/scanner/ast-detectors.js +33 -1
- package/src/scoring.js +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muaddib-scanner",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.4",
|
|
4
4
|
"description": "Supply-chain threat detection & response for npm & PyPI/Python",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -48,8 +48,10 @@
|
|
|
48
48
|
"acorn": "8.16.0",
|
|
49
49
|
"acorn-walk": "8.3.5",
|
|
50
50
|
"adm-zip": "0.5.16",
|
|
51
|
-
"js-yaml": "4.1.1"
|
|
52
|
-
|
|
51
|
+
"js-yaml": "4.1.1"
|
|
52
|
+
},
|
|
53
|
+
"overrides": {
|
|
54
|
+
"loadash": "0.0.0-security"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
57
|
"@eslint/js": "10.0.1",
|
package/src/ioc/scraper.js
CHANGED
|
@@ -8,7 +8,7 @@ const IOC_FILE = path.join(__dirname, 'data/iocs.json');
|
|
|
8
8
|
const COMPACT_IOC_FILE = path.join(__dirname, 'data/iocs-compact.json');
|
|
9
9
|
const HOME_IOC_FILE = path.join(os.homedir(), '.muaddib', 'data', 'iocs.json');
|
|
10
10
|
const STATIC_IOCS_FILE = path.join(__dirname, '../../data/static-iocs.json');
|
|
11
|
-
const { generateCompactIOCs } = require('./updater.js');
|
|
11
|
+
const { generateCompactIOCs, NEVER_WILDCARD } = require('./updater.js');
|
|
12
12
|
const { Spinner } = require('../utils.js');
|
|
13
13
|
const { NPM_PACKAGE_REGEX } = require('../shared/constants.js');
|
|
14
14
|
|
|
@@ -1146,11 +1146,17 @@ async function runScraper() {
|
|
|
1146
1146
|
let addedPackages = 0;
|
|
1147
1147
|
let upgradedPackages = 0;
|
|
1148
1148
|
let skippedInvalid = 0;
|
|
1149
|
+
let skippedNeverWildcard = 0;
|
|
1149
1150
|
for (const pkg of allPackages) {
|
|
1150
1151
|
if (!validateIOCEntry(pkg.name, pkg.version, 'npm')) {
|
|
1151
1152
|
skippedInvalid++;
|
|
1152
1153
|
continue;
|
|
1153
1154
|
}
|
|
1155
|
+
// Skip wildcard entries for packages that must stay version-specific
|
|
1156
|
+
if (pkg.version === '*' && NEVER_WILDCARD.has(pkg.name)) {
|
|
1157
|
+
skippedNeverWildcard++;
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1154
1160
|
const key = pkg.name + '@' + pkg.version;
|
|
1155
1161
|
if (!dedupMap.has(key)) {
|
|
1156
1162
|
dedupMap.set(key, pkg);
|
|
@@ -143,6 +143,22 @@ const MCP_CONFIG_PATHS = [
|
|
|
143
143
|
// MCP content indicators in written data
|
|
144
144
|
const MCP_CONTENT_PATTERNS = ['mcpServers', '"mcp"', '"server"', '"command"', '"args"'];
|
|
145
145
|
|
|
146
|
+
// Sensitive AI config files — writes to these are always suspicious regardless of content.
|
|
147
|
+
// Split into two tiers:
|
|
148
|
+
// - UNIQUE: filenames no legitimate plugin would use (always sensitive)
|
|
149
|
+
// - ROOT_ONLY: generic names (settings.json) that are sensitive ONLY when directly
|
|
150
|
+
// at config dir root (e.g. ~/.claude/settings.json), not in subdirectories
|
|
151
|
+
// (e.g. ~/.claude/my-plugin/settings.json which is legitimate plugin config)
|
|
152
|
+
const SENSITIVE_AI_CONFIG_FILES_UNIQUE = [
|
|
153
|
+
'claude.md', 'claude_desktop_config.json',
|
|
154
|
+
'mcp.json',
|
|
155
|
+
'.cursorrules', '.windsurfrules',
|
|
156
|
+
'copilot-instructions.md'
|
|
157
|
+
];
|
|
158
|
+
const SENSITIVE_AI_CONFIG_FILES_ROOT_ONLY = [
|
|
159
|
+
'settings.json', 'settings.local.json'
|
|
160
|
+
];
|
|
161
|
+
|
|
146
162
|
// Git hooks names
|
|
147
163
|
const GIT_HOOKS = [
|
|
148
164
|
'pre-commit', 'pre-push', 'post-checkout', 'post-merge',
|
|
@@ -874,9 +890,25 @@ function handleCallExpression(node, ctx) {
|
|
|
874
890
|
// Check content argument for MCP-related patterns
|
|
875
891
|
const contentArg = node.arguments[1];
|
|
876
892
|
const contentStr = extractStringValue(contentArg);
|
|
893
|
+
// Extract filename from path to distinguish sensitive config files from plugin state
|
|
894
|
+
const mcpFileName = mcpCheckPath.split(/[/\\]/).filter(Boolean).pop() || '';
|
|
895
|
+
const isUniqueSensitive = SENSITIVE_AI_CONFIG_FILES_UNIQUE.some(f => mcpFileName === f);
|
|
896
|
+
// For generic names (settings.json), only sensitive at config dir root (1 level deep)
|
|
897
|
+
// e.g. .claude/settings.json → sensitive, .claude/plugin/settings.json → not sensitive
|
|
898
|
+
let isRootOnlySensitive = false;
|
|
899
|
+
if (SENSITIVE_AI_CONFIG_FILES_ROOT_ONLY.some(f => mcpFileName === f)) {
|
|
900
|
+
const matchedDir = MCP_CONFIG_PATHS.find(p => mcpCheckPath.includes(p.toLowerCase()));
|
|
901
|
+
if (matchedDir) {
|
|
902
|
+
const idx = mcpCheckPath.indexOf(matchedDir.toLowerCase());
|
|
903
|
+
const afterDir = mcpCheckPath.slice(idx + matchedDir.length);
|
|
904
|
+
// Direct child: no further path separators (e.g. "settings.json", not "sub/settings.json")
|
|
905
|
+
isRootOnlySensitive = !afterDir.includes('/') && !afterDir.includes('\\');
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const isSensitiveConfigFile = isUniqueSensitive || isRootOnlySensitive;
|
|
877
909
|
const hasContentPattern = contentStr
|
|
878
910
|
? MCP_CONTENT_PATTERNS.some(p => contentStr.includes(p.replace(/"/g, '')))
|
|
879
|
-
:
|
|
911
|
+
: isSensitiveConfigFile; // dynamic content only suspicious for known config files
|
|
880
912
|
if (hasContentPattern) {
|
|
881
913
|
ctx.threats.push({
|
|
882
914
|
type: 'mcp_config_injection',
|
package/src/scoring.js
CHANGED
|
@@ -167,7 +167,10 @@ const DIST_BUNDLER_ARTIFACT_TYPES = new Set([
|
|
|
167
167
|
'env_access',
|
|
168
168
|
// P8: Proxy traps in dist/ are state management frameworks (MobX, Vue reactivity, Immer),
|
|
169
169
|
// not malicious data interception. Two-notch downgrade (CRITICAL→MEDIUM, HIGH→LOW).
|
|
170
|
-
'proxy_data_intercept'
|
|
170
|
+
'proxy_data_intercept',
|
|
171
|
+
// P9: fetch+eval in dist/ is Vite/Webpack code splitting (lazy chunk loading),
|
|
172
|
+
// not remote code execution. Two-notch downgrade (CRITICAL→MEDIUM, HIGH→LOW).
|
|
173
|
+
'remote_code_load'
|
|
171
174
|
]);
|
|
172
175
|
|
|
173
176
|
// Types exempt from reachability downgrade — IOC matches, lifecycle, and package-level types.
|