@mytechtoday/augment-extensions 0.1.0 → 0.1.1
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/AGENTS.md +83 -3
- package/README.md +6 -5
- package/augment-extensions/coding-standards/python/README.md +44 -0
- package/augment-extensions/coding-standards/python/module.json +26 -0
- package/augment-extensions/coding-standards/python/rules/best-practices.md +232 -0
- package/augment-extensions/coding-standards/python/rules/code-organization.md +220 -0
- package/augment-extensions/coding-standards/python/rules/error-handling.md +221 -0
- package/augment-extensions/coding-standards/python/rules/naming-conventions.md +172 -0
- package/augment-extensions/coding-standards/python/rules/type-hints.md +188 -0
- package/augment-extensions/coding-standards/react/README.md +45 -0
- package/augment-extensions/coding-standards/react/module.json +27 -0
- package/augment-extensions/coding-standards/react/rules/component-patterns.md +214 -0
- package/augment-extensions/coding-standards/react/rules/hooks-best-practices.md +235 -0
- package/augment-extensions/coding-standards/react/rules/performance.md +300 -0
- package/augment-extensions/coding-standards/react/rules/state-management.md +265 -0
- package/augment-extensions/coding-standards/react/rules/typescript-react.md +271 -0
- package/augment-extensions/domain-rules/api-design/README.md +41 -0
- package/augment-extensions/domain-rules/api-design/module.json +27 -0
- package/augment-extensions/domain-rules/api-design/rules/authentication.md +263 -0
- package/augment-extensions/domain-rules/api-design/rules/documentation.md +395 -0
- package/augment-extensions/domain-rules/api-design/rules/error-handling.md +290 -0
- package/augment-extensions/domain-rules/api-design/rules/graphql-api.md +313 -0
- package/augment-extensions/domain-rules/api-design/rules/rest-api.md +214 -0
- package/augment-extensions/domain-rules/api-design/rules/versioning.md +268 -0
- package/augment-extensions/domain-rules/security/README.md +41 -0
- package/augment-extensions/domain-rules/security/module.json +28 -0
- package/augment-extensions/domain-rules/security/rules/authentication-security.md +361 -0
- package/augment-extensions/domain-rules/security/rules/encryption.md +208 -0
- package/augment-extensions/domain-rules/security/rules/input-validation.md +294 -0
- package/augment-extensions/domain-rules/security/rules/owasp-top-10.md +339 -0
- package/augment-extensions/domain-rules/security/rules/secure-coding.md +293 -0
- package/augment-extensions/domain-rules/security/rules/web-security.md +268 -0
- package/augment-extensions/examples/design-patterns/README.md +37 -0
- package/augment-extensions/examples/design-patterns/examples/behavioral-patterns.md +370 -0
- package/augment-extensions/examples/design-patterns/examples/creational-patterns.md +250 -0
- package/augment-extensions/examples/design-patterns/examples/structural-patterns.md +264 -0
- package/augment-extensions/examples/design-patterns/module.json +27 -0
- package/{modules → augment-extensions}/workflows/beads/examples/complete-workflow-example.md +5 -5
- package/{modules → augment-extensions}/workflows/beads/rules/file-format.md +45 -1
- package/{modules → augment-extensions}/workflows/beads/rules/workflow.md +41 -0
- package/{modules → augment-extensions}/workflows/openspec/examples/complete-change-example.md +14 -0
- package/{modules → augment-extensions}/workflows/openspec/rules/spec-format.md +44 -1
- package/{modules → augment-extensions}/workflows/openspec/rules/workflow.md +25 -0
- package/cli/dist/cli.js +64 -0
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/coord.d.ts +30 -0
- package/cli/dist/commands/coord.d.ts.map +1 -0
- package/cli/dist/commands/coord.js +150 -0
- package/cli/dist/commands/coord.js.map +1 -0
- package/cli/dist/commands/link.js +1 -1
- package/cli/dist/commands/link.js.map +1 -1
- package/cli/dist/commands/list.js +1 -1
- package/cli/dist/commands/list.js.map +1 -1
- package/cli/dist/commands/search.d.ts.map +1 -1
- package/cli/dist/commands/search.js +107 -5
- package/cli/dist/commands/search.js.map +1 -1
- package/cli/dist/commands/show.js +1 -1
- package/cli/dist/commands/show.js.map +1 -1
- package/cli/dist/commands/sync.d.ts +26 -0
- package/cli/dist/commands/sync.d.ts.map +1 -0
- package/cli/dist/commands/sync.js +106 -0
- package/cli/dist/commands/sync.js.map +1 -0
- package/cli/dist/commands/update.d.ts.map +1 -1
- package/cli/dist/commands/update.js +132 -7
- package/cli/dist/commands/update.js.map +1 -1
- package/cli/dist/utils/auto-sync.d.ts +34 -0
- package/cli/dist/utils/auto-sync.d.ts.map +1 -0
- package/cli/dist/utils/auto-sync.js +172 -0
- package/cli/dist/utils/auto-sync.js.map +1 -0
- package/cli/dist/utils/beads-sync.d.ts +51 -0
- package/cli/dist/utils/beads-sync.d.ts.map +1 -0
- package/cli/dist/utils/beads-sync.js +171 -0
- package/cli/dist/utils/beads-sync.js.map +1 -0
- package/cli/dist/utils/coordination-queries.d.ts +79 -0
- package/cli/dist/utils/coordination-queries.d.ts.map +1 -0
- package/cli/dist/utils/coordination-queries.js +155 -0
- package/cli/dist/utils/coordination-queries.js.map +1 -0
- package/cli/dist/utils/file-tracking.d.ts +42 -0
- package/cli/dist/utils/file-tracking.d.ts.map +1 -0
- package/cli/dist/utils/file-tracking.js +155 -0
- package/cli/dist/utils/file-tracking.js.map +1 -0
- package/cli/dist/utils/migrate.d.ts +25 -0
- package/cli/dist/utils/migrate.d.ts.map +1 -0
- package/cli/dist/utils/migrate.js +204 -0
- package/cli/dist/utils/migrate.js.map +1 -0
- package/cli/dist/utils/openspec-sync.d.ts +48 -0
- package/cli/dist/utils/openspec-sync.d.ts.map +1 -0
- package/cli/dist/utils/openspec-sync.js +167 -0
- package/cli/dist/utils/openspec-sync.js.map +1 -0
- package/{MODULES.md → modules.md} +1 -1
- package/package.json +9 -7
- /package/{modules → augment-extensions}/coding-standards/typescript/README.md +0 -0
- /package/{modules → augment-extensions}/coding-standards/typescript/module.json +0 -0
- /package/{modules → augment-extensions}/coding-standards/typescript/rules/naming-conventions.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/README.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/module.json +0 -0
- /package/{modules → augment-extensions}/workflows/beads/rules/best-practices.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/rules/manual-setup.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/README.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/module.json +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/rules/best-practices.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/rules/manual-setup.md +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Encryption and Data Protection
|
|
2
|
+
|
|
3
|
+
Best practices for encrypting and protecting sensitive data.
|
|
4
|
+
|
|
5
|
+
## Data at Rest Encryption
|
|
6
|
+
|
|
7
|
+
### Symmetric Encryption (AES-256-GCM)
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
|
|
12
|
+
// Encrypt data
|
|
13
|
+
const encrypt = (data: string, key: Buffer): { encrypted: string; iv: string; authTag: string } => {
|
|
14
|
+
const iv = crypto.randomBytes(16);
|
|
15
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
16
|
+
|
|
17
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
18
|
+
encrypted += cipher.final('hex');
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
encrypted,
|
|
22
|
+
iv: iv.toString('hex'),
|
|
23
|
+
authTag: cipher.getAuthTag().toString('hex')
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Decrypt data
|
|
28
|
+
const decrypt = (encrypted: string, key: Buffer, iv: string, authTag: string): string => {
|
|
29
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(iv, 'hex'));
|
|
30
|
+
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
|
|
31
|
+
|
|
32
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
33
|
+
decrypted += decipher.final('utf8');
|
|
34
|
+
|
|
35
|
+
return decrypted;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Generate encryption key
|
|
39
|
+
const key = crypto.randomBytes(32); // 256 bits
|
|
40
|
+
// Store key securely (e.g., AWS KMS, Azure Key Vault, environment variable)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Database Encryption
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Good - Encrypt sensitive fields
|
|
47
|
+
const encryptedData = encrypt(user.ssn, encryptionKey);
|
|
48
|
+
|
|
49
|
+
await db.users.create({
|
|
50
|
+
email: user.email,
|
|
51
|
+
ssn: encryptedData.encrypted,
|
|
52
|
+
ssnIv: encryptedData.iv,
|
|
53
|
+
ssnAuthTag: encryptedData.authTag
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Decrypt when needed
|
|
57
|
+
const user = await db.users.findOne(userId);
|
|
58
|
+
const ssn = decrypt(user.ssn, encryptionKey, user.ssnIv, user.ssnAuthTag);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Data in Transit Encryption
|
|
62
|
+
|
|
63
|
+
### HTTPS/TLS
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Good - Force HTTPS
|
|
67
|
+
app.use((req, res, next) => {
|
|
68
|
+
if (!req.secure && process.env.NODE_ENV === 'production') {
|
|
69
|
+
return res.redirect(301, `https://${req.headers.host}${req.url}`);
|
|
70
|
+
}
|
|
71
|
+
next();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Good - HSTS header
|
|
75
|
+
app.use((req, res, next) => {
|
|
76
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
|
77
|
+
next();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Good - TLS configuration
|
|
81
|
+
import https from 'https';
|
|
82
|
+
import fs from 'fs';
|
|
83
|
+
|
|
84
|
+
const options = {
|
|
85
|
+
key: fs.readFileSync('private-key.pem'),
|
|
86
|
+
cert: fs.readFileSync('certificate.pem'),
|
|
87
|
+
minVersion: 'TLSv1.2', // Minimum TLS 1.2
|
|
88
|
+
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384'
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
https.createServer(options, app).listen(443);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Key Management
|
|
95
|
+
|
|
96
|
+
### Environment Variables
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# .env (never commit to git)
|
|
100
|
+
ENCRYPTION_KEY=hex_encoded_32_byte_key
|
|
101
|
+
JWT_SECRET=strong_random_secret
|
|
102
|
+
DATABASE_PASSWORD=strong_password
|
|
103
|
+
|
|
104
|
+
# .gitignore
|
|
105
|
+
.env
|
|
106
|
+
.env.local
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Key Rotation
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Support multiple encryption keys for rotation
|
|
113
|
+
const ENCRYPTION_KEYS = {
|
|
114
|
+
current: process.env.ENCRYPTION_KEY_V2,
|
|
115
|
+
previous: process.env.ENCRYPTION_KEY_V1
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Encrypt with current key
|
|
119
|
+
const encrypted = encrypt(data, Buffer.from(ENCRYPTION_KEYS.current, 'hex'));
|
|
120
|
+
|
|
121
|
+
// Decrypt with appropriate key version
|
|
122
|
+
const decrypt = (encrypted: string, keyVersion: string, iv: string, authTag: string): string => {
|
|
123
|
+
const key = ENCRYPTION_KEYS[keyVersion];
|
|
124
|
+
// ... decrypt logic
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Re-encrypt old data with new key
|
|
128
|
+
const migrateEncryption = async () => {
|
|
129
|
+
const users = await db.users.findMany({ keyVersion: 'previous' });
|
|
130
|
+
|
|
131
|
+
for (const user of users) {
|
|
132
|
+
const decrypted = decrypt(user.ssn, 'previous', user.ssnIv, user.ssnAuthTag);
|
|
133
|
+
const reencrypted = encrypt(decrypted, Buffer.from(ENCRYPTION_KEYS.current, 'hex'));
|
|
134
|
+
|
|
135
|
+
await db.users.update(user.id, {
|
|
136
|
+
ssn: reencrypted.encrypted,
|
|
137
|
+
ssnIv: reencrypted.iv,
|
|
138
|
+
ssnAuthTag: reencrypted.authTag,
|
|
139
|
+
keyVersion: 'current'
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Hashing
|
|
146
|
+
|
|
147
|
+
### One-Way Hashing
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import crypto from 'crypto';
|
|
151
|
+
|
|
152
|
+
// Good - SHA-256 for non-password data
|
|
153
|
+
const hash = crypto.createHash('sha256').update(data).digest('hex');
|
|
154
|
+
|
|
155
|
+
// Good - HMAC for message authentication
|
|
156
|
+
const hmac = crypto.createHmac('sha256', secret).update(data).digest('hex');
|
|
157
|
+
|
|
158
|
+
// Verify HMAC
|
|
159
|
+
const isValid = crypto.timingSafeEqual(
|
|
160
|
+
Buffer.from(receivedHmac, 'hex'),
|
|
161
|
+
Buffer.from(expectedHmac, 'hex')
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Secure Random Generation
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// Good - Cryptographically secure random
|
|
169
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
170
|
+
const uuid = crypto.randomUUID();
|
|
171
|
+
|
|
172
|
+
// Bad - Not cryptographically secure
|
|
173
|
+
const token = Math.random().toString(36); // ❌ Don't use for security
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Data Masking
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Mask sensitive data in logs
|
|
180
|
+
const maskEmail = (email: string): string => {
|
|
181
|
+
const [local, domain] = email.split('@');
|
|
182
|
+
return `${local.slice(0, 2)}***@${domain}`;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const maskCreditCard = (card: string): string => {
|
|
186
|
+
return `****-****-****-${card.slice(-4)}`;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Log with masked data
|
|
190
|
+
logger.info('User registered', {
|
|
191
|
+
email: maskEmail(user.email),
|
|
192
|
+
ip: req.ip
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Best Practices
|
|
197
|
+
|
|
198
|
+
1. **Use AES-256-GCM** - For symmetric encryption
|
|
199
|
+
2. **Always use HTTPS** - Encrypt data in transit
|
|
200
|
+
3. **Secure key storage** - Use key management services
|
|
201
|
+
4. **Rotate keys** - Periodically change encryption keys
|
|
202
|
+
5. **Hash passwords** - Use bcrypt/Argon2, not encryption
|
|
203
|
+
6. **Use crypto.randomBytes** - For secure random generation
|
|
204
|
+
7. **Encrypt at rest** - Sensitive database fields
|
|
205
|
+
8. **Mask in logs** - Don't log sensitive data
|
|
206
|
+
9. **Use HSTS** - Force HTTPS
|
|
207
|
+
10. **TLS 1.2+** - Minimum TLS version
|
|
208
|
+
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Input Validation and Sanitization
|
|
2
|
+
|
|
3
|
+
Best practices for validating and sanitizing user input.
|
|
4
|
+
|
|
5
|
+
## Validation Libraries
|
|
6
|
+
|
|
7
|
+
### Zod (TypeScript)
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
|
|
12
|
+
// Define schema
|
|
13
|
+
const userSchema = z.object({
|
|
14
|
+
email: z.string().email(),
|
|
15
|
+
password: z.string().min(12).max(100),
|
|
16
|
+
age: z.number().int().min(18).max(120),
|
|
17
|
+
role: z.enum(['admin', 'user', 'guest']),
|
|
18
|
+
website: z.string().url().optional()
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Validate input
|
|
22
|
+
app.post('/users', async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const validatedData = userSchema.parse(req.body);
|
|
25
|
+
// Use validatedData (type-safe)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof z.ZodError) {
|
|
28
|
+
return res.status(422).json({
|
|
29
|
+
error: 'Validation failed',
|
|
30
|
+
details: error.errors
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Joi (JavaScript)
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
const Joi = require('joi');
|
|
41
|
+
|
|
42
|
+
const schema = Joi.object({
|
|
43
|
+
email: Joi.string().email().required(),
|
|
44
|
+
password: Joi.string().min(12).max(100).required(),
|
|
45
|
+
age: Joi.number().integer().min(18).max(120),
|
|
46
|
+
tags: Joi.array().items(Joi.string()).max(10)
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const { error, value } = schema.validate(req.body);
|
|
50
|
+
if (error) {
|
|
51
|
+
return res.status(422).json({ error: error.details });
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## SQL Injection Prevention
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Bad - SQL injection vulnerability
|
|
59
|
+
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`; // ❌
|
|
60
|
+
|
|
61
|
+
// Good - Parameterized query
|
|
62
|
+
const query = 'SELECT * FROM users WHERE email = $1';
|
|
63
|
+
const result = await db.query(query, [req.body.email]);
|
|
64
|
+
|
|
65
|
+
// Good - ORM (Prisma)
|
|
66
|
+
const user = await prisma.user.findUnique({
|
|
67
|
+
where: { email: req.body.email }
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Good - Query builder (Knex)
|
|
71
|
+
const users = await knex('users')
|
|
72
|
+
.where('email', req.body.email)
|
|
73
|
+
.select('*');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## NoSQL Injection Prevention
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Bad - NoSQL injection
|
|
80
|
+
const user = await db.users.findOne({
|
|
81
|
+
email: req.body.email, // Could be: { $ne: null }
|
|
82
|
+
password: req.body.password
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Good - Validate input type
|
|
86
|
+
if (typeof req.body.email !== 'string' || typeof req.body.password !== 'string') {
|
|
87
|
+
return res.status(400).json({ error: 'Invalid input' });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const user = await db.users.findOne({
|
|
91
|
+
email: req.body.email,
|
|
92
|
+
password: req.body.password
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Good - Use schema validation
|
|
96
|
+
const loginSchema = z.object({
|
|
97
|
+
email: z.string().email(),
|
|
98
|
+
password: z.string()
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const { email, password } = loginSchema.parse(req.body);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## XSS Prevention
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Bad - Rendering user input directly
|
|
108
|
+
res.send(`<h1>Welcome ${req.query.name}</h1>`); // ❌ XSS vulnerability
|
|
109
|
+
|
|
110
|
+
// Good - Use template engine with auto-escaping
|
|
111
|
+
res.render('welcome', { name: req.query.name }); // Handlebars, EJS, etc.
|
|
112
|
+
|
|
113
|
+
// Good - Sanitize HTML
|
|
114
|
+
import DOMPurify from 'isomorphic-dompurify';
|
|
115
|
+
|
|
116
|
+
const clean = DOMPurify.sanitize(req.body.html);
|
|
117
|
+
|
|
118
|
+
// Good - Content Security Policy
|
|
119
|
+
app.use((req, res, next) => {
|
|
120
|
+
res.setHeader(
|
|
121
|
+
'Content-Security-Policy',
|
|
122
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
|
123
|
+
);
|
|
124
|
+
next();
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Command Injection Prevention
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// Bad - Command injection
|
|
132
|
+
const { exec } = require('child_process');
|
|
133
|
+
exec(`ping ${req.query.host}`); // ❌ Vulnerable
|
|
134
|
+
|
|
135
|
+
// Good - Validate input
|
|
136
|
+
const host = req.query.host;
|
|
137
|
+
if (!/^[a-zA-Z0-9.-]+$/.test(host)) {
|
|
138
|
+
return res.status(400).json({ error: 'Invalid host' });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Better - Use safe library
|
|
142
|
+
import { ping } from 'ping';
|
|
143
|
+
const result = await ping.promise.probe(req.query.host);
|
|
144
|
+
|
|
145
|
+
// Good - Use execFile with array
|
|
146
|
+
import { execFile } from 'child_process';
|
|
147
|
+
execFile('ping', ['-c', '4', host], (error, stdout) => {
|
|
148
|
+
// ...
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Path Traversal Prevention
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Bad - Path traversal vulnerability
|
|
156
|
+
const filePath = path.join(__dirname, 'uploads', req.query.file); // ❌
|
|
157
|
+
res.sendFile(filePath);
|
|
158
|
+
|
|
159
|
+
// Good - Validate filename
|
|
160
|
+
import path from 'path';
|
|
161
|
+
|
|
162
|
+
const filename = path.basename(req.query.file); // Remove directory components
|
|
163
|
+
const filePath = path.join(__dirname, 'uploads', filename);
|
|
164
|
+
|
|
165
|
+
// Ensure file is within uploads directory
|
|
166
|
+
const realPath = fs.realpathSync(filePath);
|
|
167
|
+
const uploadsPath = fs.realpathSync(path.join(__dirname, 'uploads'));
|
|
168
|
+
|
|
169
|
+
if (!realPath.startsWith(uploadsPath)) {
|
|
170
|
+
return res.status(400).json({ error: 'Invalid file path' });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
res.sendFile(realPath);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Email Validation
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Good - Email validation
|
|
180
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
181
|
+
|
|
182
|
+
if (!emailRegex.test(email)) {
|
|
183
|
+
return res.status(400).json({ error: 'Invalid email' });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Better - Use validation library
|
|
187
|
+
import { z } from 'zod';
|
|
188
|
+
const email = z.string().email().parse(req.body.email);
|
|
189
|
+
|
|
190
|
+
// Best - Verify email ownership
|
|
191
|
+
const verificationToken = crypto.randomBytes(32).toString('hex');
|
|
192
|
+
await sendVerificationEmail(email, verificationToken);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## URL Validation
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// Good - URL validation
|
|
199
|
+
const validateUrl = (url: string): boolean => {
|
|
200
|
+
try {
|
|
201
|
+
const parsed = new URL(url);
|
|
202
|
+
return ['http:', 'https:'].includes(parsed.protocol);
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Good - Whitelist domains
|
|
209
|
+
const allowedDomains = ['example.com', 'api.example.com'];
|
|
210
|
+
|
|
211
|
+
const url = new URL(req.body.url);
|
|
212
|
+
if (!allowedDomains.includes(url.hostname)) {
|
|
213
|
+
return res.status(400).json({ error: 'Domain not allowed' });
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## File Upload Validation
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import multer from 'multer';
|
|
221
|
+
import path from 'path';
|
|
222
|
+
|
|
223
|
+
// Good - Validate file type and size
|
|
224
|
+
const upload = multer({
|
|
225
|
+
storage: multer.diskStorage({
|
|
226
|
+
destination: 'uploads/',
|
|
227
|
+
filename: (req, file, cb) => {
|
|
228
|
+
const uniqueSuffix = Date.now() + '-' + crypto.randomBytes(6).toString('hex');
|
|
229
|
+
cb(null, uniqueSuffix + path.extname(file.originalname));
|
|
230
|
+
}
|
|
231
|
+
}),
|
|
232
|
+
limits: {
|
|
233
|
+
fileSize: 5 * 1024 * 1024 // 5MB
|
|
234
|
+
},
|
|
235
|
+
fileFilter: (req, file, cb) => {
|
|
236
|
+
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
|
|
237
|
+
|
|
238
|
+
if (!allowedTypes.includes(file.mimetype)) {
|
|
239
|
+
return cb(new Error('Invalid file type'));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
cb(null, true);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Verify file content (not just extension)
|
|
247
|
+
import fileType from 'file-type';
|
|
248
|
+
|
|
249
|
+
const type = await fileType.fromFile(filePath);
|
|
250
|
+
if (!type || !['image/jpeg', 'image/png'].includes(type.mime)) {
|
|
251
|
+
fs.unlinkSync(filePath);
|
|
252
|
+
return res.status(400).json({ error: 'Invalid file type' });
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Integer Validation
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// Good - Validate integers
|
|
260
|
+
const validateInteger = (value: any, min?: number, max?: number): number => {
|
|
261
|
+
const num = parseInt(value, 10);
|
|
262
|
+
|
|
263
|
+
if (isNaN(num) || !Number.isInteger(num)) {
|
|
264
|
+
throw new Error('Must be an integer');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (min !== undefined && num < min) {
|
|
268
|
+
throw new Error(`Must be at least ${min}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (max !== undefined && num > max) {
|
|
272
|
+
throw new Error(`Must be at most ${max}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return num;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Usage
|
|
279
|
+
const age = validateInteger(req.body.age, 18, 120);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Best Practices
|
|
283
|
+
|
|
284
|
+
1. **Validate all input** - Never trust user input
|
|
285
|
+
2. **Use validation libraries** - Zod, Joi, etc.
|
|
286
|
+
3. **Whitelist, don't blacklist** - Define what's allowed
|
|
287
|
+
4. **Parameterized queries** - Prevent SQL injection
|
|
288
|
+
5. **Type checking** - Prevent NoSQL injection
|
|
289
|
+
6. **Sanitize HTML** - Use DOMPurify
|
|
290
|
+
7. **Validate file uploads** - Type, size, content
|
|
291
|
+
8. **Validate URLs** - Check protocol and domain
|
|
292
|
+
9. **Prevent path traversal** - Use path.basename
|
|
293
|
+
10. **Fail securely** - Reject invalid input
|
|
294
|
+
|