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

moneypath

v0.2.0

Published

Static analyzer that finds payment logic flaws in Razorpay, Stripe and Cashfree checkouts — client-controlled amounts, rupee/paise unit bugs, unverified webhooks, and payments confirmed from the browser.

Downloads

468

Readme

moneypath

Your checkout trusts the browser. This tells you where.

A static analyzer that finds payment logic flaws in Razorpay, Stripe and Cashfree integrations — the class of bug that lets a customer pay ₹1 for a ₹10,000 order.

npm CI license

npx moneypath

No install, no account, no config, no network. It reads your source and exits.


Why I built this

I shipped a checkout that trusted the browser for the delivery fee. The server took whatever number arrived and handed it to Razorpay. Recomputing it server-side was a small fix — noticing it was the hard part.

So I went looking for something that would have caught it, and there wasn't anything. Generic scanners see a valid POST with valid parameters and move on. Plenty of people will tell you not to trust the client; nobody had written the thing that checks whether you did.

So: this. Narrow on purpose — one class of bug, done without lying to you.

Why generic scanners miss this

Generic security scanners cannot find payment bugs. They see a structurally valid POST /api/checkout with well-formed parameters and move on. The flaw is not in the syntax — it is in where the number came from.

So these ship, constantly:

// The client tells the server what to charge.
const { amount } = await request.json();
await razorpay.orders.create({ amount, currency: 'INR' });

One edited request and the order costs whatever the attacker types.

moneypath traces the amount backwards from the gateway call to its source, and tells you when that source is the browser.

What it finds

| Rule | Severity | Flaw | | --- | --- | --- | | MP001 | Critical | Payment amount comes from the request body or query string, never recomputed server-side | | MP002 | High | Amount not converted to the gateway minor unit — Razorpay bills paise, Stripe bills cents, so a rupee figure collects 1/100th of the price | | MP003 | High | Minor-unit conversion not rounded — 1499.99 * 100 is 149998.99999999999, which gateways reject | | MP004 | High | Amount converted twice — bills the customer 100x | | MP005 | Critical | Order marked paid by browser code or an unverified redirect | | MP006 | Critical | Payment webhook acts on payloads without verifying the signature | | MP007 | High | Amount converted to paise for Cashfree, which bills in rupees — the same conversion MP002 requires for Razorpay is a 100x overcharge here |

MP002–MP004 and MP007 are the currency unit family, the most common integration mistake in Indian checkouts and, unlike most logic flaws, decidable from the syntax alone. The unit depends on the gateway: Razorpay wants an integer count of paise, Stripe wants cents, and Cashfree wants a decimal in rupees. moneypath knows which is which, so it will not tell you to add a conversion that would overcharge by 100x.

What it looks like

  moneypath · 6 files in 97ms

   CRITICAL  Payment amount comes from the client  MP001 confirmed
  app/api/checkout/route.ts:16:5

      │ amount,

      ↳ amount  ⟵  body
      ↳ body  ⟵  await request.json()
      ↳ await request.json()  ⟵  client input via await req.json()

      The `amount` sent to Razorpay is taken from await req.json() and never
      recomputed server-side. An attacker edits the request and pays whatever
      they like — one rupee for a ten thousand rupee order.

      Fix: Look the price up server-side. Accept only identifiers from the
      client (a product id, a cart id) and compute `amount` from your own
      database rows before calling Razorpay.

  ────────────────────────────────────────────────────────────
  3 critical · 3 high  (1 needing review)

Every finding shows the traced path. If moneypath cannot show you the path, it does not claim the bug.

Try it on the deliberately broken example:

npx moneypath examples/badcart --html report.html

Use it with AI agents

moneypath ships an MCP server, so an agent can audit a checkout as a tool call instead of scraping CLI output.

claude mcp add moneypath -- npx -y moneypath-mcp
{
  "mcpServers": {
    "moneypath": {
      "command": "npx",
      "args": ["-y", "moneypath-mcp"]
    }
  }
}

Two tools are exposed:

  • scan_payment_code({ path, confirmedOnly? }) — returns structured findings with file, line, traced path, impact and fix
  • list_payment_rules() — returns the rule catalog

Agents working inside this repo should read AGENTS.md.

Use it in CI

- run: npx moneypath --sarif moneypath.sarif --fail-on critical
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: moneypath.sarif

SARIF 2.1.0 puts every finding in your repository's Security tab with the line highlighted.

Options

  --html <file>        write a shareable HTML report
  --json               print findings as JSON
  --sarif <file>       write SARIF 2.1.0 for GitHub code scanning
  --fail-on <level>    exit 1 at or above: critical | high | medium | none  (default: critical)
  --confirmed-only     hide findings the analyzer could not fully prove
  --no-color           disable ANSI colour

Exit codes: 0 clean or below threshold, 1 findings at or above it, 2 bad usage.

How it decides what to report

Every finding is labelled confirmed or needs review.

  • confirmed — the analyzer traced the whole path and stands behind it.
  • needs review — the pattern matched, but intent has to be judged by a human. Typically the value crossed a function moneypath cannot see into.

A value loaded from your database is treated as safe, even when the client chose which row to load. That is the pattern moneypath tells you to adopt, so flagging it would fire on every correct implementation:

const { productId } = await request.json();          // client picks the product
const product = await prisma.product.findUnique(...); // server owns the price
const amountInPaise = Math.round(product.price * 100);
await razorpay.orders.create({ amount: amountInPaise }); // clean

This deliberately trades false negatives for precision. On payment code a wrong accusation costs more trust than a missed edge case, and you only get one chance to spend that trust.

What it does not do

Being straight about the boundaries:

  • It is syntactic, not a full dataflow engine. It follows a value about four hops and stops. Deeply indirected code will be missed.
  • Cross-file tracing is narrow. If you wrap the gateway call in a helper, moneypath follows the amount from the caller into that helper and reports at the call site. It does this by matching the exported function name and checking the caller imports it, not by resolving modules properly, so a wrapper reached through a class method, an object property, or a re-export is still missed.
  • It only knows Razorpay, Stripe and Cashfree. PayPal, Paddle, Lemon Squeezy and PhonePe are not covered yet.
  • It does not replace a security review, a pentest, or reading your own checkout.
  • It finds logic flaws, not injection, XSS, or dependency CVEs. Use it alongside npm audit and a general scanner, not instead of them.
  • A clean run means these six bugs are absent. It is not a certificate.

Roadmap

Rough order, no dates. Opinions welcome in the issues.

  • [x] Cross-file tracing — done for the common case: an amount passed into an exported wrapper that calls the gateway. Module-path resolution and method-call wrappers are still open.
  • [x] Cashfree — done, including the unit inversion: it takes rupees rather than paise, so MP002 must not fire and a conversion is itself the bug (MP007)
  • [ ] Express and Fastify — webhook detection currently assumes Next.js route shapes
  • [ ] Quantity abuse — negative and fractional quantities that subtract from a total
  • [ ] Coupon logic — client-applied discounts, and redemption with no server-side counter
  • [ ] Idempotency — webhook retries that double-credit a wallet
  • [ ] PhonePe, PayPal, Paddle, Lemon Squeezy — lower priority, different failure modes

Not planned: a general dataflow engine, a hosted dashboard, or a paid tier. If it grows past "one thing, done carefully" it stops being useful.

Contributing

New rules are welcome, especially other gateways. The bar is precision: a rule must come with a vulnerable fixture in examples/badcart and a correct one in test/fixtures/safe, and the safe fixture must stay silent.

npm install
npm test          # 32 tests: detection, false positives, unit cases
npm run build
npm run check:mcp # MCP protocol smoke test

License

MIT