wogiflow 1.0.33 → 1.0.34
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/package.json
CHANGED
|
@@ -78,6 +78,21 @@ const KNOWN_PROVIDERS = {
|
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Check for dangerous keys that could cause prototype pollution
|
|
83
|
+
* @param {Object} obj - Object to check
|
|
84
|
+
* @returns {boolean} True if dangerous keys found
|
|
85
|
+
*/
|
|
86
|
+
function hasDangerousKeys(obj) {
|
|
87
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
88
|
+
const dangerous = ['__proto__', 'constructor', 'prototype'];
|
|
89
|
+
for (const key of Object.keys(obj)) {
|
|
90
|
+
if (dangerous.includes(key)) return true;
|
|
91
|
+
if (typeof obj[key] === 'object' && hasDangerousKeys(obj[key])) return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
/**
|
|
82
97
|
* Read config file
|
|
83
98
|
* @returns {Object} Config object
|
|
@@ -170,6 +185,12 @@ function getEnabledModels() {
|
|
|
170
185
|
* @param {boolean} [options.enabled] - Enable/disable provider
|
|
171
186
|
*/
|
|
172
187
|
function addProvider(providerName, options = {}) {
|
|
188
|
+
// Validate provider name against known providers to prevent injection
|
|
189
|
+
const validProviders = Object.keys(KNOWN_PROVIDERS);
|
|
190
|
+
if (!validProviders.includes(providerName)) {
|
|
191
|
+
throw new Error(`Unknown provider: ${providerName}. Allowed: ${validProviders.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
173
194
|
const modelsConfig = getModelsConfig();
|
|
174
195
|
const knownProvider = KNOWN_PROVIDERS[providerName];
|
|
175
196
|
|
|
@@ -214,18 +235,50 @@ function removeProvider(providerName) {
|
|
|
214
235
|
}
|
|
215
236
|
}
|
|
216
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Validate environment variable name format
|
|
240
|
+
* @param {string} name - Variable name to validate
|
|
241
|
+
* @returns {boolean} True if valid
|
|
242
|
+
*/
|
|
243
|
+
function isValidEnvVarName(name) {
|
|
244
|
+
// Must start with letter or underscore, contain only alphanumeric and underscore
|
|
245
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Escape value for .env file (quote if needed)
|
|
250
|
+
* @param {string} value - Value to escape
|
|
251
|
+
* @returns {string} Escaped value
|
|
252
|
+
*/
|
|
253
|
+
function escapeEnvValue(value) {
|
|
254
|
+
if (!value) return '';
|
|
255
|
+
// If value contains special chars, newlines, or spaces, quote it
|
|
256
|
+
if (/[\s"'`$\\#\n\r]/.test(value)) {
|
|
257
|
+
// Escape backslashes and double quotes, then wrap in double quotes
|
|
258
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
259
|
+
}
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
|
|
217
263
|
/**
|
|
218
264
|
* Update .env file with API key
|
|
219
265
|
* @param {string} keyName - Environment variable name
|
|
220
266
|
* @param {string} keyValue - API key value
|
|
221
267
|
*/
|
|
222
268
|
function updateEnvFile(keyName, keyValue) {
|
|
269
|
+
// Validate env variable name to prevent injection
|
|
270
|
+
if (!isValidEnvVarName(keyName)) {
|
|
271
|
+
throw new Error(`Invalid environment variable name: ${keyName}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
223
274
|
let envContent = '';
|
|
224
275
|
|
|
225
276
|
try {
|
|
226
277
|
envContent = fs.readFileSync(ENV_PATH, 'utf-8');
|
|
227
278
|
} catch (err) {
|
|
228
|
-
|
|
279
|
+
if (process.env.DEBUG) {
|
|
280
|
+
console.log(`[model-config] .env doesn't exist, will create`);
|
|
281
|
+
}
|
|
229
282
|
}
|
|
230
283
|
|
|
231
284
|
// Parse existing env vars
|
|
@@ -238,14 +291,15 @@ function updateEnvFile(keyName, keyValue) {
|
|
|
238
291
|
comments.push(line);
|
|
239
292
|
} else {
|
|
240
293
|
const [key, ...valueParts] = line.split('=');
|
|
241
|
-
if (key) {
|
|
294
|
+
if (key && isValidEnvVarName(key.trim())) {
|
|
295
|
+
// Store raw value (may be quoted)
|
|
242
296
|
envVars[key.trim()] = valueParts.join('=').trim();
|
|
243
297
|
}
|
|
244
298
|
}
|
|
245
299
|
}
|
|
246
300
|
|
|
247
|
-
// Update or add the key
|
|
248
|
-
envVars[keyName] = keyValue;
|
|
301
|
+
// Update or add the key with proper escaping
|
|
302
|
+
envVars[keyName] = escapeEnvValue(keyValue);
|
|
249
303
|
|
|
250
304
|
// Rebuild .env content
|
|
251
305
|
let newContent = '';
|
|
@@ -262,9 +316,9 @@ function updateEnvFile(keyName, keyValue) {
|
|
|
262
316
|
newContent += `${key}=${value}\n`;
|
|
263
317
|
}
|
|
264
318
|
|
|
265
|
-
fs.writeFileSync(ENV_PATH, newContent);
|
|
319
|
+
fs.writeFileSync(ENV_PATH, newContent, { mode: 0o600 });
|
|
266
320
|
|
|
267
|
-
// Also set in current process
|
|
321
|
+
// Also set in current process (use raw value, not escaped)
|
|
268
322
|
process.env[keyName] = keyValue;
|
|
269
323
|
}
|
|
270
324
|
|
|
@@ -400,8 +454,8 @@ async function testCloudProvider(providerName, endpoint, apiKey) {
|
|
|
400
454
|
headers = { 'Authorization': `Bearer ${apiKey}` };
|
|
401
455
|
break;
|
|
402
456
|
case 'google':
|
|
403
|
-
url = new URL(
|
|
404
|
-
headers = {};
|
|
457
|
+
url = new URL('/v1beta/models', endpoint);
|
|
458
|
+
headers = { 'x-goog-api-key': apiKey };
|
|
405
459
|
break;
|
|
406
460
|
case 'anthropic':
|
|
407
461
|
// Anthropic doesn't have a models list endpoint, just test with a simple check
|
|
@@ -419,6 +473,13 @@ async function testCloudProvider(providerName, endpoint, apiKey) {
|
|
|
419
473
|
if (res.statusCode === 200) {
|
|
420
474
|
try {
|
|
421
475
|
const parsed = JSON.parse(data);
|
|
476
|
+
|
|
477
|
+
// Check for prototype pollution in API response
|
|
478
|
+
if (hasDangerousKeys(parsed)) {
|
|
479
|
+
resolve({ success: false, message: 'Invalid API response (security check failed)' });
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
422
483
|
let models = [];
|
|
423
484
|
|
|
424
485
|
if (providerName === 'openai') {
|
|
@@ -580,9 +641,16 @@ function readSessionState() {
|
|
|
580
641
|
* @param {Object} state - State to write
|
|
581
642
|
*/
|
|
582
643
|
function writeSessionState(state) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
644
|
+
try {
|
|
645
|
+
const stateDir = path.dirname(SESSION_STATE_PATH);
|
|
646
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
647
|
+
fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
648
|
+
} catch (err) {
|
|
649
|
+
if (process.env.DEBUG) {
|
|
650
|
+
console.error(`[model-config] Failed to write session state: ${err.message}`);
|
|
651
|
+
}
|
|
652
|
+
// Don't throw - session state is non-critical
|
|
653
|
+
}
|
|
586
654
|
}
|
|
587
655
|
|
|
588
656
|
/**
|
|
@@ -627,11 +695,9 @@ function clearSessionModels() {
|
|
|
627
695
|
* @returns {boolean}
|
|
628
696
|
*/
|
|
629
697
|
function hasSessionModels(feature) {
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
return !!models;
|
|
698
|
+
const state = readSessionState();
|
|
699
|
+
const models = state.selectedModels?.[feature];
|
|
700
|
+
return Array.isArray(models) ? models.length > 0 : !!models;
|
|
635
701
|
}
|
|
636
702
|
|
|
637
703
|
// CLI interface
|
|
@@ -55,7 +55,9 @@ let modelConfig = null;
|
|
|
55
55
|
try {
|
|
56
56
|
modelConfig = require('./flow-model-config');
|
|
57
57
|
} catch (err) {
|
|
58
|
-
|
|
58
|
+
if (process.env.DEBUG) {
|
|
59
|
+
console.error(`[session-end] Model config module not available: ${err.message}`);
|
|
60
|
+
}
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
/**
|