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-sec

v0.2.1

Published

CWE-aligned static scanner for high-yield vulnerability patterns (SSRF, path traversal, prototype pollution, auth bypass, and more) in JS/TS codebases.

Readme

sourcewatch-sec

A zero-dependency, CWE-aligned static scanner for high-yield vulnerability patterns in JS/TS codebases — SSRF, path traversal, prototype pollution, auth-scheme bypass, timing-unsafe comparisons, open redirects, permissive CORS, and path-normalization mismatches.

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 across popular auth/framework/ORM libraries. Every finding comes with a file:line, a plain-English explanation of the risk, and the relevant CWE.

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 "timingSafeEqual" that's actually just ===. 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:

npx sourcewatch-sec scan .

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

npx sourcewatch-sec scan . --json
npx sourcewatch-sec scan . --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-sec scan . --update-baseline .sourcewatch-sec-baseline.json   # snapshot current findings
npx sourcewatch-sec scan . --baseline .sourcewatch-sec-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-sec
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: '.'
          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

| 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 |

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. See test-fixtures/safe/ vs test-fixtures/vulnerable/ for exactly what does and doesn't fire.
  • 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. Example found: fetch(req) was flagged as SSRF-risky purely because an unrelated req.url reference happened to appear nearby in the same function, not because req itself traced back to it. This is an inherent ceiling of a regex/proximity approach, not something a single rule tweak fully closes — treat every finding as "verify this," never as a confirmed bug.
  • 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) (the standard way to read your own incoming request's path in a fetch handler) 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," not a claim that ceiling is exactly the "true" bug count. If you find another false-positive class, please open an issue with the file:line — that's exactly how the three above were found and fixed.

Contributing

New rules are welcome, especially ones distilled from a real, merged security-fix diff (the fix diff is the pattern). Open a PR with:

  1. The rule file under src/rules/, 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