Metadata-Version: 2.4
Name: stripe-hardening-kit
Version: 1.0.0
Summary: Pre-deploy gate that blocks banned/deprecated payment integrations from leaking into shipped code, plus a fail-closed Stripe webhook reference.
Author: luandv92
License: MIT License
        
        Copyright (c) 2026 Luan Ribeiro de Brito
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/luandv92/stripe-hardening-kit
Project-URL: Issues, https://github.com/luandv92/stripe-hardening-kit/issues
Keywords: stripe,payments,deploy-gate,webhook,ci,security
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# stripe-hardening-kit

Two small, real tools for anyone shipping a Stripe integration:

1. **`scan_gate.py`** — a pre-deploy gate that scans your built/deployed site or
   app for banned/deprecated payment-integration patterns and **blocks the
   deploy** (non-zero exit code) if it finds one.
2. **`webhook_reference.js`** — a minimal, correct, **fail-closed** Stripe
   webhook handler: verifies the signature before touching the payload, and
   rejects (HTTP 400) anything that doesn't verify.

Both come from the same real incident.

## Why this exists

A project migrates its checkout from one payment provider to another —
deprecated, banned by the new provider's terms, or just replaced by something
better. The migration itself goes fine. Weeks later, a stray link or embed
snippet referencing the **old** provider survives somewhere in the codebase —
a copy-pasted template, an old component nobody deleted, a cached build
artifact — and it gets redeployed. Checkout silently breaks: the button points
at a dead or wrong destination, and because nothing throws an error and the
page "looks fine," nobody notices until real revenue has already been lost.

The fix that actually works is the same one you'd use for any other class of
regression: turn it into a gate that runs on every deploy, not a thing a human
has to remember to check. `scan_gate.py` is that gate, generalized so you can
point it at your own banned integration(s) — not just the one shipped as the
default example.

The webhook handler addresses a related but separate risk: a Stripe webhook
URL is public. Without signature verification, anyone who finds it can POST a
forged event and trigger fulfillment (deliver a product, grant access, mark an
order paid) for a payment that never happened. `webhook_reference.js` is a
clean reference for doing verification correctly and rejecting everything else.

## Part 1 — the deploy gate

### Install

```bash
pip install -e .
# or, without installing:
python -m stripe_hardening_kit.scan_gate <target-dir>
```

This registers a console script:

```bash
stripe-hardening-scan <target-dir>
```

### How it works

- Scans every file under `<target-dir>` whose extension is in the "servable"
  set (default: `.html`, `.htm`, `.js`, `.mjs`, `.cjs` — override with `--ext`).
- Skips dependency/VCS/cache directories by default (`node_modules`, `.git`,
  `__pycache__`, `.venv`, `.cache`, `.next`, `.turbo`, `.vercel`, etc. —
  extend with `--skip-dir`).
- Skips files whose name contains a skipped substring (`.bak`, `.log`, `.map`
  by default — extend with `--skip-substr`).
- Checks every line against a list of banned regex patterns.
- Prints every match (file, line number, matched line, which pattern) and
  exits **1** if anything matched, **0** if clean.

### Configuring your own banned patterns

The pattern list is a JSON file — nothing is hardcoded into the scanner. Copy
the example and edit it:

```bash
cp banned_patterns.example.json banned_patterns.json
```

```json
{
  "patterns": [
    { "name": "gumroad", "regex": "gumroad\\.com|gumroad_overlay|data-gumroad-product",
      "description": "Deprecated checkout provider — replaced by Stripe." },
    { "name": "my-old-processor", "regex": "checkout\\.old-provider\\.example" }
  ]
}
```

Then run:

```bash
stripe-hardening-scan ./dist --config banned_patterns.json
```

If you don't pass `--config`, the scanner uses a built-in default: Gumroad's
known checkout-link and embed patterns. That's included as a real, concrete,
working example — not a suggestion that Gumroad is the only thing worth
banning. Add whatever your own project needs to keep out: an old payment
processor, a deprecated analytics snippet, an internal/staging URL that should
never reach production, anything else you've decided must never leak into a
shipped file again.

### Wiring it into a deploy pipeline

Run it as a required step before deploy, and fail the build on a non-zero
exit code — the same way you'd wire up a test suite or a linter:

```bash
stripe-hardening-scan ./dist --config banned_patterns.json || exit 1
# only reached if the scan passed:
vercel deploy --prod
```

Or as a CI step (GitHub Actions example):

```yaml
- name: Block banned payment integrations
  run: |
    pip install -e .
    stripe-hardening-scan ./dist --config banned_patterns.json
```

### Running the tests

```bash
pip install -e ".[dev]"
pytest
```

The test suite creates real temporary files on disk (clean files, files with
banned patterns, files inside skipped directories, custom pattern configs) and
asserts the scanner passes/fails correctly in each case — see
`tests/test_scan_gate.py`.

## Part 2 — the webhook reference

`webhook-example/webhook_reference.js` is a minimal Stripe webhook handler
that:

1. Reads the **raw** request body (required — signature verification is
   computed over the exact bytes Stripe sent; a body already parsed by a
   framework's JSON middleware will never verify).
2. Calls `stripe.webhooks.constructEvent(rawBody, signature, webhookSecret)`.
3. Returns **HTTP 400** immediately if verification throws — never processes
   an unverified event, no matter how well-formed it looks.
4. Only after verification succeeds, handles `checkout.session.completed` as
   the example event (extend the `switch` for whatever events you subscribe
   to).

### Setup

```bash
cd webhook-example
npm install
cp .env.example .env.local   # fill in real values, never commit this file
```

Required environment variables (see `.env.example`):

- `STRIPE_SECRET_KEY` — your Stripe API secret key.
- `STRIPE_WEBHOOK_SECRET` — the signing secret for your specific webhook
  endpoint (Stripe Dashboard → Developers → Webhooks → your endpoint →
  "Signing secret"). Without it, the handler fails closed and rejects every
  request — by design, not a bug.

### Wiring it up

**Vercel / Next.js (pages API route)** — the file is already written for
this target. Drop it in as `pages/api/stripe-webhook.js` (or
`app/api/stripe-webhook/route.js` adapted to the App Router's request/response
shape) — the exported `config.api.bodyParser = false` disables Next's default
body parsing so the raw bytes reach the handler untouched.

**Express** — mount the route with the raw body parser, *not* `express.json()`:

```js
const webhookHandler = require('./webhook_reference.js');

app.post(
  '/stripe-webhook',
  express.raw({ type: 'application/json' }), // req.body is now a raw Buffer
  (req, res) => webhookHandler(req, res)
);
```

(Adjust `readRawBody()` in `webhook_reference.js` to read from `req.body`
directly instead of streaming `req.on('data', ...)`, since Express's raw
body-parser already buffers it for you.)

**Register the endpoint** in the Stripe Dashboard (or via the Stripe CLI for
local testing: `stripe listen --forward-to localhost:3000/stripe-webhook`) and
subscribe it to at least `checkout.session.completed`.

## What's real here vs. generalized

- `scan_gate.py`'s scanning logic (served-extension filtering, skip-dirs,
  skip-substrings, line-by-line regex matching, exit-code semantics) is
  generalized from a gate that runs on every deploy of a real, live Stripe-only
  site — the pattern list is now a config file instead of one hardcoded
  provider name, so it's reusable for any banned integration, not just the one
  shipped as the default example.
- `webhook_reference.js`'s signature-verification pattern (raw body handling,
  `constructEvent`, fail-closed on verification failure, `checkout.session.completed`
  as the example event) mirrors a real production webhook handler's
  verification path, stripped of business-specific fulfillment logic
  (product catalogs, email delivery, per-customer routing) so it stands alone
  as a clean reference.

## License

MIT — see [LICENSE](LICENSE).
