google-pay-button icon indicating copy to clipboard operation
google-pay-button copied to clipboard

Add CSP Nonce Support for Injected Scripts and Styles

Open jeanvcastro opened this issue 4 months ago • 3 comments

Summary

Add support for Content Security Policy (CSP) nonce parameter to allow the Google Pay Button to work with restrictive CSP policies by passing the nonce to dynamically injected scripts and styles.

Problem Statement

Currently, the Google Pay Button injects scripts and styles dynamically without CSP nonce support, making it incompatible with strict CSP policies. This is particularly problematic for:

  • Checkout-as-a-Service platforms
  • Applications with restrictive security policies
  • E-commerce sites that need to prevent unauthorized script execution while allowing legitimate payment processors

Current Workaround Limitations

The only current workaround is adding SHA-256 hashes to CSP policies for each injected script/style:

  • Hashes change when Google updates the button implementation
  • Requires manual maintenance and monitoring for updates
  • Not scalable for production environments

Proposed Solution

Add a nonce parameter to the Google Pay Button configuration that gets passed to all dynamically injected scripts and styles.

API Proposal

// Existing API
const paymentsClient = new google.payments.api.PaymentsClient({
  environment: 'TEST' // or 'PRODUCTION'
});

// Proposed enhancement
const paymentsClient = new google.payments.api.PaymentsClient({
  environment: 'TEST',
  nonce: 'abc123def456' // CSP nonce value
});

Implementation Details

When nonce is provided:

  1. All dynamically created <script> tags should include nonce="${nonce}"
  2. All dynamically created <style> tags should include nonce="${nonce}"
  3. Any inline styles should also respect the nonce parameter

Precedent

Samsung Pay Web already implements this feature successfully:

  • Documentation: https://developer.samsung.com/pay/web/integration.html
  • Shows that payment button libraries can support CSP nonces effectively

Use Case

I operate a checkout-as-a-service platform where:

  • Users can install Google Tag Manager and other third-party scripts
  • Strict CSP policies prevent unauthorized data capture (especially payment data)
  • Need to allow legitimate payment processors while maintaining security

Expected Behavior

<!-- Current CSP policy requirement (brittle) -->
<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' 'sha256-abc123...' 'sha256-def456...'; 
               style-src 'self' 'sha256-ghi789...'">

<!-- Desired CSP policy with nonce support -->
<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' 'nonce-abc123def456'; 
               style-src 'self' 'nonce-abc123def456'">

Benefits

  • Security: Better CSP compliance without compromising security
  • Maintainability: No need to update hashes when Google updates the button
  • Scalability: Works consistently across different versions
  • Industry Standard: Aligns with modern web security practices

Impact: Enables Google Pay adoption in security-restricted environments

jeanvcastro avatar Aug 01 '25 20:08 jeanvcastro

very nice proposal @jeanvcastro - we will look into it

dmengelt avatar Aug 04 '25 08:08 dmengelt

@jeanvcastro the initial loading of pay.js would also include the nonce attribute. right? Like:

<script async
  src="https://pay.google.com/gp/p/js/pay.js"
  nonce="abc123def456"
  onload="onGooglePayLoaded()">
</script>

Or would you "allow" https://pay.google.com/gp/p/js/pay.js in your Content-Security-Policy header?

dmengelt avatar Aug 04 '25 12:08 dmengelt

Hi @dmengelt,

Actually, I'm using @google-pay/button-react, so pay.js is automatically injected by the React component. In this case, the nonce should be applied to both the initial pay.js script tag and all dynamically created elements.

The proposal would be something like:

import GooglePayButton from '@google-pay/button-react'

<GooglePayButton
  environment="TEST"
  paymentRequest={paymentRequest}
  onLoadPaymentData={onLoadPaymentData}
  nonce="abc123def456" // New prop
/>

The nonce would then be used for:

  1. Initial pay.js script injected by the component:
<script async 
  src="https://pay.google.com/gp/p/js/pay.js" 
  nonce="abc123def456">
</script>
  1. All dynamic elements created subsequently by pay.js (additional scripts, inline styles, etc.)

This would cover the entire Google Pay button lifecycle, from initial injection to dynamically created elements, completely solving compatibility with restrictive CSP policies.

Thank you again for considering this feature!

jeanvcastro avatar Aug 07 '25 15:08 jeanvcastro