lsh-framework 1.7.3 → 1.8.0

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.
@@ -257,8 +257,9 @@ async function saveConfiguration(config) {
257
257
  // File doesn't exist, start fresh
258
258
  }
259
259
  // Update or add configuration
260
- // SECURITY: DO NOT save LSH_SECRETS_KEY to .env (circular encryption vulnerability)
261
- const updates = {};
260
+ const updates = {
261
+ LSH_SECRETS_KEY: config.encryptionKey,
262
+ };
262
263
  if (config.storageType === 'supabase' && config.supabaseUrl && config.supabaseKey) {
263
264
  updates.SUPABASE_URL = config.supabaseUrl;
264
265
  updates.SUPABASE_ANON_KEY = config.supabaseKey;
@@ -326,23 +327,12 @@ function showSuccessMessage(config) {
326
327
  console.log(chalk.bold.green('✨ Setup complete!'));
327
328
  console.log(chalk.gray('━'.repeat(50)));
328
329
  console.log('');
329
- // Show encryption key with shell profile instructions
330
- console.log(chalk.yellow('🔑 Your encryption key (save this securely):'));
330
+ // Show encryption key
331
+ console.log(chalk.yellow('📝 Your encryption key (save this securely):'));
331
332
  console.log(chalk.cyan(` ${config.encryptionKey}`));
332
333
  console.log('');
333
- console.log(chalk.bold('⚠️ IMPORTANT: Add this to your shell profile, NOT to .env'));
334
- console.log('');
335
- console.log(chalk.gray(' Add to your shell profile:'));
336
- console.log(chalk.cyan(` echo 'export LSH_SECRETS_KEY="${config.encryptionKey}"' >> ~/.zshrc`));
337
- console.log(chalk.cyan(' source ~/.zshrc'));
338
- console.log('');
339
- console.log(chalk.gray(' Then verify it\'s set:'));
340
- console.log(chalk.cyan(' echo $LSH_SECRETS_KEY'));
341
- console.log('');
342
- console.log(chalk.gray(' Share this key securely with your team:'));
343
- console.log(chalk.gray(' - Use 1Password, LastPass, or Bitwarden'));
344
- console.log(chalk.gray(' - Send via encrypted email or Signal'));
345
- console.log(chalk.gray(' - Never commit to git or post in public channels'));
334
+ console.log(chalk.gray(' This key is saved in your .env file.'));
335
+ console.log(chalk.gray(' Share it with your team to sync secrets.'));
346
336
  console.log('');
347
337
  // Storage info
348
338
  if (config.storageType === 'supabase') {
@@ -159,8 +159,8 @@ export class SecretsManager {
159
159
  }
160
160
  message += `\nThis is likely unintentional and could break your application.\n\n`;
161
161
  message += `To proceed anyway, use the --force flag:\n`;
162
- message += ` lsh lib secrets push --force\n`;
163
- message += ` lsh lib secrets sync --force\n`;
162
+ message += ` lsh push --force\n`;
163
+ message += ` lsh sync --force\n`;
164
164
  return message;
165
165
  }
166
166
  /**
@@ -371,84 +371,45 @@ export class SecretsManager {
371
371
  return environment;
372
372
  }
373
373
  /**
374
- * Ensure encryption key is set in environment
375
- * SECURITY: Key must be in shell profile, NEVER in project .env
374
+ * Generate encryption key if not set
376
375
  */
377
376
  async ensureEncryptionKey() {
378
- if (process.env.LSH_SECRETS_KEY || this.encryptionKey) {
379
- return true; // Key already set (env or constructor)
380
- }
381
- // DO NOT AUTO-GENERATE - This is a security violation
382
- console.log('');
383
- logger.error('❌ LSH_SECRETS_KEY environment variable not set');
384
- console.log('');
385
- logger.error('🔒 For security, this key must be stored in your shell profile,');
386
- logger.error(' NOT in your project\'s .env file.');
387
- console.log('');
388
- logger.error('📝 Quick setup:');
389
- console.log('');
390
- logger.error(' 1. Generate a key:');
391
- logger.error(' lsh key');
392
- console.log('');
393
- logger.error(' 2. Add to your shell profile:');
394
- logger.error(' echo "export LSH_SECRETS_KEY=\'your-key-here\'" >> ~/.zshrc');
395
- logger.error(' source ~/.zshrc');
396
- console.log('');
397
- logger.error(' 3. Verify it\'s set:');
398
- logger.error(' echo $LSH_SECRETS_KEY');
399
- console.log('');
400
- logger.error('📖 See SECURITY.md for complete details and team sharing.');
401
- console.log('');
402
- throw new Error('LSH_SECRETS_KEY not set in environment');
403
- }
404
- /**
405
- * Check if LSH_SECRETS_KEY is in .env file (security violation)
406
- * SECURITY: Keys in .env files create circular encryption and expose secrets
407
- */
408
- checkForKeyInEnvFile() {
409
- // Skip check in test environment
410
- if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID) {
411
- return;
377
+ if (process.env.LSH_SECRETS_KEY) {
378
+ return true; // Key already set
412
379
  }
380
+ logger.warn('⚠️ No encryption key found. Generating a new key...');
381
+ const key = crypto.randomBytes(32).toString('hex');
382
+ // Try to add to .env file
413
383
  const envPath = path.join(process.cwd(), '.env');
414
- if (!fs.existsSync(envPath)) {
415
- return; // No .env file, nothing to check
416
- }
417
384
  try {
418
- const content = fs.readFileSync(envPath, 'utf8');
385
+ let content = '';
386
+ if (fs.existsSync(envPath)) {
387
+ content = fs.readFileSync(envPath, 'utf8');
388
+ if (!content.endsWith('\n')) {
389
+ content += '\n';
390
+ }
391
+ }
392
+ // Check if LSH_SECRETS_KEY already exists (but empty)
419
393
  if (content.includes('LSH_SECRETS_KEY=')) {
420
- console.log('');
421
- logger.error('🚨 SECURITY ISSUE: LSH_SECRETS_KEY found in .env file!');
422
- console.log('');
423
- logger.error('⚠️ This is a critical security vulnerability:');
424
- logger.error(' - Creates circular encryption (key encrypts itself)');
425
- logger.error(' - Exposes key if .env is accidentally committed');
426
- logger.error(' - Makes IPFS storage completely insecure');
427
- console.log('');
428
- logger.error('🔧 Fix this immediately:');
429
- console.log('');
430
- logger.error(' 1. Copy the key value from .env');
431
- logger.error(' 2. Add to your shell profile:');
432
- logger.error(' echo "export LSH_SECRETS_KEY=\'your-key\'" >> ~/.zshrc');
433
- logger.error(' source ~/.zshrc');
434
- console.log('');
435
- logger.error(' 3. Remove LSH_SECRETS_KEY line from .env:');
436
- logger.error(' # Edit .env and DELETE the LSH_SECRETS_KEY= line');
437
- console.log('');
438
- logger.error(' 4. Verify it\'s set correctly:');
439
- logger.error(' echo $LSH_SECRETS_KEY');
440
- console.log('');
441
- logger.error('📖 See SECURITY.md for complete details.');
442
- console.log('');
443
- throw new Error('LSH_SECRETS_KEY must not be in .env file');
394
+ content = content.replace(/LSH_SECRETS_KEY=.*$/m, `LSH_SECRETS_KEY=${key}`);
395
+ }
396
+ else {
397
+ content += `\n# LSH Secrets Encryption Key (do not commit!)\nLSH_SECRETS_KEY=${key}\n`;
444
398
  }
399
+ fs.writeFileSync(envPath, content, 'utf8');
400
+ // Set in current process
401
+ process.env.LSH_SECRETS_KEY = key;
402
+ this.encryptionKey = key;
403
+ logger.info('✅ Generated and saved encryption key to .env');
404
+ logger.info('💡 Load it now: export LSH_SECRETS_KEY=' + key.substring(0, 8) + '...');
405
+ return true;
445
406
  }
446
407
  catch (error) {
447
- // If we already threw our error, re-throw it
448
- if (error.message === 'LSH_SECRETS_KEY must not be in .env file') {
449
- throw error;
450
- }
451
- // Otherwise, ignore read errors
408
+ const _err = error;
409
+ logger.error(`Failed to save encryption key: ${_err.message}`);
410
+ logger.info('Please set it manually:');
411
+ logger.info(`export LSH_SECRETS_KEY=${key}`);
412
+ return false;
452
413
  }
453
414
  }
454
415
  /**
@@ -544,7 +505,7 @@ LSH_SECRETS_KEY=${this.encryptionKey}
544
505
  * Smart sync command - automatically set up and synchronize secrets
545
506
  * This is the new enhanced sync that does everything automatically
546
507
  */
547
- async smartSync(envFilePath = '.env', environment = 'dev', autoExecute = true, loadMode = false, force = false) {
508
+ async smartSync(envFilePath = '.env', environment = 'dev', autoExecute = true, loadMode = false, force = false, forceRekey = false) {
548
509
  // In load mode, suppress all logger output to prevent zsh glob interpretation
549
510
  // Save original level and restore at the end
550
511
  const originalLogLevel = loadMode ? logger['config'].level : undefined;
@@ -567,19 +528,17 @@ LSH_SECRETS_KEY=${this.encryptionKey}
567
528
  }
568
529
  out();
569
530
  }
570
- // Step 1: Check for security violations
571
- this.checkForKeyInEnvFile(); // Must be BEFORE ensureEncryptionKey
572
- // Step 2: Ensure encryption key exists
531
+ // Step 1: Ensure encryption key exists
573
532
  if (!process.env.LSH_SECRETS_KEY) {
574
533
  logger.info('🔑 No encryption key found...');
575
534
  await this.ensureEncryptionKey();
576
535
  out();
577
536
  }
578
- // Step 3: Ensure .gitignore includes .env
537
+ // Step 2: Ensure .gitignore includes .env
579
538
  if (this.gitInfo?.isGitRepo) {
580
539
  ensureEnvInGitignore(process.cwd());
581
540
  }
582
- // Step 4: Check current status
541
+ // Step 3: Check current status
583
542
  const status = await this.status(envFilePath, effectiveEnv);
584
543
  out('📊 Current Status:');
585
544
  out(` Encryption key: ${status.keySet ? '✅' : '❌'}`);
@@ -589,14 +548,43 @@ LSH_SECRETS_KEY=${this.encryptionKey}
589
548
  out(` Key matches: ${status.keyMatches ? '✅' : '❌'}`);
590
549
  }
591
550
  out();
592
- // Step 5: Determine action and execute if auto mode
551
+ // Step 4: Determine action and execute if auto mode
593
552
  let _action = 'in-sync';
594
553
  if (status.cloudExists && status.keyMatches === false) {
554
+ if (forceRekey) {
555
+ // Force re-keying: push local secrets with current key
556
+ _action = 'push';
557
+ out('🔄 Force re-keying: Re-encrypting cloud secrets with current key...');
558
+ if (autoExecute) {
559
+ if (!status.localExists) {
560
+ out('❌ Cannot re-key: No local .env file found');
561
+ out(`💡 Pull with original key first, or create new .env`);
562
+ return;
563
+ }
564
+ out(' Pushing to cloud with new key...');
565
+ await this.push(envFilePath, effectiveEnv, true); // Force push
566
+ out();
567
+ out('✅ Re-keying complete! Cloud secrets now encrypted with current key.');
568
+ }
569
+ else {
570
+ out('💡 Run: lsh push -f ${envFilePath} -e ${environment}');
571
+ }
572
+ out();
573
+ // Output export commands in load mode
574
+ if (loadMode && fs.existsSync(envFilePath)) {
575
+ console.log(this.generateExportCommands(envFilePath));
576
+ }
577
+ return;
578
+ }
579
+ // No force-rekey: show error and options
595
580
  _action = 'key-mismatch';
596
581
  out('⚠️ Encryption key mismatch!');
597
582
  out(' The local key does not match the cloud storage.');
598
- out(' Please use the original key or push new secrets with:');
599
- out(` lsh lib secrets push -f ${envFilePath} -e ${environment}`);
583
+ out();
584
+ out(' Options:');
585
+ out(` 1. Use the original key (recommended if you have it)`);
586
+ out(` 2. Re-encrypt with current key: lsh push -f ${envFilePath} -e ${environment}`);
587
+ out(` 3. Re-key during sync: lsh sync --force-rekey -f ${envFilePath} -e ${environment}`);
600
588
  out();
601
589
  return;
602
590
  }
@@ -612,7 +600,7 @@ LSH_SECRETS_KEY=${this.encryptionKey}
612
600
  out('✅ Setup complete! Edit your .env and run sync again to update.');
613
601
  }
614
602
  else {
615
- out('💡 Run: lsh lib secrets create && lsh lib secrets push');
603
+ out('💡 Run: lsh create && lsh push');
616
604
  }
617
605
  out();
618
606
  // Output export commands in load mode
@@ -630,7 +618,7 @@ LSH_SECRETS_KEY=${this.encryptionKey}
630
618
  out('✅ Secrets pushed to cloud!');
631
619
  }
632
620
  else {
633
- out(`💡 Run: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
621
+ out(`💡 Run: lsh push -f ${envFilePath} -e ${environment}`);
634
622
  }
635
623
  out();
636
624
  // Output export commands in load mode
@@ -648,7 +636,7 @@ LSH_SECRETS_KEY=${this.encryptionKey}
648
636
  out('✅ Secrets pulled from cloud!');
649
637
  }
650
638
  else {
651
- out(`💡 Run: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
639
+ out(`💡 Run: lsh pull -f ${envFilePath} -e ${environment}`);
652
640
  }
653
641
  out();
654
642
  // Output export commands in load mode
@@ -685,7 +673,7 @@ LSH_SECRETS_KEY=${this.encryptionKey}
685
673
  out('✅ Secrets synced to cloud!');
686
674
  }
687
675
  else {
688
- out(`💡 Run: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
676
+ out(`💡 Run: lsh push -f ${envFilePath} -e ${environment}`);
689
677
  }
690
678
  }
691
679
  else {
@@ -699,7 +687,7 @@ LSH_SECRETS_KEY=${this.encryptionKey}
699
687
  out('✅ Secrets synced from cloud!');
700
688
  }
701
689
  else {
702
- out(`💡 Run: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
690
+ out(`💡 Run: lsh pull -f ${envFilePath} -e ${environment}`);
703
691
  }
704
692
  }
705
693
  out();
@@ -764,22 +752,22 @@ LSH_SECRETS_KEY=${this.encryptionKey}
764
752
  const suggestions = [];
765
753
  if (!status.keySet) {
766
754
  suggestions.push('⚠️ No encryption key set!');
767
- suggestions.push(' Generate a key: lsh lib secrets key');
755
+ suggestions.push(' Generate a key: lsh key');
768
756
  suggestions.push(' Add it to .env: LSH_SECRETS_KEY=<your-key>');
769
757
  suggestions.push(' Load it: export $(cat .env | xargs)');
770
758
  }
771
759
  if (status.cloudExists && status.keyMatches === false) {
772
760
  suggestions.push('⚠️ Encryption key does not match cloud storage!');
773
761
  suggestions.push(' Either use the original key, or push new secrets:');
774
- suggestions.push(` lsh lib secrets push -f ${envFilePath} -e ${environment}`);
762
+ suggestions.push(` lsh push -f ${envFilePath} -e ${environment}`);
775
763
  }
776
764
  if (!status.localExists && status.cloudExists && status.keyMatches) {
777
765
  suggestions.push('💡 Cloud secrets available but no local file');
778
- suggestions.push(` Pull from cloud: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
766
+ suggestions.push(` Pull from cloud: lsh pull -f ${envFilePath} -e ${environment}`);
779
767
  }
780
768
  if (status.localExists && !status.cloudExists) {
781
769
  suggestions.push('💡 Local .env exists but not in cloud');
782
- suggestions.push(` Push to cloud: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
770
+ suggestions.push(` Push to cloud: lsh push -f ${envFilePath} -e ${environment}`);
783
771
  }
784
772
  if (status.localExists && status.cloudExists && status.keyMatches) {
785
773
  if (status.localModified && status.cloudModified) {
@@ -788,11 +776,11 @@ LSH_SECRETS_KEY=${this.encryptionKey}
788
776
  const daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
789
777
  if (localNewer && daysDiff > 0) {
790
778
  suggestions.push('💡 Local file is newer than cloud');
791
- suggestions.push(` Push to cloud: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
779
+ suggestions.push(` Push to cloud: lsh push -f ${envFilePath} -e ${environment}`);
792
780
  }
793
781
  else if (!localNewer && daysDiff > 0) {
794
782
  suggestions.push('💡 Cloud is newer than local file');
795
- suggestions.push(` Pull from cloud: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
783
+ suggestions.push(` Pull from cloud: lsh pull -f ${envFilePath} -e ${environment}`);
796
784
  }
797
785
  else {
798
786
  suggestions.push('✅ Local and cloud are in sync!');
@@ -7,7 +7,6 @@ import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import * as readline from 'readline';
9
9
  import { getGitRepoInfo } from '../../lib/git-utils.js';
10
- import chalk from 'chalk';
11
10
  export async function init_secrets(program) {
12
11
  // Push secrets to cloud
13
12
  program
@@ -198,27 +197,11 @@ export async function init_secrets(program) {
198
197
  }
199
198
  else {
200
199
  // Interactive output with tips
201
- console.log('');
202
- console.log('🔑 New encryption key generated!\n');
203
- console.log(chalk.cyan(` ${key}`));
204
- console.log('');
205
- console.log(chalk.bold('⚠️ IMPORTANT: Add to your shell profile, NOT to .env'));
206
- console.log('');
207
- console.log(chalk.gray(' 1. Add to your shell profile:'));
208
- console.log(chalk.cyan(` echo 'export LSH_SECRETS_KEY="${key}"' >> ~/.zshrc`));
209
- console.log(chalk.cyan(' source ~/.zshrc'));
210
- console.log('');
211
- console.log(chalk.gray(' 2. Verify it\'s set:'));
212
- console.log(chalk.cyan(' echo $LSH_SECRETS_KEY'));
213
- console.log('');
214
- console.log(chalk.gray(' 3. Share securely with your team:'));
215
- console.log(chalk.gray(' - Use 1Password, LastPass, or Bitwarden'));
216
- console.log(chalk.gray(' - Send via encrypted email or Signal'));
217
- console.log(chalk.gray(' - NEVER commit to git or .env file'));
218
- console.log('');
219
- console.log(chalk.gray('💡 Quick load: eval "$(lsh key --export)"'));
220
- console.log(chalk.gray('📖 Details: See SECURITY.md'));
221
- console.log('');
200
+ console.log('\n🔑 New encryption key (add to your .env):\n');
201
+ console.log(`export LSH_SECRETS_KEY='${key}'\n`);
202
+ console.log('💡 Tip: Share this key securely with your team to sync secrets.');
203
+ console.log(' Never commit it to git!\n');
204
+ console.log('💡 To load immediately: eval "$(lsh key --export)"\n');
222
205
  }
223
206
  });
224
207
  // Create .env file
@@ -267,7 +250,7 @@ API_KEY=
267
250
  console.log('');
268
251
  console.log('Next steps:');
269
252
  console.log(` 1. Edit the file: ${options.file}`);
270
- console.log(` 2. Push to cloud: lsh lib secrets push -f ${options.file}`);
253
+ console.log(` 2. Push to cloud: lsh push -f ${options.file}`);
271
254
  console.log('');
272
255
  }
273
256
  catch (error) {
@@ -286,6 +269,7 @@ API_KEY=
286
269
  .option('--legacy', 'Use legacy sync mode (suggestions only)')
287
270
  .option('--load', 'Output eval-able export commands for loading secrets')
288
271
  .option('--force', 'Force sync even if destructive changes detected')
272
+ .option('--force-rekey', 'Re-encrypt cloud secrets with current local key (use when key mismatch)')
289
273
  .action(async (options) => {
290
274
  const manager = new SecretsManager();
291
275
  try {
@@ -295,7 +279,7 @@ API_KEY=
295
279
  }
296
280
  else {
297
281
  // Use new smart sync (auto-execute)
298
- await manager.smartSync(options.file, options.env, !options.dryRun, options.load, options.force);
282
+ await manager.smartSync(options.file, options.env, !options.dryRun, options.load, options.force, options.forceRekey);
299
283
  }
300
284
  }
301
285
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "1.7.3",
3
+ "version": "1.8.0",
4
4
  "description": "Simple, cross-platform encrypted secrets manager with automatic sync, IPFS audit logs, and multi-environment support. Just run lsh sync and start managing your secrets.",
5
5
  "main": "dist/app.js",
6
6
  "bin": {