@ik-firewall/core 2.4.0 → 2.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.
package/dist/index.cjs CHANGED
@@ -90,10 +90,10 @@ var Registry = class _Registry {
90
90
  /**
91
91
  * Loads instance configurations with Hook support
92
92
  */
93
- load() {
93
+ async load() {
94
94
  if (this.hooks?.loadState) {
95
95
  try {
96
- const hookData = this.hooks.loadState("ik_registry");
96
+ const hookData = await this.hooks.loadState("ik_registry");
97
97
  if (hookData) return JSON.parse(hookData);
98
98
  } catch (e) {
99
99
  console.warn("[IK_REGISTRY] Registry load via hook failed, falling back...");
@@ -114,10 +114,10 @@ var Registry = class _Registry {
114
114
  }
115
115
  return {};
116
116
  }
117
- save(data) {
117
+ async save(data) {
118
118
  const jsonStr = JSON.stringify(data, null, 2);
119
119
  if (this.hooks?.persistState) {
120
- this.hooks.persistState("ik_registry", jsonStr);
120
+ await this.hooks.persistState("ik_registry", jsonStr);
121
121
  }
122
122
  if (fs && this.registryPath) {
123
123
  try {
@@ -132,10 +132,10 @@ var Registry = class _Registry {
132
132
  }
133
133
  }
134
134
  }
135
- updateInstance(instanceId, config) {
136
- const all = this.load();
135
+ async updateInstance(instanceId, config) {
136
+ const all = await this.load();
137
137
  all[instanceId] = { ...all[instanceId] || {}, ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
138
- this.save(all);
138
+ await this.save(all);
139
139
  }
140
140
  getUsageFilePath() {
141
141
  return this.usagePath;
@@ -158,13 +158,25 @@ var Registry = class _Registry {
158
158
  } catch (e) {
159
159
  }
160
160
  }
161
- clearUsage() {
161
+ async clearUsage() {
162
162
  if (!fs || !this.usagePath) return;
163
163
  try {
164
164
  if (fs.existsSync(this.usagePath)) fs.unlinkSync(this.usagePath);
165
165
  } catch (e) {
166
166
  }
167
167
  }
168
+ async getLastFlushTime() {
169
+ if (this.hooks?.loadState) {
170
+ const val = await this.hooks.loadState("ik_last_flush");
171
+ if (val) return parseInt(val, 10);
172
+ }
173
+ return null;
174
+ }
175
+ async setLastFlushTime(time) {
176
+ if (this.hooks?.persistState) {
177
+ await this.hooks.persistState("ik_last_flush", time.toString());
178
+ }
179
+ }
168
180
  };
169
181
 
170
182
  // src/lib/hmac.ts
@@ -173,13 +185,22 @@ var IKHmac = class {
173
185
  constructor(secret) {
174
186
  this.secret = secret;
175
187
  }
188
+ sortObject(obj) {
189
+ if (obj === null || typeof obj !== "object") return obj;
190
+ if (Array.isArray(obj)) return obj.map((i) => this.sortObject(i));
191
+ const sorted = {};
192
+ Object.keys(obj).sort().forEach((key) => {
193
+ sorted[key] = this.sortObject(obj[key]);
194
+ });
195
+ return sorted;
196
+ }
176
197
  /**
177
198
  * Verifies that a response from the IK-Firewall server is authentic.
178
199
  */
179
200
  async verify(data) {
180
201
  if (!data || !data.signature) return false;
181
202
  const { signature, ...rest } = data;
182
- const payload = JSON.stringify(rest);
203
+ const payload = JSON.stringify(this.sortObject(rest));
183
204
  try {
184
205
  const crypto = typeof window === "undefined" ? (await import(
185
206
  /* webpackIgnore: true */
@@ -210,7 +231,6 @@ var ConfigManager = class {
210
231
  if (hooks) {
211
232
  this.registry.setHooks(hooks);
212
233
  }
213
- this.loadFromRegistry();
214
234
  }
215
235
  getRegistry() {
216
236
  return this.registry;
@@ -226,16 +246,16 @@ var ConfigManager = class {
226
246
  if (state) {
227
247
  const parsed = JSON.parse(state);
228
248
  const registry = this.registry;
229
- registry.save(parsed);
230
- this.loadFromRegistry();
249
+ await registry.save(parsed);
250
+ await this.loadFromRegistry();
231
251
  }
232
252
  } catch (e) {
233
253
  console.warn("[IK_CONFIG] Hydration from hooks failed:", e);
234
254
  }
235
255
  }
236
256
  }
237
- loadFromRegistry() {
238
- const instanceConfigs = this.registry.load();
257
+ async loadFromRegistry() {
258
+ const instanceConfigs = await this.registry.load();
239
259
  this.config.instanceConfigs = {
240
260
  ...this.config.instanceConfigs || {},
241
261
  ...instanceConfigs
@@ -270,14 +290,15 @@ var ConfigManager = class {
270
290
  const response = await fetch(`${endpoint}?licenseKey=${instanceConfig?.licenseKey || ""}&instanceId=${instanceId}&version=2.3.0`);
271
291
  if (response.ok) {
272
292
  const data = await response.json();
273
- if (this.config.hmacSecret) {
274
- const verifier = new IKHmac(this.config.hmacSecret);
275
- if (!verifier.verify(data)) {
276
- console.error("[IK_SYNC] \u{1F6A8} Signature verification failed! Remote config rejected.");
277
- return;
278
- }
293
+ const defaultSecret = "default-secret-change-in-production";
294
+ const hmacSecret = this.config.hmacSecret;
295
+ const verificationKey = hmacSecret && hmacSecret !== defaultSecret ? hmacSecret : defaultSecret;
296
+ const verifier = new IKHmac(verificationKey);
297
+ if (!await verifier.verify(data)) {
298
+ console.error("[IK_SYNC] \u{1F6A8} Signature verification failed! Remote config rejected.");
299
+ return;
279
300
  }
280
- this.setInstanceConfig(instanceId, {
301
+ await this.setInstanceConfig(instanceId, {
281
302
  billingStatus: data.billingStatus,
282
303
  isVerified: data.isVerified,
283
304
  pricing: data.pricing,
@@ -289,7 +310,7 @@ var ConfigManager = class {
289
310
  }
290
311
  } catch (error) {
291
312
  console.error(`[IK_CONFIG] Remote sync failed (${error.message}). Falling open (Fail-Open mode).`);
292
- this.setInstanceConfig(instanceId, {
313
+ await this.setInstanceConfig(instanceId, {
293
314
  isVerified: true,
294
315
  lastSyncDate: now.toISOString()
295
316
  });
@@ -321,13 +342,13 @@ var ConfigManager = class {
321
342
  * Updates the live blocked status for an instance (Kill-Switch).
322
343
  * This is used by UsageTracker when it receives a response from the central server.
323
344
  */
324
- setBlockedStatus(instanceId, blocked) {
345
+ async setBlockedStatus(instanceId, blocked) {
325
346
  if (blocked) {
326
347
  this.blockedInstances.add(instanceId);
327
- this.setInstanceConfig(instanceId, { billingStatus: "blocked" });
348
+ await this.setInstanceConfig(instanceId, { billingStatus: "blocked" });
328
349
  } else {
329
350
  this.blockedInstances.delete(instanceId);
330
- this.setInstanceConfig(instanceId, { billingStatus: "active" });
351
+ await this.setInstanceConfig(instanceId, { billingStatus: "active" });
331
352
  }
332
353
  }
333
354
  updateConfig(newConfig) {
@@ -361,14 +382,15 @@ var ConfigManager = class {
361
382
  });
362
383
  if (response.ok) {
363
384
  const data = await response.json();
364
- if (this.config.hmacSecret) {
365
- const verifier = new IKHmac(this.config.hmacSecret);
366
- if (!verifier.verify(data)) {
367
- console.error("[IK_REGISTRY] \u{1F6A8} Registration signature mismatch. Data rejected!");
368
- return false;
369
- }
385
+ const defaultSecret = "default-secret-change-in-production";
386
+ const hmacSecret = this.config.hmacSecret;
387
+ const verificationKey = hmacSecret && hmacSecret !== defaultSecret ? hmacSecret : defaultSecret;
388
+ const verifier = new IKHmac(verificationKey);
389
+ if (!await verifier.verify(data)) {
390
+ console.error("[IK_REGISTRY] \u{1F6A8} Registration signature mismatch. Data rejected!");
391
+ return false;
370
392
  }
371
- this.setInstanceConfig(instanceId, {
393
+ await this.setInstanceConfig(instanceId, {
372
394
  licenseKey: data.licenseKey,
373
395
  billingStatus: data.status.toLowerCase(),
374
396
  isVerified: true,
@@ -384,10 +406,10 @@ var ConfigManager = class {
384
406
  return false;
385
407
  }
386
408
  }
387
- ensureInstanceConfig(instanceId) {
388
- const all = this.registry.load();
409
+ async ensureInstanceConfig(instanceId) {
410
+ const all = await this.registry.load();
389
411
  if (!all[instanceId]) {
390
- this.registry.updateInstance(instanceId, {
412
+ await this.registry.updateInstance(instanceId, {
391
413
  balance: this.config.balance || 0.5,
392
414
  aggressiveness: this.config.aggressiveness || 0.5,
393
415
  isEnabled: true,
@@ -395,15 +417,15 @@ var ConfigManager = class {
395
417
  ownerEmail: this.config.ownerEmail
396
418
  // Inherit from global if not specific
397
419
  });
398
- this.loadFromRegistry();
420
+ await this.loadFromRegistry();
399
421
  }
400
422
  }
401
- getInstanceConfigs() {
423
+ async getInstanceConfigs() {
402
424
  return this.registry.load();
403
425
  }
404
- setInstanceConfig(instanceId, config) {
405
- this.registry.updateInstance(instanceId, config);
406
- this.loadFromRegistry();
426
+ async setInstanceConfig(instanceId, config) {
427
+ await this.registry.updateInstance(instanceId, config);
428
+ await this.loadFromRegistry();
407
429
  }
408
430
  };
409
431
 
@@ -1522,11 +1544,14 @@ var UsageTracker = class {
1522
1544
  }
1523
1545
  async checkFlushConditions(instanceId) {
1524
1546
  const now = Date.now();
1547
+ if (this.lastFlushTime === 0) {
1548
+ const persisted = await this.config.getRegistry().getLastFlushTime();
1549
+ if (persisted) this.lastFlushTime = persisted;
1550
+ else this.lastFlushTime = now;
1551
+ }
1525
1552
  const timeSinceFlush = now - this.lastFlushTime;
1526
1553
  if (this.aggregationMap.size >= this.BATCH_SIZE_THRESHOLD || timeSinceFlush >= this.TIME_THRESHOLD_MS) {
1527
- this.performAsyncFlush(instanceId).catch((err) => {
1528
- console.error("[IK_TRACKER] Background flush failed:", err);
1529
- });
1554
+ await this.performAsyncFlush(instanceId);
1530
1555
  }
1531
1556
  }
1532
1557
  async performAsyncFlush(instanceId) {
@@ -1535,7 +1560,8 @@ var UsageTracker = class {
1535
1560
  const reports = Array.from(this.aggregationMap.values());
1536
1561
  this.aggregationMap.clear();
1537
1562
  this.lastFlushTime = Date.now();
1538
- const licenseKey = this.config.getConfig().instanceConfigs?.[instanceId]?.licenseKey || this.config.get("licenseKey");
1563
+ const instanceConfigs = await this.config.getInstanceConfigs();
1564
+ const licenseKey = instanceConfigs[instanceId]?.licenseKey || this.config.get("licenseKey");
1539
1565
  const payload = {
1540
1566
  licenseKey: licenseKey || "TRIAL-KEY",
1541
1567
  instanceId,
@@ -1546,12 +1572,13 @@ var UsageTracker = class {
1546
1572
  await this.sendBatch(payload);
1547
1573
  } catch (error) {
1548
1574
  console.error("[IK_TRACKER] Batch send failed:", error.message);
1549
- const isAuthError = error.message?.includes("401");
1550
- if (!isAuthError) {
1575
+ const isAuthError = error.message?.includes("401") || error.message?.includes("403");
1576
+ if (isAuthError) {
1577
+ console.warn("[IK_TRACKER] \u{1F512} Unauthorized/Forbidden: Subscription invalid or blocked. Discarding batch.");
1578
+ this.config.setBlockedStatus(instanceId, true);
1579
+ } else {
1551
1580
  console.log("[IK_TRACKER] Queuing for retry...");
1552
1581
  this.handleFailure(payload);
1553
- } else {
1554
- console.warn("[IK_TRACKER] Unauthorized: Please check your IK_FIREWALL_SECRET and License Key. Discarding batch.");
1555
1582
  }
1556
1583
  } finally {
1557
1584
  this.flushInProgress = false;
@@ -1564,14 +1591,16 @@ var UsageTracker = class {
1564
1591
  if (process.env.IK_FIREWALL_DISABLED === "true" || process.env.NEXT_PUBLIC_IK_FIREWALL_DISABLED === "true") {
1565
1592
  return;
1566
1593
  }
1567
- if (hmacSecret) {
1568
- try {
1569
- const crypto = typeof window === "undefined" ? (await import("crypto")).default : null;
1570
- if (crypto && crypto.createHmac) {
1571
- payload.signature = crypto.createHmac("sha256", hmacSecret).update(JSON.stringify(payload)).digest("hex");
1572
- }
1573
- } catch (e) {
1594
+ const defaultSecret = "default-secret-change-in-production";
1595
+ const signingKey = hmacSecret && hmacSecret !== defaultSecret ? hmacSecret : payload.licenseKey || defaultSecret;
1596
+ try {
1597
+ const crypto = typeof window === "undefined" ? (await import("crypto")).default : null;
1598
+ if (crypto && crypto.createHmac) {
1599
+ const { signature: _sig, ...signablePayload } = payload;
1600
+ const sortedPayload = this.sortObject(signablePayload);
1601
+ payload.signature = crypto.createHmac("sha256", signingKey).update(JSON.stringify(sortedPayload)).digest("hex");
1574
1602
  }
1603
+ } catch (e) {
1575
1604
  }
1576
1605
  const response = await fetch(endpoint, {
1577
1606
  method: "POST",
@@ -1594,6 +1623,7 @@ var UsageTracker = class {
1594
1623
  }
1595
1624
  } catch (e) {
1596
1625
  }
1626
+ await this.config.getRegistry().setLastFlushTime(this.lastFlushTime);
1597
1627
  this.hooks?.onFlushSuccess?.(payload);
1598
1628
  }
1599
1629
  async handleFailure(payload) {
@@ -1609,6 +1639,15 @@ var UsageTracker = class {
1609
1639
  await this.hooks.persistState(`ik_retry_${payload.timestamp}`, JSON.stringify(payload));
1610
1640
  }
1611
1641
  }
1642
+ sortObject(obj) {
1643
+ if (obj === null || typeof obj !== "object") return obj;
1644
+ if (Array.isArray(obj)) return obj.map((i) => this.sortObject(i));
1645
+ const sorted = {};
1646
+ Object.keys(obj).sort().forEach((key) => {
1647
+ sorted[key] = this.sortObject(obj[key]);
1648
+ });
1649
+ return sorted;
1650
+ }
1612
1651
  getBuffer() {
1613
1652
  return Array.from(this.aggregationMap.values());
1614
1653
  }
@@ -1807,11 +1846,11 @@ var IKFirewallCore = class _IKFirewallCore {
1807
1846
  getSessionDNA() {
1808
1847
  return this.sessionDNA;
1809
1848
  }
1810
- getInstanceConfigs() {
1849
+ async getInstanceConfigs() {
1811
1850
  return this.configManager.getInstanceConfigs();
1812
1851
  }
1813
- setInstanceConfig(instanceId, config) {
1814
- this.configManager.setInstanceConfig(instanceId, config);
1852
+ async setInstanceConfig(instanceId, config) {
1853
+ await this.configManager.setInstanceConfig(instanceId, config);
1815
1854
  }
1816
1855
  /**
1817
1856
  * Discovers available providers by checking environment variables and local health.
@@ -1895,7 +1934,7 @@ var IKFirewallCore = class _IKFirewallCore {
1895
1934
  this.hydrated = true;
1896
1935
  }
1897
1936
  if (instanceId) {
1898
- this.configManager.ensureInstanceConfig(instanceId);
1937
+ await this.configManager.ensureInstanceConfig(instanceId);
1899
1938
  }
1900
1939
  const mergedConfig = this.configManager.getMergedConfig(instanceId);
1901
1940
  if (instanceId && mergedConfig?.billingStatus === "blocked") {
@@ -2030,7 +2069,7 @@ var IKFirewallCore = class _IKFirewallCore {
2030
2069
  }
2031
2070
  async analyzeAIAssisted(input, provider, personaName = "professional", instanceId, configOverride) {
2032
2071
  if (instanceId) {
2033
- this.configManager.ensureInstanceConfig(instanceId);
2072
+ await this.configManager.ensureInstanceConfig(instanceId);
2034
2073
  }
2035
2074
  const activeConfig = this.configManager.getMergedConfig(instanceId, configOverride);
2036
2075
  const locale = activeConfig?.locale || "en";
package/dist/index.d.cts CHANGED
@@ -340,8 +340,8 @@ declare class IKFirewallCore {
340
340
  getConfig(): IKConfig;
341
341
  getPlugins(): IKPluginInfo[];
342
342
  getSessionDNA(): number;
343
- getInstanceConfigs(): Record<string, Partial<IKConfig>>;
344
- setInstanceConfig(instanceId: string, config: Partial<IKConfig>): void;
343
+ getInstanceConfigs(): Promise<Record<string, Partial<IKConfig>>>;
344
+ setInstanceConfig(instanceId: string, config: Partial<IKConfig>): Promise<void>;
345
345
  /**
346
346
  * Discovers available providers by checking environment variables and local health.
347
347
  */
package/dist/index.d.ts CHANGED
@@ -340,8 +340,8 @@ declare class IKFirewallCore {
340
340
  getConfig(): IKConfig;
341
341
  getPlugins(): IKPluginInfo[];
342
342
  getSessionDNA(): number;
343
- getInstanceConfigs(): Record<string, Partial<IKConfig>>;
344
- setInstanceConfig(instanceId: string, config: Partial<IKConfig>): void;
343
+ getInstanceConfigs(): Promise<Record<string, Partial<IKConfig>>>;
344
+ setInstanceConfig(instanceId: string, config: Partial<IKConfig>): Promise<void>;
345
345
  /**
346
346
  * Discovers available providers by checking environment variables and local health.
347
347
  */
package/dist/index.js CHANGED
@@ -48,10 +48,10 @@ var Registry = class _Registry {
48
48
  /**
49
49
  * Loads instance configurations with Hook support
50
50
  */
51
- load() {
51
+ async load() {
52
52
  if (this.hooks?.loadState) {
53
53
  try {
54
- const hookData = this.hooks.loadState("ik_registry");
54
+ const hookData = await this.hooks.loadState("ik_registry");
55
55
  if (hookData) return JSON.parse(hookData);
56
56
  } catch (e) {
57
57
  console.warn("[IK_REGISTRY] Registry load via hook failed, falling back...");
@@ -72,10 +72,10 @@ var Registry = class _Registry {
72
72
  }
73
73
  return {};
74
74
  }
75
- save(data) {
75
+ async save(data) {
76
76
  const jsonStr = JSON.stringify(data, null, 2);
77
77
  if (this.hooks?.persistState) {
78
- this.hooks.persistState("ik_registry", jsonStr);
78
+ await this.hooks.persistState("ik_registry", jsonStr);
79
79
  }
80
80
  if (fs && this.registryPath) {
81
81
  try {
@@ -90,10 +90,10 @@ var Registry = class _Registry {
90
90
  }
91
91
  }
92
92
  }
93
- updateInstance(instanceId, config) {
94
- const all = this.load();
93
+ async updateInstance(instanceId, config) {
94
+ const all = await this.load();
95
95
  all[instanceId] = { ...all[instanceId] || {}, ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
96
- this.save(all);
96
+ await this.save(all);
97
97
  }
98
98
  getUsageFilePath() {
99
99
  return this.usagePath;
@@ -116,13 +116,25 @@ var Registry = class _Registry {
116
116
  } catch (e) {
117
117
  }
118
118
  }
119
- clearUsage() {
119
+ async clearUsage() {
120
120
  if (!fs || !this.usagePath) return;
121
121
  try {
122
122
  if (fs.existsSync(this.usagePath)) fs.unlinkSync(this.usagePath);
123
123
  } catch (e) {
124
124
  }
125
125
  }
126
+ async getLastFlushTime() {
127
+ if (this.hooks?.loadState) {
128
+ const val = await this.hooks.loadState("ik_last_flush");
129
+ if (val) return parseInt(val, 10);
130
+ }
131
+ return null;
132
+ }
133
+ async setLastFlushTime(time) {
134
+ if (this.hooks?.persistState) {
135
+ await this.hooks.persistState("ik_last_flush", time.toString());
136
+ }
137
+ }
126
138
  };
127
139
 
128
140
  // src/lib/hmac.ts
@@ -131,13 +143,22 @@ var IKHmac = class {
131
143
  constructor(secret) {
132
144
  this.secret = secret;
133
145
  }
146
+ sortObject(obj) {
147
+ if (obj === null || typeof obj !== "object") return obj;
148
+ if (Array.isArray(obj)) return obj.map((i) => this.sortObject(i));
149
+ const sorted = {};
150
+ Object.keys(obj).sort().forEach((key) => {
151
+ sorted[key] = this.sortObject(obj[key]);
152
+ });
153
+ return sorted;
154
+ }
134
155
  /**
135
156
  * Verifies that a response from the IK-Firewall server is authentic.
136
157
  */
137
158
  async verify(data) {
138
159
  if (!data || !data.signature) return false;
139
160
  const { signature, ...rest } = data;
140
- const payload = JSON.stringify(rest);
161
+ const payload = JSON.stringify(this.sortObject(rest));
141
162
  try {
142
163
  const crypto = typeof window === "undefined" ? (await import(
143
164
  /* webpackIgnore: true */
@@ -168,7 +189,6 @@ var ConfigManager = class {
168
189
  if (hooks) {
169
190
  this.registry.setHooks(hooks);
170
191
  }
171
- this.loadFromRegistry();
172
192
  }
173
193
  getRegistry() {
174
194
  return this.registry;
@@ -184,16 +204,16 @@ var ConfigManager = class {
184
204
  if (state) {
185
205
  const parsed = JSON.parse(state);
186
206
  const registry = this.registry;
187
- registry.save(parsed);
188
- this.loadFromRegistry();
207
+ await registry.save(parsed);
208
+ await this.loadFromRegistry();
189
209
  }
190
210
  } catch (e) {
191
211
  console.warn("[IK_CONFIG] Hydration from hooks failed:", e);
192
212
  }
193
213
  }
194
214
  }
195
- loadFromRegistry() {
196
- const instanceConfigs = this.registry.load();
215
+ async loadFromRegistry() {
216
+ const instanceConfigs = await this.registry.load();
197
217
  this.config.instanceConfigs = {
198
218
  ...this.config.instanceConfigs || {},
199
219
  ...instanceConfigs
@@ -228,14 +248,15 @@ var ConfigManager = class {
228
248
  const response = await fetch(`${endpoint}?licenseKey=${instanceConfig?.licenseKey || ""}&instanceId=${instanceId}&version=2.3.0`);
229
249
  if (response.ok) {
230
250
  const data = await response.json();
231
- if (this.config.hmacSecret) {
232
- const verifier = new IKHmac(this.config.hmacSecret);
233
- if (!verifier.verify(data)) {
234
- console.error("[IK_SYNC] \u{1F6A8} Signature verification failed! Remote config rejected.");
235
- return;
236
- }
251
+ const defaultSecret = "default-secret-change-in-production";
252
+ const hmacSecret = this.config.hmacSecret;
253
+ const verificationKey = hmacSecret && hmacSecret !== defaultSecret ? hmacSecret : defaultSecret;
254
+ const verifier = new IKHmac(verificationKey);
255
+ if (!await verifier.verify(data)) {
256
+ console.error("[IK_SYNC] \u{1F6A8} Signature verification failed! Remote config rejected.");
257
+ return;
237
258
  }
238
- this.setInstanceConfig(instanceId, {
259
+ await this.setInstanceConfig(instanceId, {
239
260
  billingStatus: data.billingStatus,
240
261
  isVerified: data.isVerified,
241
262
  pricing: data.pricing,
@@ -247,7 +268,7 @@ var ConfigManager = class {
247
268
  }
248
269
  } catch (error) {
249
270
  console.error(`[IK_CONFIG] Remote sync failed (${error.message}). Falling open (Fail-Open mode).`);
250
- this.setInstanceConfig(instanceId, {
271
+ await this.setInstanceConfig(instanceId, {
251
272
  isVerified: true,
252
273
  lastSyncDate: now.toISOString()
253
274
  });
@@ -279,13 +300,13 @@ var ConfigManager = class {
279
300
  * Updates the live blocked status for an instance (Kill-Switch).
280
301
  * This is used by UsageTracker when it receives a response from the central server.
281
302
  */
282
- setBlockedStatus(instanceId, blocked) {
303
+ async setBlockedStatus(instanceId, blocked) {
283
304
  if (blocked) {
284
305
  this.blockedInstances.add(instanceId);
285
- this.setInstanceConfig(instanceId, { billingStatus: "blocked" });
306
+ await this.setInstanceConfig(instanceId, { billingStatus: "blocked" });
286
307
  } else {
287
308
  this.blockedInstances.delete(instanceId);
288
- this.setInstanceConfig(instanceId, { billingStatus: "active" });
309
+ await this.setInstanceConfig(instanceId, { billingStatus: "active" });
289
310
  }
290
311
  }
291
312
  updateConfig(newConfig) {
@@ -319,14 +340,15 @@ var ConfigManager = class {
319
340
  });
320
341
  if (response.ok) {
321
342
  const data = await response.json();
322
- if (this.config.hmacSecret) {
323
- const verifier = new IKHmac(this.config.hmacSecret);
324
- if (!verifier.verify(data)) {
325
- console.error("[IK_REGISTRY] \u{1F6A8} Registration signature mismatch. Data rejected!");
326
- return false;
327
- }
343
+ const defaultSecret = "default-secret-change-in-production";
344
+ const hmacSecret = this.config.hmacSecret;
345
+ const verificationKey = hmacSecret && hmacSecret !== defaultSecret ? hmacSecret : defaultSecret;
346
+ const verifier = new IKHmac(verificationKey);
347
+ if (!await verifier.verify(data)) {
348
+ console.error("[IK_REGISTRY] \u{1F6A8} Registration signature mismatch. Data rejected!");
349
+ return false;
328
350
  }
329
- this.setInstanceConfig(instanceId, {
351
+ await this.setInstanceConfig(instanceId, {
330
352
  licenseKey: data.licenseKey,
331
353
  billingStatus: data.status.toLowerCase(),
332
354
  isVerified: true,
@@ -342,10 +364,10 @@ var ConfigManager = class {
342
364
  return false;
343
365
  }
344
366
  }
345
- ensureInstanceConfig(instanceId) {
346
- const all = this.registry.load();
367
+ async ensureInstanceConfig(instanceId) {
368
+ const all = await this.registry.load();
347
369
  if (!all[instanceId]) {
348
- this.registry.updateInstance(instanceId, {
370
+ await this.registry.updateInstance(instanceId, {
349
371
  balance: this.config.balance || 0.5,
350
372
  aggressiveness: this.config.aggressiveness || 0.5,
351
373
  isEnabled: true,
@@ -353,15 +375,15 @@ var ConfigManager = class {
353
375
  ownerEmail: this.config.ownerEmail
354
376
  // Inherit from global if not specific
355
377
  });
356
- this.loadFromRegistry();
378
+ await this.loadFromRegistry();
357
379
  }
358
380
  }
359
- getInstanceConfigs() {
381
+ async getInstanceConfigs() {
360
382
  return this.registry.load();
361
383
  }
362
- setInstanceConfig(instanceId, config) {
363
- this.registry.updateInstance(instanceId, config);
364
- this.loadFromRegistry();
384
+ async setInstanceConfig(instanceId, config) {
385
+ await this.registry.updateInstance(instanceId, config);
386
+ await this.loadFromRegistry();
365
387
  }
366
388
  };
367
389
 
@@ -1480,11 +1502,14 @@ var UsageTracker = class {
1480
1502
  }
1481
1503
  async checkFlushConditions(instanceId) {
1482
1504
  const now = Date.now();
1505
+ if (this.lastFlushTime === 0) {
1506
+ const persisted = await this.config.getRegistry().getLastFlushTime();
1507
+ if (persisted) this.lastFlushTime = persisted;
1508
+ else this.lastFlushTime = now;
1509
+ }
1483
1510
  const timeSinceFlush = now - this.lastFlushTime;
1484
1511
  if (this.aggregationMap.size >= this.BATCH_SIZE_THRESHOLD || timeSinceFlush >= this.TIME_THRESHOLD_MS) {
1485
- this.performAsyncFlush(instanceId).catch((err) => {
1486
- console.error("[IK_TRACKER] Background flush failed:", err);
1487
- });
1512
+ await this.performAsyncFlush(instanceId);
1488
1513
  }
1489
1514
  }
1490
1515
  async performAsyncFlush(instanceId) {
@@ -1493,7 +1518,8 @@ var UsageTracker = class {
1493
1518
  const reports = Array.from(this.aggregationMap.values());
1494
1519
  this.aggregationMap.clear();
1495
1520
  this.lastFlushTime = Date.now();
1496
- const licenseKey = this.config.getConfig().instanceConfigs?.[instanceId]?.licenseKey || this.config.get("licenseKey");
1521
+ const instanceConfigs = await this.config.getInstanceConfigs();
1522
+ const licenseKey = instanceConfigs[instanceId]?.licenseKey || this.config.get("licenseKey");
1497
1523
  const payload = {
1498
1524
  licenseKey: licenseKey || "TRIAL-KEY",
1499
1525
  instanceId,
@@ -1504,12 +1530,13 @@ var UsageTracker = class {
1504
1530
  await this.sendBatch(payload);
1505
1531
  } catch (error) {
1506
1532
  console.error("[IK_TRACKER] Batch send failed:", error.message);
1507
- const isAuthError = error.message?.includes("401");
1508
- if (!isAuthError) {
1533
+ const isAuthError = error.message?.includes("401") || error.message?.includes("403");
1534
+ if (isAuthError) {
1535
+ console.warn("[IK_TRACKER] \u{1F512} Unauthorized/Forbidden: Subscription invalid or blocked. Discarding batch.");
1536
+ this.config.setBlockedStatus(instanceId, true);
1537
+ } else {
1509
1538
  console.log("[IK_TRACKER] Queuing for retry...");
1510
1539
  this.handleFailure(payload);
1511
- } else {
1512
- console.warn("[IK_TRACKER] Unauthorized: Please check your IK_FIREWALL_SECRET and License Key. Discarding batch.");
1513
1540
  }
1514
1541
  } finally {
1515
1542
  this.flushInProgress = false;
@@ -1522,14 +1549,16 @@ var UsageTracker = class {
1522
1549
  if (process.env.IK_FIREWALL_DISABLED === "true" || process.env.NEXT_PUBLIC_IK_FIREWALL_DISABLED === "true") {
1523
1550
  return;
1524
1551
  }
1525
- if (hmacSecret) {
1526
- try {
1527
- const crypto = typeof window === "undefined" ? (await import("crypto")).default : null;
1528
- if (crypto && crypto.createHmac) {
1529
- payload.signature = crypto.createHmac("sha256", hmacSecret).update(JSON.stringify(payload)).digest("hex");
1530
- }
1531
- } catch (e) {
1552
+ const defaultSecret = "default-secret-change-in-production";
1553
+ const signingKey = hmacSecret && hmacSecret !== defaultSecret ? hmacSecret : payload.licenseKey || defaultSecret;
1554
+ try {
1555
+ const crypto = typeof window === "undefined" ? (await import("crypto")).default : null;
1556
+ if (crypto && crypto.createHmac) {
1557
+ const { signature: _sig, ...signablePayload } = payload;
1558
+ const sortedPayload = this.sortObject(signablePayload);
1559
+ payload.signature = crypto.createHmac("sha256", signingKey).update(JSON.stringify(sortedPayload)).digest("hex");
1532
1560
  }
1561
+ } catch (e) {
1533
1562
  }
1534
1563
  const response = await fetch(endpoint, {
1535
1564
  method: "POST",
@@ -1552,6 +1581,7 @@ var UsageTracker = class {
1552
1581
  }
1553
1582
  } catch (e) {
1554
1583
  }
1584
+ await this.config.getRegistry().setLastFlushTime(this.lastFlushTime);
1555
1585
  this.hooks?.onFlushSuccess?.(payload);
1556
1586
  }
1557
1587
  async handleFailure(payload) {
@@ -1567,6 +1597,15 @@ var UsageTracker = class {
1567
1597
  await this.hooks.persistState(`ik_retry_${payload.timestamp}`, JSON.stringify(payload));
1568
1598
  }
1569
1599
  }
1600
+ sortObject(obj) {
1601
+ if (obj === null || typeof obj !== "object") return obj;
1602
+ if (Array.isArray(obj)) return obj.map((i) => this.sortObject(i));
1603
+ const sorted = {};
1604
+ Object.keys(obj).sort().forEach((key) => {
1605
+ sorted[key] = this.sortObject(obj[key]);
1606
+ });
1607
+ return sorted;
1608
+ }
1570
1609
  getBuffer() {
1571
1610
  return Array.from(this.aggregationMap.values());
1572
1611
  }
@@ -1765,11 +1804,11 @@ var IKFirewallCore = class _IKFirewallCore {
1765
1804
  getSessionDNA() {
1766
1805
  return this.sessionDNA;
1767
1806
  }
1768
- getInstanceConfigs() {
1807
+ async getInstanceConfigs() {
1769
1808
  return this.configManager.getInstanceConfigs();
1770
1809
  }
1771
- setInstanceConfig(instanceId, config) {
1772
- this.configManager.setInstanceConfig(instanceId, config);
1810
+ async setInstanceConfig(instanceId, config) {
1811
+ await this.configManager.setInstanceConfig(instanceId, config);
1773
1812
  }
1774
1813
  /**
1775
1814
  * Discovers available providers by checking environment variables and local health.
@@ -1853,7 +1892,7 @@ var IKFirewallCore = class _IKFirewallCore {
1853
1892
  this.hydrated = true;
1854
1893
  }
1855
1894
  if (instanceId) {
1856
- this.configManager.ensureInstanceConfig(instanceId);
1895
+ await this.configManager.ensureInstanceConfig(instanceId);
1857
1896
  }
1858
1897
  const mergedConfig = this.configManager.getMergedConfig(instanceId);
1859
1898
  if (instanceId && mergedConfig?.billingStatus === "blocked") {
@@ -1988,7 +2027,7 @@ var IKFirewallCore = class _IKFirewallCore {
1988
2027
  }
1989
2028
  async analyzeAIAssisted(input, provider, personaName = "professional", instanceId, configOverride) {
1990
2029
  if (instanceId) {
1991
- this.configManager.ensureInstanceConfig(instanceId);
2030
+ await this.configManager.ensureInstanceConfig(instanceId);
1992
2031
  }
1993
2032
  const activeConfig = this.configManager.getMergedConfig(instanceId, configOverride);
1994
2033
  const locale = activeConfig?.locale || "en";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ik-firewall/core",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "type": "module",
5
5
  "description": "The core IK Firewall engine for semantic-driven AI optimization.",
6
6
  "main": "./dist/index.js",