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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -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
- // .env doesn't exist, will create
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(`/v1beta/models?key=${apiKey}`, endpoint);
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
- const stateDir = path.dirname(SESSION_STATE_PATH);
584
- fs.mkdirSync(stateDir, { recursive: true });
585
- fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(state, null, 2));
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 models = getSessionModels(feature);
631
- if (Array.isArray(models)) {
632
- return models.length > 0;
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
- // Model config module not available
58
+ if (process.env.DEBUG) {
59
+ console.error(`[session-end] Model config module not available: ${err.message}`);
60
+ }
59
61
  }
60
62
 
61
63
  /**