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