@xenterprises/fastify-xstripe 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SECURITY.md ADDED
@@ -0,0 +1,465 @@
1
+ # Security Policy
2
+
3
+ ## Overview
4
+
5
+ xStripe is a Fastify plugin for Stripe webhook handling. This document outlines the security considerations, best practices, and guidelines for using xStripe safely in production environments.
6
+
7
+ ## Security Model
8
+
9
+ ### What xStripe Does Safely
10
+ - **Webhook Signature Verification** - All webhook requests verified against Stripe signature
11
+ - **Request Validation** - Ensures only valid Stripe events are processed
12
+ - **Isolated Handler Execution** - Each handler runs in isolated context
13
+ - **Error Handling** - Errors don't expose sensitive data
14
+ - **API Key Security** - Keys never logged or exposed in errors
15
+
16
+ ### Attack Surface
17
+ The main security concerns are:
18
+
19
+ 1. **Webhook Signature Verification** - Ensuring authentic Stripe requests
20
+ 2. **Handler Code Injection** - Custom handlers with untrusted input
21
+ 3. **API Key Exposure** - Secure key management
22
+ 4. **Data Leakage** - Error messages revealing sensitive information
23
+ 5. **Idempotency** - Duplicate event handling
24
+
25
+ ## Security Guidelines
26
+
27
+ ### 1. Webhook Signature Verification
28
+
29
+ **Current Implementation** ✅
30
+ ```javascript
31
+ // Automatically verified by Stripe SDK
32
+ const event = await stripe.webhooks.constructEvent(
33
+ body,
34
+ sig,
35
+ webhookSecret
36
+ );
37
+ ```
38
+
39
+ **Always Required**
40
+ - Webhook secret must be set in production
41
+ - Never skip signature verification
42
+ - Logs indicate if verification is disabled
43
+
44
+ **Best Practice**:
45
+ ```javascript
46
+ await fastify.register(xStripe, {
47
+ apiKey: process.env.STRIPE_API_KEY,
48
+ webhookSecret: process.env.STRIPE_WEBHOOK_SECRET, // Required
49
+ webhookPath: '/stripe/webhook',
50
+ });
51
+ ```
52
+
53
+ **Failure Handling**:
54
+ ```javascript
55
+ fastify.setErrorHandler((error, request, reply) => {
56
+ if (error.code === 'SIGNATURE_VERIFY_FAILURE') {
57
+ // Log security event
58
+ fastify.log.warn('Invalid Stripe webhook signature');
59
+ reply.status(401).send({ error: 'Unauthorized' });
60
+ }
61
+ });
62
+ ```
63
+
64
+ ---
65
+
66
+ ### 2. API Key Management
67
+
68
+ **Risk**: Stripe API keys are highly sensitive credentials
69
+
70
+ **Secure Practices**:
71
+ ```javascript
72
+ // ✅ Use environment variables
73
+ const apiKey = process.env.STRIPE_API_KEY;
74
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
75
+
76
+ // ✅ Rotate keys regularly
77
+ // ✅ Use restricted API keys for specific operations
78
+ // ✅ Monitor API key usage in Stripe Dashboard
79
+
80
+ // ❌ Never hardcode credentials
81
+ // ❌ Never commit credentials to git
82
+ // ❌ Never log credentials
83
+ ```
84
+
85
+ **Environment Variable Setup**:
86
+ ```bash
87
+ # .env (NEVER commit this file)
88
+ STRIPE_API_KEY=sk_live_xxxxx
89
+ STRIPE_WEBHOOK_SECRET=whsec_xxxxx
90
+
91
+ # .gitignore
92
+ .env
93
+ .env.local
94
+ *.key
95
+ ```
96
+
97
+ **Monitoring**:
98
+ - Check Stripe Dashboard for suspicious API activity
99
+ - Rotate keys if compromised
100
+ - Use restricted API keys with specific scopes
101
+
102
+ ---
103
+
104
+ ### 3. Handler Code Security
105
+
106
+ **Risk**: Custom handlers processing untrusted event data
107
+
108
+ **Validation Pattern**:
109
+ ```javascript
110
+ // ✅ Validate all event data
111
+ const handler = async (event, fastify, stripe) => {
112
+ // Validate required fields
113
+ if (!event.data?.object?.id) {
114
+ throw new Error('Invalid event structure');
115
+ }
116
+
117
+ const customerId = event.data.object.customer;
118
+
119
+ // Verify customer exists and belongs to correct account
120
+ const customer = await stripe.customers.retrieve(customerId);
121
+ if (!customer) {
122
+ throw new Error('Customer not found');
123
+ }
124
+
125
+ // Process safely
126
+ await fastify.db.user.update({
127
+ where: { stripeCustomerId: customerId },
128
+ data: { subscriptionStatus: 'active' },
129
+ });
130
+ };
131
+
132
+ // ❌ Never trust event data blindly
133
+ // ❌ Don't use event data in database queries without validation
134
+ ```
135
+
136
+ **Error Handling**:
137
+ ```javascript
138
+ // ✅ Catch errors in handlers
139
+ try {
140
+ // Handler logic
141
+ } catch (error) {
142
+ // Log full error internally
143
+ fastify.log.error(error);
144
+
145
+ // Return generic error to Stripe
146
+ reply.status(200).send({ received: true });
147
+
148
+ // Set up alerting for failures
149
+ await alerting.notify({
150
+ severity: 'high',
151
+ message: `Webhook handler failed: ${error.message}`,
152
+ });
153
+ }
154
+
155
+ // ❌ Don't expose error details to client
156
+ // ❌ Don't return error messages in webhook response
157
+ ```
158
+
159
+ ---
160
+
161
+ ### 4. Idempotency & Duplicate Events
162
+
163
+ **Risk**: Stripe might retry failed webhooks, causing duplicate processing
164
+
165
+ **Best Practice - Idempotent Operations**:
166
+ ```javascript
167
+ // ✅ Use event ID for deduplication
168
+ const handler = async (event, fastify, stripe) => {
169
+ const eventId = event.id;
170
+
171
+ // Check if already processed
172
+ const processed = await fastify.db.webhookEvent.findUnique({
173
+ where: { stripeEventId: eventId },
174
+ });
175
+
176
+ if (processed) {
177
+ return; // Already handled, skip
178
+ }
179
+
180
+ // Process event
181
+ await processSubscription(event);
182
+
183
+ // Mark as processed
184
+ await fastify.db.webhookEvent.create({
185
+ data: {
186
+ stripeEventId: eventId,
187
+ type: event.type,
188
+ processedAt: new Date(),
189
+ },
190
+ });
191
+ };
192
+ ```
193
+
194
+ **Database Schema**:
195
+ ```javascript
196
+ // Track processed events
197
+ model WebhookEvent {
198
+ id String @id @default(cuid())
199
+ stripeEventId String @unique
200
+ type String
201
+ processedAt DateTime @default(now())
202
+ }
203
+ ```
204
+
205
+ ---
206
+
207
+ ### 5. Error Handling
208
+
209
+ **Risk**: Error messages could expose sensitive information
210
+
211
+ **Secure Error Responses**:
212
+ ```javascript
213
+ // ✅ Generic response to Stripe
214
+ reply.status(200).send({ received: true });
215
+
216
+ // ✅ Log detailed errors internally
217
+ fastify.log.error('Handler failed:', {
218
+ eventId: event.id,
219
+ type: event.type,
220
+ error: error.message,
221
+ stack: error.stack,
222
+ });
223
+
224
+ // ✅ Alert on critical failures
225
+ if (error.code === 'DATABASE_ERROR') {
226
+ await alerting.critical('Database failure in webhook handler');
227
+ }
228
+
229
+ // ❌ Never send error details in response
230
+ // ❌ Never log API keys
231
+ // ❌ Never expose stack traces to clients
232
+ ```
233
+
234
+ ---
235
+
236
+ ### 6. HTTP Endpoint Security
237
+
238
+ **Webhook Endpoint Protection**:
239
+ ```javascript
240
+ // Basic rate limiting
241
+ import rateLimit from '@fastify/rate-limit';
242
+
243
+ await fastify.register(rateLimit, {
244
+ max: 1000, // 1000 requests
245
+ timeWindow: '1 minute',
246
+ });
247
+
248
+ // Webhook-specific protection
249
+ fastify.post('/stripe/webhook', {
250
+ config: {
251
+ rateLimit: {
252
+ max: 500, // Stripe sends frequently
253
+ timeWindow: '1 minute',
254
+ },
255
+ },
256
+ }, async (request, reply) => {
257
+ // Handler
258
+ });
259
+ ```
260
+
261
+ **Input Validation**:
262
+ ```javascript
263
+ // Validate Stripe signature header
264
+ const sig = request.headers['stripe-signature'];
265
+ if (!sig) {
266
+ return reply.status(401).send({ error: 'Missing signature' });
267
+ }
268
+
269
+ // Size limits
270
+ const MAX_PAYLOAD = 1024 * 100; // 100KB
271
+ if (request.rawBody.length > MAX_PAYLOAD) {
272
+ return reply.status(413).send({ error: 'Payload too large' });
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ### 7. Logging & Monitoring
279
+
280
+ **Safe Logging**:
281
+ ```javascript
282
+ // ✅ Log event IDs and types
283
+ fastify.log.info('Processing webhook', {
284
+ eventId: event.id,
285
+ type: event.type,
286
+ timestamp: event.created,
287
+ });
288
+
289
+ // ✅ Log handler outcomes
290
+ fastify.log.info('Webhook processed', {
291
+ eventId: event.id,
292
+ handler: handlerName,
293
+ duration: endTime - startTime,
294
+ });
295
+
296
+ // ❌ Never log sensitive data
297
+ // ❌ Never log API keys
298
+ // ❌ Never log customer financial info
299
+ ```
300
+
301
+ **Monitoring Setup**:
302
+ ```javascript
303
+ // Track success/failure rates
304
+ const metrics = {
305
+ successCount: 0,
306
+ failureCount: 0,
307
+ lastEvent: null,
308
+ };
309
+
310
+ // Alert on failures
311
+ if (handlerError) {
312
+ metrics.failureCount++;
313
+
314
+ if (metrics.failureCount > 5) {
315
+ await alerting.critical('High webhook failure rate');
316
+ }
317
+ }
318
+ ```
319
+
320
+ ---
321
+
322
+ ### 8. Stripe Account Security
323
+
324
+ **Best Practices**:
325
+ - Enable 2FA on Stripe account
326
+ - Use team and roles for access control
327
+ - Rotate API keys periodically
328
+ - Monitor API activity in dashboard
329
+ - Use restricted API keys for webhooks
330
+ - Review connected accounts regularly
331
+ - Set up IP allowlisting if possible
332
+ - Enable email notifications for key events
333
+
334
+ **Webhook Settings**:
335
+ - Only subscribe to necessary events
336
+ - Use specific event types, not wildcards
337
+ - Monitor webhook failures in dashboard
338
+ - Test webhooks in test mode first
339
+ - Keep webhook endpoint updated
340
+ - Version your webhook handlers
341
+
342
+ ---
343
+
344
+ ### 9. Credential Rotation
345
+
346
+ **Rotating Stripe Keys**:
347
+ 1. Generate new API key in Stripe Dashboard
348
+ 2. Update environment variable
349
+ 3. Restart application
350
+ 4. Verify new key works
351
+ 5. Delete old key in Stripe Dashboard
352
+
353
+ **Time-based Rotation**:
354
+ ```bash
355
+ # Rotate keys every 90 days
356
+ 0 0 1 */3 * /path/to/rotate-keys.sh
357
+ ```
358
+
359
+ ---
360
+
361
+ ### 10. Incident Response
362
+
363
+ **If API Key is Compromised**:
364
+ 1. Immediately deactivate key in Stripe Dashboard
365
+ 2. Generate new key
366
+ 3. Update all applications
367
+ 4. Restart services
368
+ 5. Review API activity for unauthorized use
369
+ 6. Monitor account for suspicious activity
370
+
371
+ **If Webhook Fails Repeatedly**:
372
+ 1. Check webhook endpoint is responding
373
+ 2. Verify signature secret is correct
374
+ 3. Review error logs
375
+ 4. Check for rate limiting
376
+ 5. Contact Stripe support if issue persists
377
+
378
+ ---
379
+
380
+ ## Security Checklist for Production
381
+
382
+ - [ ] Webhook secret configured in environment
383
+ - [ ] Stripe API key in environment variable (not hardcoded)
384
+ - [ ] Handler validation for event data
385
+ - [ ] Idempotency checks implemented
386
+ - [ ] Error handling doesn't expose sensitive data
387
+ - [ ] Logging doesn't include credentials
388
+ - [ ] Rate limiting configured
389
+ - [ ] HTTPS enforced for webhook endpoint
390
+ - [ ] Database transactions for consistency
391
+ - [ ] Monitoring and alerting set up
392
+ - [ ] Backup/restore plan for webhook data
393
+ - [ ] Incident response plan documented
394
+
395
+ ---
396
+
397
+ ## Dependencies Security
398
+
399
+ ### Stripe SDK
400
+ - Keep stripe SDK updated
401
+ - Monitor npm for security advisories
402
+ - Test updates in staging first
403
+
404
+ ### Fastify
405
+ - Keep Fastify updated
406
+ - Enable security headers
407
+ - Use official Fastify modules only
408
+
409
+ ```bash
410
+ # Regular security checks
411
+ npm audit
412
+ npm audit fix
413
+
414
+ # Check outdated packages
415
+ npm outdated
416
+
417
+ # Update with caution
418
+ npm update --depth 3
419
+ ```
420
+
421
+ ---
422
+
423
+ ## Compliance
424
+
425
+ ### PCI Compliance
426
+ - Never store raw card data
427
+ - Use Stripe for payment processing
428
+ - Don't log sensitive card information
429
+ - Ensure HTTPS for all communication
430
+ - Implement access controls
431
+
432
+ ### GDPR Compliance
433
+ - Respect customer data privacy
434
+ - Implement data retention policies
435
+ - Provide data deletion on request
436
+ - Document processing activities
437
+ - Update privacy policy
438
+
439
+ ---
440
+
441
+ ## Additional Resources
442
+
443
+ - [Stripe Security](https://stripe.com/docs/security)
444
+ - [OWASP Top 10](https://owasp.org/www-project-top-ten/)
445
+ - [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
446
+ - [Fastify Security](https://www.fastify.io/docs/latest/Guides/Security/)
447
+
448
+ ---
449
+
450
+ ## Reporting Security Issues
451
+
452
+ **Please do not open public GitHub/GitLab issues for security vulnerabilities.**
453
+
454
+ Email security concerns to: [contact info when available]
455
+
456
+ Include:
457
+ - Description of vulnerability
458
+ - Steps to reproduce
459
+ - Potential impact
460
+ - Suggested fix (if any)
461
+
462
+ ---
463
+
464
+ **Last Updated**: 2024-12-29
465
+ **Maintained By**: Tim Mushen