@xenterprises/fastify-xtwilio 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,721 @@
1
+ # Security Guidelines for xTwilio
2
+
3
+ This document outlines security best practices for using the xTwilio plugin to protect your communications infrastructure, API credentials, and customer data.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Credential Management](#credential-management)
8
+ 2. [API Key Security](#api-key-security)
9
+ 3. [Webhook Security](#webhook-security)
10
+ 4. [Input Validation](#input-validation)
11
+ 5. [Phone Number Handling](#phone-number-handling)
12
+ 6. [Email Security](#email-security)
13
+ 7. [Error Handling](#error-handling)
14
+ 8. [Rate Limiting](#rate-limiting)
15
+ 9. [Compliance & Privacy](#compliance--privacy)
16
+ 10. [Incident Response](#incident-response)
17
+
18
+ ---
19
+
20
+ ## 1. Credential Management
21
+
22
+ ### Never Hardcode Credentials
23
+
24
+ Always use environment variables for sensitive credentials. Never commit API keys, auth tokens, or secrets to version control.
25
+
26
+ **❌ WRONG:**
27
+
28
+ ```javascript
29
+ const fastify = await fastify.register(xTwilio, {
30
+ twilio: {
31
+ accountSid: 'AC1234567890abcdef1234567890abcdef',
32
+ authToken: 'your_secret_auth_token_here',
33
+ }
34
+ });
35
+ ```
36
+
37
+ **✅ CORRECT:**
38
+
39
+ ```javascript
40
+ const fastify = await fastify.register(xTwilio, {
41
+ twilio: {
42
+ accountSid: process.env.TWILIO_ACCOUNT_SID,
43
+ authToken: process.env.TWILIO_AUTH_TOKEN,
44
+ }
45
+ });
46
+ ```
47
+
48
+ ### Environment Variable Management
49
+
50
+ 1. Create a `.env` file locally (never commit to Git)
51
+ 2. Use `.env.example` as a template for developers
52
+ 3. In production, set environment variables through your hosting platform:
53
+ - **Vercel**: Project Settings → Environment Variables
54
+ - **AWS**: Systems Manager → Parameter Store or Secrets Manager
55
+ - **Docker**: Use Docker secrets or `.env` files in secure storage
56
+ - **Heroku**: Config Vars in app settings
57
+
58
+ ### Rotating Credentials
59
+
60
+ Regularly rotate API keys and auth tokens:
61
+
62
+ 1. Generate new credentials in Twilio/SendGrid dashboards
63
+ 2. Update environment variables in production
64
+ 3. Test with new credentials before removing old ones
65
+ 4. Revoke old credentials once new ones are verified
66
+
67
+ ---
68
+
69
+ ## 2. API Key Security
70
+
71
+ ### Twilio Credentials
72
+
73
+ Your Twilio Account SID and Auth Token provide full access to your Twilio account. Treat them like passwords.
74
+
75
+ **Protection Level: CRITICAL**
76
+
77
+ ```bash
78
+ # Store in a secure vault, not in code or config files
79
+ TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # 34 chars
80
+ TWILIO_AUTH_TOKEN=your_secret_token_here # Very long string
81
+ ```
82
+
83
+ ### SendGrid API Key
84
+
85
+ SendGrid API keys have full API access to your account.
86
+
87
+ **Protection Level: CRITICAL**
88
+
89
+ ```bash
90
+ # Always use the least-privileged API key
91
+ # Create keys in SendGrid dashboard with specific permissions
92
+ SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
93
+ ```
94
+
95
+ #### SendGrid Key Permissions
96
+
97
+ When creating API keys, enable only the permissions you need:
98
+
99
+ - **For sending only**: Enable `mail.send` only
100
+ - **For contact management**: Enable `contacts.read`, `contacts.write`
101
+ - **For list management**: Enable `lists.read`, `lists.write`
102
+ - **For templates**: Enable `templates.read`
103
+
104
+ Example: Key for sending emails only
105
+
106
+ ```
107
+ Mail Send: Full Access
108
+ Contacts: None
109
+ Lists: None
110
+ ```
111
+
112
+ ### Logging & Exposure
113
+
114
+ Never log credentials in any form:
115
+
116
+ ```javascript
117
+ // ❌ WRONG - Exposes API key in logs
118
+ console.log('Config:', {
119
+ twilioAccountSid: process.env.TWILIO_ACCOUNT_SID,
120
+ twilioAuthToken: process.env.TWILIO_AUTH_TOKEN, // Exposed!
121
+ sendgridApiKey: process.env.SENDGRID_API_KEY, // Exposed!
122
+ });
123
+
124
+ // ✅ CORRECT - Safe logging with masking
125
+ console.log('Config loaded:', {
126
+ twilio: 'configured',
127
+ sendgrid: 'configured',
128
+ });
129
+ ```
130
+
131
+ ---
132
+
133
+ ## 3. Webhook Security
134
+
135
+ ### Webhook Signature Validation
136
+
137
+ Always validate Twilio webhooks to ensure they come from Twilio:
138
+
139
+ ```javascript
140
+ import twilio from 'twilio';
141
+
142
+ // Verify webhook signature
143
+ const isValidRequest = twilio.validateRequest(
144
+ process.env.TWILIO_AUTH_TOKEN,
145
+ req.headers['x-twilio-signature'],
146
+ 'https://yourdomain.com/webhooks/twilio',
147
+ req.rawBody
148
+ );
149
+
150
+ if (!isValidRequest) {
151
+ return reply.code(403).send({ error: 'Invalid signature' });
152
+ }
153
+ ```
154
+
155
+ ### Webhook Configuration
156
+
157
+ 1. Use HTTPS endpoints only (never HTTP in production)
158
+ 2. Set strong webhook secrets:
159
+ ```bash
160
+ TWILIO_CONVERSATIONS_WEBHOOK_SECRET=your_strong_random_secret
161
+ ```
162
+ 3. Validate signatures in all webhook handlers
163
+ 4. Implement request logging (without exposing sensitive data)
164
+
165
+ ### Webhook URL Security
166
+
167
+ - Use unique paths: `/webhooks/twilio` not just `/webhook`
168
+ - Implement IP allowlisting if possible (restrict to Twilio IPs)
169
+ - Require authentication tokens in webhook headers
170
+ - Implement rate limiting on webhook endpoints
171
+
172
+ **Example Secure Webhook Handler:**
173
+
174
+ ```javascript
175
+ import crypto from 'crypto';
176
+
177
+ fastify.post('/webhooks/twilio', async (request, reply) => {
178
+ // 1. Validate signature
179
+ const signature = request.headers['x-twilio-signature'];
180
+ const expectedSignature = crypto
181
+ .createHmac('sha1', process.env.TWILIO_AUTH_TOKEN)
182
+ .update(`https://yourdomain.com/webhooks/twilio${JSON.stringify(request.body)}`)
183
+ .digest('base64');
184
+
185
+ if (signature !== expectedSignature) {
186
+ fastify.log.error('Invalid webhook signature');
187
+ return reply.code(403).send({ error: 'Unauthorized' });
188
+ }
189
+
190
+ // 2. Process webhook
191
+ const { MessageSid, From, Body } = request.body;
192
+
193
+ // 3. Don't expose sensitive data in response
194
+ return reply.code(200).send({ status: 'received' });
195
+ });
196
+ ```
197
+
198
+ ---
199
+
200
+ ## 4. Input Validation
201
+
202
+ ### Phone Number Validation
203
+
204
+ Always validate phone numbers before sending SMS:
205
+
206
+ ```javascript
207
+ // ✅ CORRECT - Validate phone number
208
+ const validation = await fastify.sms.validatePhoneNumber(phoneNumber, {
209
+ countryCode: 'US'
210
+ });
211
+
212
+ if (!validation.isValid) {
213
+ return reply.code(400).send({
214
+ error: 'Invalid phone number',
215
+ details: validation.error
216
+ });
217
+ }
218
+
219
+ await fastify.sms.send(validation.phoneNumber, message);
220
+ ```
221
+
222
+ ### Message Content Validation
223
+
224
+ Validate message content to prevent injection attacks:
225
+
226
+ ```javascript
227
+ // ❌ WRONG - Accepts arbitrary input
228
+ await fastify.sms.send(phoneNumber, userInput);
229
+
230
+ // ✅ CORRECT - Validates message content
231
+ function validateMessage(message) {
232
+ if (!message || typeof message !== 'string') {
233
+ throw new Error('Message must be a non-empty string');
234
+ }
235
+ if (message.length > 160) {
236
+ throw new Error('Message exceeds SMS length limit');
237
+ }
238
+ // Remove potentially harmful characters
239
+ return message.replace(/[<>\"']/g, '');
240
+ }
241
+
242
+ const validMessage = validateMessage(userInput);
243
+ await fastify.sms.send(phoneNumber, validMessage);
244
+ ```
245
+
246
+ ### Email Input Validation
247
+
248
+ Validate email addresses before sending:
249
+
250
+ ```javascript
251
+ // Use a robust email validation library
252
+ import { validateEmail } from 'email-validator';
253
+
254
+ const isValid = validateEmail(userEmail);
255
+ if (!isValid) {
256
+ return reply.code(400).send({ error: 'Invalid email address' });
257
+ }
258
+
259
+ // Or validate through SendGrid
260
+ const validation = await fastify.email.validate(userEmail);
261
+ if (!validation.isValid) {
262
+ return reply.code(400).send({ error: 'Email address not deliverable' });
263
+ }
264
+
265
+ await fastify.email.send(userEmail, subject, html);
266
+ ```
267
+
268
+ ---
269
+
270
+ ## 5. Phone Number Handling
271
+
272
+ ### Prevent SMS Bombing
273
+
274
+ Implement rate limiting to prevent SMS abuse:
275
+
276
+ ```javascript
277
+ import rateLimit from '@fastify/rate-limit';
278
+
279
+ // Add rate limiting plugin
280
+ await fastify.register(rateLimit, {
281
+ max: 10, // Maximum 10 requests
282
+ timeWindow: '15 minutes'
283
+ });
284
+
285
+ fastify.post('/send-sms', async (request, reply) => {
286
+ const { phoneNumber, message } = request.body;
287
+
288
+ // Check rate limit (automatic with plugin)
289
+
290
+ await fastify.sms.send(phoneNumber, message);
291
+ return { status: 'sent' };
292
+ });
293
+ ```
294
+
295
+ ### Phone Number Privacy
296
+
297
+ Don't expose full phone numbers in logs or responses:
298
+
299
+ ```javascript
300
+ // ❌ WRONG - Exposes full phone number
301
+ fastify.log.info('SMS sent to:', phoneNumber);
302
+
303
+ // ✅ CORRECT - Masks phone number
304
+ function maskPhoneNumber(phone) {
305
+ const last4 = phone.slice(-4);
306
+ const masked = '*'.repeat(phone.length - 4) + last4;
307
+ return masked;
308
+ }
309
+
310
+ fastify.log.info('SMS sent to:', maskPhoneNumber(phoneNumber));
311
+ ```
312
+
313
+ ### International Phone Numbers
314
+
315
+ Handle international numbers securely:
316
+
317
+ ```javascript
318
+ // Use Twilio's Lookup API to validate international numbers
319
+ const validation = await fastify.sms.validatePhoneNumber(
320
+ phoneNumber,
321
+ {
322
+ type: 'carrier',
323
+ countryCode: 'FR' // Specify country for context
324
+ }
325
+ );
326
+
327
+ // Check for expensive operations (video calls, international rates)
328
+ if (validation.carrier && validation.carrierType === 'voip') {
329
+ // Handle VOIP numbers differently if needed
330
+ }
331
+ ```
332
+
333
+ ---
334
+
335
+ ## 6. Email Security
336
+
337
+ ### Prevent Email Header Injection
338
+
339
+ Sanitize email headers to prevent injection attacks:
340
+
341
+ ```javascript
342
+ // ❌ WRONG - Unsafe headers
343
+ await fastify.email.send(
344
+ userEmail,
345
+ userSubject, // User input in header!
346
+ html
347
+ );
348
+
349
+ // ✅ CORRECT - Sanitized headers
350
+ function sanitizeHeader(header) {
351
+ // Remove newlines, carriage returns, and other injection vectors
352
+ return header.replace(/[\r\n]/g, '');
353
+ }
354
+
355
+ const safeSubject = sanitizeHeader(userSubject);
356
+ await fastify.email.send(userEmail, safeSubject, html);
357
+ ```
358
+
359
+ ### Attachment Security
360
+
361
+ Be careful with email attachments:
362
+
363
+ ```javascript
364
+ // ❌ WRONG - Accepts any file
365
+ await fastify.email.sendWithAttachments(
366
+ userEmail,
367
+ subject,
368
+ html,
369
+ userProvidedAttachments // Dangerous!
370
+ );
371
+
372
+ // ✅ CORRECT - Validates attachments
373
+ function validateAttachment(attachment) {
374
+ const MAX_SIZE = 25 * 1024 * 1024; // 25 MB
375
+ const ALLOWED_TYPES = [
376
+ 'application/pdf',
377
+ 'image/jpeg',
378
+ 'image/png',
379
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
380
+ ];
381
+
382
+ if (Buffer.byteLength(attachment.content, 'base64') > MAX_SIZE) {
383
+ throw new Error('Attachment too large');
384
+ }
385
+
386
+ if (!ALLOWED_TYPES.includes(attachment.type)) {
387
+ throw new Error('File type not allowed');
388
+ }
389
+
390
+ return attachment;
391
+ }
392
+
393
+ const validAttachments = userAttachments.map(validateAttachment);
394
+ await fastify.email.sendWithAttachments(userEmail, subject, html, validAttachments);
395
+ ```
396
+
397
+ ### Unsubscribe Links
398
+
399
+ Always include unsubscribe functionality in marketing emails:
400
+
401
+ ```javascript
402
+ const htmlWithUnsubscribe = `
403
+ ${htmlContent}
404
+ <p>
405
+ <a href="https://yourdomain.com/unsubscribe?id=${userId}">Unsubscribe</a>
406
+ </p>
407
+ `;
408
+
409
+ await fastify.email.send(userEmail, subject, htmlWithUnsubscribe);
410
+ ```
411
+
412
+ ### SPF, DKIM, and DMARC Configuration
413
+
414
+ Set up email authentication in your SendGrid account:
415
+
416
+ 1. **SPF (Sender Policy Framework)**
417
+ - Add SPF record to DNS: `v=spf1 sendgrid.net ~all`
418
+
419
+ 2. **DKIM (DomainKeys Identified Mail)**
420
+ - Enable DKIM in SendGrid dashboard
421
+ - Add DKIM records to DNS
422
+
423
+ 3. **DMARC (Domain-based Message Authentication)**
424
+ - Set DMARC policy: `v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com`
425
+
426
+ ---
427
+
428
+ ## 7. Error Handling
429
+
430
+ ### Don't Expose Sensitive Data in Errors
431
+
432
+ Never include credentials, tokens, or full API responses in error messages:
433
+
434
+ ```javascript
435
+ // ❌ WRONG - Exposes sensitive information
436
+ fastify.post('/send-sms', async (request, reply) => {
437
+ try {
438
+ await fastify.sms.send(phoneNumber, message);
439
+ } catch (error) {
440
+ fastify.log.error('Full error:', error); // Logs sensitive details!
441
+ return reply.code(500).send(error); // Sends raw error to client!
442
+ }
443
+ });
444
+
445
+ // ✅ CORRECT - Safe error handling
446
+ fastify.post('/send-sms', async (request, reply) => {
447
+ try {
448
+ await fastify.sms.send(phoneNumber, message);
449
+ } catch (error) {
450
+ // Log full error internally for debugging
451
+ fastify.log.error('SMS send failed', {
452
+ code: error.code,
453
+ status: error.status,
454
+ // Don't log: message, accountSid, authToken
455
+ });
456
+
457
+ // Return safe error to client
458
+ if (error.status === 400) {
459
+ return reply.code(400).send({
460
+ error: 'Invalid phone number or message'
461
+ });
462
+ }
463
+
464
+ return reply.code(500).send({
465
+ error: 'Failed to send message. Please try again later.'
466
+ });
467
+ }
468
+ });
469
+ ```
470
+
471
+ ### Structured Error Logging
472
+
473
+ Use structured logging to capture errors without exposing secrets:
474
+
475
+ ```javascript
476
+ function logError(context, error) {
477
+ fastify.log.error({
478
+ timestamp: new Date().toISOString(),
479
+ context,
480
+ errorCode: error.code,
481
+ errorStatus: error.status,
482
+ message: 'Safe error description here',
483
+ // Never include:
484
+ // - authToken
485
+ // - apiKey
486
+ // - phoneNumber (full)
487
+ // - email addresses
488
+ // - rawErrorMessage
489
+ });
490
+ }
491
+ ```
492
+
493
+ ---
494
+
495
+ ## 8. Rate Limiting
496
+
497
+ ### SMS Rate Limiting
498
+
499
+ Prevent SMS spam with rate limiting:
500
+
501
+ ```javascript
502
+ // Configure in .env
503
+ SMS_RATE_LIMIT_PER_MINUTE=10
504
+
505
+ // Implement in your code
506
+ async function sendSMS(phoneNumber, message) {
507
+ // Check rate limit for this phone number
508
+ const key = `sms:${phoneNumber}`;
509
+ const count = await fastify.redis.incr(key);
510
+
511
+ if (count === 1) {
512
+ // Reset counter every minute
513
+ await fastify.redis.expire(key, 60);
514
+ }
515
+
516
+ if (count > parseInt(process.env.SMS_RATE_LIMIT_PER_MINUTE || '10')) {
517
+ throw new Error('Rate limit exceeded for this phone number');
518
+ }
519
+
520
+ return await fastify.sms.send(phoneNumber, message);
521
+ }
522
+ ```
523
+
524
+ ### Email Rate Limiting
525
+
526
+ Prevent email abuse:
527
+
528
+ ```javascript
529
+ // Configure in .env
530
+ EMAIL_RATE_LIMIT_PER_MINUTE=5
531
+
532
+ // Implement in your code
533
+ async function sendEmail(recipientEmail, subject, html) {
534
+ const key = `email:${recipientEmail}`;
535
+ const count = await fastify.redis.incr(key);
536
+
537
+ if (count === 1) {
538
+ await fastify.redis.expire(key, 60);
539
+ }
540
+
541
+ if (count > parseInt(process.env.EMAIL_RATE_LIMIT_PER_MINUTE || '5')) {
542
+ throw new Error('Rate limit exceeded for this recipient');
543
+ }
544
+
545
+ return await fastify.email.send(recipientEmail, subject, html);
546
+ }
547
+ ```
548
+
549
+ ---
550
+
551
+ ## 9. Compliance & Privacy
552
+
553
+ ### GDPR Compliance
554
+
555
+ - **Right to Access**: Maintain records of messages sent to users
556
+ - **Right to Erasure**: Delete user data upon request
557
+ - **Right to Rectification**: Allow users to update contact information
558
+ - **Consent**: Maintain proof of user consent before sending marketing emails
559
+
560
+ ```javascript
561
+ // Example: Request to delete user data
562
+ fastify.delete('/api/users/:userId/data', async (request, reply) => {
563
+ const { userId } = request.params;
564
+
565
+ // Delete from your database
566
+ await db.users.delete(userId);
567
+
568
+ // Request deletion from Twilio (contact Twilio support)
569
+ // Request deletion from SendGrid contacts
570
+ await fastify.email.deleteContact(userId);
571
+
572
+ return { status: 'deleted' };
573
+ });
574
+ ```
575
+
576
+ ### SMS Compliance (TCPA)
577
+
578
+ **In the United States**, SMS messages are regulated by TCPA:
579
+
580
+ - **Consent**: Obtain explicit written consent before sending SMS
581
+ - **Opt-out**: Honor opt-out requests immediately
582
+ - **Headers**: Include sender identification
583
+ - **Timing**: Don't send SMS between 9 PM and 8 AM recipient's time zone
584
+
585
+ ```javascript
586
+ // Check consent before sending
587
+ async function sendCompliantSMS(phoneNumber, message) {
588
+ const user = await db.users.findByPhone(phoneNumber);
589
+
590
+ if (!user.smsConsent) {
591
+ throw new Error('User has not consented to SMS');
592
+ }
593
+
594
+ if (user.unsubscribedFromSMS) {
595
+ throw new Error('User has opted out of SMS');
596
+ }
597
+
598
+ return await fastify.sms.send(phoneNumber, message);
599
+ }
600
+ ```
601
+
602
+ ### Email Compliance (CAN-SPAM)
603
+
604
+ **In the United States**, email marketing is regulated by CAN-SPAM:
605
+
606
+ - **Subject**: Must not be deceptive
607
+ - **Identification**: Identify message as advertisement
608
+ - **Reply Address**: Provide valid reply-to address
609
+ - **Unsubscribe**: Honor unsubscribe requests within 10 business days
610
+
611
+ ```javascript
612
+ // Send CAN-SPAM compliant email
613
+ async function sendMarketingEmail(to, subject, html) {
614
+ const complianceFooter = `
615
+ <footer style="margin-top: 2rem; padding: 1rem; border-top: 1px solid #ddd; font-size: 0.8rem;">
616
+ <p>Your Company Name<br/>
617
+ 123 Business St<br/>
618
+ City, ST 12345</p>
619
+ <p><a href="https://yourdomain.com/unsubscribe?email=${encodeURIComponent(to)}">
620
+ Unsubscribe from this mailing list
621
+ </a></p>
622
+ </footer>
623
+ `;
624
+
625
+ return await fastify.email.send(
626
+ to,
627
+ `[ADVERTISEMENT] ${subject}`,
628
+ `${html}${complianceFooter}`,
629
+ { replyTo: 'support@yourdomain.com' }
630
+ );
631
+ }
632
+ ```
633
+
634
+ ---
635
+
636
+ ## 10. Incident Response
637
+
638
+ ### What To Do If Credentials Are Exposed
639
+
640
+ 1. **Immediately Revoke Credentials**
641
+ - Twilio: https://console.twilio.com/ → Account → Security → Auth Tokens
642
+ - SendGrid: https://app.sendgrid.com/settings/api_keys
643
+ - Delete the exposed credentials
644
+
645
+ 2. **Generate New Credentials**
646
+ - Create new Auth Token/API Key in the respective dashboard
647
+ - Update all environment variables
648
+
649
+ 3. **Audit Recent Activity**
650
+ - Check Twilio audit logs for unauthorized API calls
651
+ - Check SendGrid logs for unauthorized email sends
652
+ - Review SMS/call logs for suspicious activity
653
+
654
+ 4. **Notify Users (If Applicable)**
655
+ - If user data was accessed, notify affected users
656
+ - Document the breach for regulatory reporting
657
+
658
+ 5. **Update Infrastructure**
659
+ - Restart all applications with new credentials
660
+ - Verify no hardcoded credentials in code repositories
661
+ - Scan codebase for exposed secrets
662
+
663
+ ### Monitoring for Suspicious Activity
664
+
665
+ ```javascript
666
+ // Monitor for unusual SMS volume
667
+ fastify.addHook('onResponse', async (request, reply) => {
668
+ if (request.url.includes('/send-sms')) {
669
+ const metric = {
670
+ timestamp: Date.now(),
671
+ status: reply.statusCode,
672
+ endpoint: request.url,
673
+ ip: request.ip
674
+ };
675
+
676
+ await fastify.redis.lpush('sms:metrics', JSON.stringify(metric));
677
+
678
+ // Alert on 50+ SMS in 1 minute
679
+ const count = await fastify.redis.lrange('sms:metrics', 0, -1);
680
+ const recentCount = count.filter(m => {
681
+ const data = JSON.parse(m);
682
+ return Date.now() - data.timestamp < 60000;
683
+ }).length;
684
+
685
+ if (recentCount > 50) {
686
+ fastify.log.error('ALERT: High SMS volume detected', { count: recentCount });
687
+ // Send alert to security team
688
+ }
689
+ }
690
+ });
691
+ ```
692
+
693
+ ### Security Checklist
694
+
695
+ - [ ] All credentials stored in environment variables (not in code)
696
+ - [ ] `.env` file added to `.gitignore`
697
+ - [ ] Webhook signatures validated
698
+ - [ ] HTTPS enforced for all endpoints
699
+ - [ ] Input validation implemented
700
+ - [ ] Error handling sanitized (no credential exposure)
701
+ - [ ] Rate limiting configured
702
+ - [ ] Sensitive data masked in logs
703
+ - [ ] Audit logging implemented
704
+ - [ ] Incident response plan documented
705
+
706
+ ---
707
+
708
+ ## Additional Resources
709
+
710
+ - [Twilio Security Best Practices](https://www.twilio.com/docs/security)
711
+ - [SendGrid Security Documentation](https://docs.sendgrid.com/for-developers/security)
712
+ - [OWASP Top 10](https://owasp.org/www-project-top-ten/)
713
+ - [GDPR Compliance Guide](https://gdpr-info.eu/)
714
+ - [TCPA SMS Compliance](https://www.fcc.gov/consumers/guides-and-resources-related-sms-texts)
715
+ - [CAN-SPAM Compliance](https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business)
716
+
717
+ ---
718
+
719
+ ## Reporting Security Issues
720
+
721
+ If you discover a security vulnerability in xTwilio, please email security@xenterprises.com instead of using the issue tracker. Please do not disclose the vulnerability publicly until it has been addressed.