@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.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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
|
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(?:
|
|
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
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
240
|
-
//
|
|
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
|
|
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
|
|
309
|
-
|
|
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
|
/**
|