@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 +128 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +128 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
@@ -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
|
-
|
|
2132
|
-
|
|
2133
|
-
if (typeof res_["status"] === "function") res_["status"](
|
|
2134
|
-
else res_["statusCode"] =
|
|
2135
|
-
|
|
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
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
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
|
|
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 (!
|
|
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,
|