npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sourcewatch-cli

v0.6.2

Published

Static analysis scanner for security vulnerabilities, performance issues, and correctness bugs in JS/TS codebases — npx CLI + GitHub Action

Readme

sourcewatch

A zero-dependency static scanner for JS/TS codebases covering three modes: security vulnerabilities (--mode sec), performance anti-patterns (--mode perf), and correctness bugs (--mode bugs). Run all three at once with --mode all (the default).

It does not claim to find every bug, and it does not claim every finding is exploitable. It flags candidates worth a human's attention — the same classes of bug that keep showing up in real, published CVEs and production incidents across popular auth/framework/ORM libraries. Every finding comes with a file:line, a plain-English explanation of the risk, and the relevant CWE or category.

Why

Most of these bug classes are boringly repetitive across projects: a baseURL built from unvalidated config reaching axios/fetch, a path.join fed a raw req.url, a __proto__ key surviving a dot-notation body parser, a readFileSync in a hot request handler, a .take(0) that returns all rows. They're easy to write, easy to miss in review, and easy to grep for once you know the shape. This tool encodes that shape.

Install / usage

No install needed — run directly:

# Scan everything (sec + perf + bugs)
npx sourcewatch scan .

# Scan only security rules
npx sourcewatch scan . --mode sec

# Scan only performance rules
npx sourcewatch scan . --mode perf

# Scan only correctness/bug rules
npx sourcewatch scan . --mode bugs

Hunt mode — framework-first GitHub discovery

Instead of scanning repos you already have, hunt discovers quality repos from GitHub by searching for framework/library usage patterns (e.g. express jsonwebtoken, fastify cors credentials), then runs all applicable rules against those repos' source files. Two phases:

For each active rule:

  1. Discover — search GitHub using that rule's stackHints (framework/library terms specific to the bug class, e.g. express jsonwebtoken authorization header for bearer-scheme). Filter to non-forks with 500+ stars active within 18 months — repos of consequence.
  2. Scan — fetch those repos' source files (up to 30, skipping dist/test) and run only that rule on them.

Requires the GitHub CLI (gh auth login once).

# Hunt across all rules/modes
npx sourcewatch hunt

# Hunt only security rules
npx sourcewatch hunt --mode sec

# Repos per stack hint (default 20)
npx sourcewatch hunt --limit 30

# Hunt + LLM verify findings
npx sourcewatch hunt --llm claude
npx sourcewatch hunt --llm qwen

# JSON output
npx sourcewatch hunt --json

Output includes a direct GitHub link per finding:

[HIGH] ssrf-unvalidated-outbound-url (CWE-918)
  someorg/somerepo — src/api/client.ts:42
  https://github.com/someorg/somerepo/blob/main/src/api/client.ts#L42
  axios.create({ baseURL: config.baseUrl
  -> Outbound request built from "config.baseUrl" ...

LLM verification (optional)

Pass --llm <provider> to run a second-pass LLM review on every pattern-matched finding. Without the flag the tool behaves exactly as before — pure pattern matching, zero network calls.

# Verify findings with a local Ollama model (free, no API key)
npx sourcewatch scan . --llm qwen

# Verify findings with Claude Sonnet via the claude CLI (OAuth)
npx sourcewatch scan . --llm claude

# Any Ollama model by tag
npx sourcewatch scan . --llm qwen2.5:14b

For each finding the LLM returns a CONFIRMED / DISMISSED verdict with confidence, one-sentence reasoning, and an optional fix suggestion:

[HIGH] perf-redundant-recompute-in-loop
  src/services/outreachService.js:454
  -> Expensive expression (new RegExp) called inside a loop
  [LLM:qwen] CONFIRMED (high) — new RegExp is called on every iteration; the pattern is a fixed string.
  [FIX] Precompute the regular expression outside the loop.

A summary line is printed at the end:

LLM [qwen]: 3 confirmed, 2 dismissed, 0 errored.

Providers:

  • claude — uses the claude CLI (must be installed and authenticated via OAuth — no API key needed)
  • qwen — shorthand for qwen2.5:7b via Ollama running locally on localhost:11434
  • Any Ollama model tag (e.g. llama3.1:8b, qwen2.5:14b) — passed directly to Ollama

Requirements for LLM mode:

  • claude provider: npm install -g @anthropic-ai/claude-code and run claude once to authenticate
  • qwen / Ollama provider: install Ollama and ollama pull qwen2.5:7b

JSON or SARIF output (for CI / tooling / GitHub code scanning):

npx sourcewatch scan . --json
npx sourcewatch scan . --sarif
npx sourcewatch scan . --mode sec --sarif

Exit code is 1 if a finding at or above --fail-on's threshold is present (default high), 0 otherwise.

Config file

Drop a sourcewatch-sec.config.json (or .sourcewatch-secrc.json) at the scan root to tune it per-repo:

{
  "ignorePaths": ["test/**", "**/fixtures/**", "vendor/**"],
  "ignoreRules": ["path-decode-normalization-inconsistency"],
  "failOn": "high"
}

ignorePaths supports */**/? globs matched against the file path relative to the scan root. ignoreRules is a list of rule IDs (see the table below). failOn is high | medium | low | none.

Baseline (only flag new findings)

npx sourcewatch scan . --update-baseline .sourcewatch-baseline.json   # snapshot current findings
npx sourcewatch scan . --baseline .sourcewatch-baseline.json          # only report findings not in the baseline

Useful for adopting the tool on an existing codebase without being buried in pre-existing findings on day one — baseline them, then only get flagged on new introductions.

GitHub Action

name: sourcewatch
on: [pull_request, push]

permissions:
  contents: read
  security-events: write   # to upload SARIF to the Security tab
  pull-requests: write     # to post inline PR review comments

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: tsushanth/sourcewatch-sec@v0
        with:
          path: '.'
          mode: 'all'          # sec | perf | bugs | all
          fail-on: 'high'      # high | medium | low | none
          comment-pr: 'true'   # inline review comments on lines changed in the PR
          upload-sarif: 'true' # results show up in the repo's Security tab
  • SARIF upload — findings appear natively in GitHub's Security → Code scanning alerts, alongside CodeQL/Dependabot results.
  • Inline PR comments — only posted for findings that land on a line actually changed in the PR (diffed against the PR's patch), so an existing repo's pre-existing findings don't spam every PR. Deduped against comments already posted on that line.
  • security-events: write/pull-requests: write are required for SARIF upload and PR comments respectively — omit either input if you don't want to grant that permission.

What it checks

Security rules (--mode sec)

| Rule | CWE | Severity | |---|---|---| | Unvalidated outbound URL (SSRF) | CWE-918 | high | | Path traversal via unsanitized path.join/path.resolve | CWE-22 | high | | Prototype pollution via unguarded dynamic-key assignment | CWE-1321 | high | | Auth-scheme-agnostic bearer parsing | CWE-287 | medium | | Timing-unsafe compare in a function named timingSafe*/constantTime* | CWE-208 | medium | | Open redirect via unvalidated redirect target | CWE-601 | medium | | CORS credentials:true + wildcard/reflected origin | CWE-942 | high | | Path decode/normalization inconsistency (decodeURI vs decodeURIComponent, unnormalized static-file serving) | CWE-436 | medium |

Performance rules (--mode perf)

| Rule | Severity | |---|---| | Collection grows inside a loop/handler with no size cap (unbounded memory growth) | medium | | Expensive expression (new RegExp / JSON.parse / Object.keys / Array.from) called inside a loop | medium | | Synchronous fs call (readFileSync/writeFileSync/existsSync) inside a request handler or exported function | high |

Bug / correctness rules (--mode bugs)

| Rule | Severity | |---|---| | Falsy check on a numeric/length variable — 0 is a valid value but fails the truthiness test | medium | | Predicate named isInvalid/isError/isOutOfRange used with .every()/.filter() — likely double negation | high | | .take(0) or .limit(0) — many ORMs treat 0 as "no limit" returning all rows instead of none | high | | Native automation click (robotjs/nut-js) in Electron IPC handler with no subsequent window.focus() — OS keyboard focus shifts to target window, silencing the renderer's keydown listeners | medium |

More rules land as they're validated against real, confirmed findings — see CONTRIBUTING.

Design principles

  • Heuristic, not authoritative. This is regex/proximity-based, not an AST-level type-flow analysis. It will miss things (false negatives) and it will occasionally flag something already handled elsewhere in a way it can't see (false positives). Read the message on each finding — it's written to tell you exactly what to go verify.
  • No false-positive-by-default design. Every rule requires a taint hint AND absence of a locally-visible guard before firing; guarded code paths (e.g. a value passed through a validate*/safe*/sanitiz* function before reaching a sink) are recognized and suppressed.
  • Zero runtime dependencies. Pure Node built-ins (fs, path). Nothing to audit in a supply-chain sense beyond Node itself.
  • Always verify manually. This tool is a first-pass triage aid, not a vulnerability-disclosure generator. Confirm exploitability with a real reproduction before reporting anything found here to a project's security team.

Known limitations (measured, not just claimed)

The two fixture files prove each rule fires on a hand-written example — they don't prove precision on real, messy code. Before v0.2.1, scanning 5 real open-source repos (directus, novu, cloudflare/workers-sdk, elysia, sequelize) surfaced concrete false-positive classes that the fixtures alone hadn't caught:

  • Text-window taint checks aren't real data-flow. A rule looks at a fixed window of characters before a sink for a "taint hint" — it doesn't actually trace whether that specific hint is the value reaching the sink.
  • A file-level gate can over-associate unrelated code. The prototype-pollution rule originally gated on "does this file mention dot-notation splitting anywhere," then flagged unrelated bracket-assignments elsewhere in the same file. Fixed in v0.2.1 by requiring the flagged assignment to actually index into the split's result variable.
  • A "sink" needs to actually perform I/O. new URL(x) alone does nothing dangerous — it was removed as an SSRF sink after new URL(req.url) turned out to be ~90% of all SSRF findings on cloudflare/workers-sdk, all noise.
  • Test/e2e code triggers real-code patterns without real-code risk. Excluded by default (test/, tests/, __tests__/, e2e/, *.test.*, *.spec.*) — override with "scanTests": true in the config file if you want them included.

.github/workflows/ci.yml now clones a real external repo (directus/directus) on every CI run and asserts the finding count stays under a known-good ceiling — a regression tripwire for "a rule change just got noisy again."

Contributing

New rules are welcome, especially ones distilled from a real, merged security-fix diff or a production postmortem. Open a PR with:

  1. The rule file under src/rules/sec/, src/rules/perf/, or src/rules/bugs/, registered in src/rules/index.js.
  2. A vulnerable.js-style fixture addition showing it fires, and a safe.js-style addition showing the guarded version doesn't.

License

MIT