@anomira/node-sdk 0.2.4 → 0.2.6

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.d.cts CHANGED
@@ -148,11 +148,17 @@ declare const EventName: {
148
148
  readonly SQL_ERROR: "db.sql.error";
149
149
  readonly FIREWALL_BLOCK: "http.firewall.block";
150
150
  readonly FIREWALL_FLAG: "http.firewall.flag";
151
+ readonly FIREWALL_RATE_LIMIT: "http.firewall.rate_limit";
152
+ readonly FIREWALL_REDIRECT_HONEYPOT: "http.firewall.redirect_honeypot";
153
+ readonly FIREWALL_TAG: "http.firewall.tag";
154
+ readonly FIREWALL_MONITOR: "http.firewall.monitor";
155
+ readonly FIREWALL_CHALLENGE: "http.firewall.challenge";
156
+ readonly FIREWALL_TEMP_BLOCK: "http.firewall.temporary_block";
151
157
  };
152
158
  type EventNameValue = typeof EventName[keyof typeof EventName];
153
159
  type FirewallField = "url" | "body" | "header" | "user_agent" | "ip";
154
160
  type FirewallOperator = "contains" | "equals" | "starts_with" | "ends_with" | "regex";
155
- type FirewallAction = "block" | "flag";
161
+ type FirewallAction = "block" | "flag" | "rate_limit" | "challenge" | "redirect_honeypot" | "tag" | "monitor" | "temporary_block";
156
162
  interface EndpointDeclaration {
157
163
  /** HTTP method, e.g. "GET". Use "*" to match any method. */
158
164
  method: string;
@@ -169,6 +175,8 @@ interface FirewallRule {
169
175
  value: string;
170
176
  action: FirewallAction;
171
177
  attackType: string;
178
+ redirectTarget?: string | null;
179
+ tempBlockMins?: number | null;
172
180
  }
173
181
 
174
182
  /**
package/dist/index.d.ts CHANGED
@@ -148,11 +148,17 @@ declare const EventName: {
148
148
  readonly SQL_ERROR: "db.sql.error";
149
149
  readonly FIREWALL_BLOCK: "http.firewall.block";
150
150
  readonly FIREWALL_FLAG: "http.firewall.flag";
151
+ readonly FIREWALL_RATE_LIMIT: "http.firewall.rate_limit";
152
+ readonly FIREWALL_REDIRECT_HONEYPOT: "http.firewall.redirect_honeypot";
153
+ readonly FIREWALL_TAG: "http.firewall.tag";
154
+ readonly FIREWALL_MONITOR: "http.firewall.monitor";
155
+ readonly FIREWALL_CHALLENGE: "http.firewall.challenge";
156
+ readonly FIREWALL_TEMP_BLOCK: "http.firewall.temporary_block";
151
157
  };
152
158
  type EventNameValue = typeof EventName[keyof typeof EventName];
153
159
  type FirewallField = "url" | "body" | "header" | "user_agent" | "ip";
154
160
  type FirewallOperator = "contains" | "equals" | "starts_with" | "ends_with" | "regex";
155
- type FirewallAction = "block" | "flag";
161
+ type FirewallAction = "block" | "flag" | "rate_limit" | "challenge" | "redirect_honeypot" | "tag" | "monitor" | "temporary_block";
156
162
  interface EndpointDeclaration {
157
163
  /** HTTP method, e.g. "GET". Use "*" to match any method. */
158
164
  method: string;
@@ -169,6 +175,8 @@ interface FirewallRule {
169
175
  value: string;
170
176
  action: FirewallAction;
171
177
  attackType: string;
178
+ redirectTarget?: string | null;
179
+ tempBlockMins?: number | null;
172
180
  }
173
181
 
174
182
  /**
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ var EventBuffer = class {
71
71
  headers: {
72
72
  Authorization: `Bearer ${apiKey}`,
73
73
  "Content-Type": "application/json",
74
- "User-Agent": `@sentinelapi/node-sdk/0.1.0`
74
+ "User-Agent": `@anomira/node-sdk/0.2.5`
75
75
  },
76
76
  body: JSON.stringify(payload),
77
77
  signal: AbortSignal.timeout(8e3)
@@ -400,7 +400,13 @@ var EventName = {
400
400
  SQL_ERROR: "db.sql.error",
401
401
  // Firewall (emitted when a custom request filtering rule fires)
402
402
  FIREWALL_BLOCK: "http.firewall.block",
403
- FIREWALL_FLAG: "http.firewall.flag"
403
+ FIREWALL_FLAG: "http.firewall.flag",
404
+ FIREWALL_RATE_LIMIT: "http.firewall.rate_limit",
405
+ FIREWALL_REDIRECT_HONEYPOT: "http.firewall.redirect_honeypot",
406
+ FIREWALL_TAG: "http.firewall.tag",
407
+ FIREWALL_MONITOR: "http.firewall.monitor",
408
+ FIREWALL_CHALLENGE: "http.firewall.challenge",
409
+ FIREWALL_TEMP_BLOCK: "http.firewall.temporary_block"
404
410
  };
405
411
 
406
412
  // src/agent-detection.ts
@@ -2034,6 +2040,27 @@ function createExpressMiddleware(client) {
2034
2040
  const fingerprint = computeBrowserFingerprint(headers ?? {}, ua);
2035
2041
  const h2settings = extractHttp2Settings(req);
2036
2042
  const upstreamTls = extractUpstreamTls(headers ?? {});
2043
+ const cookieHeader = headers?.["cookie"];
2044
+ const cookieToken = cookieHeader ? cookieHeader.split(";").map((c) => c.trim()).find((c) => c.startsWith("anomira_fp="))?.slice("anomira_fp=".length) ?? "" : "";
2045
+ const browserFpRaw = cookieToken || headers?.["x-anomira-fp"] || "";
2046
+ let browserFp = null;
2047
+ if (browserFpRaw) {
2048
+ try {
2049
+ const raw = JSON.parse(Buffer.from(browserFpRaw, "base64").toString("utf8"));
2050
+ if (typeof raw["v"] === "number" && typeof raw["fp"] === "string") {
2051
+ browserFp = {
2052
+ fp: raw["fp"],
2053
+ bot: raw["bot"] ?? 0,
2054
+ sigs: (raw["sigs"] ?? []).join(","),
2055
+ uid: typeof raw["uid"] === "string" && raw["uid"] ? raw["uid"] : void 0,
2056
+ pst: raw["frm"]?.pst,
2057
+ ttf: raw["frm"]?.ttf,
2058
+ tts: raw["frm"]?.tts
2059
+ };
2060
+ }
2061
+ } catch {
2062
+ }
2063
+ }
2037
2064
  if (client["canaryTokenCache"] && client["canaryTokenCache"].size > 0) {
2038
2065
  const authHeader = (typeof headers?.["authorization"] === "string" ? headers["authorization"] : "") ?? "";
2039
2066
  if (authHeader.startsWith("Bearer ")) {
@@ -2128,11 +2155,39 @@ function createExpressMiddleware(client) {
2128
2155
  userId,
2129
2156
  meta: { url, method, ruleId: fwMatch.rule.id, attackType: fwMatch.rule.attackType }
2130
2157
  });
2131
- if (fwMatch.rule.action === "block") {
2132
- const res_ = res;
2133
- if (typeof res_["status"] === "function") res_["status"](403);
2134
- else res_["statusCode"] = 403;
2135
- if (typeof res_["end"] === "function") res_["end"]('{"error":"Blocked by firewall rule"}');
2158
+ const res_ = res;
2159
+ const setStatus = (code) => {
2160
+ if (typeof res_["status"] === "function") res_["status"](code);
2161
+ else res_["statusCode"] = code;
2162
+ };
2163
+ const sendBody = (body) => {
2164
+ if (typeof res_["end"] === "function") res_["end"](body);
2165
+ };
2166
+ const setHeader = (k, v) => {
2167
+ if (typeof res_["setHeader"] === "function") res_["setHeader"](k, v);
2168
+ };
2169
+ const action = fwMatch.rule.action;
2170
+ if (action === "block" || action === "temporary_block") {
2171
+ setStatus(403);
2172
+ sendBody('{"error":"Blocked by firewall rule"}');
2173
+ return;
2174
+ }
2175
+ if (action === "rate_limit") {
2176
+ setStatus(429);
2177
+ setHeader("Retry-After", "60");
2178
+ sendBody('{"error":"Rate limit exceeded","retryAfter":60}');
2179
+ return;
2180
+ }
2181
+ if (action === "redirect_honeypot") {
2182
+ const target = fwMatch.rule.redirectTarget ?? "/.env";
2183
+ setStatus(302);
2184
+ setHeader("Location", target);
2185
+ sendBody("");
2186
+ return;
2187
+ }
2188
+ if (action === "challenge") {
2189
+ setStatus(403);
2190
+ sendBody('{"error":"Request challenge required"}');
2136
2191
  return;
2137
2192
  }
2138
2193
  }
@@ -2146,7 +2201,7 @@ function createExpressMiddleware(client) {
2146
2201
  }
2147
2202
  }
2148
2203
  const onFinish = () => {
2149
- const lateUserId = client.config.getUserId(req);
2204
+ const lateUserId = client.config.getUserId(req) || browserFp?.uid || "";
2150
2205
  if (!userIdWarnFired && userIdCheckCount < 20) {
2151
2206
  userIdCheckCount++;
2152
2207
  if (!lateUserId) userIdMissCount++;
@@ -2201,6 +2256,14 @@ function createExpressMiddleware(client) {
2201
2256
  _fp: fingerprint.score,
2202
2257
  _fpSig: fingerprint.signals.join(","),
2203
2258
  ...fingerprint.knownClient ? { _fpClient: fingerprint.knownClient } : {},
2259
+ ...browserFp ? {
2260
+ _bfp: browserFp.fp,
2261
+ _bbot: browserFp.bot,
2262
+ _bsigs: browserFp.sigs,
2263
+ ...browserFp.pst !== void 0 ? { _bpaste: browserFp.pst } : {},
2264
+ ...browserFp.ttf !== void 0 && browserFp.ttf >= 0 ? { _bttf: browserFp.ttf } : {},
2265
+ ...browserFp.tts !== void 0 && browserFp.tts >= 0 ? { _btts: browserFp.tts } : {}
2266
+ } : {},
2204
2267
  ...h2settings ? {
2205
2268
  _h2Window: h2settings.initialWindowSize,
2206
2269
  _h2HdrTable: h2settings.headerTableSize,
@@ -2328,14 +2391,32 @@ function createFastifyPlugin(client) {
2328
2391
  userId,
2329
2392
  meta: { url, method, ruleId: fwMatch.rule.id, attackType: fwMatch.rule.attackType }
2330
2393
  });
2331
- if (fwMatch.rule.action === "block") {
2332
- const rep = reply;
2333
- if (typeof rep["code"] === "function") {
2334
- const chained = rep["code"](403);
2335
- if (chained && typeof chained["send"] === "function") {
2336
- chained["send"]({ error: "Blocked by firewall rule" });
2337
- }
2338
- }
2394
+ const rep = reply;
2395
+ const replyCode = (code) => typeof rep["code"] === "function" ? rep["code"](code) : rep;
2396
+ const replyHeader = (k, v) => {
2397
+ if (typeof rep["header"] === "function") rep["header"](k, v);
2398
+ };
2399
+ const replySend = (chained, body) => {
2400
+ if (chained && typeof chained["send"] === "function") chained["send"](body);
2401
+ };
2402
+ const action = fwMatch.rule.action;
2403
+ if (action === "block" || action === "temporary_block") {
2404
+ replySend(replyCode(403), { error: "Blocked by firewall rule" });
2405
+ return;
2406
+ }
2407
+ if (action === "rate_limit") {
2408
+ replyHeader("Retry-After", "60");
2409
+ replySend(replyCode(429), { error: "Rate limit exceeded", retryAfter: 60 });
2410
+ return;
2411
+ }
2412
+ if (action === "redirect_honeypot") {
2413
+ const target = fwMatch.rule.redirectTarget ?? "/.env";
2414
+ replyHeader("Location", target);
2415
+ replySend(replyCode(302), "");
2416
+ return;
2417
+ }
2418
+ if (action === "challenge") {
2419
+ replySend(replyCode(403), { error: "Request challenge required" });
2339
2420
  return;
2340
2421
  }
2341
2422
  }
@@ -2350,11 +2431,11 @@ function createFastifyPlugin(client) {
2350
2431
  const ip = client.config.getIp(req);
2351
2432
  const url = req["url"] ?? "/";
2352
2433
  const method = req["method"]?.toUpperCase() ?? "GET";
2353
- const userId = client.config.getUserId(req);
2434
+ const serverUserId = client.config.getUserId(req);
2354
2435
  const status = reply["statusCode"] ?? 0;
2355
2436
  if (!userIdWarnFired && userIdCheckCount < 20) {
2356
2437
  userIdCheckCount++;
2357
- if (!userId) userIdMissCount++;
2438
+ if (!serverUserId) userIdMissCount++;
2358
2439
  if (userIdCheckCount === 20 && userIdMissCount >= 18) {
2359
2440
  userIdWarnFired = true;
2360
2441
  console.warn(
@@ -2369,6 +2450,28 @@ function createFastifyPlugin(client) {
2369
2450
  const fingerprint = computeBrowserFingerprint(headers ?? {}, ua);
2370
2451
  const h2settings = extractHttp2Settings(req);
2371
2452
  const upstreamTls = extractUpstreamTls(headers ?? {});
2453
+ const cookieHdrF = headers?.["cookie"];
2454
+ const cookieTokF = cookieHdrF ? cookieHdrF.split(";").map((c) => c.trim()).find((c) => c.startsWith("anomira_fp="))?.slice("anomira_fp=".length) ?? "" : "";
2455
+ const bfpRawF = cookieTokF || headers?.["x-anomira-fp"] || "";
2456
+ let browserFpF = null;
2457
+ if (bfpRawF) {
2458
+ try {
2459
+ const raw = JSON.parse(Buffer.from(bfpRawF, "base64").toString("utf8"));
2460
+ if (typeof raw["v"] === "number" && typeof raw["fp"] === "string") {
2461
+ browserFpF = {
2462
+ fp: raw["fp"],
2463
+ bot: raw["bot"] ?? 0,
2464
+ sigs: (raw["sigs"] ?? []).join(","),
2465
+ uid: typeof raw["uid"] === "string" && raw["uid"] ? raw["uid"] : void 0,
2466
+ pst: raw["frm"]?.pst,
2467
+ ttf: raw["frm"]?.ttf,
2468
+ tts: raw["frm"]?.tts
2469
+ };
2470
+ }
2471
+ } catch {
2472
+ }
2473
+ }
2474
+ const userId = serverUserId || browserFpF?.uid || "";
2372
2475
  client.track(EventName.REQUEST, {
2373
2476
  ip,
2374
2477
  userId,
@@ -2382,6 +2485,14 @@ function createFastifyPlugin(client) {
2382
2485
  _fp: fingerprint.score,
2383
2486
  _fpSig: fingerprint.signals.join(","),
2384
2487
  ...fingerprint.knownClient ? { _fpClient: fingerprint.knownClient } : {},
2488
+ ...browserFpF ? {
2489
+ _bfp: browserFpF.fp,
2490
+ _bbot: browserFpF.bot,
2491
+ _bsigs: browserFpF.sigs,
2492
+ ...browserFpF.pst !== void 0 ? { _bpaste: browserFpF.pst } : {},
2493
+ ...browserFpF.ttf !== void 0 && browserFpF.ttf >= 0 ? { _bttf: browserFpF.ttf } : {},
2494
+ ...browserFpF.tts !== void 0 && browserFpF.tts >= 0 ? { _btts: browserFpF.tts } : {}
2495
+ } : {},
2385
2496
  ...h2settings ? {
2386
2497
  _h2Window: h2settings.initialWindowSize,
2387
2498
  _h2HdrTable: h2settings.headerTableSize,