Skip to content

security: add 7 new rules to extension scanner#49

Merged
benvinegar merged 3 commits into
mainfrom
benvinegar/scanner-line-level-findings
Feb 18, 2026
Merged

security: add 7 new rules to extension scanner#49
benvinegar merged 3 commits into
mainfrom
benvinegar/scanner-line-level-findings

Conversation

@benvinegar
Copy link
Copy Markdown
Member

@benvinegar benvinegar commented Feb 18, 2026

Adds new detection rules to bin/scan-extensions.mjs:

Rule Severity What it catches
fs-write-outside-home critical writeFileSync('/etc/...') including template literals
privilege-escalation critical sudo/chmod/chown via child_process
network-listener warn createServer()/.listen(port) with http/net imports
prototype-pollution warn __proto__, Object.setPrototypeOf
unsafe-deserialization warn JSON.parse(req.body) on external input
credential-logging warn console.log(process.env.SECRET) (direct + multiline)
dynamic-require info require(variable) — dynamic module loading

Also fixes the template literal bypass in path matching (backtick was missing from the character class) and uses [\s\S]*? for cross-line regex matching in credential-logging.

25 tests (up from 15).

…d exports

- Add 5 new line rules: fs-write-outside-home, privilege-escalation,
  network-listener, prototype-pollution, unsafe-deserialization
- Add 2 new source rules: credential-logging, dynamic-require (info severity)
- Add --json flag for structured CI-consumable output
- Export scanSource(), LINE_RULES, SOURCE_RULES for programmatic use
- Add 7 new tests (22 total, up from 15)
Comment thread bin/scan-extensions.mjs Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 18, 2026

Greptile Summary

Enhances the extension/skill scanner with 7 new security rules (fs-write-outside-home, privilege-escalation, network-listener, prototype-pollution, unsafe-deserialization, credential-logging, dynamic-require), a --json output flag for CI consumption, and exported functions (scanSource, rule arrays) for programmatic use. Also introduces an info severity tier.

  • Template literal bypass: The fs-write-outside-home rule only matches " and ' quotes — backtick template literals bypass detection entirely.
  • Cross-line regex limitation: The credential-logging rule's requiresContext uses .* which doesn't cross newlines, so it only catches console.log(process.env.X) on a single line.
  • Misleading "All clean" with info findings: When only info-severity findings exist, the scanner prints "All clean" and exits 0, contradicting the info count shown in the summary.
  • Missing tests for 3 rules: credential-logging, unsafe-deserialization, and dynamic-require have no test coverage — the project's CLAUDE.md convention requires all security code to have tests before merging.

Confidence Score: 3/5

  • Functional improvements but has a regex bypass that weakens a critical security rule and is missing required test coverage for 3 new rules.
  • The template literal bypass in the fs-write-outside-home rule is a concrete gap in a critical-severity security check. Three new rules lack any test coverage, which violates the project's stated convention. The credential-logging regex limitation and misleading info-level output are lower severity but still worth addressing.
  • Pay close attention to bin/scan-extensions.mjs (rule regex patterns) and bin/scan-extensions.test.mjs (missing test coverage for 3 rules).

Important Files Changed

Filename Overview
bin/scan-extensions.mjs Adds 7 new security rules, --json output, and exported functions. Template literal bypass in fs-write rule, credential-logging regex limitation, and misleading "All clean" message with info findings.
bin/scan-extensions.test.mjs Adds 7 new tests for new rules and JSON output. Missing tests for credential-logging, unsafe-deserialization, and dynamic-require rules — violates project convention requiring test coverage for security code.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["CLI: node scan-extensions.mjs [--json] [dirs]"] --> B{Parse args}
    B --> C["--json flag?"]
    B --> D["Directory list"]
    D --> E["For each directory"]
    E --> F["walkDir: collect .js/.ts files"]
    F --> G["For each file"]
    G --> H["scanSource(source, filePath)"]
    H --> I["LINE_RULES: per-line patterns\n(dangerous-exec, eval, crypto-mining,\nfs-write-outside-home, privilege-escalation,\nnetwork-listener, prototype-pollution,\nunsafe-deserialization)"]
    H --> J["SOURCE_RULES: full-file patterns\n(exfiltration, obfuscation, env-harvesting,\ncredential-logging, dynamic-require)"]
    I --> K["Collect findings"]
    J --> K
    K --> L{Output mode}
    C -->|yes| L
    C -->|no| L
    L -->|--json| M["JSON report to stdout\n{timestamp, scanned, summary, findings}"]
    L -->|text| N["Human-readable console output"]
    M --> O{Exit code}
    N --> O
    O -->|critical > 0| P["exit(2)"]
    O -->|warn > 0| Q["exit(1)"]
    O -->|else| R["exit(0)"]
Loading

Last reviewed commit: 7c7ff9f

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment thread bin/scan-extensions.mjs
Comment on lines +62 to +66
id: "fs-write-outside-home",
severity: "critical",
message: "Filesystem write to system path detected",
pattern: /writeFileSync?\s*\(\s*["'](\/etc\/|\/usr\/|\/var\/|\/root\/)/,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Template literal bypass in path pattern

The fs-write-outside-home pattern only matches single (') and double (") quotes, but not backtick template literals. Code like writeFileSync(`/etc/passwd`, data) will bypass this rule entirely.

Suggested change
id: "fs-write-outside-home",
severity: "critical",
message: "Filesystem write to system path detected",
pattern: /writeFileSync?\s*\(\s*["'](\/etc\/|\/usr\/|\/var\/|\/root\/)/,
},
{
id: "fs-write-outside-home",
severity: "critical",
message: "Filesystem write to system path detected",
pattern: /writeFileSync?\s*\(\s*["'`](\/etc\/|\/usr\/|\/var\/|\/root\/)/,
},
Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/scan-extensions.mjs
Line: 62-66

Comment:
**Template literal bypass in path pattern**

The `fs-write-outside-home` pattern only matches single (`'`) and double (`"`) quotes, but not backtick template literals. Code like `` writeFileSync(`/etc/passwd`, data) `` will bypass this rule entirely.

```suggestion
  {
    id: "fs-write-outside-home",
    severity: "critical",
    message: "Filesystem write to system path detected",
    pattern: /writeFileSync?\s*\(\s*["'`](\/etc\/|\/usr\/|\/var\/|\/root\/)/,
  },
```

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

Comment thread bin/scan-extensions.mjs
Comment on lines 357 to 366
if (totalCritical > 0) {
console.log(`🚨 ${totalCritical} critical finding(s) — review immediately!`);
if (!jsonOutput) console.log(`🚨 ${totalCritical} critical finding(s) — review immediately!`);
process.exit(2);
} else if (totalWarn > 0) {
console.log(`⚠️ ${totalWarn} warning(s) — review recommended.`);
if (!jsonOutput) console.log(`⚠️ ${totalWarn} warning(s) — review recommended.`);
process.exit(1);
} else {
console.log("✅ All clean.");
if (!jsonOutput) console.log("✅ All clean.");
process.exit(0);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"All clean" message printed even with info-level findings

When only info-severity findings exist (e.g., dynamic-require), the exit-code logic falls through to the else branch, printing "All clean" and exiting 0. This is misleading since findings were reported. The info count is shown in the summary block (line 353), but the final status message contradicts it.

Consider something like:

Suggested change
if (totalCritical > 0) {
console.log(`🚨 ${totalCritical} critical finding(s) — review immediately!`);
if (!jsonOutput) console.log(`🚨 ${totalCritical} critical finding(s) — review immediately!`);
process.exit(2);
} else if (totalWarn > 0) {
console.log(`⚠️ ${totalWarn} warning(s) — review recommended.`);
if (!jsonOutput) console.log(`⚠️ ${totalWarn} warning(s) — review recommended.`);
process.exit(1);
} else {
console.log("✅ All clean.");
if (!jsonOutput) console.log("✅ All clean.");
process.exit(0);
}
if (totalCritical > 0) {
if (!jsonOutput) console.log(`🚨 ${totalCritical} critical finding(s) — review immediately!`);
process.exit(2);
} else if (totalWarn > 0) {
if (!jsonOutput) console.log(`⚠️ ${totalWarn} warning(s) — review recommended.`);
process.exit(1);
} else {
if (!jsonOutput) {
if (totalInfo > 0) console.log(`ℹ️ ${totalInfo} info finding(s) — no action required.`);
else console.log("✅ All clean.");
}
process.exit(0);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/scan-extensions.mjs
Line: 357-366

Comment:
**"All clean" message printed even with info-level findings**

When only `info`-severity findings exist (e.g., `dynamic-require`), the exit-code logic falls through to the `else` branch, printing "All clean" and exiting 0. This is misleading since findings *were* reported. The info count is shown in the summary block (line 353), but the final status message contradicts it.

Consider something like:
```suggestion
  if (totalCritical > 0) {
    if (!jsonOutput) console.log(`🚨 ${totalCritical} critical finding(s) — review immediately!`);
    process.exit(2);
  } else if (totalWarn > 0) {
    if (!jsonOutput) console.log(`⚠️  ${totalWarn} warning(s) — review recommended.`);
    process.exit(1);
  } else {
    if (!jsonOutput) {
      if (totalInfo > 0) console.log(`ℹ️  ${totalInfo} info finding(s) — no action required.`);
      else console.log("✅ All clean.");
    }
    process.exit(0);
  }
```

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

assert.ok(output.includes("prototype") || output.includes("pollution"), output);
});
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing tests for 3 new rules

The new rules credential-logging, unsafe-deserialization, and dynamic-require were added to the scanner but have no corresponding tests. The PR description states "22 total (up from 15)" tests, but those 3 rules are uncovered. Per the project conventions in CLAUDE.md: "All security code must have tests before merging."

Context Used: Context from dashboard - CLAUDE.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/scan-extensions.test.mjs
Line: 277

Comment:
**Missing tests for 3 new rules**

The new rules `credential-logging`, `unsafe-deserialization`, and `dynamic-require` were added to the scanner but have no corresponding tests. The PR description states "22 total (up from 15)" tests, but those 3 rules are uncovered. Per the project conventions in `CLAUDE.md`: "All security code must have tests before merging."

**Context Used:** Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=301c5d5e-f39f-40b0-952d-acd5ddc604f4))

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

Comment thread bin/scan-extensions.mjs Outdated
severity: "warn",
message: "Possible credential logging (process.env written to log/console)",
pattern: /process\.env/,
requiresContext: /console\.(log|info|warn|error|debug)\s*\(.*process\.env/,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

requiresContext regex can't match across lines

The .* in this regex won't match newlines (no s flag), so requiresContext only fires when console.log( and process.env appear on the same line. Since this is a SOURCE_RULE tested against the full file source (line 189), a multi-line pattern will not be caught:

const val = process.env.TOKEN;
console.log(val); // not detected — different lines

Only the inline form console.log(process.env.TOKEN) is detected. If that's intentional (to limit false positives), consider documenting the limitation in a comment. If it should match across lines, the regex needs [\s\S]*? instead of .*.

Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/scan-extensions.mjs
Line: 130

Comment:
**`requiresContext` regex can't match across lines**

The `.*` in this regex won't match newlines (no `s` flag), so `requiresContext` only fires when `console.log(` and `process.env` appear on the **same** line. Since this is a SOURCE_RULE tested against the full file source (line 189), a multi-line pattern will not be caught:

```js
const val = process.env.TOKEN;
console.log(val); // not detected — different lines
```

Only the inline form `console.log(process.env.TOKEN)` is detected. If that's intentional (to limit false positives), consider documenting the limitation in a comment. If it should match across lines, the regex needs `[\s\S]*?` instead of `.*`.

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

…missing tests

- Add backtick to fs-write-outside-home pattern (template literal bypass)
- Use [\s\S]*? in credential-logging requiresContext for cross-line matching
- Fix 'All clean' message showing with info-level findings
- Add 5 missing tests: credential-logging (same-line + multiline),
  unsafe-deserialization, dynamic-require, template literal fs write
- 27 tests total (up from 22)
Comment thread bin/scan-extensions.mjs
Strip dead code: --json output nobody consumes, exports nobody imports.
The scanner is a CLI tool run manually or via security-audit.sh --deep.
@benvinegar benvinegar changed the title security: improve extension scanner with new rules and --json output security: add 7 new rules to extension scanner Feb 18, 2026
@benvinegar benvinegar merged commit 755e447 into main Feb 18, 2026
8 checks passed
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.

1 participant