• Agents
  • Pricing
  • Blog
Log in
Get started

Security for apps built with AI. Paste a URL, get a report, fix what matters.

Product

  • How it works
  • What we find
  • Pricing
  • Agents
  • MCP Server
  • CLI
  • GitHub Action

Resources

  • Guides
  • Blog
  • Docs
  • OWASP Top 10
  • Glossary
  • FAQ

Security

  • Supabase Security
  • Next.js Security
  • Lovable Security
  • Cursor Security
  • Bolt Security

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Imprint
© 2026 Flowpatrol. All rights reserved.
Home/OWASP Top 10/Web Top 10/A08: Software and Data Integrity Failures
A08CWE-502CWE-345CWE-494CWE-829

Deploy script with no signature
Software and Data Integrity Failures

The bug where you trust a package, a webhook, or an update without ever checking who sent it.

One of the fastest-growing categories in tested apps.

Reference: Web Top 10 (2021) — A08·Last updated April 7, 2026·By Flowpatrol Team
Software and Data Integrity Failures illustration

Your app trusts a lot of things it did not write. A package from npm. A webhook from Stripe. An update that auto-installs from a CDN. Each of those is a pipe into your server. The question nobody asks is whether the pipe has a valve on it.

Software and data integrity failures happen when your app trusts code or data it cannot verify came from the right place. Unsigned updates, unverified webhooks, insecure deserialization, CI pipelines that install whatever the registry serves. The pattern is always the same — a channel you trusted turns out to have no lock on it.

What your AI actually built

You asked for a Stripe webhook handler, and the model wrote one. It parses the JSON body, reads the event type, marks the order as paid, and sends the receipt. It works the first time you test it. You ship it.

What it skipped was the signature check. Stripe signs every webhook with a secret only you and Stripe share — and the handler is supposed to verify that signature before trusting a single byte. Without it, anyone who finds the endpoint can POST their own fake 'payment succeeded' event.

The same shape appears everywhere. A deploy script that curls a tarball and runs it unsigned. A plugin loader that eval's remote JavaScript. A CI step that installs from a typo-squatted package. The code does what you asked. It just trusts an input it has no business trusting.

How it gets exploited

The attacker finds your Stripe webhook URL in a network tab or a leaked .env. The handler is live.

  • 1
    Read the docs
    They look up the exact JSON shape of a Stripe 'checkout.session.completed' event in the public documentation.
  • 2
    Forge a payload
    They build a fake event with an order ID from your public order flow and the amount set to whatever they want.
  • 3
    POST it cold
    They send the fake event directly to your webhook endpoint. No signature header, no Stripe involvement — just a raw POST.
  • 4
    Free orders forever
    Your handler parses the JSON, marks the order paid, and ships the product. The real Stripe dashboard shows nothing because the real Stripe never saw the request.
  • The attacker places unlimited orders at zero cost until someone manually reconciles the ledger. By then the inventory is gone.

    Vulnerable vs Fixed

    Vulnerable — trusts any POST
    // app/api/webhooks/stripe/route.ts
    export async function POST(req) {
      const event = await req.json();
    
      if (event.type === 'checkout.session.completed') {
        await db.order.update({
          where: { id: event.data.object.metadata.orderId },
          data: { status: 'paid' },
        });
      }
    
      return Response.json({ received: true });
    }
    Fixed — verified signature
    // app/api/webhooks/stripe/route.ts
    import Stripe from 'stripe';
    
    const stripe = new Stripe(process.env.STRIPE_SECRET!);
    
    export async function POST(req) {
      const body = await req.text();
      const sig = req.headers.get('stripe-signature')!;
    
      let event;
      try {
        event = stripe.webhooks.constructEvent(
          body,
          sig,
          process.env.STRIPE_WEBHOOK_SECRET!,
        );
      } catch {
        return Response.json({ error: 'Bad signature' }, { status: 400 });
      }
    
      if (event.type === 'checkout.session.completed') {
        await db.order.update({
          where: { id: event.data.object.metadata.orderId },
          data: { status: 'paid' },
        });
      }
    
      return Response.json({ received: true });
    }

    The fix is one call — constructEvent — that verifies the signature against the shared secret before the handler trusts anything. If the signature is missing or wrong, the request dies at the door.

    A real case

    SolarWinds shipped a signed update that contained a backdoor

    In 2020 attackers planted malicious code inside a SolarWinds build pipeline, so the company's own signed update went out to 18,000 customers including US federal agencies.

    Read the case study

    Related reading

    Glossary

    Untrusted Data Deserialization (Insecure Deserialization)Supply Chain Attack (Dependency Confusion)

    What we find

    business logic bugs

    From the blog

    zero dollar forever stripe webhook walkthrough

    References

    • A08: Software and Data Integrity Failures — official OWASP entry
    • OWASP Top 10 for Web Applications (2021) — full list
    • CWE-502 on cwe.mitre.org
    • CWE-345 on cwe.mitre.org
    • CWE-494 on cwe.mitre.org
    • CWE-829 on cwe.mitre.org

    Check who your app is actually trusting.

    Flowpatrol probes every webhook and integration endpoint for missing signature checks. Five minutes. One URL.

    Try it free