lsh-framework 3.1.23 → 3.1.25
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/dist/cli.js +1 -0
- package/dist/constants/ui.js +1 -0
- package/dist/lib/optimized-job-scheduler.js +3 -2
- package/dist/services/secrets/secrets.js +125 -0
- package/package.json +1 -1
- package/dist/commands/storacha.js +0 -268
- package/dist/lib/input-validator.js +0 -318
- package/dist/lib/safe-eval.js +0 -124
- package/dist/lib/storacha-client.js +0 -692
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Input Validation Utilities
|
|
3
|
-
* Provides secure input validation for user-facing data
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Common disposable email domains that should be blocked for signups.
|
|
7
|
-
* This is a minimal list - consider using a more comprehensive service
|
|
8
|
-
* for production use.
|
|
9
|
-
*/
|
|
10
|
-
const DISPOSABLE_EMAIL_DOMAINS = new Set([
|
|
11
|
-
'mailinator.com',
|
|
12
|
-
'guerrillamail.com',
|
|
13
|
-
'guerrillamail.org',
|
|
14
|
-
'tempmail.com',
|
|
15
|
-
'temp-mail.org',
|
|
16
|
-
'10minutemail.com',
|
|
17
|
-
'throwaway.email',
|
|
18
|
-
'fakeinbox.com',
|
|
19
|
-
'trashmail.com',
|
|
20
|
-
'yopmail.com',
|
|
21
|
-
'maildrop.cc',
|
|
22
|
-
'getnada.com',
|
|
23
|
-
'sharklasers.com',
|
|
24
|
-
'dispostable.com',
|
|
25
|
-
'mailnesia.com',
|
|
26
|
-
]);
|
|
27
|
-
/**
|
|
28
|
-
* RFC 5322 compliant email regex (simplified but comprehensive).
|
|
29
|
-
* Allows most valid email formats while rejecting clearly invalid ones.
|
|
30
|
-
*/
|
|
31
|
-
const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
32
|
-
/**
|
|
33
|
-
* Maximum email length per RFC 5321
|
|
34
|
-
*/
|
|
35
|
-
const MAX_EMAIL_LENGTH = 254;
|
|
36
|
-
/**
|
|
37
|
-
* Maximum local part length per RFC 5321
|
|
38
|
-
*/
|
|
39
|
-
const MAX_LOCAL_PART_LENGTH = 64;
|
|
40
|
-
/**
|
|
41
|
-
* Validate an email address.
|
|
42
|
-
*
|
|
43
|
-
* Performs the following checks:
|
|
44
|
-
* 1. Not empty or whitespace-only
|
|
45
|
-
* 2. Within length limits (254 chars total, 64 chars local part)
|
|
46
|
-
* 3. Matches RFC 5322 email format
|
|
47
|
-
* 4. Domain has at least one dot (e.g., rejects "user@localhost")
|
|
48
|
-
* 5. Optionally blocks disposable email domains
|
|
49
|
-
*
|
|
50
|
-
* @param email - Email address to validate
|
|
51
|
-
* @param options - Validation options
|
|
52
|
-
* @returns Validation result with normalized email if valid
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* ```typescript
|
|
56
|
-
* const result = validateEmail('User@Example.COM');
|
|
57
|
-
* if (result.valid) {
|
|
58
|
-
* console.log(result.normalized); // 'user@example.com'
|
|
59
|
-
* } else {
|
|
60
|
-
* console.error(result.message);
|
|
61
|
-
* }
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
export function validateEmail(email, options = {}) {
|
|
65
|
-
const { blockDisposable = false, requireTld = true } = options;
|
|
66
|
-
// Check for empty/null/undefined
|
|
67
|
-
if (!email || typeof email !== 'string') {
|
|
68
|
-
return {
|
|
69
|
-
valid: false,
|
|
70
|
-
error: 'EMPTY',
|
|
71
|
-
message: 'Email address is required',
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
// Trim whitespace
|
|
75
|
-
const trimmed = email.trim();
|
|
76
|
-
if (trimmed.length === 0) {
|
|
77
|
-
return {
|
|
78
|
-
valid: false,
|
|
79
|
-
error: 'EMPTY',
|
|
80
|
-
message: 'Email address is required',
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
// Check total length
|
|
84
|
-
if (trimmed.length > MAX_EMAIL_LENGTH) {
|
|
85
|
-
return {
|
|
86
|
-
valid: false,
|
|
87
|
-
error: 'TOO_LONG',
|
|
88
|
-
message: `Email address must be ${MAX_EMAIL_LENGTH} characters or less`,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
// Split into local and domain parts
|
|
92
|
-
const atIndex = trimmed.lastIndexOf('@');
|
|
93
|
-
if (atIndex === -1) {
|
|
94
|
-
return {
|
|
95
|
-
valid: false,
|
|
96
|
-
error: 'INVALID_FORMAT',
|
|
97
|
-
message: 'Email address must contain @',
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
const localPart = trimmed.substring(0, atIndex);
|
|
101
|
-
const domain = trimmed.substring(atIndex + 1);
|
|
102
|
-
// Check local part length
|
|
103
|
-
if (localPart.length === 0) {
|
|
104
|
-
return {
|
|
105
|
-
valid: false,
|
|
106
|
-
error: 'INVALID_LOCAL_PART',
|
|
107
|
-
message: 'Email local part (before @) is required',
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (localPart.length > MAX_LOCAL_PART_LENGTH) {
|
|
111
|
-
return {
|
|
112
|
-
valid: false,
|
|
113
|
-
error: 'INVALID_LOCAL_PART',
|
|
114
|
-
message: `Email local part must be ${MAX_LOCAL_PART_LENGTH} characters or less`,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
// Check domain
|
|
118
|
-
if (domain.length === 0) {
|
|
119
|
-
return {
|
|
120
|
-
valid: false,
|
|
121
|
-
error: 'INVALID_DOMAIN',
|
|
122
|
-
message: 'Email domain (after @) is required',
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
// Require TLD (at least one dot in domain)
|
|
126
|
-
if (requireTld && !domain.includes('.')) {
|
|
127
|
-
return {
|
|
128
|
-
valid: false,
|
|
129
|
-
error: 'DOMAIN_TOO_SHORT',
|
|
130
|
-
message: 'Email domain must include a valid TLD (e.g., .com, .org)',
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
// Check domain TLD length (must be at least 2 chars)
|
|
134
|
-
if (requireTld) {
|
|
135
|
-
const lastDot = domain.lastIndexOf('.');
|
|
136
|
-
const tld = domain.substring(lastDot + 1);
|
|
137
|
-
if (tld.length < 2) {
|
|
138
|
-
return {
|
|
139
|
-
valid: false,
|
|
140
|
-
error: 'DOMAIN_TOO_SHORT',
|
|
141
|
-
message: 'Email domain TLD must be at least 2 characters',
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
// Validate format with regex
|
|
146
|
-
if (!EMAIL_REGEX.test(trimmed)) {
|
|
147
|
-
return {
|
|
148
|
-
valid: false,
|
|
149
|
-
error: 'INVALID_FORMAT',
|
|
150
|
-
message: 'Email address format is invalid',
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
// Normalize to lowercase
|
|
154
|
-
const normalized = trimmed.toLowerCase();
|
|
155
|
-
const normalizedDomain = domain.toLowerCase();
|
|
156
|
-
// Check for disposable domains
|
|
157
|
-
if (blockDisposable && DISPOSABLE_EMAIL_DOMAINS.has(normalizedDomain)) {
|
|
158
|
-
return {
|
|
159
|
-
valid: false,
|
|
160
|
-
error: 'DISPOSABLE_DOMAIN',
|
|
161
|
-
message: 'Disposable email addresses are not allowed',
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
valid: true,
|
|
166
|
-
normalized,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Quick check if an email is valid (without detailed error info).
|
|
171
|
-
*
|
|
172
|
-
* @param email - Email to check
|
|
173
|
-
* @returns true if valid, false otherwise
|
|
174
|
-
*/
|
|
175
|
-
export function isValidEmail(email) {
|
|
176
|
-
return validateEmail(email).valid;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Normalize an email address (lowercase, trimmed).
|
|
180
|
-
* Returns null if email is invalid.
|
|
181
|
-
*
|
|
182
|
-
* @param email - Email to normalize
|
|
183
|
-
* @returns Normalized email or null if invalid
|
|
184
|
-
*/
|
|
185
|
-
export function normalizeEmail(email) {
|
|
186
|
-
const result = validateEmail(email);
|
|
187
|
-
return result.valid ? result.normalized : null;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Common passwords to reject (minimal list - use larger database in production)
|
|
191
|
-
*/
|
|
192
|
-
const COMMON_PASSWORDS = new Set([
|
|
193
|
-
'password',
|
|
194
|
-
'password1',
|
|
195
|
-
'password123',
|
|
196
|
-
'123456',
|
|
197
|
-
'12345678',
|
|
198
|
-
'123456789',
|
|
199
|
-
'1234567890',
|
|
200
|
-
'qwerty',
|
|
201
|
-
'qwerty123',
|
|
202
|
-
'letmein',
|
|
203
|
-
'welcome',
|
|
204
|
-
'admin',
|
|
205
|
-
'login',
|
|
206
|
-
'abc123',
|
|
207
|
-
'monkey',
|
|
208
|
-
'master',
|
|
209
|
-
'dragon',
|
|
210
|
-
'sunshine',
|
|
211
|
-
'princess',
|
|
212
|
-
'football',
|
|
213
|
-
'baseball',
|
|
214
|
-
'iloveyou',
|
|
215
|
-
'trustno1',
|
|
216
|
-
'superman',
|
|
217
|
-
'batman',
|
|
218
|
-
]);
|
|
219
|
-
/**
|
|
220
|
-
* Minimum password length
|
|
221
|
-
*/
|
|
222
|
-
const MIN_PASSWORD_LENGTH = 8;
|
|
223
|
-
/**
|
|
224
|
-
* Maximum password length (bcrypt has a 72-byte limit)
|
|
225
|
-
*/
|
|
226
|
-
const MAX_PASSWORD_LENGTH = 72;
|
|
227
|
-
/**
|
|
228
|
-
* Validate a password against security requirements.
|
|
229
|
-
*
|
|
230
|
-
* Requirements:
|
|
231
|
-
* - 8-72 characters
|
|
232
|
-
* - At least one lowercase letter
|
|
233
|
-
* - At least one uppercase letter
|
|
234
|
-
* - At least one digit
|
|
235
|
-
* - At least one special character
|
|
236
|
-
* - Not a common password
|
|
237
|
-
*
|
|
238
|
-
* @param password - Password to validate
|
|
239
|
-
* @param options - Validation options
|
|
240
|
-
* @returns Validation result with strength score
|
|
241
|
-
*/
|
|
242
|
-
export function validatePassword(password, options = {}) {
|
|
243
|
-
const { minLength = MIN_PASSWORD_LENGTH, requireUppercase = true, requireLowercase = true, requireDigit = true, requireSpecial = true, checkCommon = true, } = options;
|
|
244
|
-
const errors = [];
|
|
245
|
-
let strength = 0;
|
|
246
|
-
// Handle null/undefined
|
|
247
|
-
if (!password || typeof password !== 'string') {
|
|
248
|
-
return {
|
|
249
|
-
valid: false,
|
|
250
|
-
errors: ['TOO_SHORT'],
|
|
251
|
-
strength: 0,
|
|
252
|
-
strengthLabel: 'very_weak',
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
// Check length
|
|
256
|
-
if (password.length < minLength) {
|
|
257
|
-
errors.push('TOO_SHORT');
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
strength++;
|
|
261
|
-
}
|
|
262
|
-
if (password.length > MAX_PASSWORD_LENGTH) {
|
|
263
|
-
errors.push('TOO_LONG');
|
|
264
|
-
}
|
|
265
|
-
// Check for lowercase
|
|
266
|
-
if (requireLowercase && !/[a-z]/.test(password)) {
|
|
267
|
-
errors.push('NO_LOWERCASE');
|
|
268
|
-
}
|
|
269
|
-
else if (/[a-z]/.test(password)) {
|
|
270
|
-
strength++;
|
|
271
|
-
}
|
|
272
|
-
// Check for uppercase
|
|
273
|
-
if (requireUppercase && !/[A-Z]/.test(password)) {
|
|
274
|
-
errors.push('NO_UPPERCASE');
|
|
275
|
-
}
|
|
276
|
-
else if (/[A-Z]/.test(password)) {
|
|
277
|
-
strength++;
|
|
278
|
-
}
|
|
279
|
-
// Check for digit
|
|
280
|
-
if (requireDigit && !/\d/.test(password)) {
|
|
281
|
-
errors.push('NO_DIGIT');
|
|
282
|
-
}
|
|
283
|
-
else if (/\d/.test(password)) {
|
|
284
|
-
strength++;
|
|
285
|
-
}
|
|
286
|
-
// Check for special character
|
|
287
|
-
if (requireSpecial && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]/.test(password)) {
|
|
288
|
-
errors.push('NO_SPECIAL');
|
|
289
|
-
}
|
|
290
|
-
// Check for common passwords
|
|
291
|
-
if (checkCommon && COMMON_PASSWORDS.has(password.toLowerCase())) {
|
|
292
|
-
errors.push('COMMON_PASSWORD');
|
|
293
|
-
}
|
|
294
|
-
// Cap strength at 4
|
|
295
|
-
const cappedStrength = Math.min(strength, 4);
|
|
296
|
-
const strengthLabels = {
|
|
297
|
-
0: 'very_weak',
|
|
298
|
-
1: 'weak',
|
|
299
|
-
2: 'fair',
|
|
300
|
-
3: 'strong',
|
|
301
|
-
4: 'very_strong',
|
|
302
|
-
};
|
|
303
|
-
return {
|
|
304
|
-
valid: errors.length === 0,
|
|
305
|
-
errors,
|
|
306
|
-
strength: cappedStrength,
|
|
307
|
-
strengthLabel: strengthLabels[cappedStrength],
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Quick check if a password meets minimum requirements.
|
|
312
|
-
*
|
|
313
|
-
* @param password - Password to check
|
|
314
|
-
* @returns true if valid, false otherwise
|
|
315
|
-
*/
|
|
316
|
-
export function isValidPassword(password) {
|
|
317
|
-
return validatePassword(password).valid;
|
|
318
|
-
}
|
package/dist/lib/safe-eval.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Safe Evaluation Utilities
|
|
3
|
-
*
|
|
4
|
-
* This module centralizes all intentional uses of the Function constructor
|
|
5
|
-
* to provide a single, well-documented location for dynamic code execution.
|
|
6
|
-
*
|
|
7
|
-
* SECURITY NOTICE:
|
|
8
|
-
* These utilities use the Function constructor which can execute arbitrary code.
|
|
9
|
-
* They should ONLY be used in controlled scenarios where:
|
|
10
|
-
* 1. Input is strictly validated/sanitized
|
|
11
|
-
* 2. The execution context is understood
|
|
12
|
-
* 3. There is no viable alternative (e.g., for runtime environment detection)
|
|
13
|
-
*
|
|
14
|
-
* Each function in this module documents its specific use case and security
|
|
15
|
-
* considerations.
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Pattern matching only allowed mathematical expression characters.
|
|
19
|
-
* Includes: digits, decimal points, basic operators, parentheses, whitespace,
|
|
20
|
-
* and common math function names.
|
|
21
|
-
*/
|
|
22
|
-
const SAFE_MATH_EXPRESSION_PATTERN = /^[\d\s+\-*/().%^eE,]+$|^(Math\.(abs|ceil|floor|round|sqrt|pow|min|max|random|sin|cos|tan|log|exp)|\d)+[\d\s+\-*/().%^eE,()]*$/;
|
|
23
|
-
/**
|
|
24
|
-
* Validate that a mathematical expression contains only safe characters.
|
|
25
|
-
*
|
|
26
|
-
* @param expression - The expression string to validate
|
|
27
|
-
* @returns true if the expression is safe, false otherwise
|
|
28
|
-
*/
|
|
29
|
-
export function isValidMathExpression(expression) {
|
|
30
|
-
// Empty or whitespace-only strings are invalid
|
|
31
|
-
if (!expression || !expression.trim()) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
// Check for dangerous patterns first
|
|
35
|
-
const dangerousPatterns = [
|
|
36
|
-
/\beval\b/i,
|
|
37
|
-
/\bFunction\b/i,
|
|
38
|
-
/\bimport\b/i,
|
|
39
|
-
/\brequire\b/i,
|
|
40
|
-
/\bglobalThis\b/i,
|
|
41
|
-
/\bwindow\b/i,
|
|
42
|
-
/\bprocess\b/i,
|
|
43
|
-
/\bconstructor\b/i,
|
|
44
|
-
/\bprototype\b/i,
|
|
45
|
-
/\b__proto__\b/i,
|
|
46
|
-
/\[\s*['"]/, // Property access with string literals
|
|
47
|
-
/['"`]/, // String literals
|
|
48
|
-
];
|
|
49
|
-
for (const pattern of dangerousPatterns) {
|
|
50
|
-
if (pattern.test(expression)) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// Check that expression only contains allowed characters
|
|
55
|
-
return SAFE_MATH_EXPRESSION_PATTERN.test(expression.trim());
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Safely evaluate a mathematical expression.
|
|
59
|
-
*
|
|
60
|
-
* SECURITY: This function uses the Function constructor for dynamic evaluation.
|
|
61
|
-
* It MUST only be called with validated input from isValidMathExpression().
|
|
62
|
-
*
|
|
63
|
-
* Use case: Calculator functionality requiring dynamic expression evaluation
|
|
64
|
-
* Alternative considered: Using a proper expression parser library (recommended
|
|
65
|
-
* for production with complex expressions)
|
|
66
|
-
*
|
|
67
|
-
* @param expression - A pre-validated mathematical expression
|
|
68
|
-
* @returns The numeric result of the expression
|
|
69
|
-
* @throws Error if evaluation fails or result is not a valid number
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* ```typescript
|
|
73
|
-
* if (isValidMathExpression(expr)) {
|
|
74
|
-
* const result = evaluateMathExpression(expr);
|
|
75
|
-
* }
|
|
76
|
-
* ```
|
|
77
|
-
*/
|
|
78
|
-
export function evaluateMathExpression(expression) {
|
|
79
|
-
// Double-check validation (defense in depth)
|
|
80
|
-
if (!isValidMathExpression(expression)) {
|
|
81
|
-
throw new Error('Invalid mathematical expression');
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
// eslint-disable-next-line no-new-func
|
|
85
|
-
const func = new Function(`"use strict"; return (${expression})`);
|
|
86
|
-
const result = func();
|
|
87
|
-
if (typeof result !== 'number' || !isFinite(result)) {
|
|
88
|
-
throw new Error('Expression did not evaluate to a finite number');
|
|
89
|
-
}
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
-
throw new Error(`Expression evaluation failed: ${message}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Check if the current module is the main entry point (ESM-compatible).
|
|
99
|
-
*
|
|
100
|
-
* SECURITY: This function uses the Function constructor to access import.meta.url
|
|
101
|
-
* in a way that's compatible with both ESM and CommonJS environments without
|
|
102
|
-
* causing parse-time errors.
|
|
103
|
-
*
|
|
104
|
-
* Use case: Runtime environment detection for CLI entry points
|
|
105
|
-
* Why Function constructor: import.meta syntax causes parse errors in CommonJS/Jest
|
|
106
|
-
* Alternative: Separate entry point files (increases maintenance burden)
|
|
107
|
-
*
|
|
108
|
-
* @returns true if running as main module, false otherwise
|
|
109
|
-
*/
|
|
110
|
-
export function isMainModule() {
|
|
111
|
-
try {
|
|
112
|
-
// Use Function constructor to avoid parse-time errors with import.meta in CommonJS/Jest.
|
|
113
|
-
// This is a well-known pattern for ESM/CJS interoperability.
|
|
114
|
-
// The constructed function only accesses import.meta.url which is safe.
|
|
115
|
-
// eslint-disable-next-line no-new-func
|
|
116
|
-
const getImportMetaUrl = new Function('return import.meta.url');
|
|
117
|
-
const metaUrl = getImportMetaUrl();
|
|
118
|
-
return metaUrl === `file://${process.argv[1]}`;
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
// In CommonJS or environments where import.meta is not available
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
}
|