Skip to content

fix(scanner): resolve false positives blocking legit skills (#129)#131

Merged
rohitg00 merged 2 commits into
mainfrom
fix/scanner-false-positives
Jun 2, 2026
Merged

fix(scanner): resolve false positives blocking legit skills (#129)#131
rohitg00 merged 2 commits into
mainfrom
fix/scanner-false-positives

Conversation

@rohitg00

@rohitg00 rohitg00 commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Closes #129.

Summary

Four scanner rules over-fired on standard Node patterns and safety-documentation prose, blocking skillkit install on most node-script skills. Plus skillkit install <bare-name> failed to resolve marketplace catalog entries even when skillkit list showed them.

Verified against rohitg00/pro-workflow (42 skills, 37 Node hook scripts): all four critical/high false positives gone, install now succeeds without --force.

Changes

TA002 (autonomy abuse) — negation-aware

Patterns 2 and 3 use negative lookbehind for negation context. Restrictive prose no longer trips the rule.

Input Before After
Auto-approve all tools fires fires
**Never auto-approve** (high risk): fires (FP) clean
Each phase must require user approval fires (FP) clean
Don't ask for permission fires fires

CI003 (child_process) — usage-only

Dropped bare require('child_process') pattern. Importing the module is not the risk surface; exec() and execSync() are, and they have their own patterns. The safe form const { execFileSync } = require('child_process') is what every well-written Node hook uses.

CI005 (template literal injection) — shell-context only

Narrowed to template literals inside shell-spawning calls (exec/execSync/spawn/spawnSync). Previously fired on any `text ${var}` regardless of call context.

Input Before After
`Bearer ${process.env.TOKEN}` fires (FP) clean
fs.writeFileSync(f, `${a}${b}`) fires (FP) clean
exec(`rm -rf ${userInput}`) fires fires

CI007 (shell chaining) — bash files only

Dropped markdown from fileTypes. Documentation of dangerous patterns (deny-list tables, security guides) is documentation, not execution. Real .sh / .bash files still scanned. If a SKILL.md instructs the agent to run curl | sh, that is autonomy abuse (TA002), not command injection.

install: marketplace name lookup

skillkit install <bare-name> now consults marketplace/skills.json for entries matching by name or id and resolves to the canonical source string before falling back to the existing "Could not detect provider" error. Mirrors what skillkit list already reads from.

Test plan

  • New: 13 regression tests in packages/core/src/scanner/__tests__/rules.test.ts covering each rule's fires/no-fires matrix
  • Full core suite: 1048/1048 pass (1035 prior + 13 new)
  • Typecheck: clean across 22 workspaces
  • E2E: node apps/skillkit/dist/cli.js install ~/skills/pro-workflow --agent claude-code succeeds (42 skills installed, only 4 unrelated MEDIUM warnings on wiki-research-loop, no --force needed)
  • E2E: node apps/skillkit/dist/cli.js install "Vercel React Best Practices" resolves to vercel-labs/agent-skills via marketplace catalog

Out of scope

  • survey-generator still emits 4 MEDIUM CI005 warnings on legit template literals inside non-shell contexts — those are now correctly classified as warnings, not failures
  • No version bump in this PR; patch bump on merge per repo convention

Summary by CodeRabbit

  • New Features

    • Install command now resolves marketplace skill identifiers to their full sources automatically.
  • Bug Fixes

    • Improved security scanner rules to reduce false positives and better target command-injection and autonomy-abuse patterns.
  • Tests

    • Added targeted tests covering multiple security scanner rules and edge cases to ensure detection accuracy.

Four scanner rules over-fired on standard Node patterns and safety documentation, blocking install of skills like pro-workflow on every node-script skill. Findings tracked at #129.

TA002 (autonomy abuse): patterns 2 and 3 now use negative lookbehind for negation context. 'Never auto-approve' / 'must not bypass approval' no longer trip the rule, but bare 'Auto-approve all tools' still fires.

CI003 (child_process): dropped the bare 'require(child_process)' pattern. Importing the module is not the risk surface — exec() and execSync() are, and they have their own patterns. const { execFileSync } = require('child_process') is the recommended safe form.

CI005 (template literal injection): narrowed pattern to template literals inside shell-spawning calls (exec/execSync/spawn/spawnSync). Authorization headers like `Bearer ${env.TOKEN}` and string concatenation like fs.writeFileSync(file, `${header}${rows}`) are no longer flagged.

CI007 (shell chaining): dropped 'markdown' from fileTypes. Markdown documentation of dangerous shell patterns (deny-list tables, security guides) is documentation, not execution. Real .sh/.bash files still scanned.

install command: marketplace name lookup. 'skillkit install <bare-name>' now consults marketplace/skills.json for entries matching by name or id and resolves to the canonical source string before failing with 'Could not detect provider'.

Tests: 13 new scanner regression tests in packages/core/src/scanner/__tests__/rules.test.ts. Full suite 1048/1048 pass.
@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
skillkit Ignored Ignored Jun 1, 2026 11:50am
skillkit-docs Ignored Ignored Jun 1, 2026 11:50am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c1e71ed-d8dd-4cd3-af15-84f26459713f

📥 Commits

Reviewing files that changed from the base of the PR and between 45c94fd and 3e16ac2.

📒 Files selected for processing (3)
  • packages/cli/src/commands/install.ts
  • packages/core/src/scanner/__tests__/rules.test.ts
  • packages/core/src/scanner/rules/tool-abuse.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/scanner/rules/tool-abuse.ts
  • packages/core/src/scanner/tests/rules.test.ts
  • packages/cli/src/commands/install.ts

📝 Walkthrough

Walkthrough

Install now resolves bare marketplace skill names to catalog sources; scanner rules TA002/CI003/CI005/CI007 are narrowed to reduce false positives; tests assert correct triggering/non-triggering behavior for each refined rule.

Changes

Install marketplace skill resolution and scanner rule refinements

Layer / File(s) Summary
Install marketplace catalog resolution
packages/cli/src/commands/install.ts
Import skills.json and add a resolveSource step that looks up bare skill name/id in the marketplace and rewrites this.source to the catalog source when matched.
Security rule pattern refinements
packages/core/src/scanner/rules/tool-abuse.ts, packages/core/src/scanner/rules/command-injection.ts
TA002 adds negative lookbehind guards to avoid matching negation/restrictive language. CI003 removes a broad require('child_process') pattern and focuses on direct exec/execSync usage. CI005 replaces a broad template-literal match with two targeted interpolation-in-exec/spawn patterns and removes excludePatterns. CI007 restricts fileTypes to ['bash'].
Comprehensive security rule test coverage
packages/core/src/scanner/__tests__/rules.test.ts
Adds scanContent test helper and Vitest suites verifying TA002, CI003, CI005, and CI007 positive and negative cases (including markdown vs. bash, safe execFileSync, header templates, and exec interpolation).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A bunny's paws now parse the marketplace with care, 🐰
Negation-wise rules that know when NOT to scare,
Shell patterns scanned with focus, false positives spared,
Skills hop clean through security, wonderfully prepared! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: fixing scanner false positives and marketplace skill resolution, matching the core objectives.
Linked Issues check ✅ Passed All four objectives from issue #129 are addressed: bare-name marketplace resolution [install.ts], TA002 negation-awareness [tool-abuse.ts], CI007 markdown exclusion [command-injection.ts], and CI003/CI005 scope narrowing [command-injection.ts].
Out of Scope Changes check ✅ Passed All changes directly support the four stated objectives from issue #129; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/scanner-false-positives

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/core/src/scanner/__tests__/rules.test.ts (1)

7-15: ⚡ Quick win

Ensure temp directory cleanup runs on analyzer failure.

rm(...) should be in a finally block so failed scans don’t leak temp dirs.

Suggested fix
 async function scanContent(filename: string, content: string) {
   const dir = await mkdtemp(join(tmpdir(), 'skscan-'));
   const file = join(dir, filename);
-  await writeFile(file, content, 'utf-8');
-  const analyzer = new StaticAnalyzer();
-  const findings = await analyzer.analyze(dir, [file]);
-  await rm(dir, { recursive: true, force: true });
-  return findings;
+  try {
+    await writeFile(file, content, 'utf-8');
+    const analyzer = new StaticAnalyzer();
+    return await analyzer.analyze(dir, [file]);
+  } finally {
+    await rm(dir, { recursive: true, force: true });
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/scanner/__tests__/rules.test.ts` around lines 7 - 15, The
temp-dir cleanup in scanContent can leak if analyzer.analyze throws; wrap the
use of the temporary directory in a try/finally: create dir with mkdtemp, write
the file, then call analyzer.analyze inside a try block and move the rm(dir, {
recursive: true, force: true }) into the finally block so the temporary
directory is removed regardless of success or failure; reference scanContent,
mkdtemp, writeFile, StaticAnalyzer.analyze and rm when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/commands/install.ts`:
- Line 220: Guard the unconditional console.log that prints marketplace
resolution so it doesn't run in machine-readable/JSON or quiet modes: wrap the
console.log(colors.muted(`Resolved "${this.source}" to ${match.source} via
marketplace catalog`)) call with a condition that checks the CLI mode flags
(e.g. this.machineReadable || this.flags.json || this.flags.quiet) and only
print when none of those are set (i.e. if (!this.machineReadable &&
!this.flags.json && !this.flags.quiet) { ... }). Ensure you reference the same
symbols used elsewhere in this command class (install command instance
properties like this.machineReadable and this.flags.json/this.flags.quiet) so
the log is suppressed for --json/--quiet/machine-readable invocations.

In `@packages/core/src/scanner/rules/tool-abuse.ts`:
- Line 21: The negative lookbehind in the TA002 regex inside
packages/core/src/scanner/rules/tool-abuse.ts is too broad and prevents flagging
explicit malicious directives like "Always run without confirmation"; update the
regex used in that rule so it no longer suppresses matches when the phrase is
preceded by words such as "always", "will", or "to" — specifically, edit the
lookbehind attached to the pattern (?<!(?:must|should|always|will|to)\s+) to
remove or narrow the banned tokens (e.g., keep only true permissive modifiers
like must/should if intended) so "run|execute|proceed without confirmation"
still matches in cases like "Always run without confirmation"; ensure the
modified pattern remains case-insensitive and preserves other intended
suppressions.

---

Nitpick comments:
In `@packages/core/src/scanner/__tests__/rules.test.ts`:
- Around line 7-15: The temp-dir cleanup in scanContent can leak if
analyzer.analyze throws; wrap the use of the temporary directory in a
try/finally: create dir with mkdtemp, write the file, then call analyzer.analyze
inside a try block and move the rm(dir, { recursive: true, force: true }) into
the finally block so the temporary directory is removed regardless of success or
failure; reference scanContent, mkdtemp, writeFile, StaticAnalyzer.analyze and
rm when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b1261eb-a934-46bc-8764-6dbbfa3bfd56

📥 Commits

Reviewing files that changed from the base of the PR and between a9e5c83 and 45c94fd.

📒 Files selected for processing (4)
  • packages/cli/src/commands/install.ts
  • packages/core/src/scanner/__tests__/rules.test.ts
  • packages/core/src/scanner/rules/command-injection.ts
  • packages/core/src/scanner/rules/tool-abuse.ts

Comment thread packages/cli/src/commands/install.ts Outdated
Comment thread packages/core/src/scanner/rules/tool-abuse.ts Outdated
1. install.ts: gate marketplace-resolution console.log behind !this.json && !this.quiet so --json output stays a single JSON blob and --quiet stays silent. Uses real flag names from the InstallCommand class (this.json, this.quiet).

2. tool-abuse.ts: TA002 pattern 2 lookbehind was too broad. The original list (must|should|always|will|to) included malicious intensifiers — 'Always run without confirmation' and 'Will execute without approval' were incorrectly suppressed. Replaced with negation-only tokens (not|n'?t|never|avoid|forbid|deny|reject|prevent|cannot|can'?t) so genuine negation like 'Do not run without confirmation' still suppresses, but malicious intensifiers fire.

3. rules.test.ts: scanContent helper now uses try/finally so the temp directory is cleaned up even when analyzer.analyze throws.

Tests: 3 new cases for the TA002 lookbehind fix. Full core suite 1051/1051 pass.
@rohitg00 rohitg00 merged commit d2e5c34 into main Jun 2, 2026
7 checks passed
@rohitg00 rohitg00 deleted the fix/scanner-false-positives branch June 2, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skillkit install: bare-name resolution + scanner false positives block legit skills

1 participant