@anomira/node-sdk 0.2.4 → 0.2.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
@@ -404,7 +404,13 @@ var EventName = {
404
404
  SQL_ERROR: "db.sql.error",
405
405
  // Firewall (emitted when a custom request filtering rule fires)
406
406
  FIREWALL_BLOCK: "http.firewall.block",
407
- FIREWALL_FLAG: "http.firewall.flag"
407
+ FIREWALL_FLAG: "http.firewall.flag",
408
+ FIREWALL_RATE_LIMIT: "http.firewall.rate_limit",
409
+ FIREWALL_REDIRECT_HONEYPOT: "http.firewall.redirect_honeypot",
410
+ FIREWALL_TAG: "http.firewall.tag",
411
+ FIREWALL_MONITOR: "http.firewall.monitor",
412
+ FIREWALL_CHALLENGE: "http.firewall.challenge",
413
+ FIREWALL_TEMP_BLOCK: "http.firewall.temporary_block"
408
414
  };
409
415
 
410
416
  // src/agent-detection.ts
@@ -2038,6 +2044,27 @@ function createExpressMiddleware(client) {
2038
2044
  const fingerprint = computeBrowserFingerprint(headers ?? {}, ua);
2039
2045
  const h2settings = extractHttp2Settings(req);
2040
2046
  const upstreamTls = extractUpstreamTls(headers ?? {});
2047
+ const cookieHeader = headers?.["cookie"];
2048
+ const cookieToken = cookieHeader ? cookieHeader.split(";").map((c) => c.trim()).find((c) => c.startsWith("anomira_fp="))?.slice("anomira_fp=".length) ?? "" : "";
2049
+ const browserFpRaw = cookieToken || headers?.["x-anomira-fp"] || "";
2050
+ let browserFp = null;
2051
+ if (browserFpRaw) {
2052
+ try {
2053
+ const raw = JSON.parse(Buffer.from(browserFpRaw, "base64").toString("utf8"));
2054
+ if (typeof raw["v"] === "number" && typeof raw["fp"] === "string") {
2055
+ browserFp = {
2056
+ fp: raw["fp"],
2057
+ bot: raw["bot"] ?? 0,
2058
+ sigs: (raw["sigs"] ?? []).join(","),
2059
+ uid: typeof raw["uid"] === "string" && raw["uid"] ? raw["uid"] : void 0,
2060
+ pst: raw["frm"]?.pst,
2061
+ ttf: raw["frm"]?.ttf,
2062
+ tts: raw["frm"]?.tts
2063
+ };
2064
+ }
2065
+ } catch {
2066
+ }
2067
+ }
2041
2068
  if (client["canaryTokenCache"] && client["canaryTokenCache"].size > 0) {
2042
2069
  const authHeader = (typeof headers?.["authorization"] === "string" ? headers["authorization"] : "") ?? "";
2043
2070
  if (authHeader.startsWith("Bearer ")) {
@@ -2132,11 +2159,39 @@ function createExpressMiddleware(client) {
2132
2159
  userId,
2133
2160
  meta: { url, method, ruleId: fwMatch.rule.id, attackType: fwMatch.rule.attackType }
2134
2161
  });
2135
- if (fwMatch.rule.action === "block") {
2136
- const res_ = res;
2137
- if (typeof res_["status"] === "function") res_["status"](403);
2138
- else res_["statusCode"] = 403;
2139
- if (typeof res_["end"] === "function") res_["end"]('{"error":"Blocked by firewall rule"}');
2162
+ const res_ = res;
2163
+ const setStatus = (code) => {
2164
+ if (typeof res_["status"] === "function") res_["status"](code);
2165
+ else res_["statusCode"] = code;
2166
+ };
2167
+ const sendBody = (body) => {
2168
+ if (typeof res_["end"] === "function") res_["end"](body);
2169
+ };
2170
+ const setHeader = (k, v) => {
2171
+ if (typeof res_["setHeader"] === "function") res_["setHeader"](k, v);
2172
+ };
2173
+ const action = fwMatch.rule.action;
2174
+ if (action === "block" || action === "temporary_block") {
2175
+ setStatus(403);
2176
+ sendBody('{"error":"Blocked by firewall rule"}');
2177
+ return;
2178
+ }
2179
+ if (action === "rate_limit") {
2180
+ setStatus(429);
2181
+ setHeader("Retry-After", "60");
2182
+ sendBody('{"error":"Rate limit exceeded","retryAfter":60}');
2183
+ return;
2184
+ }
2185
+ if (action === "redirect_honeypot") {
2186
+ const target = fwMatch.rule.redirectTarget ?? "/.env";
2187
+ setStatus(302);
2188
+ setHeader("Location", target);
2189
+ sendBody("");
2190
+ return;
2191
+ }
2192
+ if (action === "challenge") {
2193
+ setStatus(403);
2194
+ sendBody('{"error":"Request challenge required"}');
2140
2195
  return;
2141
2196
  }
2142
2197
  }
@@ -2150,7 +2205,7 @@ function createExpressMiddleware(client) {
2150
2205
  }
2151
2206
  }
2152
2207
  const onFinish = () => {
2153
- const lateUserId = client.config.getUserId(req);
2208
+ const lateUserId = client.config.getUserId(req) || browserFp?.uid || "";
2154
2209
  if (!userIdWarnFired && userIdCheckCount < 20) {
2155
2210
  userIdCheckCount++;
2156
2211
  if (!lateUserId) userIdMissCount++;
@@ -2205,6 +2260,14 @@ function createExpressMiddleware(client) {
2205
2260
  _fp: fingerprint.score,
2206
2261
  _fpSig: fingerprint.signals.join(","),
2207
2262
  ...fingerprint.knownClient ? { _fpClient: fingerprint.knownClient } : {},
2263
+ ...browserFp ? {
2264
+ _bfp: browserFp.fp,
2265
+ _bbot: browserFp.bot,
2266
+ _bsigs: browserFp.sigs,
2267
+ ...browserFp.pst !== void 0 ? { _bpaste: browserFp.pst } : {},
2268
+ ...browserFp.ttf !== void 0 && browserFp.ttf >= 0 ? { _bttf: browserFp.ttf } : {},
2269
+ ...browserFp.tts !== void 0 && browserFp.tts >= 0 ? { _btts: browserFp.tts } : {}
2270
+ } : {},
2208
2271
  ...h2settings ? {
2209
2272
  _h2Window: h2settings.initialWindowSize,
2210
2273
  _h2HdrTable: h2settings.headerTableSize,
@@ -2332,14 +2395,32 @@ function createFastifyPlugin(client) {
2332
2395
  userId,
2333
2396
  meta: { url, method, ruleId: fwMatch.rule.id, attackType: fwMatch.rule.attackType }
2334
2397
  });
2335
- if (fwMatch.rule.action === "block") {
2336
- const rep = reply;
2337
- if (typeof rep["code"] === "function") {
2338
- const chained = rep["code"](403);
2339
- if (chained && typeof chained["send"] === "function") {
2340
- chained["send"]({ error: "Blocked by firewall rule" });
2341
- }
2342
- }
2398
+ const rep = reply;
2399
+ const replyCode = (code) => typeof rep["code"] === "function" ? rep["code"](code) : rep;
2400
+ const replyHeader = (k, v) => {
2401
+ if (typeof rep["header"] === "function") rep["header"](k, v);
2402
+ };
2403
+ const replySend = (chained, body) => {
2404
+ if (chained && typeof chained["send"] === "function") chained["send"](body);
2405
+ };
2406
+ const action = fwMatch.rule.action;
2407
+ if (action === "block" || action === "temporary_block") {
2408
+ replySend(replyCode(403), { error: "Blocked by firewall rule" });
2409
+ return;
2410
+ }
2411
+ if (action === "rate_limit") {
2412
+ replyHeader("Retry-After", "60");
2413
+ replySend(replyCode(429), { error: "Rate limit exceeded", retryAfter: 60 });
2414
+ return;
2415
+ }
2416
+ if (action === "redirect_honeypot") {
2417
+ const target = fwMatch.rule.redirectTarget ?? "/.env";
2418
+ replyHeader("Location", target);
2419
+ replySend(replyCode(302), "");
2420
+ return;
2421
+ }
2422
+ if (action === "challenge") {
2423
+ replySend(replyCode(403), { error: "Request challenge required" });
2343
2424
  return;
2344
2425
  }
2345
2426
  }
@@ -2354,11 +2435,11 @@ function createFastifyPlugin(client) {
2354
2435
  const ip = client.config.getIp(req);
2355
2436
  const url = req["url"] ?? "/";
2356
2437
  const method = req["method"]?.toUpperCase() ?? "GET";
2357
- const userId = client.config.getUserId(req);
2438
+ const serverUserId = client.config.getUserId(req);
2358
2439
  const status = reply["statusCode"] ?? 0;
2359
2440
  if (!userIdWarnFired && userIdCheckCount < 20) {
2360
2441
  userIdCheckCount++;
2361
- if (!userId) userIdMissCount++;
2442
+ if (!serverUserId) userIdMissCount++;
2362
2443
  if (userIdCheckCount === 20 && userIdMissCount >= 18) {
2363
2444
  userIdWarnFired = true;
2364
2445
  console.warn(
@@ -2373,6 +2454,28 @@ function createFastifyPlugin(client) {
2373
2454
  const fingerprint = computeBrowserFingerprint(headers ?? {}, ua);
2374
2455
  const h2settings = extractHttp2Settings(req);
2375
2456
  const upstreamTls = extractUpstreamTls(headers ?? {});
2457
+ const cookieHdrF = headers?.["cookie"];
2458
+ const cookieTokF = cookieHdrF ? cookieHdrF.split(";").map((c) => c.trim()).find((c) => c.startsWith("anomira_fp="))?.slice("anomira_fp=".length) ?? "" : "";
2459
+ const bfpRawF = cookieTokF || headers?.["x-anomira-fp"] || "";
2460
+ let browserFpF = null;
2461
+ if (bfpRawF) {
2462
+ try {
2463
+ const raw = JSON.parse(Buffer.from(bfpRawF, "base64").toString("utf8"));
2464
+ if (typeof raw["v"] === "number" && typeof raw["fp"] === "string") {
2465
+ browserFpF = {
2466
+ fp: raw["fp"],
2467
+ bot: raw["bot"] ?? 0,
2468
+ sigs: (raw["sigs"] ?? []).join(","),
2469
+ uid: typeof raw["uid"] === "string" && raw["uid"] ? raw["uid"] : void 0,
2470
+ pst: raw["frm"]?.pst,
2471
+ ttf: raw["frm"]?.ttf,
2472
+ tts: raw["frm"]?.tts
2473
+ };
2474
+ }
2475
+ } catch {
2476
+ }
2477
+ }
2478
+ const userId = serverUserId || browserFpF?.uid || "";
2376
2479
  client.track(EventName.REQUEST, {
2377
2480
  ip,
2378
2481
  userId,
@@ -2386,6 +2489,14 @@ function createFastifyPlugin(client) {
2386
2489
  _fp: fingerprint.score,
2387
2490
  _fpSig: fingerprint.signals.join(","),
2388
2491
  ...fingerprint.knownClient ? { _fpClient: fingerprint.knownClient } : {},
2492
+ ...browserFpF ? {
2493
+ _bfp: browserFpF.fp,
2494
+ _bbot: browserFpF.bot,
2495
+ _bsigs: browserFpF.sigs,
2496
+ ...browserFpF.pst !== void 0 ? { _bpaste: browserFpF.pst } : {},
2497
+ ...browserFpF.ttf !== void 0 && browserFpF.ttf >= 0 ? { _bttf: browserFpF.ttf } : {},
2498
+ ...browserFpF.tts !== void 0 && browserFpF.tts >= 0 ? { _btts: browserFpF.tts } : {}
2499
+ } : {},
2389
2500
  ...h2settings ? {
2390
2501
  _h2Window: h2settings.initialWindowSize,
2391
2502
  _h2HdrTable: h2settings.headerTableSize,