@aj-archipelago/cortex 1.4.1 β†’ 1.4.2

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.
@@ -52,7 +52,6 @@ test('OpenAI vendor streaming over subscriptions emits OAI-style deltas', async
52
52
 
53
53
  t.true(events.length > 0);
54
54
 
55
- // Ensure streamed chunks (when they include model) use gpt-4.1 (not mini)
56
55
  const models = events
57
56
  .map(e => {
58
57
  try {
@@ -64,8 +63,7 @@ test('OpenAI vendor streaming over subscriptions emits OAI-style deltas', async
64
63
  .filter(Boolean);
65
64
 
66
65
  if (models.length > 0) {
67
- t.truthy(models.find(m => /gpt-4\.1(?!-mini)/.test(m)));
68
- t.falsy(models.find(m => /gpt-4\.1-mini/.test(m)));
66
+ t.truthy(models.find(m => /gpt-5-chat/.test(m)));
69
67
  }
70
68
  });
71
69
 
@@ -2,7 +2,7 @@
2
2
  // Tests for encryption and decryption functions in cortex/lib/crypto.js
3
3
 
4
4
  import test from 'ava';
5
- import { encrypt, decrypt, doubleEncrypt, doubleDecrypt } from '../../../lib/crypto.js';
5
+ import { encrypt, decrypt } from '../../../lib/crypto.js';
6
6
 
7
7
  // Test data
8
8
  const testData = 'Hello, this is test data!';
@@ -35,85 +35,6 @@ test('decrypt should return original data when no key provided', t => {
35
35
  t.is(result, testData);
36
36
  });
37
37
 
38
- // Double encryption tests
39
- test('doubleEncrypt should encrypt with system key only when no user key', t => {
40
- const encrypted = doubleEncrypt(testData, null, systemKey);
41
- t.truthy(encrypted);
42
- t.not(encrypted, testData);
43
-
44
- // Should be decryptable with system key only
45
- const decrypted = decrypt(encrypted, systemKey);
46
- t.is(decrypted, testData);
47
- });
48
-
49
- test('doubleEncrypt should encrypt with both keys when user key provided', t => {
50
- const encrypted = doubleEncrypt(testData, userKey, systemKey);
51
- t.truthy(encrypted);
52
- t.not(encrypted, testData);
53
-
54
- // Should NOT be decryptable with system key only (it will be user-encrypted data)
55
- const systemOnlyDecrypted = decrypt(encrypted, systemKey);
56
- t.not(systemOnlyDecrypted, testData);
57
- t.truthy(systemOnlyDecrypted); // Should return user-encrypted data, not null
58
- });
59
-
60
- test('doubleEncrypt should fail when no system key provided', t => {
61
- const encrypted = doubleEncrypt(testData, userKey, null);
62
- t.is(encrypted, null);
63
- });
64
-
65
- test('doubleEncrypt should fallback to system key when user encryption fails', t => {
66
- // Use invalid user key to force fallback
67
- const invalidUserKey = 'invalid';
68
- const encrypted = doubleEncrypt(testData, invalidUserKey, systemKey);
69
- t.truthy(encrypted);
70
-
71
- // Should be decryptable with system key only (fallback behavior)
72
- const decrypted = decrypt(encrypted, systemKey);
73
- t.is(decrypted, testData);
74
- });
75
-
76
- // Double decryption tests
77
- test('doubleDecrypt should decrypt with system key only when no user key', t => {
78
- const encrypted = encrypt(testData, systemKey);
79
- const decrypted = doubleDecrypt(encrypted, null, systemKey);
80
- t.is(decrypted, testData);
81
- });
82
-
83
- test('doubleDecrypt should decrypt double-encrypted data with both keys', t => {
84
- const encrypted = doubleEncrypt(testData, userKey, systemKey);
85
- const decrypted = doubleDecrypt(encrypted, userKey, systemKey);
86
- t.is(decrypted, testData);
87
- });
88
-
89
- test('doubleDecrypt should handle single-encrypted data when user key provided', t => {
90
- // This is the key scenario we fixed!
91
- const singleEncrypted = encrypt(testData, systemKey);
92
- const decrypted = doubleDecrypt(singleEncrypted, userKey, systemKey);
93
- t.is(decrypted, testData);
94
- });
95
-
96
- test('doubleDecrypt should fail when no system key provided', t => {
97
- const encrypted = encrypt(testData, systemKey);
98
- const decrypted = doubleDecrypt(encrypted, userKey, null);
99
- t.is(decrypted, null);
100
- });
101
-
102
- test('doubleDecrypt should fail when system decryption fails', t => {
103
- const encrypted = encrypt(testData, systemKey);
104
- const wrongSystemKey = 'wrongkey123456789012345678901234567890123456789012345678901234567890';
105
- const decrypted = doubleDecrypt(encrypted, userKey, wrongSystemKey);
106
- t.is(decrypted, null);
107
- });
108
-
109
- test('doubleDecrypt should return system-decrypted data when user decryption fails for double-encrypted data', t => {
110
- const encrypted = doubleEncrypt(testData, userKey, systemKey);
111
- const decrypted = doubleDecrypt(encrypted, wrongUserKey, systemKey);
112
- // Should return the user-encrypted data (system decryption succeeded, user decryption failed)
113
- t.not(decrypted, testData); // Should not be the original data
114
- t.truthy(decrypted); // Should return some data (user-encrypted)
115
- });
116
-
117
38
  // Edge cases and error handling
118
39
  test('encrypt should handle empty string', t => {
119
40
  const encrypted = encrypt('', systemKey);
@@ -136,28 +57,9 @@ test('encrypt should handle unicode characters', t => {
136
57
  t.is(decrypted, unicodeData);
137
58
  });
138
59
 
139
- test('doubleEncrypt should handle JSON data', t => {
60
+ test('encrypt should handle JSON data', t => {
140
61
  const jsonData = JSON.stringify({ message: 'test', number: 42, array: [1, 2, 3] });
141
- const encrypted = doubleEncrypt(jsonData, userKey, systemKey);
142
- const decrypted = doubleDecrypt(encrypted, userKey, systemKey);
62
+ const encrypted = encrypt(jsonData, systemKey);
63
+ const decrypted = decrypt(encrypted, systemKey);
143
64
  t.is(decrypted, jsonData);
144
65
  });
145
-
146
- // Integration test for the specific scenario we fixed
147
- test('CRITICAL: doubleDecrypt should handle mixed encryption states', t => {
148
- // Simulate a migration scenario where some data is single-encrypted
149
- // and some is double-encrypted, but we always pass both keys
150
-
151
- // Single-encrypted data (old format)
152
- const singleEncrypted = encrypt(testData, systemKey);
153
-
154
- // Double-encrypted data (new format)
155
- const doubleEncrypted = doubleEncrypt(testData, userKey, systemKey);
156
-
157
- // Both should be readable with both keys provided
158
- const singleDecrypted = doubleDecrypt(singleEncrypted, userKey, systemKey);
159
- const doubleDecrypted = doubleDecrypt(doubleEncrypted, userKey, systemKey);
160
-
161
- t.is(singleDecrypted, testData, 'Single-encrypted data should be readable with both keys');
162
- t.is(doubleDecrypted, testData, 'Double-encrypted data should be readable with both keys');
163
- });
@@ -0,0 +1,262 @@
1
+ // doubleEncryptionStorageClient.test.js
2
+ // Tests for context key encryption integration in keyValueStorageClient
3
+
4
+ import test from 'ava';
5
+ import { setvWithDoubleEncryption, getvWithDoubleDecryption, setv, getv } from '../../../lib/keyValueStorageClient.js';
6
+ import { encrypt, decrypt } from '../../../lib/crypto.js';
7
+
8
+ // Test data
9
+ const testData = { message: 'Hello, this is test data!', number: 42, array: [1, 2, 3] };
10
+ const systemKey = '1234567890123456789012345678901234567890123456789012345678901234'; // 64 hex chars
11
+ const userKey = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'; // 64 hex chars
12
+
13
+ // Mock the config to provide test keys
14
+ import { config } from '../../../config.js';
15
+ const originalGet = config.get;
16
+ const mockGet = (key) => {
17
+ switch (key) {
18
+ case 'storageConnectionString':
19
+ return 'redis://localhost:6379'; // Use in-memory Redis for tests
20
+ case 'cortexId':
21
+ return 'test-cortex';
22
+ case 'redisEncryptionKey':
23
+ return systemKey;
24
+ default:
25
+ return originalGet(key);
26
+ }
27
+ };
28
+ config.get = mockGet;
29
+
30
+ // Helper function to clear storage between tests
31
+ async function clearStorage() {
32
+ // Clear any existing test data
33
+ try {
34
+ await setvWithDoubleEncryption('test-key', null, userKey);
35
+ await setv('test-key', null);
36
+ } catch (error) {
37
+ // Ignore errors when clearing
38
+ }
39
+ }
40
+
41
+ test.beforeEach(async t => {
42
+ await clearStorage();
43
+ });
44
+
45
+ // Test 1: Double encryption/decryption with both contextKey and redisEncryptionKey
46
+ test('should store and retrieve data with double encryption when both keys provided', async t => {
47
+ const key = 'test-double-encryption';
48
+
49
+ // Store with double encryption
50
+ await setvWithDoubleEncryption(key, testData, userKey);
51
+
52
+ // Retrieve with double decryption
53
+ const retrieved = await getvWithDoubleDecryption(key, userKey);
54
+
55
+ t.deepEqual(retrieved, testData);
56
+ });
57
+
58
+ // Test 2: Double encryption/decryption with only redisEncryptionKey (no contextKey)
59
+ test('should store and retrieve data with single encryption when no contextKey provided', async t => {
60
+ const key = 'test-single-encryption';
61
+
62
+ // Store with single encryption (no contextKey)
63
+ await setvWithDoubleEncryption(key, testData, null);
64
+
65
+ // Retrieve with single decryption
66
+ const retrieved = await getvWithDoubleDecryption(key, null);
67
+
68
+ t.deepEqual(retrieved, testData);
69
+ });
70
+
71
+ // Test 3: Reading single-encrypted data (from keyValueStorageClient) with doubleDecryption
72
+ test('should read single-encrypted data from keyValueStorageClient with doubleDecryption', async t => {
73
+ const key = 'test-single-to-double';
74
+
75
+ // Store using keyValueStorageClient (single encryption)
76
+ await setv(key, testData);
77
+
78
+ // Read using doubleEncryptionStorageClient (should handle single-encrypted data)
79
+ const retrieved = await getvWithDoubleDecryption(key, userKey);
80
+
81
+ t.deepEqual(retrieved, testData);
82
+ });
83
+
84
+ // Test 4: Reading single-encrypted data without contextKey
85
+ test('should read single-encrypted data from keyValueStorageClient without contextKey', async t => {
86
+ const key = 'test-single-to-double-no-context';
87
+
88
+ // Store using keyValueStorageClient (single encryption)
89
+ await setv(key, testData);
90
+
91
+ // Read using doubleEncryptionStorageClient without contextKey
92
+ const retrieved = await getvWithDoubleDecryption(key, null);
93
+
94
+ t.deepEqual(retrieved, testData);
95
+ });
96
+
97
+ // Test 5: Reading unencrypted data with doubleDecryption
98
+ test('should read unencrypted data with doubleDecryption', async t => {
99
+ const key = 'test-unencrypted';
100
+
101
+ // Store unencrypted data using keyValueStorageClient with no encryption key
102
+ const originalRedisKey = config.get('redisEncryptionKey');
103
+ config.get = (key) => {
104
+ switch (key) {
105
+ case 'storageConnectionString':
106
+ return 'redis://localhost:6379';
107
+ case 'cortexId':
108
+ return 'test-cortex';
109
+ case 'redisEncryptionKey':
110
+ return null; // No encryption
111
+ default:
112
+ return originalGet(key);
113
+ }
114
+ };
115
+
116
+ // Store unencrypted data
117
+ await setv(key, testData);
118
+
119
+ // Restore mock config
120
+ config.get = mockGet;
121
+
122
+ // Read using doubleEncryptionStorageClient (should handle unencrypted data)
123
+ const retrieved = await getvWithDoubleDecryption(key, userKey);
124
+
125
+ t.deepEqual(retrieved, testData);
126
+ });
127
+
128
+ // Test 6: Reading unencrypted data without contextKey
129
+ test('should read unencrypted data without contextKey', async t => {
130
+ const key = 'test-unencrypted-no-context';
131
+
132
+ // Store unencrypted data using keyValueStorageClient with no encryption key
133
+ const originalRedisKey = config.get('redisEncryptionKey');
134
+ config.get = (key) => {
135
+ switch (key) {
136
+ case 'storageConnectionString':
137
+ return 'redis://localhost:6379';
138
+ case 'cortexId':
139
+ return 'test-cortex';
140
+ case 'redisEncryptionKey':
141
+ return null; // No encryption
142
+ default:
143
+ return originalGet(key);
144
+ }
145
+ };
146
+
147
+ // Store unencrypted data
148
+ await setv(key, testData);
149
+
150
+ // Restore mock config
151
+ config.get = mockGet;
152
+
153
+ // Read using doubleEncryptionStorageClient without contextKey
154
+ const retrieved = await getvWithDoubleDecryption(key, null);
155
+
156
+ t.deepEqual(retrieved, testData);
157
+ });
158
+
159
+ // Test 7: Mixed data types - some double-encrypted, some single-encrypted
160
+ test('should handle mixed encryption states in storage', async t => {
161
+ const doubleKey = 'test-mixed-double';
162
+ const singleKey = 'test-mixed-single';
163
+
164
+ // Store with different encryption methods
165
+ await setvWithDoubleEncryption(doubleKey, testData, userKey); // Double encrypted
166
+ await setv(singleKey, testData); // Single encrypted
167
+
168
+ // Both should be readable with doubleDecryption
169
+ const doubleRetrieved = await getvWithDoubleDecryption(doubleKey, userKey);
170
+ const singleRetrieved = await getvWithDoubleDecryption(singleKey, userKey);
171
+
172
+ t.deepEqual(doubleRetrieved, testData, 'Double-encrypted data should be readable');
173
+ t.deepEqual(singleRetrieved, testData, 'Single-encrypted data should be readable');
174
+ });
175
+
176
+ // Test 8: Edge case - null/undefined data handling
177
+ test('should handle null and undefined data gracefully', async t => {
178
+ const key1 = 'test-null-data';
179
+ const key2 = 'test-undefined-data';
180
+
181
+ // Store null data
182
+ await setvWithDoubleEncryption(key1, null, userKey);
183
+ const retrieved1 = await getvWithDoubleDecryption(key1, userKey);
184
+ t.is(retrieved1, null);
185
+
186
+ // Store undefined data
187
+ await setvWithDoubleEncryption(key2, undefined, userKey);
188
+ const retrieved2 = await getvWithDoubleDecryption(key2, userKey);
189
+ t.is(retrieved2, undefined);
190
+ });
191
+
192
+ // Test 9: Edge case - empty object handling
193
+ test('should handle empty objects', async t => {
194
+ const key = 'test-empty-object';
195
+ const emptyData = {};
196
+
197
+ await setvWithDoubleEncryption(key, emptyData, userKey);
198
+ const retrieved = await getvWithDoubleDecryption(key, userKey);
199
+
200
+ t.deepEqual(retrieved, emptyData);
201
+ });
202
+
203
+ // Test 10: Context key changes between operations
204
+ test('should handle context key changes between operations', async t => {
205
+ const key1 = 'test-context-change-1';
206
+ const key2 = 'test-context-change-2';
207
+ const newUserKey = '1111111111111111111111111111111111111111111111111111111111111111';
208
+
209
+ // Store with first context key
210
+ await setvWithDoubleEncryption(key1, testData, userKey);
211
+
212
+ // Retrieve with same context key
213
+ const retrieved1 = await getvWithDoubleDecryption(key1, userKey);
214
+ t.deepEqual(retrieved1, testData);
215
+
216
+ // Store with different context key (different key to avoid conflicts)
217
+ await setvWithDoubleEncryption(key2, testData, newUserKey);
218
+
219
+ // Retrieve with new context key
220
+ const retrieved2 = await getvWithDoubleDecryption(key2, newUserKey);
221
+ t.deepEqual(retrieved2, testData);
222
+
223
+ // Verify that data encrypted with one key cannot be decrypted with another
224
+ const wrongKeyRetrieved = await getvWithDoubleDecryption(key1, newUserKey);
225
+ t.notDeepEqual(wrongKeyRetrieved, testData, 'Data encrypted with one key should not be readable with different key');
226
+ });
227
+
228
+ // Test 11: Large data handling
229
+ test('should handle large data objects', async t => {
230
+ const key = 'test-large-data';
231
+ const largeData = {
232
+ message: 'Large data test',
233
+ array: Array(1000).fill(0).map((_, i) => i),
234
+ nested: {
235
+ level1: {
236
+ level2: {
237
+ level3: Array(100).fill('test')
238
+ }
239
+ }
240
+ }
241
+ };
242
+
243
+ await setvWithDoubleEncryption(key, largeData, userKey);
244
+ const retrieved = await getvWithDoubleDecryption(key, userKey);
245
+
246
+ t.deepEqual(retrieved, largeData);
247
+ });
248
+
249
+ // Test 12: Special characters and unicode
250
+ test('should handle special characters and unicode', async t => {
251
+ const key = 'test-special-chars';
252
+ const specialData = {
253
+ message: 'Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?',
254
+ unicode: 'Unicode: πŸš€ 🌟 ñÑéíóú δΈ­ζ–‡ Ψ§Ω„ΨΉΨ±Ψ¨ΩŠΨ©',
255
+ emoji: 'πŸ˜€πŸ˜πŸ˜‚πŸ€£πŸ˜ƒπŸ˜„πŸ˜…πŸ˜†πŸ˜‰πŸ˜Š'
256
+ };
257
+
258
+ await setvWithDoubleEncryption(key, specialData, userKey);
259
+ const retrieved = await getvWithDoubleDecryption(key, userKey);
260
+
261
+ t.deepEqual(retrieved, specialData);
262
+ });
@@ -1,97 +0,0 @@
1
- import Keyv from 'keyv';
2
- import { config } from '../config.js';
3
- import { encrypt, decrypt, doubleEncrypt, doubleDecrypt } from './crypto.js';
4
- import logger from './logger.js';
5
-
6
- const storageConnectionString = config.get('storageConnectionString');
7
- const cortexId = config.get('cortexId');
8
- const redisEncryptionKey = config.get('redisEncryptionKey');
9
-
10
- // Create a keyv client to store data with double encryption support
11
- const doubleEncryptionStorageClient = new Keyv(storageConnectionString, {
12
- ssl: true,
13
- abortConnect: false,
14
- serialize: (data) => JSON.stringify(data),
15
- deserialize: (data) => {
16
- try {
17
- return JSON.parse(data);
18
- } catch (error) {
19
- logger.error(`Failed to parse stored data: ${error}`);
20
- return {};
21
- }
22
- },
23
- namespace: `${cortexId}-cortex-context`
24
- });
25
-
26
- // Handle Redis connection errors to prevent crashes
27
- doubleEncryptionStorageClient.on('error', (error) => {
28
- logger.error(`Keyv Redis connection error: ${error}`);
29
- });
30
-
31
- // Set values with double encryption support
32
- async function setvWithDoubleEncryption(key, value, userContextKey) {
33
- if (!doubleEncryptionStorageClient) return false;
34
-
35
- // Validate that system key is present
36
- if (!redisEncryptionKey) {
37
- logger.error('System encryption key is required but not configured');
38
- return false;
39
- }
40
-
41
- try {
42
- // Always use doubleEncrypt which handles both scenarios:
43
- // 1. systemKey only (when userContextKey is null/undefined)
44
- // 2. userContextKey + systemKey (when userContextKey is provided)
45
- const encryptedValue = doubleEncrypt(JSON.stringify(value), userContextKey, redisEncryptionKey);
46
-
47
- if (!encryptedValue) {
48
- logger.error('Encryption failed, cannot store data');
49
- return false;
50
- }
51
-
52
- return await doubleEncryptionStorageClient.set(key, encryptedValue);
53
- } catch (error) {
54
- logger.error(`Failed to store data with double encryption: ${error}`);
55
- return false;
56
- }
57
- }
58
-
59
- // Get values with double decryption support
60
- async function getvWithDoubleDecryption(key, userContextKey) {
61
- if (!doubleEncryptionStorageClient) return null;
62
-
63
- // Validate that system key is present
64
- if (!redisEncryptionKey) {
65
- logger.error('System encryption key is required but not configured');
66
- return null;
67
- }
68
-
69
- try {
70
- const encryptedData = await doubleEncryptionStorageClient.get(key);
71
- if (!encryptedData) return null;
72
-
73
- // Always use doubleDecrypt which handles both scenarios:
74
- // 1. systemKey only (when userContextKey is null/undefined)
75
- // 2. userContextKey + systemKey (when userContextKey is provided)
76
- const decryptedData = doubleDecrypt(encryptedData, userContextKey, redisEncryptionKey);
77
- if (decryptedData) {
78
- return JSON.parse(decryptedData);
79
- }
80
-
81
- // If decryption fails, try to parse as plain JSON (for backward compatibility with unencrypted data)
82
- try {
83
- return JSON.parse(encryptedData);
84
- } catch {
85
- return encryptedData;
86
- }
87
- } catch (error) {
88
- logger.error(`Failed to retrieve and decrypt data: ${error}`);
89
- return null;
90
- }
91
- }
92
-
93
- export {
94
- doubleEncryptionStorageClient,
95
- setvWithDoubleEncryption,
96
- getvWithDoubleDecryption
97
- };