fix(security): block plugin install/load on critical source scan findings#11032
fix(security): block plugin install/load on critical source scan findings#11032coygeek wants to merge 1 commit intoopenclaw:mainfrom
Conversation
…ings The existing skill-scanner was invoked during plugin installation but operated in warn-only mode, allowing plugins with dangerous code patterns (eval, child_process exec, crypto mining, env harvesting) to install and load without restriction. This change: - Makes installPluginFromPackageDir() block installation when critical scan findings are detected (with skipScan escape hatch for --force) - Adds source scanning to installPluginFromFile() which previously had no scanning at all - Adds pre-load scanning in the plugin loader before jiti() execution, preventing already-installed malicious plugins from being loaded - Propagates skipScan parameter through all install API entry points Scan failures are non-blocking (logged as warnings) to avoid breaking legitimate plugins due to scanner errors. Co-Authored-By: Claude Opus 4.6 <[email protected]>
| // Scan plugin source for dangerous code patterns before loading | ||
| if (isScannable(candidate.source)) { | ||
| try { | ||
| const rawSource = fs.readFileSync(candidate.source, "utf-8"); | ||
| const scanFindings = scanSource(rawSource, candidate.source); | ||
| const criticalFindings = scanFindings.filter((f) => f.severity === "critical"); | ||
| if (criticalFindings.length > 0) { | ||
| const details = criticalFindings | ||
| .map((f) => `${f.message} (line ${f.line})`) | ||
| .join("; "); | ||
| logger.error( | ||
| `[plugins] ${pluginId} blocked from loading: dangerous code patterns detected in ${candidate.source}: ${details}`, | ||
| ); | ||
| record.status = "error"; | ||
| record.error = `blocked: dangerous code patterns detected: ${details}`; | ||
| registry.plugins.push(record); | ||
| seenIds.set(pluginId, candidate.origin); | ||
| registry.diagnostics.push({ | ||
| level: "error", | ||
| pluginId: record.id, | ||
| source: record.source, | ||
| message: record.error, | ||
| }); | ||
| continue; | ||
| } | ||
| } catch { | ||
| // Scan failure should not prevent loading — log and continue | ||
| logger.warn(`[plugins] ${pluginId} source scan failed for ${candidate.source}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
Crash on scan error
The new pre-load scan path uses logger.warn(...) in the catch block, but logger comes from options.logger ?? defaultLogger() where the PluginLogger type is optional methods in some call sites. Elsewhere in this file you consistently use optional chaining (e.g. logger.error?.(...)). If logger.warn is undefined, this catch block will throw and abort plugin loading whenever scanning throws.
| // Scan plugin source for dangerous code patterns before loading | |
| if (isScannable(candidate.source)) { | |
| try { | |
| const rawSource = fs.readFileSync(candidate.source, "utf-8"); | |
| const scanFindings = scanSource(rawSource, candidate.source); | |
| const criticalFindings = scanFindings.filter((f) => f.severity === "critical"); | |
| if (criticalFindings.length > 0) { | |
| const details = criticalFindings | |
| .map((f) => `${f.message} (line ${f.line})`) | |
| .join("; "); | |
| logger.error( | |
| `[plugins] ${pluginId} blocked from loading: dangerous code patterns detected in ${candidate.source}: ${details}`, | |
| ); | |
| record.status = "error"; | |
| record.error = `blocked: dangerous code patterns detected: ${details}`; | |
| registry.plugins.push(record); | |
| seenIds.set(pluginId, candidate.origin); | |
| registry.diagnostics.push({ | |
| level: "error", | |
| pluginId: record.id, | |
| source: record.source, | |
| message: record.error, | |
| }); | |
| continue; | |
| } | |
| } catch { | |
| // Scan failure should not prevent loading — log and continue | |
| logger.warn(`[plugins] ${pluginId} source scan failed for ${candidate.source}`); | |
| } | |
| } | |
| logger.warn?.(`[plugins] ${pluginId} source scan failed for ${candidate.source}`); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/loader.ts
Line: 295:324
Comment:
**Crash on scan error**
The new pre-load scan path uses `logger.warn(...)` in the `catch` block, but `logger` comes from `options.logger ?? defaultLogger()` where the `PluginLogger` type is optional methods in some call sites. Elsewhere in this file you consistently use optional chaining (e.g. `logger.error?.(...)`). If `logger.warn` is undefined, this catch block will throw and abort plugin loading whenever scanning throws.
```suggestion
logger.warn?.(`[plugins] ${pluginId} source scan failed for ${candidate.source}`);
```
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (2)
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/plugins/install.test.ts
Line: 373:408
Comment:
**Behavior change breaks test**
`installPluginFromPackageDir` now blocks installs on *critical* scan findings unless `skipScan` is set, but this test still expects installs with `child_process.exec(...)` to succeed. As written, it will now fail deterministically because `dangerous-exec` is `critical` in `src/security/skill-scanner.ts`. Update the test to either expect a blocked install, or pass `skipScan: true` and assert the overridden-warning message.
How can I resolve this? If you propose a fix, please make it concise.
This test also expects the install to succeed while the extension entry contains Prompt To Fix With AIThis is a comment left during a code review.
Path: src/plugins/install.test.ts
Line: 410:445
Comment:
**Critical scan now blocks**
This test also expects the install to succeed while the extension entry contains `child_process.exec(...)`, which the scanner marks as `critical`. After this PR, `installPluginFromPackageDir` returns `{ ok: false }` unless `skipScan` is set, so this assertion will now fail. Adjust expectations (blocked vs overridden with `skipScan: true`) to match the new behavior.
How can I resolve this? If you propose a fix, please make it concise. |
bfc1ccb to
f92900f
Compare
|
This pull request has been automatically marked as stale due to inactivity. |
|
Closing: linked issue #11030 was closed as not planned. |
Fix Summary
Add source code scanning at both plugin install and load time. The existing
skill-scanner.tsmodule already detects dangerous patterns (child_process, eval/new Function, crypto mining, obfuscated code, env harvesting) but its findings were warn-only at install time and completely absent at load time.Changes:
scanDirectoryWithSummary()blocking on critical findings (was warn-only). Add scanning toinstallPluginFromFile()which previously had zero checks. AddskipScanparameter for--forceflag support.jiti(candidate.source)call. Block loading when critical findings detected.Issue Linkage
Fixes #11030
Security Snapshot
Implementation Details
Files Changed
src/plugins/install.ts(+52/-3)src/plugins/loader.ts(+32/-0)Technical Analysis
Add source code scanning at both plugin install and load time. The existing
skill-scanner.tsmodule already detects dangerous patterns (child_process, eval/new Function, crypto mining, obfuscated code, env harvesting) but its findings were warn-only at install time and completely absent at load time.Validation Evidence
install.tsRisk and Compatibility
non-breaking; compatibility impact was not explicitly documented in the original PR body.
AI-Assisted Disclosure
Greptile Overview
Greptile Summary
skipScanescape hatch.jiti()execution when critical patterns are detected.skill-scannerlogic into both install-time and load-time plugin workflows to reduce arbitrary code execution risk.Confidence Score: 3/5
Context used:
dashboard- CLAUDE.md (source)dashboard- AGENTS.md (source)