maiass 5.9.5 → 5.9.6

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.
@@ -1,11 +1,67 @@
1
1
  // Secure storage functionality for nodemaiass
2
2
  // Uses OS keychain (macOS) or secret-tool (Linux) for sensitive data storage
3
+ // Windows uses encrypted file storage in AppData
3
4
  // Compatible with bashmaiass approach but uses NODEMAIASS service names
4
5
 
5
6
  import { execSync } from 'child_process';
6
7
  import os from 'os';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import crypto from 'crypto';
7
11
  import { log, logger } from './logger.js';
8
12
 
13
+ /**
14
+ * Encryption key derivation for Windows storage
15
+ * Uses machine-specific data to create a unique encryption key
16
+ * @returns {Buffer} Encryption key
17
+ */
18
+ function getEncryptionKey() {
19
+ // Use machine-specific data to derive a key
20
+ // This provides basic protection while being deterministic per machine
21
+ const machineId = os.hostname() + os.userInfo().username;
22
+ return crypto.createHash('sha256').update(machineId).digest();
23
+ }
24
+
25
+ /**
26
+ * Encrypt data for Windows storage
27
+ * @param {string} data - Data to encrypt
28
+ * @returns {string} Encrypted data (base64)
29
+ */
30
+ function encryptData(data) {
31
+ const key = getEncryptionKey();
32
+ const iv = crypto.randomBytes(16);
33
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
34
+
35
+ let encrypted = cipher.update(data, 'utf8', 'base64');
36
+ encrypted += cipher.final('base64');
37
+
38
+ // Prepend IV to encrypted data
39
+ return iv.toString('base64') + ':' + encrypted;
40
+ }
41
+
42
+ /**
43
+ * Decrypt data from Windows storage
44
+ * @param {string} encryptedData - Encrypted data (base64 with IV)
45
+ * @returns {string} Decrypted data
46
+ */
47
+ function decryptData(encryptedData) {
48
+ const key = getEncryptionKey();
49
+ const parts = encryptedData.split(':');
50
+
51
+ if (parts.length !== 2) {
52
+ throw new Error('Invalid encrypted data format');
53
+ }
54
+
55
+ const iv = Buffer.from(parts[0], 'base64');
56
+ const encrypted = parts[1];
57
+
58
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
59
+ let decrypted = decipher.update(encrypted, 'base64', 'utf8');
60
+ decrypted += decipher.final('utf8');
61
+
62
+ return decrypted;
63
+ }
64
+
9
65
  /**
10
66
  * Get environment-specific service name for secure storage
11
67
  * Uses NODEMAIASS prefix to differentiate from bashmaiass
@@ -46,6 +102,42 @@ export function storeSecureVariable(varName, varValue) {
46
102
  execSync(`security add-generic-password -U -s "${serviceName}" -a "${varName}" -w "${varValue}"`, {
47
103
  stdio: 'pipe'
48
104
  });
105
+ } else if (os.platform() === 'win32') {
106
+ // Windows: Use encrypted file storage in AppData
107
+ // cmdkey doesn't support password retrieval, so we use a file-based approach
108
+ const storageDir = path.join(os.homedir(), 'AppData', 'Local', 'maiass');
109
+ const storageFile = path.join(storageDir, `${serviceName}.dat`);
110
+
111
+ // Create directory if it doesn't exist
112
+ if (!fs.existsSync(storageDir)) {
113
+ fs.mkdirSync(storageDir, { recursive: true, mode: 0o700 });
114
+ }
115
+
116
+ // Read existing data or create new
117
+ let data = {};
118
+ if (fs.existsSync(storageFile)) {
119
+ try {
120
+ const encrypted = fs.readFileSync(storageFile, 'utf8');
121
+ const decrypted = decryptData(encrypted);
122
+ data = JSON.parse(decrypted);
123
+ } catch (error) {
124
+ // If decryption fails, start fresh
125
+ if (debugMode) {
126
+ logger.debug(`Could not read existing storage file: ${error.message}`);
127
+ }
128
+ }
129
+ }
130
+
131
+ // Update the value
132
+ data[varName] = varValue;
133
+
134
+ // Encrypt and save
135
+ const encrypted = encryptData(JSON.stringify(data));
136
+ fs.writeFileSync(storageFile, encrypted, { mode: 0o600 });
137
+
138
+ if (debugMode) {
139
+ logger.debug(`Stored ${varName} in Windows secure storage (encrypted file)`);
140
+ }
49
141
  } else {
50
142
  // Linux: Use secret-tool if available
51
143
  try {
@@ -96,6 +188,33 @@ export function retrieveSecureVariable(varName) {
96
188
  stdio: 'pipe',
97
189
  encoding: 'utf8'
98
190
  }).trim();
191
+ } else if (os.platform() === 'win32') {
192
+ // Windows: Read from encrypted file storage
193
+ const storageDir = path.join(os.homedir(), 'AppData', 'Local', 'maiass');
194
+ const storageFile = path.join(storageDir, `${serviceName}.dat`);
195
+
196
+ if (!fs.existsSync(storageFile)) {
197
+ if (debugMode) {
198
+ logger.debug(`Storage file does not exist: ${storageFile}`);
199
+ }
200
+ return null;
201
+ }
202
+
203
+ try {
204
+ const encrypted = fs.readFileSync(storageFile, 'utf8');
205
+ const decrypted = decryptData(encrypted);
206
+ const data = JSON.parse(decrypted);
207
+ value = data[varName] || null;
208
+
209
+ if (debugMode && value) {
210
+ logger.debug(`Retrieved ${varName} from Windows secure storage (encrypted file)`);
211
+ }
212
+ } catch (error) {
213
+ if (debugMode) {
214
+ logger.debug(`Could not retrieve ${varName}: ${error.message}`);
215
+ }
216
+ return null;
217
+ }
99
218
  } else {
100
219
  // Linux: Use secret-tool if available
101
220
  try {
@@ -144,6 +263,44 @@ export function removeSecureVariable(varName) {
144
263
  execSync(`security delete-generic-password -s "${serviceName}" -a "${varName}"`, {
145
264
  stdio: 'pipe'
146
265
  });
266
+ } else if (os.platform() === 'win32') {
267
+ // Windows: Remove from encrypted file storage
268
+ const storageDir = path.join(os.homedir(), 'AppData', 'Local', 'maiass');
269
+ const storageFile = path.join(storageDir, `${serviceName}.dat`);
270
+
271
+ if (!fs.existsSync(storageFile)) {
272
+ // File doesn't exist, nothing to remove
273
+ if (debugMode) {
274
+ logger.debug(`Storage file does not exist, nothing to remove`);
275
+ }
276
+ return true;
277
+ }
278
+
279
+ try {
280
+ const encrypted = fs.readFileSync(storageFile, 'utf8');
281
+ const decrypted = decryptData(encrypted);
282
+ const data = JSON.parse(decrypted);
283
+
284
+ // Remove the variable
285
+ delete data[varName];
286
+
287
+ // If no more data, delete the file
288
+ if (Object.keys(data).length === 0) {
289
+ fs.unlinkSync(storageFile);
290
+ if (debugMode) {
291
+ logger.debug(`Removed storage file (no more credentials)`);
292
+ }
293
+ } else {
294
+ // Save updated data
295
+ const newEncrypted = encryptData(JSON.stringify(data));
296
+ fs.writeFileSync(storageFile, newEncrypted, { mode: 0o600 });
297
+ }
298
+ } catch (error) {
299
+ if (debugMode) {
300
+ logger.debug(`Error removing ${varName}: ${error.message}`);
301
+ }
302
+ return false;
303
+ }
147
304
  } else {
148
305
  // Linux: secret-tool doesn't have direct delete, but we can try to clear it
149
306
  try {
@@ -236,7 +393,11 @@ export function isSecureStorageAvailable() {
236
393
  if (os.platform() === 'darwin') {
237
394
  execSync('which security', { stdio: 'pipe' });
238
395
  return true;
396
+ } else if (os.platform() === 'win32') {
397
+ // Windows: Always available (uses encrypted file storage)
398
+ return true;
239
399
  } else {
400
+ // Linux: Check for secret-tool
240
401
  execSync('which secret-tool', { stdio: 'pipe' });
241
402
  return true;
242
403
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "maiass",
3
3
  "type": "module",
4
- "version": "5.9.5",
4
+ "version": "5.9.6",
5
5
  "description": "MAIASS - Modular AI-Augmented Semantic Scribe - Intelligent Git workflow automation",
6
6
  "main": "maiass.mjs",
7
7
  "bin": {