@anomira/node-sdk 0.1.5 → 0.1.7
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 +28 -6
- package/dist/cli.cjs +54 -25
- package/dist/index.cjs +67 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -22
- package/dist/index.d.ts +45 -22
- package/dist/index.js +65 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from 'express';
|
|
2
2
|
import { FastifyPluginAsync } from 'fastify';
|
|
3
3
|
|
|
4
|
-
interface
|
|
5
|
-
/** SDK API key — get this from the
|
|
4
|
+
interface AnomiraConfig {
|
|
5
|
+
/** SDK API key — get this from the Anomira dashboard */
|
|
6
6
|
apiKey: string;
|
|
7
|
-
/** Your app ID from the
|
|
7
|
+
/** Your app ID from the Anomira dashboard */
|
|
8
8
|
appId: string;
|
|
9
9
|
/**
|
|
10
10
|
* Ingest endpoint URL.
|
|
@@ -13,7 +13,7 @@ interface SentinelConfig {
|
|
|
13
13
|
ingestUrl?: string;
|
|
14
14
|
/**
|
|
15
15
|
* Geo-lookup endpoint URL for client-side geo-velocity checks.
|
|
16
|
-
* Should point to your
|
|
16
|
+
* Should point to your Anomira ingest service geo endpoint.
|
|
17
17
|
* If not provided, client-side geo-velocity is skipped (server-side still runs).
|
|
18
18
|
* @example "https://ingest.anomira.io/v1/geo"
|
|
19
19
|
*/
|
|
@@ -40,7 +40,7 @@ interface SentinelConfig {
|
|
|
40
40
|
debug?: boolean;
|
|
41
41
|
/**
|
|
42
42
|
* If true, automatically intercept console.log/info/warn/error/debug
|
|
43
|
-
* and forward them to the
|
|
43
|
+
* and forward them to the Anomira Logs dashboard.
|
|
44
44
|
* Existing console output is preserved — logs still print to your terminal.
|
|
45
45
|
* @default false
|
|
46
46
|
*/
|
|
@@ -116,6 +116,14 @@ type EventNameValue = typeof EventName[keyof typeof EventName];
|
|
|
116
116
|
type FirewallField = "url" | "body" | "header" | "user_agent" | "ip";
|
|
117
117
|
type FirewallOperator = "contains" | "equals" | "starts_with" | "ends_with" | "regex";
|
|
118
118
|
type FirewallAction = "block" | "flag";
|
|
119
|
+
interface EndpointDeclaration {
|
|
120
|
+
/** HTTP method, e.g. "GET". Use "*" to match any method. */
|
|
121
|
+
method: string;
|
|
122
|
+
/** Express-style path, e.g. "/api/users/:id" — colons are normalized automatically. */
|
|
123
|
+
path: string;
|
|
124
|
+
/** Whether this endpoint requires authentication. Defaults to true. */
|
|
125
|
+
auth?: boolean;
|
|
126
|
+
}
|
|
119
127
|
interface FirewallRule {
|
|
120
128
|
id: string;
|
|
121
129
|
field: FirewallField;
|
|
@@ -127,25 +135,25 @@ interface FirewallRule {
|
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
/**
|
|
130
|
-
*
|
|
138
|
+
* AnomiraClient — the core SDK object.
|
|
131
139
|
*
|
|
132
140
|
* Instantiate once and reuse across your application:
|
|
133
141
|
*
|
|
134
142
|
* ```ts
|
|
135
|
-
* import {
|
|
143
|
+
* import { Anomira } from "@anomira/node-sdk";
|
|
136
144
|
*
|
|
137
|
-
* export const sentinel = new
|
|
145
|
+
* export const sentinel = new Anomira({
|
|
138
146
|
* apiKey: process.env.SENTINEL_API_KEY!,
|
|
139
147
|
* appId: process.env.SENTINEL_APP_ID!,
|
|
140
148
|
* });
|
|
141
149
|
* ```
|
|
142
150
|
*/
|
|
143
|
-
declare class
|
|
151
|
+
declare class AnomiraClient {
|
|
144
152
|
#private;
|
|
145
|
-
readonly config: Required<Omit<
|
|
146
|
-
getUserId: NonNullable<
|
|
147
|
-
getIp: NonNullable<
|
|
148
|
-
detect: Required<NonNullable<
|
|
153
|
+
readonly config: Required<Omit<AnomiraConfig, "getUserId" | "getIp" | "detect">> & {
|
|
154
|
+
getUserId: NonNullable<AnomiraConfig["getUserId"]>;
|
|
155
|
+
getIp: NonNullable<AnomiraConfig["getIp"]>;
|
|
156
|
+
detect: Required<NonNullable<AnomiraConfig["detect"]>>;
|
|
149
157
|
captureConsole: boolean;
|
|
150
158
|
service: string;
|
|
151
159
|
};
|
|
@@ -163,7 +171,7 @@ declare class SentinelClient {
|
|
|
163
171
|
private readonly _origLog;
|
|
164
172
|
private readonly _origWarn;
|
|
165
173
|
private readonly _origError;
|
|
166
|
-
constructor(config:
|
|
174
|
+
constructor(config: AnomiraConfig);
|
|
167
175
|
/**
|
|
168
176
|
* Track a custom security event.
|
|
169
177
|
*
|
|
@@ -230,7 +238,7 @@ declare class SentinelClient {
|
|
|
230
238
|
meta?: Record<string, unknown>;
|
|
231
239
|
}): void;
|
|
232
240
|
/**
|
|
233
|
-
* Send a structured log entry to the
|
|
241
|
+
* Send a structured log entry to the Anomira Logs dashboard.
|
|
234
242
|
*
|
|
235
243
|
* ```ts
|
|
236
244
|
* sentinel.log("info", "User registered", { userId: user.id });
|
|
@@ -241,6 +249,21 @@ declare class SentinelClient {
|
|
|
241
249
|
log(level: "debug" | "info" | "warn" | "error" | "fatal", message: string, meta?: Record<string, unknown> & {
|
|
242
250
|
service?: string;
|
|
243
251
|
}): void;
|
|
252
|
+
/**
|
|
253
|
+
* Declare your API's known endpoints so Anomira can flag undiscovered
|
|
254
|
+
* traffic as shadow endpoints.
|
|
255
|
+
*
|
|
256
|
+
* Call this once on startup after your routes are registered:
|
|
257
|
+
*
|
|
258
|
+
* ```ts
|
|
259
|
+
* await sentinel.declareEndpoints([
|
|
260
|
+
* { method: "GET", path: "/api/users/:id", auth: true },
|
|
261
|
+
* { method: "POST", path: "/api/orders", auth: true },
|
|
262
|
+
* { method: "GET", path: "/api/health", auth: false },
|
|
263
|
+
* ]);
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
declareEndpoints(endpoints: EndpointDeclaration[]): Promise<void>;
|
|
244
267
|
/**
|
|
245
268
|
* Flush all pending events immediately.
|
|
246
269
|
* Useful before a graceful shutdown outside of the process lifecycle hooks.
|
|
@@ -278,10 +301,10 @@ declare class SentinelClient {
|
|
|
278
301
|
*
|
|
279
302
|
* ```ts
|
|
280
303
|
* import express from "express";
|
|
281
|
-
* import {
|
|
304
|
+
* import { Anomira } from "@sentinelapi/node-sdk";
|
|
282
305
|
*
|
|
283
306
|
* const app = express();
|
|
284
|
-
* const sentinel = new
|
|
307
|
+
* const sentinel = new Anomira({ apiKey: "...", appId: "..." });
|
|
285
308
|
*
|
|
286
309
|
* app.use(sentinel.express()); // auto-instrument all routes
|
|
287
310
|
*
|
|
@@ -290,7 +313,7 @@ declare class SentinelClient {
|
|
|
290
313
|
* app.use(createExpressMiddleware(sentinel));
|
|
291
314
|
* ```
|
|
292
315
|
*/
|
|
293
|
-
declare function createExpressMiddleware(client:
|
|
316
|
+
declare function createExpressMiddleware(client: AnomiraClient): (req: Request, res: Response, next: NextFunction) => void;
|
|
294
317
|
|
|
295
318
|
/**
|
|
296
319
|
* Typed Fastify plugin — exported separately for projects that want
|
|
@@ -304,14 +327,14 @@ declare function createExpressMiddleware(client: SentinelClient): (req: Request,
|
|
|
304
327
|
*
|
|
305
328
|
* ```ts
|
|
306
329
|
* import Fastify from "fastify";
|
|
307
|
-
* import {
|
|
330
|
+
* import { Anomira } from "@sentinelapi/node-sdk";
|
|
308
331
|
*
|
|
309
332
|
* const app = Fastify();
|
|
310
|
-
* const sentinel = new
|
|
333
|
+
* const sentinel = new Anomira({ apiKey: "...", appId: "..." });
|
|
311
334
|
*
|
|
312
335
|
* await app.register(sentinel.fastify());
|
|
313
336
|
* ```
|
|
314
337
|
*/
|
|
315
|
-
declare function createFastifyPlugin(client:
|
|
338
|
+
declare function createFastifyPlugin(client: AnomiraClient): FastifyPluginAsync;
|
|
316
339
|
|
|
317
|
-
export { EventName, type EventNameValue, type IngestPayload, type SdkEvent,
|
|
340
|
+
export { AnomiraClient as Anomira, type AnomiraConfig, type EndpointDeclaration, EventName, type EventNameValue, type IngestPayload, type SdkEvent, AnomiraClient as SentinelAPI, type AnomiraConfig as SentinelConfig, createExpressMiddleware, createFastifyPlugin, AnomiraClient as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from 'express';
|
|
2
2
|
import { FastifyPluginAsync } from 'fastify';
|
|
3
3
|
|
|
4
|
-
interface
|
|
5
|
-
/** SDK API key — get this from the
|
|
4
|
+
interface AnomiraConfig {
|
|
5
|
+
/** SDK API key — get this from the Anomira dashboard */
|
|
6
6
|
apiKey: string;
|
|
7
|
-
/** Your app ID from the
|
|
7
|
+
/** Your app ID from the Anomira dashboard */
|
|
8
8
|
appId: string;
|
|
9
9
|
/**
|
|
10
10
|
* Ingest endpoint URL.
|
|
@@ -13,7 +13,7 @@ interface SentinelConfig {
|
|
|
13
13
|
ingestUrl?: string;
|
|
14
14
|
/**
|
|
15
15
|
* Geo-lookup endpoint URL for client-side geo-velocity checks.
|
|
16
|
-
* Should point to your
|
|
16
|
+
* Should point to your Anomira ingest service geo endpoint.
|
|
17
17
|
* If not provided, client-side geo-velocity is skipped (server-side still runs).
|
|
18
18
|
* @example "https://ingest.anomira.io/v1/geo"
|
|
19
19
|
*/
|
|
@@ -40,7 +40,7 @@ interface SentinelConfig {
|
|
|
40
40
|
debug?: boolean;
|
|
41
41
|
/**
|
|
42
42
|
* If true, automatically intercept console.log/info/warn/error/debug
|
|
43
|
-
* and forward them to the
|
|
43
|
+
* and forward them to the Anomira Logs dashboard.
|
|
44
44
|
* Existing console output is preserved — logs still print to your terminal.
|
|
45
45
|
* @default false
|
|
46
46
|
*/
|
|
@@ -116,6 +116,14 @@ type EventNameValue = typeof EventName[keyof typeof EventName];
|
|
|
116
116
|
type FirewallField = "url" | "body" | "header" | "user_agent" | "ip";
|
|
117
117
|
type FirewallOperator = "contains" | "equals" | "starts_with" | "ends_with" | "regex";
|
|
118
118
|
type FirewallAction = "block" | "flag";
|
|
119
|
+
interface EndpointDeclaration {
|
|
120
|
+
/** HTTP method, e.g. "GET". Use "*" to match any method. */
|
|
121
|
+
method: string;
|
|
122
|
+
/** Express-style path, e.g. "/api/users/:id" — colons are normalized automatically. */
|
|
123
|
+
path: string;
|
|
124
|
+
/** Whether this endpoint requires authentication. Defaults to true. */
|
|
125
|
+
auth?: boolean;
|
|
126
|
+
}
|
|
119
127
|
interface FirewallRule {
|
|
120
128
|
id: string;
|
|
121
129
|
field: FirewallField;
|
|
@@ -127,25 +135,25 @@ interface FirewallRule {
|
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
/**
|
|
130
|
-
*
|
|
138
|
+
* AnomiraClient — the core SDK object.
|
|
131
139
|
*
|
|
132
140
|
* Instantiate once and reuse across your application:
|
|
133
141
|
*
|
|
134
142
|
* ```ts
|
|
135
|
-
* import {
|
|
143
|
+
* import { Anomira } from "@anomira/node-sdk";
|
|
136
144
|
*
|
|
137
|
-
* export const sentinel = new
|
|
145
|
+
* export const sentinel = new Anomira({
|
|
138
146
|
* apiKey: process.env.SENTINEL_API_KEY!,
|
|
139
147
|
* appId: process.env.SENTINEL_APP_ID!,
|
|
140
148
|
* });
|
|
141
149
|
* ```
|
|
142
150
|
*/
|
|
143
|
-
declare class
|
|
151
|
+
declare class AnomiraClient {
|
|
144
152
|
#private;
|
|
145
|
-
readonly config: Required<Omit<
|
|
146
|
-
getUserId: NonNullable<
|
|
147
|
-
getIp: NonNullable<
|
|
148
|
-
detect: Required<NonNullable<
|
|
153
|
+
readonly config: Required<Omit<AnomiraConfig, "getUserId" | "getIp" | "detect">> & {
|
|
154
|
+
getUserId: NonNullable<AnomiraConfig["getUserId"]>;
|
|
155
|
+
getIp: NonNullable<AnomiraConfig["getIp"]>;
|
|
156
|
+
detect: Required<NonNullable<AnomiraConfig["detect"]>>;
|
|
149
157
|
captureConsole: boolean;
|
|
150
158
|
service: string;
|
|
151
159
|
};
|
|
@@ -163,7 +171,7 @@ declare class SentinelClient {
|
|
|
163
171
|
private readonly _origLog;
|
|
164
172
|
private readonly _origWarn;
|
|
165
173
|
private readonly _origError;
|
|
166
|
-
constructor(config:
|
|
174
|
+
constructor(config: AnomiraConfig);
|
|
167
175
|
/**
|
|
168
176
|
* Track a custom security event.
|
|
169
177
|
*
|
|
@@ -230,7 +238,7 @@ declare class SentinelClient {
|
|
|
230
238
|
meta?: Record<string, unknown>;
|
|
231
239
|
}): void;
|
|
232
240
|
/**
|
|
233
|
-
* Send a structured log entry to the
|
|
241
|
+
* Send a structured log entry to the Anomira Logs dashboard.
|
|
234
242
|
*
|
|
235
243
|
* ```ts
|
|
236
244
|
* sentinel.log("info", "User registered", { userId: user.id });
|
|
@@ -241,6 +249,21 @@ declare class SentinelClient {
|
|
|
241
249
|
log(level: "debug" | "info" | "warn" | "error" | "fatal", message: string, meta?: Record<string, unknown> & {
|
|
242
250
|
service?: string;
|
|
243
251
|
}): void;
|
|
252
|
+
/**
|
|
253
|
+
* Declare your API's known endpoints so Anomira can flag undiscovered
|
|
254
|
+
* traffic as shadow endpoints.
|
|
255
|
+
*
|
|
256
|
+
* Call this once on startup after your routes are registered:
|
|
257
|
+
*
|
|
258
|
+
* ```ts
|
|
259
|
+
* await sentinel.declareEndpoints([
|
|
260
|
+
* { method: "GET", path: "/api/users/:id", auth: true },
|
|
261
|
+
* { method: "POST", path: "/api/orders", auth: true },
|
|
262
|
+
* { method: "GET", path: "/api/health", auth: false },
|
|
263
|
+
* ]);
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
declareEndpoints(endpoints: EndpointDeclaration[]): Promise<void>;
|
|
244
267
|
/**
|
|
245
268
|
* Flush all pending events immediately.
|
|
246
269
|
* Useful before a graceful shutdown outside of the process lifecycle hooks.
|
|
@@ -278,10 +301,10 @@ declare class SentinelClient {
|
|
|
278
301
|
*
|
|
279
302
|
* ```ts
|
|
280
303
|
* import express from "express";
|
|
281
|
-
* import {
|
|
304
|
+
* import { Anomira } from "@sentinelapi/node-sdk";
|
|
282
305
|
*
|
|
283
306
|
* const app = express();
|
|
284
|
-
* const sentinel = new
|
|
307
|
+
* const sentinel = new Anomira({ apiKey: "...", appId: "..." });
|
|
285
308
|
*
|
|
286
309
|
* app.use(sentinel.express()); // auto-instrument all routes
|
|
287
310
|
*
|
|
@@ -290,7 +313,7 @@ declare class SentinelClient {
|
|
|
290
313
|
* app.use(createExpressMiddleware(sentinel));
|
|
291
314
|
* ```
|
|
292
315
|
*/
|
|
293
|
-
declare function createExpressMiddleware(client:
|
|
316
|
+
declare function createExpressMiddleware(client: AnomiraClient): (req: Request, res: Response, next: NextFunction) => void;
|
|
294
317
|
|
|
295
318
|
/**
|
|
296
319
|
* Typed Fastify plugin — exported separately for projects that want
|
|
@@ -304,14 +327,14 @@ declare function createExpressMiddleware(client: SentinelClient): (req: Request,
|
|
|
304
327
|
*
|
|
305
328
|
* ```ts
|
|
306
329
|
* import Fastify from "fastify";
|
|
307
|
-
* import {
|
|
330
|
+
* import { Anomira } from "@sentinelapi/node-sdk";
|
|
308
331
|
*
|
|
309
332
|
* const app = Fastify();
|
|
310
|
-
* const sentinel = new
|
|
333
|
+
* const sentinel = new Anomira({ apiKey: "...", appId: "..." });
|
|
311
334
|
*
|
|
312
335
|
* await app.register(sentinel.fastify());
|
|
313
336
|
* ```
|
|
314
337
|
*/
|
|
315
|
-
declare function createFastifyPlugin(client:
|
|
338
|
+
declare function createFastifyPlugin(client: AnomiraClient): FastifyPluginAsync;
|
|
316
339
|
|
|
317
|
-
export { EventName, type EventNameValue, type IngestPayload, type SdkEvent,
|
|
340
|
+
export { AnomiraClient as Anomira, type AnomiraConfig, type EndpointDeclaration, EventName, type EventNameValue, type IngestPayload, type SdkEvent, AnomiraClient as SentinelAPI, type AnomiraConfig as SentinelConfig, createExpressMiddleware, createFastifyPlugin, AnomiraClient as default };
|
package/dist/index.js
CHANGED
|
@@ -105,10 +105,10 @@ var EventBuffer = class {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
log(...args) {
|
|
108
|
-
if (this.opts.debug) console.log("[
|
|
108
|
+
if (this.opts.debug) console.log("[Anomira]", ...args);
|
|
109
109
|
}
|
|
110
110
|
warn(...args) {
|
|
111
|
-
console.warn("[
|
|
111
|
+
console.warn("[Anomira]", ...args);
|
|
112
112
|
}
|
|
113
113
|
};
|
|
114
114
|
function sleep(ms) {
|
|
@@ -195,17 +195,15 @@ async function checkGeoVelocity(userId, ip, tsMs, lookupUrl) {
|
|
|
195
195
|
|
|
196
196
|
// src/sensitive.ts
|
|
197
197
|
var PATTERNS = [
|
|
198
|
-
// ── Cryptographic keys
|
|
198
|
+
// ── Cryptographic private keys ────────────────────────────────────────────
|
|
199
|
+
// NOTE: -----BEGIN CERTIFICATE----- is intentionally excluded — public
|
|
200
|
+
// certificates are designed to be public and committing them is correct.
|
|
201
|
+
// Only PRIVATE keys are dangerous.
|
|
199
202
|
{
|
|
200
203
|
type: "private_key",
|
|
201
204
|
label: "Private Key",
|
|
202
205
|
regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/
|
|
203
206
|
},
|
|
204
|
-
{
|
|
205
|
-
type: "certificate",
|
|
206
|
-
label: "Certificate / Public Key",
|
|
207
|
-
regex: /-----BEGIN CERTIFICATE-----/
|
|
208
|
-
},
|
|
209
207
|
// ── Cloud provider credentials ────────────────────────────────────────────
|
|
210
208
|
{
|
|
211
209
|
// AWS access key ID — highly specific, almost no false positives
|
|
@@ -320,26 +318,30 @@ var PATTERNS = [
|
|
|
320
318
|
regex: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/
|
|
321
319
|
},
|
|
322
320
|
{
|
|
323
|
-
// Generic API key / token
|
|
321
|
+
// Generic API key / token — only flag QUOTED literals, not variable/env references.
|
|
322
|
+
// Skips: api_key = process.env.KEY, token = myVar
|
|
323
|
+
// Matches: api_key = "sk-abc123...", bearer: "eyJhb..."
|
|
324
324
|
type: "api_key",
|
|
325
325
|
label: "API Key / Token",
|
|
326
|
-
regex: /\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|bearer|client[_-]?secret)\s*[:=]\s*["']
|
|
326
|
+
regex: /\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|bearer|client[_-]?secret)\s*[:=]\s*["'][A-Za-z0-9_.\/+\-]{20,}["']/i
|
|
327
327
|
},
|
|
328
328
|
// ── Password fields ───────────────────────────────────────────────────────
|
|
329
329
|
{
|
|
330
|
-
//
|
|
331
|
-
//
|
|
330
|
+
// Only flag QUOTED string literals after a password/secret keyword.
|
|
331
|
+
// Skips: process.env.*, variable references, undefined/null, template literals.
|
|
332
|
+
// Matches: password: "hunter2", secret: 'abc123def', pass="hardcoded!"
|
|
332
333
|
type: "password",
|
|
333
|
-
label: "Password
|
|
334
|
-
regex: /\b(?:password|passwd|pwd|pass|secret|credentials?)\s*[:=]\s*["']
|
|
334
|
+
label: "Hardcoded Password",
|
|
335
|
+
regex: /\b(?:password|passwd|pwd|pass|secret|credentials?)\s*[:=]\s*["'][^"'$\s]{6,}["']/i
|
|
335
336
|
},
|
|
336
337
|
// ── Nigeria-specific PII ──────────────────────────────────────────────────
|
|
337
338
|
{
|
|
338
|
-
// BVN / NIN: 11 digits, first digit 1-9 (not a phone starting with 0)
|
|
339
|
-
//
|
|
339
|
+
// BVN / NIN: 11 digits, first digit 1-9 (not a phone number starting with 0)
|
|
340
|
+
// Exclude: inside URLs (preceded by / : @ - %), hex strings (followed by a-f),
|
|
341
|
+
// and UUIDs/hashes (surrounded by alphanumeric chars)
|
|
340
342
|
type: "bvn",
|
|
341
343
|
label: "BVN / NIN",
|
|
342
|
-
regex: /(
|
|
344
|
+
regex: /(?<![/\-:@%=a-fA-F\w])[1-9]\d{10}(?![a-fA-F\d])/
|
|
343
345
|
},
|
|
344
346
|
{
|
|
345
347
|
// Nigerian phone numbers: 080x, 081x, 070x, 090x, 091x — or with +234 prefix
|
|
@@ -406,7 +408,7 @@ var DEFAULT_INGEST_URL = "https://ingest.anomira.io/v1/events";
|
|
|
406
408
|
var DEFAULT_BATCH_SIZE = 100;
|
|
407
409
|
var DEFAULT_FLUSH_MS = 5e3;
|
|
408
410
|
var DEFAULT_MAX_RETRIES = 3;
|
|
409
|
-
var
|
|
411
|
+
var AnomiraClient = class {
|
|
410
412
|
constructor(config) {
|
|
411
413
|
this.logBuffer = [];
|
|
412
414
|
this.logFlushTimer = null;
|
|
@@ -505,7 +507,7 @@ var SentinelClient = class {
|
|
|
505
507
|
console[method] = (...args) => {
|
|
506
508
|
original(...args);
|
|
507
509
|
const first = args[0];
|
|
508
|
-
if (typeof first === "string" && first.startsWith("[
|
|
510
|
+
if (typeof first === "string" && first.startsWith("[Anomira]")) return;
|
|
509
511
|
const message = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
510
512
|
const ctx = requestContext.getStore();
|
|
511
513
|
this.log(level, message, {
|
|
@@ -529,7 +531,7 @@ var SentinelClient = class {
|
|
|
529
531
|
const data = await res.json();
|
|
530
532
|
this.blockedIpCache = new Set(data.ips ?? []);
|
|
531
533
|
if (this.config.debug && this.blockedIpCache.size > 0) {
|
|
532
|
-
this._origLog(`[
|
|
534
|
+
this._origLog(`[Anomira] blocklist refreshed \u2014 ${this.blockedIpCache.size} blocked IPs`);
|
|
533
535
|
}
|
|
534
536
|
} catch {
|
|
535
537
|
}
|
|
@@ -557,7 +559,7 @@ var SentinelClient = class {
|
|
|
557
559
|
})() : void 0
|
|
558
560
|
}));
|
|
559
561
|
if (this.config.debug && this.compiledRules.length > 0) {
|
|
560
|
-
this._origLog(`[
|
|
562
|
+
this._origLog(`[Anomira] firewall rules refreshed \u2014 ${this.compiledRules.length} active rules`);
|
|
561
563
|
}
|
|
562
564
|
} catch {
|
|
563
565
|
}
|
|
@@ -608,7 +610,7 @@ var SentinelClient = class {
|
|
|
608
610
|
signal: AbortSignal.timeout(8e3)
|
|
609
611
|
});
|
|
610
612
|
if (this.config.debug) {
|
|
611
|
-
this._origLog(`[
|
|
613
|
+
this._origLog(`[Anomira] [logs] \u2705 sent ${batch.length} log entries (${res.status})`);
|
|
612
614
|
}
|
|
613
615
|
} catch {
|
|
614
616
|
this.logBuffer.unshift(...batch);
|
|
@@ -627,24 +629,24 @@ var SentinelClient = class {
|
|
|
627
629
|
signal: AbortSignal.timeout(5e3)
|
|
628
630
|
});
|
|
629
631
|
if (res.status >= 300 && res.status < 400) {
|
|
630
|
-
this._origWarn(`[
|
|
632
|
+
this._origWarn(`[Anomira] \u274C Wrong ingest URL \u2014 got redirect to ${res.headers.get("location")}. Check SENTINEL_INGEST_URL.`);
|
|
631
633
|
return;
|
|
632
634
|
}
|
|
633
635
|
if (res.ok) {
|
|
634
|
-
this._origLog(`[
|
|
636
|
+
this._origLog(`[Anomira] \u2705 Connected (appId: ${this.config.appId.slice(0, 8)}\u2026)`);
|
|
635
637
|
return;
|
|
636
638
|
}
|
|
637
639
|
if (res.status === 401) {
|
|
638
|
-
this._origWarn("[
|
|
640
|
+
this._origWarn("[Anomira] \u274C Invalid API key \u2014 check your SENTINEL_API_KEY");
|
|
639
641
|
return;
|
|
640
642
|
}
|
|
641
643
|
if (res.status === 403) {
|
|
642
|
-
this._origWarn("[
|
|
644
|
+
this._origWarn("[Anomira] \u274C App not found or appId mismatch \u2014 check your SENTINEL_APP_ID");
|
|
643
645
|
return;
|
|
644
646
|
}
|
|
645
|
-
this._origWarn(`[
|
|
647
|
+
this._origWarn(`[Anomira] \u26A0\uFE0F Ingest returned HTTP ${res.status} \u2014 check your configuration`);
|
|
646
648
|
} catch {
|
|
647
|
-
this._origWarn("[
|
|
649
|
+
this._origWarn("[Anomira] \u26A0\uFE0F Could not reach ingest endpoint \u2014 check SENTINEL_INGEST_URL (current: " + this.config.ingestUrl + ")");
|
|
648
650
|
}
|
|
649
651
|
}
|
|
650
652
|
// ─── Public API ────────────────────────────────────────────────────────────
|
|
@@ -741,7 +743,7 @@ var SentinelClient = class {
|
|
|
741
743
|
});
|
|
742
744
|
}
|
|
743
745
|
/**
|
|
744
|
-
* Send a structured log entry to the
|
|
746
|
+
* Send a structured log entry to the Anomira Logs dashboard.
|
|
745
747
|
*
|
|
746
748
|
* ```ts
|
|
747
749
|
* sentinel.log("info", "User registered", { userId: user.id });
|
|
@@ -757,16 +759,47 @@ var SentinelClient = class {
|
|
|
757
759
|
rest["sensitiveLeaks"] = leaks.map((l) => l.type);
|
|
758
760
|
if (this.config.debug) {
|
|
759
761
|
this._origWarn(
|
|
760
|
-
`[
|
|
762
|
+
`[Anomira] \u26A0\uFE0F Sensitive data in log (${leaks.map((l) => l.label).join(", ")}): "${message.slice(0, 60)}\u2026"`
|
|
761
763
|
);
|
|
762
764
|
}
|
|
763
765
|
}
|
|
764
766
|
this.logBuffer.push({ level, service: service ?? this.config.service, message, meta: rest, ts: Date.now() });
|
|
765
767
|
if (this.config.debug && leaks.length === 0) {
|
|
766
|
-
this._origLog(`[
|
|
768
|
+
this._origLog(`[Anomira] log:${level} ${message}`);
|
|
767
769
|
}
|
|
768
770
|
if (this.logBuffer.length >= 50) void this.#flushLogs();
|
|
769
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* Declare your API's known endpoints so Anomira can flag undiscovered
|
|
774
|
+
* traffic as shadow endpoints.
|
|
775
|
+
*
|
|
776
|
+
* Call this once on startup after your routes are registered:
|
|
777
|
+
*
|
|
778
|
+
* ```ts
|
|
779
|
+
* await sentinel.declareEndpoints([
|
|
780
|
+
* { method: "GET", path: "/api/users/:id", auth: true },
|
|
781
|
+
* { method: "POST", path: "/api/orders", auth: true },
|
|
782
|
+
* { method: "GET", path: "/api/health", auth: false },
|
|
783
|
+
* ]);
|
|
784
|
+
* ```
|
|
785
|
+
*/
|
|
786
|
+
async declareEndpoints(endpoints) {
|
|
787
|
+
if (this.disabled || endpoints.length === 0) return;
|
|
788
|
+
const url = this.config.ingestUrl.replace(/\/v1\/events$/, "/v1/declare-endpoints");
|
|
789
|
+
try {
|
|
790
|
+
await fetch(url, {
|
|
791
|
+
method: "POST",
|
|
792
|
+
headers: {
|
|
793
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
794
|
+
"Content-Type": "application/json",
|
|
795
|
+
"User-Agent": `@anomira/node-sdk/0.1.0`
|
|
796
|
+
},
|
|
797
|
+
body: JSON.stringify({ appId: this.config.appId, endpoints }),
|
|
798
|
+
signal: AbortSignal.timeout(1e4)
|
|
799
|
+
});
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
}
|
|
770
803
|
/**
|
|
771
804
|
* Flush all pending events immediately.
|
|
772
805
|
* Useful before a graceful shutdown outside of the process lifecycle hooks.
|
|
@@ -1107,6 +1140,6 @@ function createFastifyPlugin2(client) {
|
|
|
1107
1140
|
};
|
|
1108
1141
|
}
|
|
1109
1142
|
|
|
1110
|
-
export { EventName,
|
|
1143
|
+
export { AnomiraClient as Anomira, EventName, AnomiraClient as SentinelAPI, createExpressMiddleware2 as createExpressMiddleware, createFastifyPlugin2 as createFastifyPlugin, AnomiraClient as default };
|
|
1111
1144
|
//# sourceMappingURL=index.js.map
|
|
1112
1145
|
//# sourceMappingURL=index.js.map
|