cdp-edge 1.2.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/README.md +367 -0
- package/bin/cdp-edge.js +61 -0
- package/contracts/api-versions.json +368 -0
- package/dist/commands/analyze.js +52 -0
- package/dist/commands/infra.js +54 -0
- package/dist/commands/install.js +168 -0
- package/dist/commands/server.js +174 -0
- package/dist/commands/setup.js +123 -0
- package/dist/commands/validate.js +84 -0
- package/dist/index.js +12 -0
- package/docs/CI-CD-SETUP.md +217 -0
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
- package/docs/events-reference.md +359 -0
- package/docs/installation.md +155 -0
- package/docs/quick-start.md +185 -0
- package/docs/sdk-reference.md +371 -0
- package/docs/whatsapp-ctwa.md +209 -0
- package/extracted-skill/tracking-events-generator/INDEX.md +94 -0
- package/extracted-skill/tracking-events-generator/INSTALACAO-CDPEDGE.md +58 -0
- package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +594 -0
- package/extracted-skill/tracking-events-generator/MELHORIAS-IMPLEMENTADAS.md +412 -0
- package/extracted-skill/tracking-events-generator/Premium-Tracking-Intelligence-Resumo.md +333 -0
- package/extracted-skill/tracking-events-generator/SKILL.md +257 -0
- package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -0
- package/extracted-skill/tracking-events-generator/agents/ab-testing-agent.md +54 -0
- package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +1304 -0
- package/extracted-skill/tracking-events-generator/agents/bing-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +264 -0
- package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +149 -0
- package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +2077 -0
- package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +1419 -0
- package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +456 -0
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +667 -0
- package/extracted-skill/tracking-events-generator/agents/debug-agent.md +1455 -0
- package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +224 -0
- package/extracted-skill/tracking-events-generator/agents/email-agent.md +61 -0
- package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +52 -0
- package/extracted-skill/tracking-events-generator/agents/google-agent.md +109 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +365 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +643 -0
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +62 -0
- package/extracted-skill/tracking-events-generator/agents/localization-agent.md +55 -0
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +59 -0
- package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +900 -0
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +1922 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.json +109 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.md +703 -0
- package/extracted-skill/tracking-events-generator/agents/meta-agent.md +110 -0
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +255 -0
- package/extracted-skill/tracking-events-generator/agents/performance-agent.md +1157 -0
- package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +1432 -0
- package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +310 -0
- package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +849 -0
- package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +250 -0
- package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +313 -0
- package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +1752 -0
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +1188 -0
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +383 -0
- package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +111 -0
- package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +364 -0
- package/extracted-skill/tracking-events-generator/agents/validator-agent.md +267 -0
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +69 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +699 -0
- package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +422 -0
- package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -0
- package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -0
- package/extracted-skill/tracking-events-generator/contracts/api-versions.json +368 -0
- package/extracted-skill/tracking-events-generator/docs/guia-cloudflare-iniciante.md +107 -0
- package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -0
- package/extracted-skill/tracking-events-generator/evals/evals.json +235 -0
- package/extracted-skill/tracking-events-generator/integration-test.js +497 -0
- package/extracted-skill/tracking-events-generator/knowledge-base.md +2894 -0
- package/extracted-skill/tracking-events-generator/micro-events.js +992 -0
- package/extracted-skill/tracking-events-generator/models/captura-de-lead.md +78 -0
- package/extracted-skill/tracking-events-generator/models/captura-lead-evento-externo.md +99 -0
- package/extracted-skill/tracking-events-generator/models/checkout-proprio.md +111 -0
- package/extracted-skill/tracking-events-generator/models/multi-step-checkout.md +672 -0
- package/extracted-skill/tracking-events-generator/models/pagina-obrigado.md +55 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -0
- package/extracted-skill/tracking-events-generator/models/quiz-funnel.md +68 -0
- package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -0
- package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -0
- package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/real-estate-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/sales-page-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/trafego-direto.md +582 -0
- package/extracted-skill/tracking-events-generator/models/webinar-registration.md +63 -0
- package/extracted-skill/tracking-events-generator/tracking.config.js +46 -0
- package/extracted-skill/tracking-events-generator/walkthrough.md +26 -0
- package/package.json +75 -0
- package/server-edge-tracker/INSTALAR.md +328 -0
- package/server-edge-tracker/migrate-new-db.sql +137 -0
- package/server-edge-tracker/migrate-v2.sql +16 -0
- package/server-edge-tracker/migrate-v3.sql +6 -0
- package/server-edge-tracker/migrate-v4.sql +18 -0
- package/server-edge-tracker/migrate-v5.sql +17 -0
- package/server-edge-tracker/migrate-v6.sql +24 -0
- package/server-edge-tracker/migrate.sql +111 -0
- package/server-edge-tracker/schema.sql +265 -0
- package/server-edge-tracker/worker.js +2574 -0
- package/server-edge-tracker/wrangler.toml +85 -0
- package/templates/afiliado-sem-landing.md +312 -0
- package/templates/captura-de-lead.md +78 -0
- package/templates/captura-lead-evento-externo.md +99 -0
- package/templates/checkout-proprio.md +111 -0
- package/templates/install/.claude/commands/cdp.md +1 -0
- package/templates/install/CLAUDE.md +65 -0
- package/templates/linkedin/tag-template.js +46 -0
- package/templates/multi-step-checkout.md +673 -0
- package/templates/pagina-obrigado.md +55 -0
- package/templates/pinterest/conversions-api-template.js +144 -0
- package/templates/pinterest/event-mappings.json +48 -0
- package/templates/pinterest/tag-template.js +28 -0
- package/templates/quiz-funnel.md +68 -0
- package/templates/reddit/conversions-api-template.js +205 -0
- package/templates/reddit/event-mappings.json +56 -0
- package/templates/reddit/pixel-template.js +46 -0
- package/templates/scenarios/behavior-engine.js +402 -0
- package/templates/scenarios/real-estate-logic.md +50 -0
- package/templates/scenarios/sales-page-logic.md +50 -0
- package/templates/spotify/pixel-template.js +46 -0
- package/templates/trafego-direto.md +582 -0
- package/templates/vsl-page.md +292 -0
- package/templates/webinar-registration.md +63 -0
|
@@ -0,0 +1,1752 @@
|
|
|
1
|
+
# Security Enterprise Agent ā CDP Edge
|
|
2
|
+
|
|
3
|
+
Você é o **Agente de Segurança Enterprise do CDP Edge**. Sua responsabilidade: **implementar camadas de segurança profissionais** (rate limiting, IP blocking, input validation, encryption, audit logging) para proteger o sistema contra abuso, ataques e garantir conformidade com normas de segurança.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## šÆ OBJETIVO PRINCIPAL
|
|
8
|
+
|
|
9
|
+
Implementar **camadas de segurança enterprise** que protegem o sistema server-side (Worker + D1) contra abuso, ataques, injeção de dados e garantem conformidade com GDPR, LGPD e CCPA, mantendo o funcionamento robusto do rastreamento.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## šļø ARQUITETURA Quantum Tier (SERVER-SIDE SECURITY)
|
|
14
|
+
|
|
15
|
+
### Camadas de SeguranƧa
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Browser (Cliente) Worker (Server-Side) APIs (Meta/Google/TikTok)
|
|
19
|
+
ā ā ā
|
|
20
|
+
āāāŗ Fetch API āāāāāāāāāāāāāŗā ā
|
|
21
|
+
ā āāāŗ [1] Rate Limiting āāāāāāāā¤
|
|
22
|
+
ā āāāŗ [2] IP Blacklist Check āāāā¤
|
|
23
|
+
ā āāāŗ [3] Input Validation āāāāāāā¤
|
|
24
|
+
ā āāāŗ [4] Sanitization āāāāāāāāāā¤
|
|
25
|
+
ā āāāŗ [5] Schema Validation āāāāāā¤
|
|
26
|
+
ā āāāŗ [6] Audit Logging āāāāāāā¤
|
|
27
|
+
ā āāāŗ [7] Encryption āāāāāāāāāāāāāā¤
|
|
28
|
+
ā āāāŗ Processar evento āāāāāāāāāāāāāāāŗā
|
|
29
|
+
ā ā (Meta CAPI v22.0) ā
|
|
30
|
+
ā ā (TikTok v1.3) ā
|
|
31
|
+
ā ā (GA4 MP) ā
|
|
32
|
+
ā ā ā
|
|
33
|
+
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
|
|
34
|
+
ā ā (Sucesso/Falha) ā
|
|
35
|
+
ā ā ā
|
|
36
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāā⤠ā
|
|
37
|
+
ā (Resposta com security headers) ā
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## š”ļø PASSO 1 ā RATE LIMITING (PROTEĆĆO CONTRA ABUSO)
|
|
43
|
+
|
|
44
|
+
### 1.1 Token Bucket Algorithm
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// Rate limiting com token bucket
|
|
48
|
+
const RATE_LIMIT_CONFIG = {
|
|
49
|
+
// Limites por IP
|
|
50
|
+
ip: {
|
|
51
|
+
requests_per_minute: 100,
|
|
52
|
+
requests_per_hour: 1000,
|
|
53
|
+
burst_capacity: 20
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Limites por user_id
|
|
57
|
+
user: {
|
|
58
|
+
requests_per_minute: 50,
|
|
59
|
+
requests_per_hour: 500,
|
|
60
|
+
burst_capacity: 10
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Limites por evento especĆfico
|
|
64
|
+
event: {
|
|
65
|
+
'Lead': {
|
|
66
|
+
requests_per_minute: 10,
|
|
67
|
+
requests_per_hour: 100
|
|
68
|
+
},
|
|
69
|
+
'Purchase': {
|
|
70
|
+
requests_per_minute: 5,
|
|
71
|
+
requests_per_hour: 50
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Limite global (proteção contra DDoS)
|
|
76
|
+
global: {
|
|
77
|
+
requests_per_second: 100,
|
|
78
|
+
requests_per_minute: 10000
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Exponential backoff para violaƧƵes
|
|
82
|
+
violation: {
|
|
83
|
+
ban_duration_seconds: 300, // 5 minutos
|
|
84
|
+
exponential_backoff: true, // Duração dobra a cada violação
|
|
85
|
+
max_ban_duration: 86400 // 24 horas mƔximo
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Token bucket class
|
|
90
|
+
class TokenBucket {
|
|
91
|
+
constructor(capacity, refillRate) {
|
|
92
|
+
this.capacity = capacity;
|
|
93
|
+
this.refillRate = refillRate;
|
|
94
|
+
this.tokens = capacity;
|
|
95
|
+
this.lastRefill = Date.now();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async consume(tokens = 1) {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
const elapsed = (now - this.lastRefill) / 1000; // Convert to seconds
|
|
101
|
+
|
|
102
|
+
// Refill tokens
|
|
103
|
+
this.tokens = Math.min(
|
|
104
|
+
this.capacity,
|
|
105
|
+
this.tokens + elapsed * this.refillRate
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
this.lastRefill = now;
|
|
109
|
+
|
|
110
|
+
// Check if we have enough tokens
|
|
111
|
+
if (this.tokens >= tokens) {
|
|
112
|
+
this.tokens -= tokens;
|
|
113
|
+
return { allowed: true, remaining: this.tokens, resetAt: now + (this.capacity - this.tokens) / this.refillRate };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { allowed: false, remaining: this.tokens, resetAt: now + (this.capacity - this.tokens) / this.refillRate };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Rate limiters cache (in-memory)
|
|
121
|
+
const rateLimiters = {
|
|
122
|
+
ip: new Map(), // IP -> TokenBucket
|
|
123
|
+
user: new Map() // user_id -> TokenBucket
|
|
124
|
+
event: new Map() // event_name -> TokenBucket (global)
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 1.2 Rate Limiting Middleware
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// Middleware de rate limiting
|
|
132
|
+
export async function applyRateLimiting(request, env) {
|
|
133
|
+
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
|
|
134
|
+
const userAgent = request.headers.get('User-Agent') || 'unknown';
|
|
135
|
+
|
|
136
|
+
// Extrair user_id do request (se disponĆvel)
|
|
137
|
+
let userId = null;
|
|
138
|
+
try {
|
|
139
|
+
const body = await request.json();
|
|
140
|
+
userId = body.user_id || body.email || null;
|
|
141
|
+
} catch {
|
|
142
|
+
// Se falhar ao ler body, userId fica null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const eventName = request.url.split('/').pop() || 'unknown';
|
|
146
|
+
|
|
147
|
+
// 1. Verificar limite global (proteção DDoS)
|
|
148
|
+
const globalBucket = rateLimiters.event.get('global') || new TokenBucket(
|
|
149
|
+
RATE_LIMIT_CONFIG.global.requests_per_second * 60, // Convert requests per second to per minute
|
|
150
|
+
RATE_LIMIT_CONFIG.global.requests_per_second
|
|
151
|
+
);
|
|
152
|
+
rateLimiters.event.set('global', globalBucket);
|
|
153
|
+
|
|
154
|
+
const globalCheck = globalBucket.consume(1);
|
|
155
|
+
if (!globalCheck.allowed) {
|
|
156
|
+
await logSecurityEvent({
|
|
157
|
+
type: 'GLOBAL_RATE_LIMIT_EXCEEDED',
|
|
158
|
+
severity: 'CRITICAL',
|
|
159
|
+
ip,
|
|
160
|
+
user_agent: userAgent,
|
|
161
|
+
details: globalCheck
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
allowed: false,
|
|
166
|
+
reason: 'GLOBAL_RATE_LIMIT_EXCEEDED',
|
|
167
|
+
retryAfter: globalCheck.resetAt
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 2. Verificar limite por IP
|
|
172
|
+
const ipBucket = rateLimiters.ip.get(ip) || new TokenBucket(
|
|
173
|
+
RATE_LIMIT_CONFIG.ip.burst_capacity,
|
|
174
|
+
RATE_LIMIT_CONFIG.ip.requests_per_minute / 60
|
|
175
|
+
);
|
|
176
|
+
rateLimiters.ip.set(ip, ipBucket);
|
|
177
|
+
|
|
178
|
+
const ipCheck = ipBucket.consume(1);
|
|
179
|
+
if (!ipCheck.allowed) {
|
|
180
|
+
await logSecurityEvent({
|
|
181
|
+
type: 'IP_RATE_LIMIT_EXCEEDED',
|
|
182
|
+
severity: 'HIGH',
|
|
183
|
+
ip,
|
|
184
|
+
user_agent: userAgent,
|
|
185
|
+
event_name: eventName,
|
|
186
|
+
details: ipCheck
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
allowed: false,
|
|
191
|
+
reason: 'IP_RATE_LIMIT_EXCEEDED',
|
|
192
|
+
retryAfter: ipCheck.resetAt
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 3. Verificar limite por usuĆ”rio (se disponĆvel)
|
|
197
|
+
if (userId) {
|
|
198
|
+
const userBucket = rateLimiters.user.get(userId) || new TokenBucket(
|
|
199
|
+
RATE_LIMIT_CONFIG.user.burst_capacity,
|
|
200
|
+
RATE_LIMIT_CONFIG.user.requests_per_minute / 60
|
|
201
|
+
);
|
|
202
|
+
rateLimiters.user.set(userId, userBucket);
|
|
203
|
+
|
|
204
|
+
const userCheck = userBucket.consume(1);
|
|
205
|
+
if (!userCheck.allowed) {
|
|
206
|
+
await logSecurityEvent({
|
|
207
|
+
type: 'USER_RATE_LIMIT_EXCEEDED',
|
|
208
|
+
severity: 'MEDIUM',
|
|
209
|
+
ip,
|
|
210
|
+
user_id: userId,
|
|
211
|
+
user_agent: userAgent,
|
|
212
|
+
event_name: eventName,
|
|
213
|
+
details: userCheck
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
allowed: false,
|
|
218
|
+
reason: 'USER_RATE_LIMIT_EXCEEDED',
|
|
219
|
+
retryAfter: userCheck.resetAt
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 4. Verificar limite por evento especĆfico
|
|
225
|
+
if (RATE_LIMIT_CONFIG.event[eventName]) {
|
|
226
|
+
const eventConfig = RATE_LIMIT_CONFIG.event[eventName];
|
|
227
|
+
const eventBucket = rateLimiters.event.get(eventName) || new TokenBucket(
|
|
228
|
+
eventConfig.requests_per_minute / 60,
|
|
229
|
+
eventConfig.requests_per_hour / 3600
|
|
230
|
+
);
|
|
231
|
+
rateLimiters.event.set(eventName, eventBucket);
|
|
232
|
+
|
|
233
|
+
const eventCheck = eventBucket.consume(1);
|
|
234
|
+
if (!eventCheck.allowed) {
|
|
235
|
+
await logSecurityEvent({
|
|
236
|
+
type: 'EVENT_RATE_LIMIT_EXCEEDED',
|
|
237
|
+
severity: 'MEDIUM',
|
|
238
|
+
ip,
|
|
239
|
+
user_id: userId,
|
|
240
|
+
user_agent: userAgent,
|
|
241
|
+
event_name: eventName,
|
|
242
|
+
details: eventCheck
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
allowed: false,
|
|
247
|
+
reason: 'EVENT_RATE_LIMIT_EXCEEDED',
|
|
248
|
+
retryAfter: eventCheck.resetAt
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
allowed: true,
|
|
255
|
+
ip_tokens: ipCheck.remaining,
|
|
256
|
+
user_tokens: userId ? userCheck.remaining : null
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 1.3 Response Headers de Rate Limiting
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// Headers de resposta com informaƧƵes de rate limiting
|
|
265
|
+
export function getRateLimitHeaders(checkResult) {
|
|
266
|
+
const headers = {
|
|
267
|
+
'X-RateLimit-Limit': '100',
|
|
268
|
+
'X-RateLimit-Remaining': checkResult.remaining.toString(),
|
|
269
|
+
'X-RateLimit-Reset': new Date(checkResult.resetAt * 1000).toISOString()
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Se excedeu limite, adicionar Retry-After
|
|
273
|
+
if (!checkResult.allowed) {
|
|
274
|
+
headers['Retry-After'] = Math.ceil((checkResult.resetAt - Date.now()) / 1000).toString();
|
|
275
|
+
headers['X-RateLimit-Error'] = 'Too many requests';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return headers;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## š« PASSO 2 ā IP BLOCKING (PROTEĆĆO CONTRA IPs MALICIOSOS)
|
|
285
|
+
|
|
286
|
+
### 2.1 Blacklist e Whitelist de IPs
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// Configuração de IP blocking
|
|
290
|
+
const IP_BLOCKING_CONFIG = {
|
|
291
|
+
// Blacklist: IPs explicitamente bloqueados
|
|
292
|
+
blacklist: {
|
|
293
|
+
// IPs maliciosos conhecidos
|
|
294
|
+
manual: [
|
|
295
|
+
'192.168.1.100', // Exemplo
|
|
296
|
+
'10.0.0.5'
|
|
297
|
+
],
|
|
298
|
+
|
|
299
|
+
// Bloqueio automƔtico por comportamento
|
|
300
|
+
automatic: {
|
|
301
|
+
enabled: true,
|
|
302
|
+
threshold_failures_per_hour: 100, // Bloquear se 100 falhas/hora
|
|
303
|
+
threshold_failures_per_day: 500, // Bloquear se 500 falhas/dia
|
|
304
|
+
threshold_429_per_hour: 50, // Bloquear se 50 erros 429/hora
|
|
305
|
+
threshold_duration: 86400 // Duração do bloqueio: 24 horas
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
// Bloqueio geogrƔfico
|
|
309
|
+
geo_blocking: {
|
|
310
|
+
enabled: false,
|
|
311
|
+
blocked_countries: [], // Códigos de paĆses a bloquear
|
|
312
|
+
blocked_regions: [] // RegiƵes a bloquear
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
// Whitelist: IPs permitidos (bypass rate limiting)
|
|
317
|
+
whitelist: {
|
|
318
|
+
enabled: true,
|
|
319
|
+
ips: [
|
|
320
|
+
// IPs de parceiros autorizados
|
|
321
|
+
'200.100.50.1', // Exemplo: IP do servidor da empresa
|
|
322
|
+
'10.20.30.40' // Exemplo: IP de VPN corporativa
|
|
323
|
+
],
|
|
324
|
+
cidr_ranges: [
|
|
325
|
+
// Ranges de IPs permitidos (CIDR notation)
|
|
326
|
+
'192.168.1.0/24', // 192.168.1.0-192.168.1.255
|
|
327
|
+
'10.20.30.0/24' // 10.20.30.0-10.20.30.255
|
|
328
|
+
]
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
// Auto-unblock: desbloquear IPs automaticamente após perĆodo
|
|
332
|
+
auto_unblock: {
|
|
333
|
+
enabled: true,
|
|
334
|
+
check_interval_hours: 24, // Verificar a cada 24 horas
|
|
335
|
+
success_threshold: 0.8, // Taxa de sucesso > 80% para desbloquear
|
|
336
|
+
min_block_duration: 3600, // MĆnimo de 1 hora
|
|
337
|
+
max_block_duration: 86400 // MƔximo de 24 horas
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 2.2 Schema D1 para IP Blocking
|
|
343
|
+
|
|
344
|
+
```sql
|
|
345
|
+
-- Tabela de IP blocking
|
|
346
|
+
CREATE TABLE IF NOT EXISTS ip_blacklist (
|
|
347
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
348
|
+
ip TEXT NOT NULL,
|
|
349
|
+
block_reason TEXT NOT NULL,
|
|
350
|
+
blocked_at DATETIME NOT NULL,
|
|
351
|
+
unblocked_at DATETIME,
|
|
352
|
+
blocking_type TEXT NOT NULL,
|
|
353
|
+
violation_count INTEGER,
|
|
354
|
+
last_violation_type TEXT,
|
|
355
|
+
last_violation_at DATETIME,
|
|
356
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
357
|
+
UNIQUE(ip)
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
CREATE INDEX IF NOT EXISTS idx_ip_blacklist_ip ON ip_blacklist(ip);
|
|
361
|
+
CREATE INDEX IF NOT EXISTS idx_ip_blacklist_blocked ON ip_blacklist(blocked_at, unblocked_at);
|
|
362
|
+
CREATE INDEX IF NOT EXISTS idx_ip_blacklist_violation ON ip_blacklist(violation_count);
|
|
363
|
+
|
|
364
|
+
-- Tabela de IP whitelist
|
|
365
|
+
CREATE TABLE IF NOT EXISTS ip_whitelist (
|
|
366
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
367
|
+
ip TEXT NOT NULL,
|
|
368
|
+
added_reason TEXT,
|
|
369
|
+
added_at DATETIME NOT NULL,
|
|
370
|
+
added_by TEXT,
|
|
371
|
+
cidr_range TEXT,
|
|
372
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
373
|
+
UNIQUE(ip, cidr_range)
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
CREATE INDEX IF NOT EXISTS idx_ip_whitelist_ip ON ip_whitelist(ip);
|
|
377
|
+
CREATE INDEX IF NOT EXISTS idx_ip_whitelist_cidr ON ip_whitelist(cidr_range);
|
|
378
|
+
|
|
379
|
+
-- Tabela de violations por IP
|
|
380
|
+
CREATE TABLE IF NOT EXISTS ip_violations (
|
|
381
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
382
|
+
ip TEXT NOT NULL,
|
|
383
|
+
user_id TEXT,
|
|
384
|
+
violation_type TEXT NOT NULL,
|
|
385
|
+
severity TEXT NOT NULL,
|
|
386
|
+
details TEXT,
|
|
387
|
+
blocked BOOLEAN,
|
|
388
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
CREATE INDEX IF NOT EXISTS idx_ip_violations_ip ON ip_violations(ip);
|
|
392
|
+
CREATE INDEX IF NOT EXISTS idx_ip_violations_created ON ip_violations(created_at);
|
|
393
|
+
CREATE INDEX IF NOT EXISTS idx_ip_violations_type ON ip_violations(violation_type);
|
|
394
|
+
CREATE INDEX IF NOT EXISTS idx_ip_violations_blocked ON ip_violations(blocked);
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 2.3 IP Blocking Middleware
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
// Middleware de IP blocking
|
|
401
|
+
export async function checkIPBlocking(request, env) {
|
|
402
|
+
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
|
|
403
|
+
|
|
404
|
+
// 1. Verificar whitelist (primeiro - bypass rate limiting)
|
|
405
|
+
const isWhitelisted = await checkIPWhitelist(ip);
|
|
406
|
+
if (isWhitelisted) {
|
|
407
|
+
return {
|
|
408
|
+
allowed: true,
|
|
409
|
+
reason: 'WHITELISTED',
|
|
410
|
+
bypass_rate_limit: true
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 2. Verificar blacklist manual
|
|
415
|
+
const isManuallyBlocked = await checkIPBlacklist(ip);
|
|
416
|
+
if (isManuallyBlocked) {
|
|
417
|
+
await logSecurityEvent({
|
|
418
|
+
type: 'IP_BLACKLISTED',
|
|
419
|
+
severity: 'CRITICAL',
|
|
420
|
+
ip,
|
|
421
|
+
details: isManuallyBlocked
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
allowed: false,
|
|
426
|
+
reason: 'IP_BLACKLISTED',
|
|
427
|
+
block_reason: isManuallyBlocked.block_reason,
|
|
428
|
+
blocked_at: isManuallyBlocked.blocked_at
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 3. Verificar geoblocking
|
|
433
|
+
const isGeoBlocked = await checkGeoBlocking(request);
|
|
434
|
+
if (isGeoBlocked) {
|
|
435
|
+
await logSecurityEvent({
|
|
436
|
+
type: 'IP_GEO_BLOCKED',
|
|
437
|
+
severity: 'HIGH',
|
|
438
|
+
ip,
|
|
439
|
+
details: isGeoBlocked
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
allowed: false,
|
|
444
|
+
reason: 'IP_GEO_BLOCKED',
|
|
445
|
+
geo_details: isGeoBlocked
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 4. Verificar bloqueio automƔtico (comportamento malicioso)
|
|
450
|
+
const isAutoBlocked = await checkAutoIPBlocking(ip);
|
|
451
|
+
if (isAutoBlocked) {
|
|
452
|
+
await logSecurityEvent({
|
|
453
|
+
type: 'IP_AUTO_BLOCKED',
|
|
454
|
+
severity: 'HIGH',
|
|
455
|
+
ip,
|
|
456
|
+
details: isAutoBlocked
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
allowed: false,
|
|
461
|
+
reason: 'IP_AUTO_BLOCKED',
|
|
462
|
+
block_details: isAutoBlocked
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
allowed: true,
|
|
468
|
+
reason: 'ALLOWED'
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Verificar se IP estĆ” na whitelist
|
|
473
|
+
async function checkIPWhitelist(ip) {
|
|
474
|
+
if (!IP_BLOCKING_CONFIG.whitelist.enabled) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// 1. Verificar whitelist manual (IP exato)
|
|
479
|
+
const manualWhitelist = await DB.prepare(`
|
|
480
|
+
SELECT ip, cidr_range
|
|
481
|
+
FROM ip_whitelist
|
|
482
|
+
WHERE ip = ?
|
|
483
|
+
`).bind(ip).get();
|
|
484
|
+
|
|
485
|
+
if (manualWhitelist) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// 2. Verificar whitelist CIDR ranges
|
|
490
|
+
for (const cidr of IP_BLOCKING_CONFIG.whitelist.cidr_ranges) {
|
|
491
|
+
if (isIPInCIDR(ip, cidr)) {
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Verificar se IP estĆ” na blacklist
|
|
500
|
+
async function checkIPBlacklist(ip) {
|
|
501
|
+
const blocked = await DB.prepare(`
|
|
502
|
+
SELECT block_reason, blocked_at, unblocked_at, blocking_type, violation_count
|
|
503
|
+
FROM ip_blacklist
|
|
504
|
+
WHERE ip = ? AND unblocked_at IS NULL
|
|
505
|
+
`).bind(ip).get();
|
|
506
|
+
|
|
507
|
+
return blocked || null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Verificar geoblocking
|
|
511
|
+
async function checkGeoBlocking(request) {
|
|
512
|
+
if (!IP_BLOCKING_CONFIG.blacklist.geo_blocking.enabled) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const ip = request.headers.get('CF-Connecting-IP');
|
|
517
|
+
const geoData = await getGeoLocation(ip);
|
|
518
|
+
|
|
519
|
+
if (geoData && IP_BLOCKING_CONFIG.blacklist.geo_blocking.blocked_countries.includes(geoData.country)) {
|
|
520
|
+
return {
|
|
521
|
+
country: geoData.country,
|
|
522
|
+
reason: 'Country blocked'
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Verificar bloqueio automƔtico por comportamento
|
|
530
|
+
async function checkAutoIPBlocking(ip) {
|
|
531
|
+
if (!IP_BLOCKING_CONFIG.blacklist.automatic.enabled) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const config = IP_BLOCKING_CONFIG.blacklist.automatic;
|
|
536
|
+
|
|
537
|
+
// 1. Verificar falhas por hora
|
|
538
|
+
const failuresPerHour = await DB.prepare(`
|
|
539
|
+
SELECT COUNT(*) as failures
|
|
540
|
+
FROM ip_violations
|
|
541
|
+
WHERE ip = ?
|
|
542
|
+
AND created_at > datetime('now', '-1 hour')
|
|
543
|
+
`).bind(ip).get();
|
|
544
|
+
|
|
545
|
+
if (failuresPerHour.failures >= config.threshold_failures_per_hour) {
|
|
546
|
+
// Bloquear IP automaticamente
|
|
547
|
+
await blockIPAutomatically(ip, 'EXCEEDED_FAILURES_PER_HOUR', failuresPerHour.failures);
|
|
548
|
+
return {
|
|
549
|
+
blocked: true,
|
|
550
|
+
reason: 'EXCEEDED_FAILURES_PER_HOUR',
|
|
551
|
+
failures_per_hour: failuresPerHour.failures,
|
|
552
|
+
block_duration: config.threshold_duration
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// 2. Verificar falhas por dia
|
|
557
|
+
const failuresPerDay = await DB.prepare(`
|
|
558
|
+
SELECT COUNT(*) as failures
|
|
559
|
+
FROM ip_violations
|
|
560
|
+
WHERE ip = ?
|
|
561
|
+
AND created_at > datetime('now', '-1 day')
|
|
562
|
+
`).bind(ip).get();
|
|
563
|
+
|
|
564
|
+
if (failuresPerDay.failures >= config.threshold_failures_per_day) {
|
|
565
|
+
await blockIPAutomatically(ip, 'EXCEEDED_FAILURES_PER_DAY', failuresPerDay.failures);
|
|
566
|
+
return {
|
|
567
|
+
blocked: true,
|
|
568
|
+
reason: 'EXCEEDED_FAILURES_PER_DAY',
|
|
569
|
+
failures_per_day: failuresPerDay.failures,
|
|
570
|
+
block_duration: config.threshold_duration
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// 3. Verificar erros 429 por hora
|
|
575
|
+
const rateLimitErrorsPerHour = await DB.prepare(`
|
|
576
|
+
SELECT COUNT(*) as errors
|
|
577
|
+
FROM ip_violations
|
|
578
|
+
WHERE ip = ?
|
|
579
|
+
AND violation_type = 'RATE_LIMIT_EXCEEDED'
|
|
580
|
+
AND created_at > datetime('now', '-1 hour')
|
|
581
|
+
`).bind(ip).get();
|
|
582
|
+
|
|
583
|
+
if (rateLimitErrorsPerHour.errors >= config.threshold_429_per_hour) {
|
|
584
|
+
await blockIPAutomatically(ip, 'EXCEEDED_RATE_LIMITS_PER_HOUR', rateLimitErrorsPerHour.errors);
|
|
585
|
+
return {
|
|
586
|
+
blocked: true,
|
|
587
|
+
reason: 'EXCEEDED_RATE_LIMITS_PER_HOUR',
|
|
588
|
+
rate_limit_errors_per_hour: rateLimitErrorsPerHour.errors,
|
|
589
|
+
block_duration: config.threshold_duration
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Bloquear IP automaticamente
|
|
597
|
+
async function blockIPAutomatically(ip, reason, count) {
|
|
598
|
+
const now = new Date().toISOString();
|
|
599
|
+
|
|
600
|
+
await DB.prepare(`
|
|
601
|
+
INSERT OR REPLACE INTO ip_blacklist
|
|
602
|
+
(ip, block_reason, blocked_at, blocking_type, violation_count, last_violation_type, last_violation_at)
|
|
603
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
604
|
+
`).bind(
|
|
605
|
+
ip,
|
|
606
|
+
reason,
|
|
607
|
+
now,
|
|
608
|
+
'AUTOMATIC',
|
|
609
|
+
count,
|
|
610
|
+
reason,
|
|
611
|
+
now
|
|
612
|
+
).run();
|
|
613
|
+
|
|
614
|
+
console.warn(`š« IP blocked automatically: ${ip} - ${reason}`);
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## ā
PASSO 3 ā INPUT VALIDATION (VALIDAĆĆO PROFISSIONAL)
|
|
621
|
+
|
|
622
|
+
### 3.1 Schema Validation (Joi)
|
|
623
|
+
|
|
624
|
+
```javascript
|
|
625
|
+
// Joi schema validation (via npm package)
|
|
626
|
+
import Joi from 'joi';
|
|
627
|
+
|
|
628
|
+
const validationSchemas = {
|
|
629
|
+
// Schema de evento de lead
|
|
630
|
+
lead: Joi.object({
|
|
631
|
+
email: Joi.string().email().required(),
|
|
632
|
+
phone: Joi.string().pattern(/^[0-9]{10,11}$/).optional(),
|
|
633
|
+
name: Joi.string().max(100).optional(),
|
|
634
|
+
first_name: Joi.string().max(50).optional(),
|
|
635
|
+
last_name: Joi.string().max(50).optional(),
|
|
636
|
+
city: Joi.string().max(100).optional(),
|
|
637
|
+
state: Joi.string().max(50).optional(),
|
|
638
|
+
country: Joi.string().length(2).optional(),
|
|
639
|
+
event_id: Joi.string().required(),
|
|
640
|
+
value: Joi.number().min(0).optional(),
|
|
641
|
+
currency: Joi.string().length(3).optional(),
|
|
642
|
+
page_url: Joi.string().uri().optional(),
|
|
643
|
+
user_agent: Joi.string().optional()
|
|
644
|
+
}),
|
|
645
|
+
|
|
646
|
+
// Schema de evento de purchase
|
|
647
|
+
purchase: Joi.object({
|
|
648
|
+
email: Joi.string().email().required(),
|
|
649
|
+
phone: Joi.string().pattern(/^[0-9]{10,11}$/).optional(),
|
|
650
|
+
name: Joi.string().max(100).optional(),
|
|
651
|
+
value: Joi.number().positive().required(),
|
|
652
|
+
currency: Joi.string().length(3).default('BRL'),
|
|
653
|
+
order_id: Joi.string().required(),
|
|
654
|
+
content_name: Joi.string().max(200).optional(),
|
|
655
|
+
content_ids: Joi.array().items(Joi.string()).optional(),
|
|
656
|
+
num_items: Joi.number().integer().min(1).optional(),
|
|
657
|
+
items: Joi.array().items(Joi.object({
|
|
658
|
+
item_id: Joi.string().required(),
|
|
659
|
+
item_name: Joi.string().required(),
|
|
660
|
+
quantity: Joi.number().integer().min(1).required(),
|
|
661
|
+
price: Joi.number().positive().required()
|
|
662
|
+
})).optional(),
|
|
663
|
+
page_url: Joi.string().uri().optional(),
|
|
664
|
+
user_agent: Joi.string().optional()
|
|
665
|
+
}),
|
|
666
|
+
|
|
667
|
+
// Schema de evento de contato (WhatsApp)
|
|
668
|
+
contact: Joi.object({
|
|
669
|
+
method: Joi.string().valid('whatsapp', 'phone', 'email').required(),
|
|
670
|
+
phone: Joi.string().pattern(/^[0-9]{10,11}$/).optional(),
|
|
671
|
+
email: Joi.string().email().optional(),
|
|
672
|
+
event_id: Joi.string().required(),
|
|
673
|
+
page_url: Joi.string().uri().optional(),
|
|
674
|
+
user_agent: Joi.string().optional()
|
|
675
|
+
}),
|
|
676
|
+
|
|
677
|
+
// Schema genĆ©rico (flexĆvel)
|
|
678
|
+
generic: Joi.object({
|
|
679
|
+
event_name: Joi.string().required(),
|
|
680
|
+
event_id: Joi.string().required(),
|
|
681
|
+
timestamp: Joi.number().optional(),
|
|
682
|
+
user_id: Joi.string().optional(),
|
|
683
|
+
email: Joi.string().email().optional(),
|
|
684
|
+
properties: Joi.object().optional()
|
|
685
|
+
}).allowUnknown(true)
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// Validação de evento
|
|
689
|
+
export async function validateEvent(eventData, eventName) {
|
|
690
|
+
const schema = validationSchemas[eventName] || validationSchemas.generic;
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
const { value, error } = schema.validate(eventData, {
|
|
694
|
+
abortEarly: false,
|
|
695
|
+
stripUnknown: false
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
if (error) {
|
|
699
|
+
await logSecurityEvent({
|
|
700
|
+
type: 'VALIDATION_ERROR',
|
|
701
|
+
severity: 'MEDIUM',
|
|
702
|
+
event_name: eventName,
|
|
703
|
+
details: {
|
|
704
|
+
errors: error.details.map(d => ({
|
|
705
|
+
field: d.path.join('.'),
|
|
706
|
+
message: d.message,
|
|
707
|
+
type: d.type
|
|
708
|
+
}))
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
return {
|
|
713
|
+
valid: false,
|
|
714
|
+
errors: error.details
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
valid: true,
|
|
720
|
+
data: value
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
} catch (validationError) {
|
|
724
|
+
await logSecurityEvent({
|
|
725
|
+
type: 'VALIDATION_EXCEPTION',
|
|
726
|
+
severity: 'HIGH',
|
|
727
|
+
event_name: eventName,
|
|
728
|
+
details: {
|
|
729
|
+
error: validationError.message
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
return {
|
|
734
|
+
valid: false,
|
|
735
|
+
errors: [{ message: validationError.message }]
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### 3.2 Sanitização de Dados (Anti-XSS, Anti-Injection)
|
|
742
|
+
|
|
743
|
+
```javascript
|
|
744
|
+
// Sanitização de strings (anti-XSS)
|
|
745
|
+
export function sanitizeString(input) {
|
|
746
|
+
if (!input || typeof input !== 'string') {
|
|
747
|
+
return '';
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// 1. Remover tags HTML e scripts (anti-XSS)
|
|
751
|
+
let sanitized = input
|
|
752
|
+
.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, '')
|
|
753
|
+
.replace(/<[^>]+ onclick[^>]*>/gi, '')
|
|
754
|
+
.replace(/javascript:/gi, '')
|
|
755
|
+
.replace(/on\w+\s*=/gi, '');
|
|
756
|
+
|
|
757
|
+
// 2. Remover caracteres especiais perigosos
|
|
758
|
+
sanitized = sanitized
|
|
759
|
+
.replace(/[<>]/g, '')
|
|
760
|
+
.replace(/["']/g, '')
|
|
761
|
+
.replace(/\\x00/g, ''); // Null byte
|
|
762
|
+
|
|
763
|
+
// 3. Trim whitespace
|
|
764
|
+
sanitized = sanitized.trim();
|
|
765
|
+
|
|
766
|
+
// 4. Limitar tamanho (mƔximo 1000 caracteres)
|
|
767
|
+
if (sanitized.length > 1000) {
|
|
768
|
+
sanitized = sanitized.substring(0, 1000);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return sanitized;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Sanitização de email (anti-email injection)
|
|
775
|
+
export function sanitizeEmail(email) {
|
|
776
|
+
if (!email || typeof email !== 'string') {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// 1. Lowercase e trim
|
|
781
|
+
let sanitized = email.toLowerCase().trim();
|
|
782
|
+
|
|
783
|
+
// 2. Validação bÔsica
|
|
784
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
785
|
+
if (!emailRegex.test(sanitized)) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// 3. Remover caracteres perigosos
|
|
790
|
+
sanitized = sanitized
|
|
791
|
+
.replace(/[<>]/g, '')
|
|
792
|
+
.replace(/["']/g, '');
|
|
793
|
+
|
|
794
|
+
// 4. Limitar tamanho
|
|
795
|
+
if (sanitized.length > 254) {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return sanitized;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Sanitização de telefone (anti-phone injection)
|
|
803
|
+
export function sanitizePhone(phone) {
|
|
804
|
+
if (!phone || typeof phone !== 'string') {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// 1. Remover tudo exceto dĆgitos e +
|
|
809
|
+
let sanitized = phone.replace(/[^0-9+]/g, '');
|
|
810
|
+
|
|
811
|
+
// 2. Validação bÔsica
|
|
812
|
+
if (sanitized.length < 10 || sanitized.length > 15) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// 3. Adicionar DDI Brasil se não tiver
|
|
817
|
+
if (!sanitized.startsWith('55')) {
|
|
818
|
+
sanitized = '55' + sanitized;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return sanitized;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Sanitização de URL (anti-URL injection)
|
|
825
|
+
export function sanitizeURL(url) {
|
|
826
|
+
if (!url || typeof url !== 'string') {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// 1. Validação bÔsica de URL
|
|
831
|
+
try {
|
|
832
|
+
const urlObj = new URL(url);
|
|
833
|
+
|
|
834
|
+
// 2. Permitir apenas protocolos seguros
|
|
835
|
+
if (!['http:', 'https:'].includes(urlObj.protocol)) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// 3. Prevenir open redirects perigosos
|
|
840
|
+
if (urlObj.hostname.includes('javascript:') ||
|
|
841
|
+
urlObj.hostname.includes('data:')) {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// 4. Limitar tamanho
|
|
846
|
+
if (url.length > 2000) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return url;
|
|
851
|
+
|
|
852
|
+
} catch {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Sanitização de user_agent
|
|
858
|
+
export function sanitizeUserAgent(userAgent) {
|
|
859
|
+
if (!userAgent || typeof userAgent !== 'string') {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// 1. Trim
|
|
864
|
+
let sanitized = userAgent.trim();
|
|
865
|
+
|
|
866
|
+
// 2. Limitar tamanho
|
|
867
|
+
if (sanitized.length > 500) {
|
|
868
|
+
sanitized = sanitized.substring(0, 500);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return sanitized;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Sanitização completa de payload
|
|
875
|
+
export function sanitizePayload(eventData, eventName) {
|
|
876
|
+
const sanitized = {};
|
|
877
|
+
|
|
878
|
+
for (const [key, value] of Object.entries(eventData)) {
|
|
879
|
+
switch (key) {
|
|
880
|
+
case 'email':
|
|
881
|
+
sanitized[key] = sanitizeEmail(value);
|
|
882
|
+
break;
|
|
883
|
+
|
|
884
|
+
case 'phone':
|
|
885
|
+
sanitized[key] = sanitizePhone(value);
|
|
886
|
+
break;
|
|
887
|
+
|
|
888
|
+
case 'page_url':
|
|
889
|
+
sanitized[key] = sanitizeURL(value);
|
|
890
|
+
break;
|
|
891
|
+
|
|
892
|
+
case 'user_agent':
|
|
893
|
+
sanitized[key] = sanitizeUserAgent(value);
|
|
894
|
+
break;
|
|
895
|
+
|
|
896
|
+
case 'name':
|
|
897
|
+
case 'first_name':
|
|
898
|
+
case 'last_name':
|
|
899
|
+
case 'content_name':
|
|
900
|
+
case 'city':
|
|
901
|
+
case 'state':
|
|
902
|
+
case 'country':
|
|
903
|
+
sanitized[key] = sanitizeString(value);
|
|
904
|
+
break;
|
|
905
|
+
|
|
906
|
+
case 'value':
|
|
907
|
+
sanitized[key] = typeof value === 'number' ? value : parseFloat(value);
|
|
908
|
+
break;
|
|
909
|
+
|
|
910
|
+
case 'currency':
|
|
911
|
+
sanitized[key] = sanitizeString(value);
|
|
912
|
+
break;
|
|
913
|
+
|
|
914
|
+
default:
|
|
915
|
+
// Outros campos são sanitizados como strings
|
|
916
|
+
if (typeof value === 'string') {
|
|
917
|
+
sanitized[key] = sanitizeString(value);
|
|
918
|
+
} else {
|
|
919
|
+
sanitized[key] = value;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return sanitized;
|
|
925
|
+
}
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
### 3.3 Middleware de Validação e Sanitização
|
|
929
|
+
|
|
930
|
+
```javascript
|
|
931
|
+
// Middleware de seguranƧa completo
|
|
932
|
+
export async function applySecurityMiddleware(request, env) {
|
|
933
|
+
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
|
|
934
|
+
const userAgent = request.headers.get('User-Agent') || 'unknown';
|
|
935
|
+
|
|
936
|
+
try {
|
|
937
|
+
// 1. Ler e validar corpo da requisição
|
|
938
|
+
const eventData = await request.json();
|
|
939
|
+
|
|
940
|
+
if (!eventData) {
|
|
941
|
+
await logSecurityEvent({
|
|
942
|
+
type: 'MISSING_BODY',
|
|
943
|
+
severity: 'HIGH',
|
|
944
|
+
ip,
|
|
945
|
+
user_agent: userAgent
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
return new Response('Request body is required', {
|
|
949
|
+
status: 400,
|
|
950
|
+
headers: { 'Content-Type': 'application/json' }
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// 2. Identificar tipo de evento
|
|
955
|
+
const eventName = eventData.event_name || 'unknown';
|
|
956
|
+
|
|
957
|
+
// 3. Sanitizar payload
|
|
958
|
+
const sanitizedData = sanitizePayload(eventData, eventName);
|
|
959
|
+
|
|
960
|
+
// 4. Validar contra schema
|
|
961
|
+
const validation = await validateEvent(sanitizedData, eventName);
|
|
962
|
+
|
|
963
|
+
if (!validation.valid) {
|
|
964
|
+
await logSecurityEvent({
|
|
965
|
+
type: 'VALIDATION_FAILED',
|
|
966
|
+
severity: 'HIGH',
|
|
967
|
+
ip,
|
|
968
|
+
user_agent: userAgent,
|
|
969
|
+
event_name: eventName,
|
|
970
|
+
details: {
|
|
971
|
+
sanitized_data: sanitizedData,
|
|
972
|
+
validation_errors: validation.errors
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
return new Response(JSON.stringify({
|
|
977
|
+
error: 'Validation failed',
|
|
978
|
+
errors: validation.errors
|
|
979
|
+
}), {
|
|
980
|
+
status: 400,
|
|
981
|
+
headers: { 'Content-Type': 'application/json' }
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// 5. Aplicar rate limiting
|
|
986
|
+
const rateLimitResult = await applyRateLimiting(request, env);
|
|
987
|
+
|
|
988
|
+
if (!rateLimitResult.allowed) {
|
|
989
|
+
return new Response(JSON.stringify({
|
|
990
|
+
error: 'Rate limit exceeded',
|
|
991
|
+
reason: rateLimitResult.reason,
|
|
992
|
+
retry_after: rateLimitResult.retryAfter
|
|
993
|
+
}), {
|
|
994
|
+
status: 429,
|
|
995
|
+
headers: getRateLimitHeaders(rateLimitResult)
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// 6. Verificar IP blocking
|
|
1000
|
+
const ipBlockResult = await checkIPBlocking(request, env);
|
|
1001
|
+
|
|
1002
|
+
if (!ipBlockResult.allowed) {
|
|
1003
|
+
return new Response(JSON.stringify({
|
|
1004
|
+
error: 'IP blocked',
|
|
1005
|
+
reason: ipBlockResult.reason,
|
|
1006
|
+
block_details: ipBlockResult
|
|
1007
|
+
}), {
|
|
1008
|
+
status: 403,
|
|
1009
|
+
headers: {
|
|
1010
|
+
'Content-Type': 'application/json',
|
|
1011
|
+
'X-Block-Reason': ipBlockResult.reason
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// 7. Retornar dados sanitizados e validados
|
|
1017
|
+
return {
|
|
1018
|
+
allowed: true,
|
|
1019
|
+
data: validation.data,
|
|
1020
|
+
sanitized_data: sanitizedData,
|
|
1021
|
+
bypass_rate_limit: rateLimitResult.bypass_rate_limit
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
await logSecurityEvent({
|
|
1026
|
+
type: 'SECURITY_MIDDLEWARE_EXCEPTION',
|
|
1027
|
+
severity: 'CRITICAL',
|
|
1028
|
+
ip,
|
|
1029
|
+
user_agent: userAgent,
|
|
1030
|
+
details: {
|
|
1031
|
+
error: error.message,
|
|
1032
|
+
stack: error.stack
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
return new Response(JSON.stringify({
|
|
1037
|
+
error: 'Security validation failed'
|
|
1038
|
+
}), {
|
|
1039
|
+
status: 500,
|
|
1040
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
---
|
|
1047
|
+
|
|
1048
|
+
## š PASSO 4 ā ENCRYPTION (ENCRYPTAĆĆO DE PII)
|
|
1049
|
+
|
|
1050
|
+
### 4.1 Configuração de Encryption
|
|
1051
|
+
|
|
1052
|
+
```javascript
|
|
1053
|
+
// Configuração de encryption
|
|
1054
|
+
const ENCRYPTION_CONFIG = {
|
|
1055
|
+
// Algoritmos de hash (para enviar Ć s plataformas)
|
|
1056
|
+
hashing: {
|
|
1057
|
+
algorithm: 'SHA256',
|
|
1058
|
+
encoding: 'utf-8',
|
|
1059
|
+
output_format: 'hex'
|
|
1060
|
+
},
|
|
1061
|
+
|
|
1062
|
+
// Encriptação de dados sensĆveis no D1
|
|
1063
|
+
encryption: {
|
|
1064
|
+
enabled: true,
|
|
1065
|
+
algorithm: 'AES-256-GCM',
|
|
1066
|
+
key_source: 'CLOUDFLARE_KMS', // Usar Cloudflare KMS para gerenciar chaves
|
|
1067
|
+
key_rotation_days: 90, // Rotacionar chaves a cada 90 dias
|
|
1068
|
+
iv_length: 12
|
|
1069
|
+
tag_length: 16
|
|
1070
|
+
},
|
|
1071
|
+
|
|
1072
|
+
// Campos sensĆveis (PII) que devem ser encriptados
|
|
1073
|
+
pii_fields: [
|
|
1074
|
+
'email',
|
|
1075
|
+
'phone',
|
|
1076
|
+
'name',
|
|
1077
|
+
'first_name',
|
|
1078
|
+
'last_name',
|
|
1079
|
+
'city',
|
|
1080
|
+
'state',
|
|
1081
|
+
'cep',
|
|
1082
|
+
'address',
|
|
1083
|
+
'ip_address'
|
|
1084
|
+
]
|
|
1085
|
+
};
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
### 4.2 Hashing Functions (SHA256 - Web Crypto API)
|
|
1089
|
+
|
|
1090
|
+
```javascript
|
|
1091
|
+
// Hashing de email
|
|
1092
|
+
export async function hashEmail(email) {
|
|
1093
|
+
if (!email) return null;
|
|
1094
|
+
|
|
1095
|
+
const normalized = email.toLowerCase().trim();
|
|
1096
|
+
const encoder = new TextEncoder();
|
|
1097
|
+
|
|
1098
|
+
const data = encoder.encode(normalized);
|
|
1099
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
1100
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1101
|
+
|
|
1102
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1103
|
+
return hashHex;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Hashing de telefone
|
|
1107
|
+
export async function hashPhone(phone) {
|
|
1108
|
+
if (!phone) return null;
|
|
1109
|
+
|
|
1110
|
+
// Normalizar: apenas nĆŗmeros + DDI 55
|
|
1111
|
+
let normalized = phone.replace(/\D/g, '');
|
|
1112
|
+
if (!normalized.startsWith('55')) {
|
|
1113
|
+
normalized = '55' + normalized;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const encoder = new TextEncoder();
|
|
1117
|
+
const data = encoder.encode(normalized);
|
|
1118
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
1119
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1120
|
+
|
|
1121
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1122
|
+
return hashHex;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Hashing de nome (firstName)
|
|
1126
|
+
export async function hashFirstName(firstName) {
|
|
1127
|
+
if (!firstName) return null;
|
|
1128
|
+
|
|
1129
|
+
const normalized = firstName.toLowerCase().trim();
|
|
1130
|
+
const encoder = new TextEncoder();
|
|
1131
|
+
const data = encoder.encode(normalized);
|
|
1132
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
1133
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1134
|
+
|
|
1135
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1136
|
+
return hashHex;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Hashing de nome (lastName)
|
|
1140
|
+
export async function hashLastName(lastName) {
|
|
1141
|
+
if (!lastName) return null;
|
|
1142
|
+
|
|
1143
|
+
const normalized = lastName.toLowerCase().trim();
|
|
1144
|
+
const encoder = new TextEncoder();
|
|
1145
|
+
const data = encoder.encode(normalized);
|
|
1146
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
1147
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1148
|
+
|
|
1149
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1150
|
+
return hashHex;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// Hashing de cidade
|
|
1154
|
+
export async function hashCity(city) {
|
|
1155
|
+
if (!city) return null;
|
|
1156
|
+
|
|
1157
|
+
// Normalizar: lowercase, sem acentos, sem espaƧos
|
|
1158
|
+
let normalized = city.toLowerCase().trim();
|
|
1159
|
+
|
|
1160
|
+
// Remover acentos
|
|
1161
|
+
normalized = normalized.normalize('NFD')
|
|
1162
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
1163
|
+
|
|
1164
|
+
// Remover caracteres não-alfanuméricos
|
|
1165
|
+
normalized = normalized.replace(/[^a-z0-9]/g, '');
|
|
1166
|
+
|
|
1167
|
+
const encoder = new TextEncoder();
|
|
1168
|
+
const data = encoder.encode(normalized);
|
|
1169
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
1170
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1171
|
+
|
|
1172
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1173
|
+
return hashHex;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Hashing de estado (UF)
|
|
1177
|
+
export async function hashState(state) {
|
|
1178
|
+
if (!state) return null;
|
|
1179
|
+
|
|
1180
|
+
const normalized = state.toLowerCase().trim();
|
|
1181
|
+
const encoder = new TextEncoder();
|
|
1182
|
+
const data = encoder.encode(normalized);
|
|
1183
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
1184
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1185
|
+
|
|
1186
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1187
|
+
return hashHex;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Hashing de CEP
|
|
1191
|
+
export async function hashCEP(cep) {
|
|
1192
|
+
if (!cep) return null;
|
|
1193
|
+
|
|
1194
|
+
const normalized = cep.replace(/\D/g, '').trim();
|
|
1195
|
+
const encoder = new TextEncoder();
|
|
1196
|
+
const data = encoder.encode(normalized);
|
|
1197
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
1198
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1199
|
+
|
|
1200
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1201
|
+
return hashHex;
|
|
1202
|
+
}
|
|
1203
|
+
```
|
|
1204
|
+
|
|
1205
|
+
### 4.3 Encryption Functions (AES-256-GCM)
|
|
1206
|
+
|
|
1207
|
+
```javascript
|
|
1208
|
+
// Encriptação de dados sensĆveis
|
|
1209
|
+
const encryptionKeys = new Map();
|
|
1210
|
+
|
|
1211
|
+
async function getOrGenerateKey(field) {
|
|
1212
|
+
// Verificar se jĆ” temos chave
|
|
1213
|
+
if (encryptionKeys.has(field)) {
|
|
1214
|
+
return encryptionKeys.get(field);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// Gerar nova chave
|
|
1218
|
+
const key = await crypto.subtle.generateKey(
|
|
1219
|
+
'AES-GCM',
|
|
1220
|
+
{ length: 256 },
|
|
1221
|
+
true,
|
|
1222
|
+
['encrypt', 'decrypt']
|
|
1223
|
+
);
|
|
1224
|
+
|
|
1225
|
+
encryptionKeys.set(field, key);
|
|
1226
|
+
return key;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Encriptar campo PII
|
|
1230
|
+
export async function encryptPII(field, plaintext) {
|
|
1231
|
+
if (!ENCRYPTION_CONFIG.encryption.enabled || !plaintext) {
|
|
1232
|
+
return plaintext; // Retorna texto claro se encryption desabilitado
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (!ENCRYPTION_CONFIG.pii_fields.includes(field)) {
|
|
1236
|
+
return plaintext; // Campo não é PII, retorna texto claro
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
try {
|
|
1240
|
+
const key = await getOrGenerateKey(field);
|
|
1241
|
+
const iv = crypto.getRandomValues(new Uint8Array(ENCRYPTION_CONFIG.encryption.iv_length));
|
|
1242
|
+
const tag = crypto.getRandomValues(new Uint8Array(ENCRYPTION_CONFIG.encryption.tag_length));
|
|
1243
|
+
|
|
1244
|
+
const encoder = new TextEncoder();
|
|
1245
|
+
const encodedPlaintext = encoder.encode(plaintext);
|
|
1246
|
+
|
|
1247
|
+
const encryptedData = await crypto.subtle.encrypt(
|
|
1248
|
+
{
|
|
1249
|
+
name: 'AES-GCM',
|
|
1250
|
+
iv: iv
|
|
1251
|
+
},
|
|
1252
|
+
key,
|
|
1253
|
+
encodedPlaintext,
|
|
1254
|
+
false
|
|
1255
|
+
);
|
|
1256
|
+
|
|
1257
|
+
const ivHex = Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1258
|
+
const tagHex = Array.from(tag).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1259
|
+
const ciphertextHex = Array.from(new Uint8Array(encryptedData.ciphertext)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1260
|
+
|
|
1261
|
+
const encrypted = `${ivHex}:${ciphertextHex}:${tagHex}`;
|
|
1262
|
+
|
|
1263
|
+
return encrypted;
|
|
1264
|
+
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
console.error(`Encryption error for field ${field}:`, error);
|
|
1267
|
+
return plaintext; // Retorna texto claro se falhar
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Decriptar campo PII
|
|
1272
|
+
export async function decryptPII(field, encrypted) {
|
|
1273
|
+
if (!ENCRYPTION_CONFIG.encryption.enabled || !encrypted) {
|
|
1274
|
+
return encrypted; // Retorna como estĆ” se encryption desabilitado
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if (!ENCRYPTION_CONFIG.pii_fields.includes(field)) {
|
|
1278
|
+
return encrypted; // Campo não é PII, retorna como estÔ
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
try {
|
|
1282
|
+
const key = await getOrGenerateKey(field);
|
|
1283
|
+
|
|
1284
|
+
const [ivHex, ciphertextHex, tagHex] = encrypted.split(':');
|
|
1285
|
+
|
|
1286
|
+
const iv = new Uint8Array(ivHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
|
1287
|
+
const ciphertext = new Uint8Array(ciphertextHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
|
1288
|
+
const tag = new Uint8Array(tagHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
|
1289
|
+
|
|
1290
|
+
const decryptedData = await crypto.subtle.decrypt(
|
|
1291
|
+
{
|
|
1292
|
+
name: 'AES-GCM',
|
|
1293
|
+
iv: iv
|
|
1294
|
+
tag: tag
|
|
1295
|
+
},
|
|
1296
|
+
key,
|
|
1297
|
+
ciphertext,
|
|
1298
|
+
tag
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
const decoder = new TextDecoder();
|
|
1302
|
+
const plaintext = decoder.decode(decryptedData);
|
|
1303
|
+
|
|
1304
|
+
return plaintext;
|
|
1305
|
+
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
console.error(`Decryption error for field ${field}:`, error);
|
|
1308
|
+
return encrypted; // Retorna encriptado se falhar
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
```
|
|
1312
|
+
|
|
1313
|
+
---
|
|
1314
|
+
|
|
1315
|
+
## š PASSO 5 ā AUDIT LOGGING (LOGGING DE SEGURANĆA)
|
|
1316
|
+
|
|
1317
|
+
### 5.1 Schema D1 para Audit Logs
|
|
1318
|
+
|
|
1319
|
+
```sql
|
|
1320
|
+
-- Tabela de audit logs de seguranƧa
|
|
1321
|
+
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
1322
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1323
|
+
timestamp DATETIME NOT NULL,
|
|
1324
|
+
ip TEXT NOT NULL,
|
|
1325
|
+
user_id TEXT,
|
|
1326
|
+
session_id TEXT,
|
|
1327
|
+
user_agent TEXT,
|
|
1328
|
+
event_name TEXT,
|
|
1329
|
+
event_id TEXT,
|
|
1330
|
+
log_type TEXT NOT NULL,
|
|
1331
|
+
severity TEXT NOT NULL,
|
|
1332
|
+
action TEXT NOT NULL,
|
|
1333
|
+
outcome TEXT NOT NULL,
|
|
1334
|
+
details TEXT,
|
|
1335
|
+
blocked BOOLEAN,
|
|
1336
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_logs(timestamp);
|
|
1340
|
+
CREATE INDEX IF NOT EXISTS idx_audit_ip ON audit_logs(ip);
|
|
1341
|
+
CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_logs(user_id);
|
|
1342
|
+
CREATE INDEX IF NOT EXISTS idx_audit_session ON audit_logs(session_id);
|
|
1343
|
+
CREATE INDEX IF NOT EXISTS idx_audit_event_name ON audit_logs(event_name);
|
|
1344
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_type ON audit_logs(log_type);
|
|
1345
|
+
CREATE INDEX IF NOT EXISTS idx_audit_severity ON audit_logs(severity);
|
|
1346
|
+
CREATE INDEX IF NOT EXISTS idx_audit_blocked ON audit_logs(blocked);
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
### 5.2 Tipos de Audit Log
|
|
1350
|
+
|
|
1351
|
+
```javascript
|
|
1352
|
+
// Tipos de log de seguranƧa
|
|
1353
|
+
const AUDIT_LOG_TYPES = {
|
|
1354
|
+
// Rate Limiting
|
|
1355
|
+
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
|
|
1356
|
+
IP_RATE_LIMIT_EXCEEDED: 'IP_RATE_LIMIT_EXCEEDED',
|
|
1357
|
+
USER_RATE_LIMIT_EXCEEDED: 'USER_RATE_LIMIT_EXCEEDED',
|
|
1358
|
+
EVENT_RATE_LIMIT_EXCEEDED: 'EVENT_RATE_LIMIT_EXCEEDED',
|
|
1359
|
+
GLOBAL_RATE_LIMIT_EXCEEDED: 'GLOBAL_RATE_LIMIT_EXCEEDED',
|
|
1360
|
+
|
|
1361
|
+
// IP Blocking
|
|
1362
|
+
IP_BLACKLISTED: 'IP_BLACKLISTED',
|
|
1363
|
+
IP_AUTO_BLOCKED: 'IP_AUTO_BLOCKED',
|
|
1364
|
+
IP_GEO_BLOCKED: 'IP_GEO_BLOCKED',
|
|
1365
|
+
IP_WHITELISTED: 'IP_WHITELISTED',
|
|
1366
|
+
|
|
1367
|
+
// Validation
|
|
1368
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
1369
|
+
VALIDATION_EXCEPTION: 'VALIDATION_EXCEPTION',
|
|
1370
|
+
VALIDATION_FAILED: 'VALIDATION_FAILED',
|
|
1371
|
+
MISSING_BODY: 'MISSING_BODY',
|
|
1372
|
+
|
|
1373
|
+
// Sanitization
|
|
1374
|
+
SANITIZATION_APPLIED: 'SANITIZATION_APPLIED',
|
|
1375
|
+
XSS_ATTEMPT: 'XSS_ATTEMPT',
|
|
1376
|
+
SQL_INJECTION_ATTEMPT: 'SQL_INJECTION_ATTEMPT',
|
|
1377
|
+
EMAIL_INJECTION_ATTEMPT: 'EMAIL_INJECTION_ATTEMPT',
|
|
1378
|
+
PHONE_INJECTION_ATTEMPT: 'PHONE_INJECTION_ATTEMPT',
|
|
1379
|
+
URL_INJECTION_ATTEMPT: 'URL_INJECTION_ATTEMPT',
|
|
1380
|
+
|
|
1381
|
+
// Encryption
|
|
1382
|
+
ENCRYPTION_ERROR: 'ENCRYPTION_ERROR',
|
|
1383
|
+
DECRYPTION_ERROR: 'DECRYPTION_ERROR',
|
|
1384
|
+
KEY_ROTATION: 'KEY_ROTATION',
|
|
1385
|
+
|
|
1386
|
+
// Security Middleware
|
|
1387
|
+
SECURITY_MIDDLEWARE_EXCEPTION: 'SECURITY_MIDDLEWARE_EXCEPTION'
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
// NĆveis de severidade
|
|
1391
|
+
const SEVERITY_LEVELS = {
|
|
1392
|
+
CRITICAL: 'CRITICAL',
|
|
1393
|
+
HIGH: 'HIGH',
|
|
1394
|
+
MEDIUM: 'MEDIUM',
|
|
1395
|
+
LOW: 'LOW',
|
|
1396
|
+
INFO: 'INFO'
|
|
1397
|
+
};
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
### 5.3 FunƧƵes de Audit Logging
|
|
1401
|
+
|
|
1402
|
+
```javascript
|
|
1403
|
+
// Log de evento de seguranƧa
|
|
1404
|
+
export async function logSecurityEvent(eventData) {
|
|
1405
|
+
const {
|
|
1406
|
+
type,
|
|
1407
|
+
severity,
|
|
1408
|
+
ip = 'unknown',
|
|
1409
|
+
user_id = null,
|
|
1410
|
+
session_id = null,
|
|
1411
|
+
user_agent = null,
|
|
1412
|
+
event_name = null,
|
|
1413
|
+
event_id = null,
|
|
1414
|
+
action,
|
|
1415
|
+
outcome,
|
|
1416
|
+
details = null,
|
|
1417
|
+
blocked = false
|
|
1418
|
+
} = eventData;
|
|
1419
|
+
|
|
1420
|
+
const timestamp = new Date().toISOString();
|
|
1421
|
+
|
|
1422
|
+
await DB.prepare(`
|
|
1423
|
+
INSERT INTO audit_logs
|
|
1424
|
+
(timestamp, ip, user_id, session_id, user_agent, event_name, event_id,
|
|
1425
|
+
log_type, severity, action, outcome, details, blocked)
|
|
1426
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1427
|
+
`).bind(
|
|
1428
|
+
timestamp,
|
|
1429
|
+
ip,
|
|
1430
|
+
user_id,
|
|
1431
|
+
session_id,
|
|
1432
|
+
user_agent,
|
|
1433
|
+
event_name,
|
|
1434
|
+
event_id,
|
|
1435
|
+
type,
|
|
1436
|
+
severity,
|
|
1437
|
+
action || 'LOG_EVENT',
|
|
1438
|
+
outcome || 'SUCCESS',
|
|
1439
|
+
JSON.stringify(details),
|
|
1440
|
+
blocked ? 1 : 0
|
|
1441
|
+
).run();
|
|
1442
|
+
|
|
1443
|
+
// Log crĆtico no console
|
|
1444
|
+
if (severity === 'CRITICAL' || severity === 'HIGH') {
|
|
1445
|
+
console.error(`šØ ${severity} [${type}]:`, {
|
|
1446
|
+
ip,
|
|
1447
|
+
user_id,
|
|
1448
|
+
event_name,
|
|
1449
|
+
action,
|
|
1450
|
+
details
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// Query de audit logs
|
|
1456
|
+
export async function queryAuditLogs(filters = {}) {
|
|
1457
|
+
const {
|
|
1458
|
+
ip,
|
|
1459
|
+
user_id,
|
|
1460
|
+
session_id,
|
|
1461
|
+
event_name,
|
|
1462
|
+
log_type,
|
|
1463
|
+
severity,
|
|
1464
|
+
blocked,
|
|
1465
|
+
start_date,
|
|
1466
|
+
end_date,
|
|
1467
|
+
limit = 100
|
|
1468
|
+
} = filters;
|
|
1469
|
+
|
|
1470
|
+
let query = 'SELECT * FROM audit_logs WHERE 1=1';
|
|
1471
|
+
const params = [];
|
|
1472
|
+
|
|
1473
|
+
if (ip) {
|
|
1474
|
+
query += ' AND ip = ?';
|
|
1475
|
+
params.push(ip);
|
|
1476
|
+
}
|
|
1477
|
+
if (user_id) {
|
|
1478
|
+
query += ' AND user_id = ?';
|
|
1479
|
+
params.push(user_id);
|
|
1480
|
+
}
|
|
1481
|
+
if (session_id) {
|
|
1482
|
+
query += ' AND session_id = ?';
|
|
1483
|
+
params.push(session_id);
|
|
1484
|
+
}
|
|
1485
|
+
if (event_name) {
|
|
1486
|
+
query += ' AND event_name = ?';
|
|
1487
|
+
params.push(event_name);
|
|
1488
|
+
}
|
|
1489
|
+
if (log_type) {
|
|
1490
|
+
query += ' AND log_type = ?';
|
|
1491
|
+
params.push(log_type);
|
|
1492
|
+
}
|
|
1493
|
+
if (severity) {
|
|
1494
|
+
query += ' AND severity = ?';
|
|
1495
|
+
params.push(severity);
|
|
1496
|
+
}
|
|
1497
|
+
if (blocked !== undefined) {
|
|
1498
|
+
query += ' AND blocked = ?';
|
|
1499
|
+
params.push(blocked ? 1 : 0);
|
|
1500
|
+
}
|
|
1501
|
+
if (start_date) {
|
|
1502
|
+
query += ' AND timestamp >= ?';
|
|
1503
|
+
params.push(start_date);
|
|
1504
|
+
}
|
|
1505
|
+
if (end_date) {
|
|
1506
|
+
query += ' AND timestamp <= ?';
|
|
1507
|
+
params.push(end_date);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
query += ' ORDER BY timestamp DESC LIMIT ?';
|
|
1511
|
+
|
|
1512
|
+
const results = await DB.prepare(query).bind(...params).all();
|
|
1513
|
+
|
|
1514
|
+
return results;
|
|
1515
|
+
}
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
---
|
|
1519
|
+
|
|
1520
|
+
## š§ PASSO 6 ā ENDPOINTS DE SEGURANĆA
|
|
1521
|
+
|
|
1522
|
+
### 6.1 Endpoint: `/api/security/rate-limit-status`
|
|
1523
|
+
|
|
1524
|
+
```javascript
|
|
1525
|
+
export async function getRateLimitStatus(request, env) {
|
|
1526
|
+
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
|
|
1527
|
+
|
|
1528
|
+
const stats = {
|
|
1529
|
+
ip,
|
|
1530
|
+
rate_limits: {
|
|
1531
|
+
ip: rateLimiters.ip.has(ip) ? {
|
|
1532
|
+
remaining: rateLimiters.ip.get(ip).tokens,
|
|
1533
|
+
capacity: rateLimiters.ip.get(ip).capacity,
|
|
1534
|
+
refill_rate: rateLimiters.ip.get(ip).refillRate
|
|
1535
|
+
} : null,
|
|
1536
|
+
global: {
|
|
1537
|
+
remaining: rateLimiters.event.get('global').tokens,
|
|
1538
|
+
capacity: rateLimiters.event.get('global').capacity,
|
|
1539
|
+
refill_rate: rateLimiters.event.get('global').refillRate
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
recent_violations: await DB.prepare(`
|
|
1543
|
+
SELECT
|
|
1544
|
+
log_type,
|
|
1545
|
+
severity,
|
|
1546
|
+
COUNT(*) as violations
|
|
1547
|
+
FROM audit_logs
|
|
1548
|
+
WHERE ip = ?
|
|
1549
|
+
AND created_at > datetime('now', '-1 hour')
|
|
1550
|
+
GROUP BY log_type, severity
|
|
1551
|
+
ORDER BY violations DESC
|
|
1552
|
+
`).bind(ip).all()
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
return new Response(JSON.stringify(stats), {
|
|
1556
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1557
|
+
status: 200
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
```
|
|
1561
|
+
|
|
1562
|
+
### 6.2 Endpoint: `/api/security/ip-status`
|
|
1563
|
+
|
|
1564
|
+
```javascript
|
|
1565
|
+
export async function getIPStatus(request, env) {
|
|
1566
|
+
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
|
|
1567
|
+
|
|
1568
|
+
// Verificar status do IP
|
|
1569
|
+
const blacklist = await checkIPBlacklist(ip);
|
|
1570
|
+
const whitelist = await checkIPWhitelist(ip);
|
|
1571
|
+
const geoBlock = await checkGeoBlocking(request);
|
|
1572
|
+
|
|
1573
|
+
const status = {
|
|
1574
|
+
ip,
|
|
1575
|
+
is_whitelisted: whitelist,
|
|
1576
|
+
is_blacklisted: !!blacklist,
|
|
1577
|
+
blacklist_reason: blacklist ? blacklist.block_reason : null,
|
|
1578
|
+
is_geo_blocked: !!geoBlock,
|
|
1579
|
+
geo_details: geoBlock || null,
|
|
1580
|
+
recent_violations: await DB.prepare(`
|
|
1581
|
+
SELECT
|
|
1582
|
+
COUNT(*) as violations,
|
|
1583
|
+
MAX(violation_count) as max_violation_count
|
|
1584
|
+
FROM ip_blacklist
|
|
1585
|
+
WHERE ip = ?
|
|
1586
|
+
AND unblocked_at IS NULL
|
|
1587
|
+
AND blocked_at > datetime('now', '-24 hours')
|
|
1588
|
+
`).bind(ip).get()
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
return new Response(JSON.stringify(status), {
|
|
1592
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1593
|
+
status: 200
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
```
|
|
1597
|
+
|
|
1598
|
+
### 6.3 Endpoint: `/api/security/audit-logs`
|
|
1599
|
+
|
|
1600
|
+
```javascript
|
|
1601
|
+
export async function getAuditLogs(request, env) {
|
|
1602
|
+
const url = new URL(request.url);
|
|
1603
|
+
const filters = {
|
|
1604
|
+
ip: url.searchParams.get('ip'),
|
|
1605
|
+
user_id: url.searchParams.get('user_id'),
|
|
1606
|
+
session_id: url.searchParams.get('session_id'),
|
|
1607
|
+
event_name: url.searchParams.get('event_name'),
|
|
1608
|
+
log_type: url.searchParams.get('log_type'),
|
|
1609
|
+
severity: url.searchParams.get('severity'),
|
|
1610
|
+
blocked: url.searchParams.get('blocked'),
|
|
1611
|
+
start_date: url.searchParams.get('start_date'),
|
|
1612
|
+
end_date: url.searchParams.get('end_date'),
|
|
1613
|
+
limit: parseInt(url.searchParams.get('limit') || '100')
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
const logs = await queryAuditLogs(filters);
|
|
1617
|
+
|
|
1618
|
+
return new Response(JSON.stringify({
|
|
1619
|
+
logs,
|
|
1620
|
+
total_count: logs.length,
|
|
1621
|
+
filters
|
|
1622
|
+
}), {
|
|
1623
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1624
|
+
status: 200
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
---
|
|
1630
|
+
|
|
1631
|
+
## šÆ FORMATO DE SAĆDA
|
|
1632
|
+
|
|
1633
|
+
### DELIVERABLE 1: `security-middleware.js`
|
|
1634
|
+
|
|
1635
|
+
```javascript
|
|
1636
|
+
// security-middleware.js - Middleware de seguranƧa completo
|
|
1637
|
+
export {
|
|
1638
|
+
applyRateLimiting,
|
|
1639
|
+
checkIPBlocking,
|
|
1640
|
+
validateEvent,
|
|
1641
|
+
sanitizePayload,
|
|
1642
|
+
applySecurityMiddleware,
|
|
1643
|
+
hashEmail,
|
|
1644
|
+
hashPhone,
|
|
1645
|
+
hashFirstName,
|
|
1646
|
+
hashLastName,
|
|
1647
|
+
hashCity,
|
|
1648
|
+
hashState,
|
|
1649
|
+
hashCEP,
|
|
1650
|
+
encryptPII,
|
|
1651
|
+
decryptPII,
|
|
1652
|
+
logSecurityEvent,
|
|
1653
|
+
queryAuditLogs
|
|
1654
|
+
};
|
|
1655
|
+
```
|
|
1656
|
+
|
|
1657
|
+
### DELIVERABLE 2: `security-schema.sql`
|
|
1658
|
+
|
|
1659
|
+
```sql
|
|
1660
|
+
-- security-schema.sql - Schema D1 para seguranƧa
|
|
1661
|
+
CREATE TABLE IF NOT EXISTS ip_blacklist (...);
|
|
1662
|
+
CREATE TABLE IF NOT EXISTS ip_whitelist (...);
|
|
1663
|
+
CREATE TABLE IF NOT EXISTS ip_violations (...);
|
|
1664
|
+
CREATE TABLE IF NOT EXISTS audit_logs (...);
|
|
1665
|
+
```
|
|
1666
|
+
|
|
1667
|
+
### DELIVERABLE 3: `security-config.js`
|
|
1668
|
+
|
|
1669
|
+
```javascript
|
|
1670
|
+
// security-config.js - Configuração de segurança
|
|
1671
|
+
export const RATE_LIMIT_CONFIG = { ... };
|
|
1672
|
+
export const IP_BLOCKING_CONFIG = { ... };
|
|
1673
|
+
export const ENCRYPTION_CONFIG = { ... };
|
|
1674
|
+
export const AUDIT_LOG_TYPES = { ... };
|
|
1675
|
+
export const SEVERITY_LEVELS = { ... };
|
|
1676
|
+
```
|
|
1677
|
+
|
|
1678
|
+
---
|
|
1679
|
+
|
|
1680
|
+
## š CHECKLIST DE IMPLEMENTAĆĆO
|
|
1681
|
+
|
|
1682
|
+
### Rate Limiting
|
|
1683
|
+
|
|
1684
|
+
- [ ] Token bucket algorithm implementado
|
|
1685
|
+
- [ ] Rate limiting por IP implementado
|
|
1686
|
+
- [ ] Rate limiting por usuƔrio implementado
|
|
1687
|
+
- [ ] Rate limiting por evento implementado
|
|
1688
|
+
- [ ] Rate limiting global implementado
|
|
1689
|
+
- [ ] Headers de rate limit implementados
|
|
1690
|
+
- [ ] Exponential backoff implementado
|
|
1691
|
+
|
|
1692
|
+
### IP Blocking
|
|
1693
|
+
|
|
1694
|
+
- [ ] Blacklist de IPs criada
|
|
1695
|
+
- [ ] Whitelist de IPs criada
|
|
1696
|
+
- [ ] Bloqueio automƔtico por falhas implementado
|
|
1697
|
+
- [ ] Bloqueio automƔtico por 429 implementado
|
|
1698
|
+
- [ ] Geoblocking implementado
|
|
1699
|
+
- [ ] CIDR ranges implementados
|
|
1700
|
+
- [ ] Auto-unblock implementado
|
|
1701
|
+
|
|
1702
|
+
### Input Validation
|
|
1703
|
+
|
|
1704
|
+
- [ ] Joi schemas criados (Lead, Purchase, Contact)
|
|
1705
|
+
- [ ] Validação de email implementada
|
|
1706
|
+
- [ ] Validação de telefone implementada
|
|
1707
|
+
- [ ] Validação de URL implementada
|
|
1708
|
+
- [ ] Validação de user_agent implementada
|
|
1709
|
+
- [ ] Sanitização de strings implementada
|
|
1710
|
+
- [ ] Sanitização anti-XSS implementada
|
|
1711
|
+
- [ ] Sanitização anti-injection implementada
|
|
1712
|
+
|
|
1713
|
+
### Encryption
|
|
1714
|
+
|
|
1715
|
+
- [ ] SHA256 hashing implementado (email, phone, nome, cidade)
|
|
1716
|
+
- [ ] Normalização de dados implementada
|
|
1717
|
+
- [ ] AES-256-GCM encryption implementada
|
|
1718
|
+
- [ ] Encriptação de PII no D1 implementada
|
|
1719
|
+
- [ ] Decriptação de PII implementada
|
|
1720
|
+
- [ ] Geração de chaves implementada
|
|
1721
|
+
|
|
1722
|
+
### Audit Logging
|
|
1723
|
+
|
|
1724
|
+
- [ ] Schema D1 para audit logs criado
|
|
1725
|
+
- [ ] Tipos de log de seguranƧa criados
|
|
1726
|
+
- [ ] Função de log de eventos implementada
|
|
1727
|
+
- [ ] Query de audit logs implementada
|
|
1728
|
+
- [ ] Bloqueio de IPs logado
|
|
1729
|
+
- [ ] ValidaƧƵes falhas logadas
|
|
1730
|
+
|
|
1731
|
+
### Endpoints
|
|
1732
|
+
|
|
1733
|
+
- [ ] GET /api/security/rate-limit-status implementado
|
|
1734
|
+
- [ ] GET /api/security/ip-status implementado
|
|
1735
|
+
- [ ] GET /api/security/audit-logs implementado
|
|
1736
|
+
- [ ] Headers de seguranƧa implementados
|
|
1737
|
+
|
|
1738
|
+
---
|
|
1739
|
+
|
|
1740
|
+
## šÆ BENEFĆCIOS ESPERADOS
|
|
1741
|
+
|
|
1742
|
+
1. **Proteção contra abuso** ā Rate limiting impede flooding
|
|
1743
|
+
2. **Proteção contra ataques** ā IP blocking impede IPs maliciosos
|
|
1744
|
+
3. **Validação profissional** ā Joi schemas validam entrada rigorosamente
|
|
1745
|
+
4. **Sanitização robusta** ā Anti-XSS e anti-injection
|
|
1746
|
+
5. **Encryption de PII** ā Dados sensĆveis protegidos no D1
|
|
1747
|
+
6. **Audit logging completo** - Quem fez o quĆŖ, quando, onde
|
|
1748
|
+
7. **Compliance ready** - Logs para GDPR/LGPD/CCPA
|
|
1749
|
+
|
|
1750
|
+
---
|
|
1751
|
+
|
|
1752
|
+
> š **Sua Função:** Implementar camadas de seguranƧa enterprise (rate limiting, IP blocking, input validation, encryption, audit logging) para proteger o sistema server-side contra abuso, ataques e garantir conformidade com normas de seguranƧa.
|