tlc-claude-code 1.4.9 → 1.5.2

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 (122) hide show
  1. package/CLAUDE.md +23 -0
  2. package/CODING-STANDARDS.md +408 -0
  3. package/bin/install.js +2 -0
  4. package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
  5. package/dashboard/dist/components/QualityGatePane.js +31 -0
  6. package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
  7. package/dashboard/dist/components/QualityGatePane.test.js +147 -0
  8. package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
  9. package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
  10. package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
  11. package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
  12. package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
  13. package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
  14. package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
  15. package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
  16. package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
  17. package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
  18. package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
  19. package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
  20. package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
  21. package/dashboard/dist/components/orchestration/AgentList.js +47 -0
  22. package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
  23. package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
  24. package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
  25. package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
  26. package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
  27. package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
  28. package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
  29. package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
  30. package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
  31. package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
  32. package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
  33. package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
  34. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
  35. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
  36. package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
  37. package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
  38. package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
  39. package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
  40. package/dashboard/dist/components/orchestration/index.d.ts +8 -0
  41. package/dashboard/dist/components/orchestration/index.js +8 -0
  42. package/package.json +1 -1
  43. package/server/lib/access-control.js +352 -0
  44. package/server/lib/access-control.test.js +322 -0
  45. package/server/lib/agents-cancel-command.js +139 -0
  46. package/server/lib/agents-cancel-command.test.js +180 -0
  47. package/server/lib/agents-get-command.js +159 -0
  48. package/server/lib/agents-get-command.test.js +167 -0
  49. package/server/lib/agents-list-command.js +150 -0
  50. package/server/lib/agents-list-command.test.js +149 -0
  51. package/server/lib/agents-logs-command.js +126 -0
  52. package/server/lib/agents-logs-command.test.js +198 -0
  53. package/server/lib/agents-retry-command.js +117 -0
  54. package/server/lib/agents-retry-command.test.js +192 -0
  55. package/server/lib/budget-limits.js +222 -0
  56. package/server/lib/budget-limits.test.js +214 -0
  57. package/server/lib/code-generator.js +291 -0
  58. package/server/lib/code-generator.test.js +307 -0
  59. package/server/lib/cost-command.js +290 -0
  60. package/server/lib/cost-command.test.js +202 -0
  61. package/server/lib/cost-optimizer.js +404 -0
  62. package/server/lib/cost-optimizer.test.js +232 -0
  63. package/server/lib/cost-projections.js +302 -0
  64. package/server/lib/cost-projections.test.js +217 -0
  65. package/server/lib/cost-reports.js +277 -0
  66. package/server/lib/cost-reports.test.js +254 -0
  67. package/server/lib/cost-tracker.js +216 -0
  68. package/server/lib/cost-tracker.test.js +302 -0
  69. package/server/lib/crypto-patterns.js +433 -0
  70. package/server/lib/crypto-patterns.test.js +346 -0
  71. package/server/lib/design-command.js +385 -0
  72. package/server/lib/design-command.test.js +249 -0
  73. package/server/lib/design-parser.js +237 -0
  74. package/server/lib/design-parser.test.js +290 -0
  75. package/server/lib/gemini-vision.js +377 -0
  76. package/server/lib/gemini-vision.test.js +282 -0
  77. package/server/lib/input-validator.js +360 -0
  78. package/server/lib/input-validator.test.js +295 -0
  79. package/server/lib/litellm-client.js +232 -0
  80. package/server/lib/litellm-client.test.js +267 -0
  81. package/server/lib/litellm-command.js +291 -0
  82. package/server/lib/litellm-command.test.js +260 -0
  83. package/server/lib/litellm-config.js +273 -0
  84. package/server/lib/litellm-config.test.js +212 -0
  85. package/server/lib/model-pricing.js +189 -0
  86. package/server/lib/model-pricing.test.js +178 -0
  87. package/server/lib/models-command.js +223 -0
  88. package/server/lib/models-command.test.js +193 -0
  89. package/server/lib/optimize-command.js +197 -0
  90. package/server/lib/optimize-command.test.js +193 -0
  91. package/server/lib/orchestration-integration.js +206 -0
  92. package/server/lib/orchestration-integration.test.js +235 -0
  93. package/server/lib/output-encoder.js +308 -0
  94. package/server/lib/output-encoder.test.js +312 -0
  95. package/server/lib/quality-evaluator.js +396 -0
  96. package/server/lib/quality-evaluator.test.js +337 -0
  97. package/server/lib/quality-gate-command.js +340 -0
  98. package/server/lib/quality-gate-command.test.js +321 -0
  99. package/server/lib/quality-gate-scorer.js +378 -0
  100. package/server/lib/quality-gate-scorer.test.js +376 -0
  101. package/server/lib/quality-history.js +265 -0
  102. package/server/lib/quality-history.test.js +359 -0
  103. package/server/lib/quality-presets.js +288 -0
  104. package/server/lib/quality-presets.test.js +269 -0
  105. package/server/lib/quality-retry.js +323 -0
  106. package/server/lib/quality-retry.test.js +325 -0
  107. package/server/lib/quality-thresholds.js +255 -0
  108. package/server/lib/quality-thresholds.test.js +237 -0
  109. package/server/lib/secure-auth.js +333 -0
  110. package/server/lib/secure-auth.test.js +288 -0
  111. package/server/lib/secure-code-command.js +540 -0
  112. package/server/lib/secure-code-command.test.js +309 -0
  113. package/server/lib/secure-errors.js +521 -0
  114. package/server/lib/secure-errors.test.js +298 -0
  115. package/server/lib/vision-command.js +372 -0
  116. package/server/lib/vision-command.test.js +255 -0
  117. package/server/lib/visual-command.js +350 -0
  118. package/server/lib/visual-command.test.js +256 -0
  119. package/server/lib/visual-testing.js +315 -0
  120. package/server/lib/visual-testing.test.js +357 -0
  121. package/server/package-lock.json +2 -2
  122. package/server/package.json +1 -1
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Input Validator Module
3
+ *
4
+ * Input sanitization and validation patterns for secure code generation
5
+ */
6
+
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Create an input validator
11
+ * @param {Object} options - Validator options
12
+ * @returns {Object} Validator instance
13
+ */
14
+ function createInputValidator(options = {}) {
15
+ return {
16
+ rules: {
17
+ maxLength: options.rules?.maxLength || 10000,
18
+ ...options.rules,
19
+ },
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Sanitize a string input
25
+ * @param {string} input - Input string
26
+ * @param {Object} options - Sanitization options
27
+ * @returns {string} Sanitized string
28
+ */
29
+ function sanitizeString(input, options = {}) {
30
+ if (input === null || input === undefined) {
31
+ return '';
32
+ }
33
+
34
+ let result = String(input);
35
+
36
+ // Remove null bytes
37
+ result = result.replace(/\x00/g, '');
38
+
39
+ // Trim whitespace
40
+ result = result.trim();
41
+
42
+ // Normalize unicode
43
+ if (options.normalize) {
44
+ result = result.normalize('NFC');
45
+ }
46
+
47
+ // Escape HTML entities
48
+ if (options.escapeHtml) {
49
+ result = result
50
+ .replace(/&/g, '&')
51
+ .replace(/</g, '&lt;')
52
+ .replace(/>/g, '&gt;')
53
+ .replace(/"/g, '&quot;')
54
+ .replace(/'/g, '&#x27;');
55
+ }
56
+
57
+ // Enforce max length
58
+ if (options.maxLength && result.length > options.maxLength) {
59
+ result = result.substring(0, options.maxLength);
60
+ }
61
+
62
+ return result;
63
+ }
64
+
65
+ /**
66
+ * Validate email address
67
+ * @param {string} email - Email to validate
68
+ * @returns {Object} Validation result
69
+ */
70
+ function validateEmail(email) {
71
+ if (!email) {
72
+ return { valid: false, error: 'Email is required' };
73
+ }
74
+
75
+ // Reject dangerous characters
76
+ if (/["<>]/.test(email)) {
77
+ return { valid: false, error: 'Email contains invalid characters' };
78
+ }
79
+
80
+ // RFC 5322 compliant regex (simplified)
81
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
82
+
83
+ if (!emailRegex.test(email)) {
84
+ return { valid: false, error: 'Invalid email format' };
85
+ }
86
+
87
+ return { valid: true };
88
+ }
89
+
90
+ /**
91
+ * Validate file path
92
+ * @param {string} filePath - Path to validate
93
+ * @param {Object} options - Validation options
94
+ * @returns {Object} Validation result
95
+ */
96
+ function validatePath(filePath, options = {}) {
97
+ const { basePath } = options;
98
+
99
+ if (!filePath) {
100
+ return { valid: false, error: 'Path is required' };
101
+ }
102
+
103
+ // Check for null bytes
104
+ if (filePath.includes('\x00')) {
105
+ return { valid: false, error: 'Path contains null bytes' };
106
+ }
107
+
108
+ // Normalize the path
109
+ const normalized = path.normalize(filePath);
110
+
111
+ // Check for path traversal
112
+ if (filePath.includes('..') && basePath) {
113
+ const resolved = path.resolve(basePath, filePath);
114
+ const baseResolved = path.resolve(basePath);
115
+
116
+ if (!resolved.startsWith(baseResolved + path.sep) && resolved !== baseResolved) {
117
+ return { valid: false, error: 'Path traversal detected' };
118
+ }
119
+ }
120
+
121
+ // Check if within base path (for absolute paths)
122
+ if (basePath && path.isAbsolute(filePath)) {
123
+ const resolved = path.resolve(filePath);
124
+ const baseResolved = path.resolve(basePath);
125
+
126
+ if (!resolved.startsWith(baseResolved + path.sep) && resolved !== baseResolved) {
127
+ return { valid: false, error: 'Path is outside allowed directory' };
128
+ }
129
+ }
130
+
131
+ return { valid: true, normalized };
132
+ }
133
+
134
+ /**
135
+ * Validate URL
136
+ * @param {string} url - URL to validate
137
+ * @param {Object} options - Validation options
138
+ * @returns {Object} Validation result
139
+ */
140
+ function validateUrl(url, options = {}) {
141
+ if (!url) {
142
+ return { valid: false, error: 'URL is required' };
143
+ }
144
+
145
+ // Check dangerous protocols
146
+ const dangerousProtocols = ['javascript:', 'data:', 'vbscript:'];
147
+ const lowerUrl = url.toLowerCase();
148
+
149
+ for (const protocol of dangerousProtocols) {
150
+ if (lowerUrl.startsWith(protocol)) {
151
+ return { valid: false, error: `Dangerous protocol: ${protocol}` };
152
+ }
153
+ }
154
+
155
+ try {
156
+ const parsed = new URL(url);
157
+
158
+ // Check allowed hosts
159
+ if (options.allowedHosts && options.allowedHosts.length > 0) {
160
+ if (!options.allowedHosts.includes(parsed.hostname)) {
161
+ return { valid: false, error: 'Host not in allowed list' };
162
+ }
163
+ }
164
+
165
+ // Block private IP addresses
166
+ if (options.blockPrivate) {
167
+ const privatePatterns = [
168
+ /^192\.168\./,
169
+ /^10\./,
170
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./,
171
+ /^127\./,
172
+ /^localhost$/i,
173
+ ];
174
+
175
+ for (const pattern of privatePatterns) {
176
+ if (pattern.test(parsed.hostname)) {
177
+ return { valid: false, error: 'Private IP addresses are blocked' };
178
+ }
179
+ }
180
+ }
181
+
182
+ return { valid: true };
183
+ } catch {
184
+ return { valid: false, error: 'Invalid URL format' };
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Check for SQL injection patterns
190
+ * @param {string} input - Input to check
191
+ * @returns {Object} Detection result
192
+ */
193
+ function preventSqlInjection(input) {
194
+ if (!input) {
195
+ return { dangerous: false, patterns: [] };
196
+ }
197
+
198
+ const patterns = [];
199
+
200
+ // Check for common SQL injection patterns
201
+ const sqlPatterns = [
202
+ { regex: /['"]\s*;\s*--/i, name: 'comment-injection' },
203
+ { regex: /'\s*OR\s+['"]?\d+['"]?\s*=\s*['"]?\d+/i, name: 'or-injection' },
204
+ { regex: /UNION\s+SELECT/i, name: 'union-injection' },
205
+ { regex: /DROP\s+TABLE/i, name: 'drop-table' },
206
+ { regex: /DELETE\s+FROM/i, name: 'delete-injection' },
207
+ { regex: /INSERT\s+INTO/i, name: 'insert-injection' },
208
+ { regex: /EXEC\s*\(/i, name: 'exec-injection' },
209
+ { regex: /xp_cmdshell/i, name: 'cmdshell-injection' },
210
+ ];
211
+
212
+ for (const { regex, name } of sqlPatterns) {
213
+ if (regex.test(input)) {
214
+ patterns.push(name);
215
+ }
216
+ }
217
+
218
+ // Check for string concatenation in queries
219
+ if (/["']\s*\+\s*["']?[^"']*["']?/.test(input)) {
220
+ patterns.push('string-concatenation');
221
+ }
222
+
223
+ const dangerous = patterns.length > 0;
224
+
225
+ return {
226
+ dangerous,
227
+ patterns,
228
+ suggestion: 'Use parameterized queries instead of string concatenation',
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Check for command injection patterns
234
+ * @param {string} input - Input to check
235
+ * @returns {Object} Detection result
236
+ */
237
+ function preventCommandInjection(input) {
238
+ if (!input) {
239
+ return { dangerous: false, safePattern: null };
240
+ }
241
+
242
+ const dangerous = [
243
+ /[;&|]/, // Command chaining
244
+ /\|/, // Pipe
245
+ /`/, // Backtick substitution
246
+ /\$\(/, // $() substitution
247
+ />\s*\//, // Output redirection
248
+ /<\s*\//, // Input redirection
249
+ /\n/, // Newline
250
+ /\r/, // Carriage return
251
+ ].some((pattern) => pattern.test(input));
252
+
253
+ return {
254
+ dangerous,
255
+ safePattern: 'Use allowlist validation for filenames and escape shell arguments',
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Generate validation code
261
+ * @param {Object} options - Generation options
262
+ * @returns {string} Generated code
263
+ */
264
+ function generateValidationCode(options = {}) {
265
+ const { type, language = 'javascript', includeErrors = false, rules = {} } = options;
266
+
267
+ const generators = {
268
+ javascript: {
269
+ email: () => `
270
+ function validateEmail(email) {
271
+ const regex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
272
+ if (!regex.test(email)) {
273
+ ${includeErrors ? 'return { valid: false, error: "Invalid email format" };' : 'return false;'}
274
+ }
275
+ ${includeErrors ? 'return { valid: true };' : 'return true;'}
276
+ }`,
277
+ path: () => `
278
+ function validatePath(filePath, basePath) {
279
+ const path = require('path');
280
+ const resolved = path.resolve(basePath, filePath);
281
+ if (!resolved.startsWith(path.resolve(basePath))) {
282
+ ${includeErrors ? 'return { valid: false, error: "Path traversal detected" };' : 'return false;'}
283
+ }
284
+ ${includeErrors ? 'return { valid: true };' : 'return true;'}
285
+ }`,
286
+ custom: () => `
287
+ function validate(input) {
288
+ ${rules.minLength ? `if (input.length < ${rules.minLength}) return false;` : ''}
289
+ ${rules.maxLength ? `if (input.length > ${rules.maxLength}) return false;` : ''}
290
+ ${rules.pattern ? `if (!/${rules.pattern}/.test(input)) return false;` : ''}
291
+ return true;
292
+ }`,
293
+ },
294
+ typescript: {
295
+ email: () => `
296
+ function validateEmail(email: string): { valid: boolean; error?: string } {
297
+ const regex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
298
+ if (!regex.test(email)) {
299
+ return { valid: false, error: "Invalid email format" };
300
+ }
301
+ return { valid: true };
302
+ }`,
303
+ path: () => `
304
+ function validatePath(filePath: string, basePath: string): { valid: boolean; error?: string } {
305
+ const path = require('path');
306
+ const resolved = path.resolve(basePath, filePath);
307
+ if (!resolved.startsWith(path.resolve(basePath))) {
308
+ return { valid: false, error: "Path traversal detected" };
309
+ }
310
+ return { valid: true };
311
+ }`,
312
+ custom: () => `
313
+ function validate(input: string): boolean {
314
+ ${rules.minLength ? `if (input.length < ${rules.minLength}) return false;` : ''}
315
+ ${rules.maxLength ? `if (input.length > ${rules.maxLength}) return false;` : ''}
316
+ ${rules.pattern ? `if (!/${rules.pattern}/.test(input)) return false;` : ''}
317
+ return true;
318
+ }`,
319
+ },
320
+ python: {
321
+ email: () => `
322
+ import re
323
+
324
+ def validate_email(email: str) -> dict:
325
+ pattern = r'^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$'
326
+ if not re.match(pattern, email):
327
+ return {"valid": False, "error": "Invalid email format"}
328
+ return {"valid": True}`,
329
+ path: () => `
330
+ import os
331
+
332
+ def validate_path(file_path: str, base_path: str) -> dict:
333
+ resolved = os.path.abspath(os.path.join(base_path, file_path))
334
+ if not resolved.startswith(os.path.abspath(base_path)):
335
+ return {"valid": False, "error": "Path traversal detected"}
336
+ return {"valid": True}`,
337
+ custom: () => `
338
+ def validate(input: str) -> bool:
339
+ ${rules.minLength ? `if len(input) < ${rules.minLength}: return False` : ''}
340
+ ${rules.maxLength ? `if len(input) > ${rules.maxLength}: return False` : ''}
341
+ return True`,
342
+ },
343
+ };
344
+
345
+ const lang = generators[language] || generators.javascript;
346
+ const gen = lang[type] || lang.email;
347
+
348
+ return gen();
349
+ }
350
+
351
+ module.exports = {
352
+ createInputValidator,
353
+ sanitizeString,
354
+ validateEmail,
355
+ validatePath,
356
+ validateUrl,
357
+ preventSqlInjection,
358
+ preventCommandInjection,
359
+ generateValidationCode,
360
+ };
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Input Validator Tests
3
+ *
4
+ * Input sanitization and validation patterns for secure code generation
5
+ */
6
+
7
+ const { describe, it, beforeEach } = require('node:test');
8
+ const assert = require('node:assert');
9
+
10
+ const {
11
+ createInputValidator,
12
+ sanitizeString,
13
+ validateEmail,
14
+ validatePath,
15
+ validateUrl,
16
+ preventSqlInjection,
17
+ preventCommandInjection,
18
+ generateValidationCode,
19
+ } = require('./input-validator.js');
20
+
21
+ describe('Input Validator', () => {
22
+ let validator;
23
+
24
+ beforeEach(() => {
25
+ validator = createInputValidator();
26
+ });
27
+
28
+ describe('createInputValidator', () => {
29
+ it('creates validator with default config', () => {
30
+ assert.ok(validator);
31
+ assert.ok(validator.rules);
32
+ });
33
+
34
+ it('accepts custom rules', () => {
35
+ const custom = createInputValidator({
36
+ rules: {
37
+ maxLength: 100,
38
+ },
39
+ });
40
+
41
+ assert.strictEqual(custom.rules.maxLength, 100);
42
+ });
43
+ });
44
+
45
+ describe('sanitizeString', () => {
46
+ it('removes null bytes', () => {
47
+ const result = sanitizeString('hello\x00world');
48
+
49
+ assert.strictEqual(result, 'helloworld');
50
+ });
51
+
52
+ it('trims whitespace', () => {
53
+ const result = sanitizeString(' hello ');
54
+
55
+ assert.strictEqual(result, 'hello');
56
+ });
57
+
58
+ it('escapes HTML entities', () => {
59
+ const result = sanitizeString('<script>alert("xss")</script>', { escapeHtml: true });
60
+
61
+ assert.ok(!result.includes('<script>'));
62
+ assert.ok(result.includes('&lt;'));
63
+ });
64
+
65
+ it('enforces max length', () => {
66
+ const result = sanitizeString('a'.repeat(1000), { maxLength: 100 });
67
+
68
+ assert.strictEqual(result.length, 100);
69
+ });
70
+
71
+ it('handles unicode normalization', () => {
72
+ const result = sanitizeString('café', { normalize: true });
73
+
74
+ assert.ok(result.includes('caf'));
75
+ });
76
+ });
77
+
78
+ describe('validateEmail', () => {
79
+ it('accepts valid email', () => {
80
+ const result = validateEmail('user@example.com');
81
+
82
+ assert.ok(result.valid);
83
+ });
84
+
85
+ it('rejects invalid email', () => {
86
+ const result = validateEmail('not-an-email');
87
+
88
+ assert.strictEqual(result.valid, false);
89
+ assert.ok(result.error);
90
+ });
91
+
92
+ it('rejects email with dangerous characters', () => {
93
+ const result = validateEmail('user"@example.com');
94
+
95
+ assert.strictEqual(result.valid, false);
96
+ });
97
+
98
+ it('handles internationalized domains', () => {
99
+ const result = validateEmail('user@münchen.de');
100
+
101
+ assert.ok(result.valid);
102
+ });
103
+ });
104
+
105
+ describe('validatePath', () => {
106
+ it('accepts valid path', () => {
107
+ const result = validatePath('/var/data/file.txt', { basePath: '/var/data' });
108
+
109
+ assert.ok(result.valid);
110
+ });
111
+
112
+ it('rejects path traversal', () => {
113
+ const result = validatePath('/var/data/../etc/passwd', { basePath: '/var/data' });
114
+
115
+ assert.strictEqual(result.valid, false);
116
+ assert.ok(result.error.includes('traversal'));
117
+ });
118
+
119
+ it('rejects null bytes in path', () => {
120
+ const result = validatePath('/var/data/file.txt\x00.jpg', { basePath: '/var/data' });
121
+
122
+ assert.strictEqual(result.valid, false);
123
+ });
124
+
125
+ it('normalizes path before validation', () => {
126
+ const result = validatePath('/var/data/./subdir/../file.txt', { basePath: '/var/data' });
127
+
128
+ assert.ok(result.valid);
129
+ assert.strictEqual(result.normalized, '/var/data/file.txt');
130
+ });
131
+
132
+ it('rejects paths outside base', () => {
133
+ const result = validatePath('/etc/passwd', { basePath: '/var/data' });
134
+
135
+ assert.strictEqual(result.valid, false);
136
+ });
137
+ });
138
+
139
+ describe('validateUrl', () => {
140
+ it('accepts valid HTTPS URL', () => {
141
+ const result = validateUrl('https://example.com/path');
142
+
143
+ assert.ok(result.valid);
144
+ });
145
+
146
+ it('rejects javascript protocol', () => {
147
+ const result = validateUrl('javascript:alert(1)');
148
+
149
+ assert.strictEqual(result.valid, false);
150
+ assert.ok(result.error.includes('protocol'));
151
+ });
152
+
153
+ it('rejects data protocol', () => {
154
+ const result = validateUrl('data:text/html,<script>alert(1)</script>');
155
+
156
+ assert.strictEqual(result.valid, false);
157
+ });
158
+
159
+ it('validates allowed hosts', () => {
160
+ const result = validateUrl('https://evil.com', {
161
+ allowedHosts: ['example.com', 'trusted.com'],
162
+ });
163
+
164
+ assert.strictEqual(result.valid, false);
165
+ });
166
+
167
+ it('blocks private IP addresses', () => {
168
+ const result = validateUrl('http://192.168.1.1', { blockPrivate: true });
169
+
170
+ assert.strictEqual(result.valid, false);
171
+ });
172
+ });
173
+
174
+ describe('preventSqlInjection', () => {
175
+ it('detects SQL injection patterns', () => {
176
+ const result = preventSqlInjection("'; DROP TABLE users; --");
177
+
178
+ assert.ok(result.dangerous);
179
+ assert.ok(result.patterns.length > 0);
180
+ });
181
+
182
+ it('allows safe strings', () => {
183
+ const result = preventSqlInjection('John Doe');
184
+
185
+ assert.strictEqual(result.dangerous, false);
186
+ });
187
+
188
+ it('detects union-based injection', () => {
189
+ const result = preventSqlInjection('1 UNION SELECT * FROM users');
190
+
191
+ assert.ok(result.dangerous);
192
+ });
193
+
194
+ it('generates parameterized query suggestion', () => {
195
+ const result = preventSqlInjection("user_id = '" + "123" + "'");
196
+
197
+ assert.ok(result.suggestion);
198
+ assert.ok(result.suggestion.includes('parameterized'));
199
+ });
200
+ });
201
+
202
+ describe('preventCommandInjection', () => {
203
+ it('detects command chaining', () => {
204
+ const result = preventCommandInjection('file.txt; rm -rf /');
205
+
206
+ assert.ok(result.dangerous);
207
+ });
208
+
209
+ it('detects pipe injection', () => {
210
+ const result = preventCommandInjection('file.txt | cat /etc/passwd');
211
+
212
+ assert.ok(result.dangerous);
213
+ });
214
+
215
+ it('detects backtick injection', () => {
216
+ const result = preventCommandInjection('`cat /etc/passwd`');
217
+
218
+ assert.ok(result.dangerous);
219
+ });
220
+
221
+ it('detects $() injection', () => {
222
+ const result = preventCommandInjection('$(cat /etc/passwd)');
223
+
224
+ assert.ok(result.dangerous);
225
+ });
226
+
227
+ it('allows safe filenames', () => {
228
+ const result = preventCommandInjection('document.pdf');
229
+
230
+ assert.strictEqual(result.dangerous, false);
231
+ });
232
+
233
+ it('suggests safe alternatives', () => {
234
+ const result = preventCommandInjection('user-input');
235
+
236
+ assert.ok(result.safePattern);
237
+ });
238
+ });
239
+
240
+ describe('generateValidationCode', () => {
241
+ it('generates JavaScript validation function', () => {
242
+ const code = generateValidationCode({
243
+ type: 'email',
244
+ language: 'javascript',
245
+ });
246
+
247
+ assert.ok(code.includes('function'));
248
+ assert.ok(code.includes('email'));
249
+ });
250
+
251
+ it('generates TypeScript validation function', () => {
252
+ const code = generateValidationCode({
253
+ type: 'email',
254
+ language: 'typescript',
255
+ });
256
+
257
+ assert.ok(code.includes('string'));
258
+ assert.ok(code.includes(':'));
259
+ });
260
+
261
+ it('generates Python validation function', () => {
262
+ const code = generateValidationCode({
263
+ type: 'email',
264
+ language: 'python',
265
+ });
266
+
267
+ assert.ok(code.includes('def'));
268
+ });
269
+
270
+ it('includes error messages', () => {
271
+ const code = generateValidationCode({
272
+ type: 'path',
273
+ language: 'javascript',
274
+ includeErrors: true,
275
+ });
276
+
277
+ assert.ok(code.includes('error') || code.includes('Error'));
278
+ });
279
+
280
+ it('generates validation for custom rules', () => {
281
+ const code = generateValidationCode({
282
+ type: 'custom',
283
+ language: 'javascript',
284
+ rules: {
285
+ minLength: 8,
286
+ maxLength: 100,
287
+ pattern: '^[a-zA-Z0-9]+$',
288
+ },
289
+ });
290
+
291
+ assert.ok(code.includes('8'));
292
+ assert.ok(code.includes('100'));
293
+ });
294
+ });
295
+ });