@anomira/node-sdk 0.1.3 → 0.1.5

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
@@ -199,18 +199,124 @@ async function checkGeoVelocity(userId, ip, tsMs, lookupUrl) {
199
199
 
200
200
  // src/sensitive.ts
201
201
  var PATTERNS = [
202
+ // ── Cryptographic keys and certificates ───────────────────────────────────
202
203
  {
203
- // "password: abc123", "pwd=secret", or standalone "password123"
204
- type: "password",
205
- label: "Password / Secret",
206
- regex: /\bpassword\w*|\b(?:passwd|pwd|pass|secret|credentials?)\s*[:=]\s*\S+/i
204
+ type: "private_key",
205
+ label: "Private Key",
206
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/
207
207
  },
208
208
  {
209
- // Nigerian BVN / NIN — exactly 11 digits, not a phone number (phones start with 0)
210
- type: "bvn",
211
- label: "BVN / NIN",
212
- regex: /\b[1-9]\d{10}\b/
209
+ type: "certificate",
210
+ label: "Certificate / Public Key",
211
+ regex: /-----BEGIN CERTIFICATE-----/
212
+ },
213
+ // ── Cloud provider credentials ────────────────────────────────────────────
214
+ {
215
+ // AWS access key ID — highly specific, almost no false positives
216
+ type: "aws_key",
217
+ label: "AWS Access Key",
218
+ regex: /\bAKIA[0-9A-Z]{16}\b/
219
+ },
220
+ {
221
+ // AWS secret access key — 40-char base64 string after keyword
222
+ type: "aws_secret",
223
+ label: "AWS Secret Key",
224
+ regex: /\b(?:aws[_-]?secret|AWS_SECRET_ACCESS_KEY)\s*[:=]\s*[A-Za-z0-9+/]{40}\b/i
225
+ },
226
+ {
227
+ // Google API key
228
+ type: "google_key",
229
+ label: "Google API Key",
230
+ regex: /\bAIza[0-9A-Za-z\-_]{35}\b/
231
+ },
232
+ {
233
+ // Google OAuth client secret
234
+ type: "google_oauth",
235
+ label: "Google OAuth Secret",
236
+ regex: /\bGOCSP[A-Za-z0-9\-_]{28}\b/
237
+ },
238
+ {
239
+ // Firebase server key
240
+ type: "firebase_key",
241
+ label: "Firebase Server Key",
242
+ regex: /\bAAAA[A-Za-z0-9_-]{7}:[A-Za-z0-9_-]{140}\b/
213
243
  },
244
+ {
245
+ // Azure storage/connection string
246
+ type: "azure_key",
247
+ label: "Azure Key",
248
+ regex: /\bDefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]{88}/
249
+ },
250
+ // ── Source control & CI tokens ────────────────────────────────────────────
251
+ {
252
+ // GitHub personal access tokens (classic and fine-grained)
253
+ type: "github_token",
254
+ label: "GitHub Token",
255
+ regex: /\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{36,255}\b/
256
+ },
257
+ {
258
+ // GitLab personal/project/group tokens
259
+ type: "gitlab_token",
260
+ label: "GitLab Token",
261
+ regex: /\bglpat-[A-Za-z0-9\-_]{20}\b/
262
+ },
263
+ {
264
+ // NPM access tokens
265
+ type: "npm_token",
266
+ label: "NPM Token",
267
+ regex: /\bnpm_[A-Za-z0-9]{36}\b/
268
+ },
269
+ // ── Payment providers ─────────────────────────────────────────────────────
270
+ {
271
+ // Stripe — secret, restricted, webhook keys
272
+ type: "stripe_key",
273
+ label: "Stripe Key",
274
+ regex: /\b(?:sk|rk|whsec)_(?:live|test)_[A-Za-z0-9]{24,}\b/
275
+ },
276
+ {
277
+ // Paystack secret/public keys
278
+ type: "paystack_key",
279
+ label: "Paystack Key",
280
+ regex: /\b(?:sk|pk)_(?:live|test)_[A-Za-z0-9]{40,}\b/
281
+ },
282
+ {
283
+ // Card PANs: Visa (4), Mastercard (51-55), Amex (34/37), Discover (6011/65)
284
+ type: "card_pan",
285
+ label: "Card PAN",
286
+ regex: /\b(?:4[0-9]{15}|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b/
287
+ },
288
+ // ── Communication & messaging ─────────────────────────────────────────────
289
+ {
290
+ // Slack bot/user/app tokens
291
+ type: "slack_token",
292
+ label: "Slack Token",
293
+ regex: /\bxox[baprs]-[0-9A-Za-z]{10,48}\b/
294
+ },
295
+ {
296
+ // Slack webhook URL
297
+ type: "slack_webhook",
298
+ label: "Slack Webhook URL",
299
+ regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+/
300
+ },
301
+ {
302
+ // Twilio account SID and auth token
303
+ type: "twilio",
304
+ label: "Twilio Credential",
305
+ regex: /\bAC[a-z0-9]{32}\b|\bSK[a-z0-9]{32}\b/
306
+ },
307
+ {
308
+ // SendGrid / Brevo / Mailgun API keys
309
+ type: "email_provider_key",
310
+ label: "Email Provider API Key",
311
+ regex: /\bSG\.[A-Za-z0-9._-]{66}\b|\bkey-[0-9a-zA-Z]{32}\b/
312
+ },
313
+ // ── Database connection strings with embedded credentials ─────────────────
314
+ {
315
+ type: "db_connection",
316
+ label: "Database Connection String",
317
+ regex: /(?:postgresql|postgres|mysql|mongodb(?:\+srv)?|redis|amqp(?:s)?):\/\/[^:]+:[^@\s]{3,}@/i
318
+ },
319
+ // ── Auth tokens ───────────────────────────────────────────────────────────
214
320
  {
215
321
  // JWT — three base64url segments separated by dots
216
322
  type: "jwt",
@@ -218,36 +324,49 @@ var PATTERNS = [
218
324
  regex: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/
219
325
  },
220
326
  {
221
- // API keys: sk_live_*, pk_live_*, or key/token/bearer label with long value
327
+ // Generic API key / token assigned to a keyword (must have 20+ char value)
222
328
  type: "api_key",
223
329
  label: "API Key / Token",
224
- regex: /\b(?:sk|pk|rk)[-_](?:live|test|prod|secret)[-_][A-Za-z0-9]{16,}|\b(?:token|bearer|access_token|api_key)\s*[:=]\s*[A-Za-z0-9_./-]{20,}/i
330
+ regex: /\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|bearer|client[_-]?secret)\s*[:=]\s*["']?[A-Za-z0-9_.\/+\-]{20,}["']?/i
225
331
  },
332
+ // ── Password fields ───────────────────────────────────────────────────────
226
333
  {
227
- // Card PANs: Visa (4), Mastercard (51-55), Amex (34/37), Discover (6011/65)
228
- type: "card_pan",
229
- label: "Card PAN",
230
- regex: /\b(?:4[0-9]{15}|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b/
334
+ // Password/secret keyword immediately followed by a non-placeholder value
335
+ // Avoids false positives on "password: ${PASSWORD}" or "password: xxxx"
336
+ type: "password",
337
+ label: "Password / Secret",
338
+ regex: /\b(?:password|passwd|pwd|pass|secret|credentials?)\s*[:=]\s*["']?(?!\$\{)[^\s"'$]{6,}["']?/i
339
+ },
340
+ // ── Nigeria-specific PII ──────────────────────────────────────────────────
341
+ {
342
+ // BVN / NIN: 11 digits, first digit 1-9 (not a phone starting with 0)
343
+ // Require a non-digit boundary on both sides to reduce false positives
344
+ type: "bvn",
345
+ label: "BVN / NIN",
346
+ regex: /(?<!\d)[1-9]\d{10}(?!\d)/
231
347
  },
232
348
  {
233
349
  // Nigerian phone numbers: 080x, 081x, 070x, 090x, 091x — or with +234 prefix
234
350
  type: "ng_phone",
235
- label: "Phone Number",
351
+ label: "Nigerian Phone Number",
236
352
  regex: /\b(?:\+?234|0)(?:7[0-9]|8[0-1]|9[0-1])\d{8}\b/
237
353
  },
354
+ // ── PII in credential context ─────────────────────────────────────────────
238
355
  {
239
- // Email address appearing alongside other content possible credential combo
240
- // e.g. "john@test.com password123"
356
+ // Email only flagged when adjacent to a password/credential keyword
357
+ // Prevents false positives on normal email references in code
241
358
  type: "email_credential",
242
- label: "Email in Log",
243
- regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/
359
+ label: "Email + Password Combo",
360
+ regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\s+(?:password|passwd|pwd|secret|pass)\s*[:=]?\s*\S+/i
244
361
  }
245
362
  ];
246
363
  function scanForLeaks(message) {
364
+ const seen = /* @__PURE__ */ new Set();
247
365
  const found = [];
248
366
  for (const p of PATTERNS) {
249
367
  p.regex.lastIndex = 0;
250
- if (p.regex.test(message)) {
368
+ if (p.regex.test(message) && !seen.has(p.type)) {
369
+ seen.add(p.type);
251
370
  found.push({ type: p.type, label: p.label });
252
371
  }
253
372
  }
@@ -297,6 +416,8 @@ var SentinelClient = class {
297
416
  this.logFlushTimer = null;
298
417
  this.blocklistTimer = null;
299
418
  this.firewallTimer = null;
419
+ /** True when credentials are missing — all operations become no-ops. */
420
+ this.disabled = false;
300
421
  /** In-process cache of blocked IPs — refreshed every 60 s from the ingest server. */
301
422
  this.blockedIpCache = /* @__PURE__ */ new Set();
302
423
  /** In-process cache of firewall rules with pre-compiled regex — refreshed every 60 s. */
@@ -305,8 +426,28 @@ var SentinelClient = class {
305
426
  this._origLog = console.log.bind(console);
306
427
  this._origWarn = console.warn.bind(console);
307
428
  this._origError = console.error.bind(console);
308
- if (!config.apiKey) throw new Error("[SentinelAPI] apiKey is required");
309
- if (!config.appId) throw new Error("[SentinelAPI] appId is required");
429
+ if (!config.apiKey || !config.appId) {
430
+ const missing = [!config.apiKey && "apiKey", !config.appId && "appId"].filter(Boolean).join(", ");
431
+ console.warn(`[Anomira] SDK disabled \u2014 missing config: ${missing}. Set SENTINEL_API_KEY and SENTINEL_APP_ID to enable monitoring.`);
432
+ this.disabled = true;
433
+ this.config = {
434
+ apiKey: "",
435
+ appId: "",
436
+ ingestUrl: DEFAULT_INGEST_URL,
437
+ geoLookupUrl: "",
438
+ maxBatchSize: DEFAULT_BATCH_SIZE,
439
+ flushIntervalMs: DEFAULT_FLUSH_MS,
440
+ maxRetries: DEFAULT_MAX_RETRIES,
441
+ debug: false,
442
+ captureConsole: false,
443
+ service: "app",
444
+ getUserId: defaultGetUserId,
445
+ getIp: defaultGetIp,
446
+ detect: { bruteForce: true, rateAbuse: true, pathTraversal: true, xss: true, scanDetection: true, geoVelocity: true }
447
+ };
448
+ this.buffer = new EventBuffer({ appId: "", apiKey: "", ingestUrl: DEFAULT_INGEST_URL, maxBatchSize: 0, flushIntervalMs: 999999999, maxRetries: 0, debug: false });
449
+ return;
450
+ }
310
451
  this.config = {
311
452
  apiKey: config.apiKey,
312
453
  appId: config.appId,
@@ -525,6 +666,7 @@ var SentinelClient = class {
525
666
  */
526
667
  /** Returns true if the IP is in the current blocked list. Synchronous — no network call. */
527
668
  isBlocked(ip) {
669
+ if (this.disabled) return false;
528
670
  return this.blockedIpCache.has(ip);
529
671
  }
530
672
  /** Evaluate firewall rules against a request. Returns the matched rule or null. Synchronous. */
@@ -532,6 +674,7 @@ var SentinelClient = class {
532
674
  return this.#matchFirewallRule(req);
533
675
  }
534
676
  track(eventName, data) {
677
+ if (this.disabled) return;
535
678
  const event = {
536
679
  name: eventName,
537
680
  ts: Date.now(),
@@ -554,6 +697,7 @@ var SentinelClient = class {
554
697
  * ```
555
698
  */
556
699
  async trackLogin(data) {
700
+ if (this.disabled) return;
557
701
  const tsMs = Date.now();
558
702
  this.track(EventName.LOGIN_SUCCESS, { ...data, meta: { ...data.meta } });
559
703
  if (!this.config.detect.geoVelocity) return;
@@ -593,6 +737,7 @@ var SentinelClient = class {
593
737
  * ```
594
738
  */
595
739
  trackPhoneAuth(data) {
740
+ if (this.disabled) return;
596
741
  this.track(EventName.PHONE_AUTH, {
597
742
  ip: data.ip,
598
743
  userId: data.userId,
@@ -609,6 +754,7 @@ var SentinelClient = class {
609
754
  * ```
610
755
  */
611
756
  log(level, message, meta) {
757
+ if (this.disabled) return;
612
758
  const { service, ...rest } = meta ?? {};
613
759
  const leaks = scanForLeaks(message);
614
760
  if (leaks.length > 0) {
@@ -630,6 +776,7 @@ var SentinelClient = class {
630
776
  * Useful before a graceful shutdown outside of the process lifecycle hooks.
631
777
  */
632
778
  async flush() {
779
+ if (this.disabled) return;
633
780
  await Promise.all([this.buffer.flush(), this.#flushLogs()]);
634
781
  }
635
782
  /**