@ranimontagna/agent-toolkit 0.1.4 → 0.1.5

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.
Files changed (30) hide show
  1. package/README.md +282 -277
  2. package/docs/assets/install-plan.svg +29 -0
  3. package/docs/assets/install-skill-packages.svg +31 -0
  4. package/docs/assets/install-status.svg +32 -0
  5. package/package.json +10 -9
  6. package/setup-agent-toolkit.sh +1 -1
  7. package/skills/backend/fastify-best-practices/LICENSE +21 -0
  8. package/skills/backend/fastify-best-practices/NOTICE.md +11 -0
  9. package/skills/backend/fastify-best-practices/SKILL.md +75 -0
  10. package/skills/backend/fastify-best-practices/rules/authentication.md +521 -0
  11. package/skills/backend/fastify-best-practices/rules/configuration.md +217 -0
  12. package/skills/backend/fastify-best-practices/rules/content-type.md +387 -0
  13. package/skills/backend/fastify-best-practices/rules/cors-security.md +445 -0
  14. package/skills/backend/fastify-best-practices/rules/database.md +320 -0
  15. package/skills/backend/fastify-best-practices/rules/decorators.md +416 -0
  16. package/skills/backend/fastify-best-practices/rules/deployment.md +423 -0
  17. package/skills/backend/fastify-best-practices/rules/error-handling.md +412 -0
  18. package/skills/backend/fastify-best-practices/rules/hooks.md +464 -0
  19. package/skills/backend/fastify-best-practices/rules/http-proxy.md +247 -0
  20. package/skills/backend/fastify-best-practices/rules/logging.md +402 -0
  21. package/skills/backend/fastify-best-practices/rules/performance.md +425 -0
  22. package/skills/backend/fastify-best-practices/rules/plugins.md +320 -0
  23. package/skills/backend/fastify-best-practices/rules/routes.md +467 -0
  24. package/skills/backend/fastify-best-practices/rules/schemas.md +585 -0
  25. package/skills/backend/fastify-best-practices/rules/serialization.md +475 -0
  26. package/skills/backend/fastify-best-practices/rules/testing.md +536 -0
  27. package/skills/backend/fastify-best-practices/rules/typescript.md +458 -0
  28. package/skills/backend/fastify-best-practices/rules/websockets.md +421 -0
  29. package/skills/backend/fastify-best-practices/tile.json +11 -0
  30. package/skills/core/agent-toolkit-maintainer/SKILL.md +16 -14
@@ -0,0 +1,445 @@
1
+ ---
2
+ name: cors-security
3
+ description: CORS and security headers in Fastify
4
+ metadata:
5
+ tags: cors, security, headers, helmet, csrf
6
+ ---
7
+
8
+ # CORS and Security
9
+
10
+ ## CORS with @fastify/cors
11
+
12
+ Enable Cross-Origin Resource Sharing:
13
+
14
+ ```typescript
15
+ import Fastify from 'fastify';
16
+ import cors from '@fastify/cors';
17
+
18
+ const app = Fastify();
19
+
20
+ // Simple CORS - allow all origins
21
+ app.register(cors);
22
+
23
+ // Configured CORS
24
+ app.register(cors, {
25
+ origin: ['https://example.com', 'https://app.example.com'],
26
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
27
+ allowedHeaders: ['Content-Type', 'Authorization'],
28
+ exposedHeaders: ['X-Total-Count'],
29
+ credentials: true,
30
+ maxAge: 86400, // 24 hours
31
+ });
32
+ ```
33
+
34
+ ## Dynamic CORS Origin
35
+
36
+ Validate origins dynamically:
37
+
38
+ ```typescript
39
+ app.register(cors, {
40
+ origin: (origin, callback) => {
41
+ // Allow requests with no origin (mobile apps, curl, etc.)
42
+ if (!origin) {
43
+ return callback(null, true);
44
+ }
45
+
46
+ // Check against allowed origins
47
+ const allowedOrigins = [
48
+ 'https://example.com',
49
+ 'https://app.example.com',
50
+ /\.example\.com$/,
51
+ ];
52
+
53
+ const isAllowed = allowedOrigins.some((allowed) => {
54
+ if (allowed instanceof RegExp) {
55
+ return allowed.test(origin);
56
+ }
57
+ return allowed === origin;
58
+ });
59
+
60
+ if (isAllowed) {
61
+ callback(null, true);
62
+ } else {
63
+ callback(new Error('Not allowed by CORS'), false);
64
+ }
65
+ },
66
+ credentials: true,
67
+ });
68
+ ```
69
+
70
+ ## Per-Route CORS
71
+
72
+ Configure CORS for specific routes:
73
+
74
+ ```typescript
75
+ app.register(cors, {
76
+ origin: true, // Reflect request origin
77
+ credentials: true,
78
+ });
79
+
80
+ // Or disable CORS for specific routes
81
+ app.route({
82
+ method: 'GET',
83
+ url: '/internal',
84
+ config: {
85
+ cors: false,
86
+ },
87
+ handler: async () => {
88
+ return { internal: true };
89
+ },
90
+ });
91
+ ```
92
+
93
+ ## Security Headers with @fastify/helmet
94
+
95
+ Add security headers:
96
+
97
+ ```typescript
98
+ import helmet from '@fastify/helmet';
99
+
100
+ app.register(helmet, {
101
+ contentSecurityPolicy: {
102
+ directives: {
103
+ defaultSrc: ["'self'"],
104
+ scriptSrc: ["'self'", "'unsafe-inline'"],
105
+ styleSrc: ["'self'", "'unsafe-inline'"],
106
+ imgSrc: ["'self'", 'data:', 'https:'],
107
+ connectSrc: ["'self'", 'https://api.example.com'],
108
+ },
109
+ },
110
+ crossOriginEmbedderPolicy: false, // Disable if embedding external resources
111
+ });
112
+ ```
113
+
114
+ ## Configure Individual Headers
115
+
116
+ Fine-tune security headers:
117
+
118
+ ```typescript
119
+ app.register(helmet, {
120
+ // Strict Transport Security
121
+ hsts: {
122
+ maxAge: 31536000, // 1 year
123
+ includeSubDomains: true,
124
+ preload: true,
125
+ },
126
+
127
+ // Content Security Policy
128
+ contentSecurityPolicy: {
129
+ useDefaults: true,
130
+ directives: {
131
+ 'script-src': ["'self'", 'https://trusted-cdn.com'],
132
+ },
133
+ },
134
+
135
+ // X-Frame-Options
136
+ frameguard: {
137
+ action: 'deny', // or 'sameorigin'
138
+ },
139
+
140
+ // X-Content-Type-Options
141
+ noSniff: true,
142
+
143
+ // X-XSS-Protection (legacy)
144
+ xssFilter: true,
145
+
146
+ // Referrer-Policy
147
+ referrerPolicy: {
148
+ policy: 'strict-origin-when-cross-origin',
149
+ },
150
+
151
+ // X-Permitted-Cross-Domain-Policies
152
+ permittedCrossDomainPolicies: false,
153
+
154
+ // X-DNS-Prefetch-Control
155
+ dnsPrefetchControl: {
156
+ allow: false,
157
+ },
158
+ });
159
+ ```
160
+
161
+ ## Rate Limiting
162
+
163
+ Protect against abuse:
164
+
165
+ ```typescript
166
+ import rateLimit from '@fastify/rate-limit';
167
+
168
+ app.register(rateLimit, {
169
+ max: 100,
170
+ timeWindow: '1 minute',
171
+ errorResponseBuilder: (request, context) => ({
172
+ statusCode: 429,
173
+ error: 'Too Many Requests',
174
+ message: `Rate limit exceeded. Retry in ${context.after}`,
175
+ retryAfter: context.after,
176
+ }),
177
+ });
178
+
179
+ // Per-route rate limit
180
+ app.get('/expensive', {
181
+ config: {
182
+ rateLimit: {
183
+ max: 10,
184
+ timeWindow: '1 minute',
185
+ },
186
+ },
187
+ }, handler);
188
+
189
+ // Skip rate limit for certain routes
190
+ app.get('/health', {
191
+ config: {
192
+ rateLimit: false,
193
+ },
194
+ }, () => ({ status: 'ok' }));
195
+ ```
196
+
197
+ ## Redis-Based Rate Limiting
198
+
199
+ Use Redis for distributed rate limiting:
200
+
201
+ ```typescript
202
+ import rateLimit from '@fastify/rate-limit';
203
+ import Redis from 'ioredis';
204
+
205
+ const redis = new Redis(process.env.REDIS_URL);
206
+
207
+ app.register(rateLimit, {
208
+ max: 100,
209
+ timeWindow: '1 minute',
210
+ redis,
211
+ nameSpace: 'rate-limit:',
212
+ keyGenerator: (request) => {
213
+ // Rate limit by user ID if authenticated, otherwise by IP
214
+ return request.user?.id || request.ip;
215
+ },
216
+ });
217
+ ```
218
+
219
+ ## CSRF Protection
220
+
221
+ Protect against Cross-Site Request Forgery:
222
+
223
+ ```typescript
224
+ import fastifyCsrf from '@fastify/csrf-protection';
225
+ import fastifyCookie from '@fastify/cookie';
226
+
227
+ app.register(fastifyCookie);
228
+ app.register(fastifyCsrf, {
229
+ cookieOpts: {
230
+ signed: true,
231
+ httpOnly: true,
232
+ sameSite: 'strict',
233
+ },
234
+ });
235
+
236
+ // Generate token
237
+ app.get('/csrf-token', async (request, reply) => {
238
+ const token = reply.generateCsrf();
239
+ return { token };
240
+ });
241
+
242
+ // Protected route
243
+ app.post('/transfer', {
244
+ preHandler: app.csrfProtection,
245
+ }, async (request) => {
246
+ // CSRF token validated
247
+ return { success: true };
248
+ });
249
+ ```
250
+
251
+ ## Custom Security Headers
252
+
253
+ Add custom headers:
254
+
255
+ ```typescript
256
+ app.addHook('onSend', async (request, reply) => {
257
+ // Custom security headers
258
+ reply.header('X-Request-ID', request.id);
259
+ reply.header('X-Content-Type-Options', 'nosniff');
260
+ reply.header('X-Frame-Options', 'DENY');
261
+ reply.header('Permissions-Policy', 'geolocation=(), camera=()');
262
+ });
263
+
264
+ // Per-route headers
265
+ app.get('/download', async (request, reply) => {
266
+ reply.header('Content-Disposition', 'attachment; filename="file.pdf"');
267
+ reply.header('X-Download-Options', 'noopen');
268
+ return reply.send(fileStream);
269
+ });
270
+ ```
271
+
272
+ ## Secure Cookies
273
+
274
+ Configure secure cookies:
275
+
276
+ ```typescript
277
+ import cookie from '@fastify/cookie';
278
+
279
+ app.register(cookie, {
280
+ secret: process.env.COOKIE_SECRET,
281
+ parseOptions: {
282
+ httpOnly: true,
283
+ secure: process.env.NODE_ENV === 'production',
284
+ sameSite: 'strict',
285
+ path: '/',
286
+ maxAge: 3600, // 1 hour
287
+ },
288
+ });
289
+
290
+ // Set secure cookie
291
+ app.post('/login', async (request, reply) => {
292
+ const token = await createSession(request.body);
293
+
294
+ reply.setCookie('session', token, {
295
+ httpOnly: true,
296
+ secure: true,
297
+ sameSite: 'strict',
298
+ path: '/',
299
+ maxAge: 86400,
300
+ signed: true,
301
+ });
302
+
303
+ return { success: true };
304
+ });
305
+
306
+ // Read signed cookie
307
+ app.get('/profile', async (request) => {
308
+ const session = request.cookies.session;
309
+ const unsigned = request.unsignCookie(session);
310
+
311
+ if (!unsigned.valid) {
312
+ throw { statusCode: 401, message: 'Invalid session' };
313
+ }
314
+
315
+ return { sessionId: unsigned.value };
316
+ });
317
+ ```
318
+
319
+ ## Request Validation Security
320
+
321
+ Validate and sanitize input:
322
+
323
+ ```typescript
324
+ // Schema-based validation protects against injection
325
+ app.post('/users', {
326
+ schema: {
327
+ body: {
328
+ type: 'object',
329
+ properties: {
330
+ email: {
331
+ type: 'string',
332
+ format: 'email',
333
+ maxLength: 254,
334
+ },
335
+ name: {
336
+ type: 'string',
337
+ minLength: 1,
338
+ maxLength: 100,
339
+ pattern: '^[a-zA-Z\\s]+$', // Only letters and spaces
340
+ },
341
+ },
342
+ required: ['email', 'name'],
343
+ additionalProperties: false,
344
+ },
345
+ },
346
+ }, handler);
347
+ ```
348
+
349
+ ## IP Filtering
350
+
351
+ Restrict access by IP:
352
+
353
+ ```typescript
354
+ const allowedIps = new Set([
355
+ '192.168.1.0/24',
356
+ '10.0.0.0/8',
357
+ ]);
358
+
359
+ app.addHook('onRequest', async (request, reply) => {
360
+ if (request.url.startsWith('/admin')) {
361
+ const clientIp = request.ip;
362
+
363
+ if (!isIpAllowed(clientIp, allowedIps)) {
364
+ reply.code(403).send({ error: 'Forbidden' });
365
+ }
366
+ }
367
+ });
368
+
369
+ function isIpAllowed(ip: string, allowed: Set<string>): boolean {
370
+ // Implement IP/CIDR matching
371
+ for (const range of allowed) {
372
+ if (ipInRange(ip, range)) return true;
373
+ }
374
+ return false;
375
+ }
376
+ ```
377
+
378
+ ## Trust Proxy
379
+
380
+ Configure for reverse proxy environments:
381
+
382
+ ```typescript
383
+ const app = Fastify({
384
+ trustProxy: true, // Trust X-Forwarded-* headers
385
+ });
386
+
387
+ // Or specific proxy configuration
388
+ const app = Fastify({
389
+ trustProxy: ['127.0.0.1', '10.0.0.0/8'],
390
+ });
391
+
392
+ // Now request.ip returns the real client IP
393
+ app.get('/ip', async (request) => {
394
+ return {
395
+ ip: request.ip,
396
+ ips: request.ips, // Array of all IPs in chain
397
+ };
398
+ });
399
+ ```
400
+
401
+ ## HTTPS Redirect
402
+
403
+ Force HTTPS in production:
404
+
405
+ ```typescript
406
+ app.addHook('onRequest', async (request, reply) => {
407
+ if (
408
+ process.env.NODE_ENV === 'production' &&
409
+ request.headers['x-forwarded-proto'] !== 'https'
410
+ ) {
411
+ const httpsUrl = `https://${request.hostname}${request.url}`;
412
+ reply.redirect(301, httpsUrl);
413
+ }
414
+ });
415
+ ```
416
+
417
+ ## Security Best Practices Summary
418
+
419
+ ```typescript
420
+ import Fastify from 'fastify';
421
+ import cors from '@fastify/cors';
422
+ import helmet from '@fastify/helmet';
423
+ import rateLimit from '@fastify/rate-limit';
424
+
425
+ const app = Fastify({
426
+ trustProxy: true,
427
+ bodyLimit: 1048576, // 1MB max body
428
+ });
429
+
430
+ // Security plugins
431
+ app.register(helmet);
432
+ app.register(cors, {
433
+ origin: process.env.ALLOWED_ORIGINS?.split(','),
434
+ credentials: true,
435
+ });
436
+ app.register(rateLimit, {
437
+ max: 100,
438
+ timeWindow: '1 minute',
439
+ });
440
+
441
+ // Validate all input with schemas
442
+ // Never expose internal errors in production
443
+ // Use parameterized queries for database
444
+ // Keep dependencies updated
445
+ ```