T09 · Insecure Skill Coding Practices
Error
- Location
- references/examples.md:95
- Finding
- Client-Controlled Payment Amount and Excessive Stripe Error Disclosure## Vulnerability Details **File Location**: `references/examples.md`, lines 95–108 **Vulnerability Type**: Client-controlled transaction integrity and excessive error disclosure **Risk Level**: High ### Vulnerable Code ```python @app.route('/api/create-payment-intent', methods=['POST']) def create_payment(): try: data = request.get_json() intent = stripe.PaymentIntent.create( amount=data['amount'], currency='usd', automatic_payment_methods={'enabled': True}, idempotency_key=data.get('idempotency_key') ) return jsonify(client_secret=intent.client_secret) except stripe.error.StripeError as e: return jsonify(error=str(e)), 400 ``` ### Technical Analysis The example creates a Stripe PaymentIntent using `data['amount']`, which is supplied directly by the requesting client. It does not show an authenticated order lookup, server-side price calculation, ownership or authorization validation, accepted-currency validation, amount bounds checking, or binding of the PaymentIntent to an authoritative order record. Client input cannot be trusted to define the amount owed. A user can modify the HTTP request independently of any browser-side validation and request a PaymentIntent for an arbitrarily reduced amount. If fulfillment relies only on the PaymentIntent succeeding and does not independently compare its amount and currency with the authoritative order total, the user may obtain higher-value goods or services after making an insufficient payment. The handler also returns `str(e)` directly to the requester. Stripe exception strings can contain provider diagnostics and integration details that should generally remain in sanitized server-side logs. Although the affected content is reference documentation rather than an automatically executed application, the example is presented as implementation guidance and could propagate th ...[truncated 1558 chars]
- Remediation
- ## Remediation Suggestions 1. Do not accept the payable amount as authoritative client input. Accept only an authenticated cart or order identifier. 2. Load the order from trusted server-side storage and verify that it belongs to the authenticated user and remains payable. 3. Recalculate the total server-side using authoritative product prices, quantities, discounts, taxes, shipping charges, and currency rules. 4. Validate that the amount is an integer in the currency's smallest unit and falls within permitted bounds. 5. Generate the idempotency key server-side or bind it to the authenticated order and operation. Do not rely on an arbitrary client-provided key. 6. Store the PaymentIntent ID against the order and include a non-sensitive internal order identifier in Stripe metadata. 7. Before fulfillment, verify the webhook signature and confirm the PaymentIntent ID, status, amount received, currency, order association, and fulfillment state against authoritative records. 8. Make fulfillment idempotent and perform the validation and state transition atomically to prevent duplicate or mismatched fulfillment. 9. Return a generic error message and stable application error code to the client. Log sanitized Stripe diagnostics server-side without exposing secrets or sensitive payment information. 10. Add negative tests that modify the request amount, currency, order identifier, ownership, and idempotency key, and verify that underpaid or mismatched transactions cannot be fulfilled. A safer design is: ```python @app.route('/api/create-payment-intent', methods=['POST']) @login_required def create_payment(): data = request.get_json(silent=True) or {} order = Order.get_payable_for_user( order_id=data.get('order_id'), user_id=current_user.id ) if order is None: return jsonify(error='Invalid order'), 400 amount = order.calculate_total_minor_units() try: intent = stripe. ...[truncated 588 chars]
