@arcis/node 1.4.3 → 1.4.4
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 +11 -3
- package/dist/cli/arcis.d.ts +23 -0
- package/dist/cli/arcis.d.ts.map +1 -0
- package/dist/cli/arcis.js +312 -0
- package/dist/cli/arcis.js.map +1 -0
- package/dist/cli/arcis.mjs +309 -0
- package/dist/cli/arcis.mjs.map +1 -0
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/index.js +4 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +4 -1
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/types.d.ts +11 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.js +253 -141
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +253 -141
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs.map +1 -1
- package/dist/middleware/bot-detection.d.ts.map +1 -1
- package/dist/middleware/csrf.d.ts.map +1 -1
- package/dist/middleware/index.js +224 -3
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +224 -3
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/middleware/main.d.ts.map +1 -1
- package/dist/sanitizers/index.d.ts +2 -1
- package/dist/sanitizers/index.d.ts.map +1 -1
- package/dist/sanitizers/index.js +213 -145
- package/dist/sanitizers/index.js.map +1 -1
- package/dist/sanitizers/index.mjs +213 -146
- package/dist/sanitizers/index.mjs.map +1 -1
- package/dist/sanitizers/sanitize.d.ts +13 -0
- package/dist/sanitizers/sanitize.d.ts.map +1 -1
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/index.mjs.map +1 -1
- package/dist/telemetry/client.d.ts +3 -0
- package/dist/telemetry/client.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +12 -0
- package/dist/telemetry/types.d.ts.map +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.mjs
CHANGED
|
@@ -74,7 +74,10 @@ var XSS_PATTERNS = [
|
|
|
74
74
|
/** base href hijacking — redirects all relative URLs to attacker domain */
|
|
75
75
|
/<base[\s>]/gi,
|
|
76
76
|
/** link tag injection — stylesheet or preload CSRF attacks */
|
|
77
|
-
/<link[\s>]/gi
|
|
77
|
+
/<link[\s>]/gi,
|
|
78
|
+
/** style tag — CSS expression() / behavior: / IE-era attacks. Mirrors
|
|
79
|
+
* Python's xss-style-tag from packages/core/patterns.json. */
|
|
80
|
+
/<style[\s>]/gi
|
|
78
81
|
];
|
|
79
82
|
var XSS_REMOVE_PATTERNS = [
|
|
80
83
|
/** Full script blocks (content + tags) */
|
|
@@ -889,144 +892,6 @@ function detectCommandInjection(input) {
|
|
|
889
892
|
return false;
|
|
890
893
|
}
|
|
891
894
|
|
|
892
|
-
// src/sanitizers/sanitize.ts
|
|
893
|
-
function sanitizeString(value, options = {}) {
|
|
894
|
-
if (typeof value !== "string") return value;
|
|
895
|
-
const maxSize = options.maxSize ?? INPUT.DEFAULT_MAX_SIZE;
|
|
896
|
-
if (value.length > maxSize) {
|
|
897
|
-
throw new InputTooLargeError(maxSize, value.length);
|
|
898
|
-
}
|
|
899
|
-
const reject = options.mode === "reject";
|
|
900
|
-
let result = value;
|
|
901
|
-
if (options.sql !== false) {
|
|
902
|
-
if (reject) {
|
|
903
|
-
if (detectSql(result)) {
|
|
904
|
-
throw new SecurityThreatError("sql_injection", "SQL pattern detected in input");
|
|
905
|
-
}
|
|
906
|
-
} else {
|
|
907
|
-
result = sanitizeSql(result);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
if (options.path !== false) {
|
|
911
|
-
result = sanitizePath(result);
|
|
912
|
-
}
|
|
913
|
-
if (options.command !== false) {
|
|
914
|
-
if (reject) {
|
|
915
|
-
if (detectCommandInjection(result)) {
|
|
916
|
-
throw new SecurityThreatError("command_injection", "Shell metacharacter detected in input");
|
|
917
|
-
}
|
|
918
|
-
} else {
|
|
919
|
-
result = sanitizeCommand(result);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
if (options.xss !== false) {
|
|
923
|
-
result = sanitizeXss(result, false, options.htmlEncode ?? false);
|
|
924
|
-
}
|
|
925
|
-
return result;
|
|
926
|
-
}
|
|
927
|
-
function sanitizeObject(obj, options = {}) {
|
|
928
|
-
if (obj === null || obj === void 0) return obj;
|
|
929
|
-
if (typeof obj === "string") return sanitizeString(obj, options);
|
|
930
|
-
if (typeof obj !== "object") return obj;
|
|
931
|
-
if (Array.isArray(obj)) return obj.map((item) => sanitizeObject(item, options));
|
|
932
|
-
const result = sanitizeObjectDepth(obj, options, 0);
|
|
933
|
-
return options.freeze ? Object.freeze(result) : result;
|
|
934
|
-
}
|
|
935
|
-
function sanitizeObjectDepth(obj, options, depth) {
|
|
936
|
-
if (depth >= INPUT.MAX_RECURSION_DEPTH) return obj;
|
|
937
|
-
const result = {};
|
|
938
|
-
for (const key of Object.keys(obj)) {
|
|
939
|
-
if (options.proto !== false && DANGEROUS_PROTO_KEYS.has(key.toLowerCase())) {
|
|
940
|
-
continue;
|
|
941
|
-
}
|
|
942
|
-
if (options.nosql !== false && NOSQL_DANGEROUS_KEYS.has(key)) {
|
|
943
|
-
continue;
|
|
944
|
-
}
|
|
945
|
-
const sanitizedKey = sanitizeString(key, options);
|
|
946
|
-
const value = obj[key];
|
|
947
|
-
if (value === null || value === void 0) {
|
|
948
|
-
result[sanitizedKey] = value;
|
|
949
|
-
} else if (typeof value === "string") {
|
|
950
|
-
result[sanitizedKey] = sanitizeString(value, options);
|
|
951
|
-
} else if (Array.isArray(value)) {
|
|
952
|
-
result[sanitizedKey] = value.map((item) => sanitizeObject(item, options));
|
|
953
|
-
} else if (typeof value === "object") {
|
|
954
|
-
result[sanitizedKey] = sanitizeObjectDepth(value, options, depth + 1);
|
|
955
|
-
} else {
|
|
956
|
-
result[sanitizedKey] = value;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
return result;
|
|
960
|
-
}
|
|
961
|
-
function createSanitizer(options = {}) {
|
|
962
|
-
return (req, _res, next) => {
|
|
963
|
-
try {
|
|
964
|
-
if (req.body && typeof req.body === "object") {
|
|
965
|
-
req.body = sanitizeObject(req.body, options);
|
|
966
|
-
}
|
|
967
|
-
if (req.query && typeof req.query === "object") {
|
|
968
|
-
const sanitizedQuery = sanitizeObject(req.query, options);
|
|
969
|
-
Object.defineProperty(req, "query", { value: sanitizedQuery, writable: true, configurable: true });
|
|
970
|
-
}
|
|
971
|
-
if (req.params && typeof req.params === "object") {
|
|
972
|
-
const sanitizedParams = sanitizeObject(req.params, options);
|
|
973
|
-
Object.defineProperty(req, "params", { value: sanitizedParams, writable: true, configurable: true });
|
|
974
|
-
}
|
|
975
|
-
next();
|
|
976
|
-
} catch (err) {
|
|
977
|
-
next(err);
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// src/sanitizers/nosql.ts
|
|
983
|
-
function isDangerousNoSqlKey(key) {
|
|
984
|
-
return NOSQL_DANGEROUS_KEYS.has(key);
|
|
985
|
-
}
|
|
986
|
-
function detectNoSqlInjection(obj, maxDepth = 10) {
|
|
987
|
-
if (maxDepth <= 0) return false;
|
|
988
|
-
if (obj === null || typeof obj !== "object") return false;
|
|
989
|
-
if (Array.isArray(obj)) {
|
|
990
|
-
return obj.some((item) => detectNoSqlInjection(item, maxDepth - 1));
|
|
991
|
-
}
|
|
992
|
-
for (const key of Object.keys(obj)) {
|
|
993
|
-
if (isDangerousNoSqlKey(key)) {
|
|
994
|
-
return true;
|
|
995
|
-
}
|
|
996
|
-
const value = obj[key];
|
|
997
|
-
if (typeof value === "object" && value !== null) {
|
|
998
|
-
if (detectNoSqlInjection(value, maxDepth - 1)) {
|
|
999
|
-
return true;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
return false;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// src/sanitizers/prototype.ts
|
|
1007
|
-
function isDangerousProtoKey(key) {
|
|
1008
|
-
return DANGEROUS_PROTO_KEYS.has(key.toLowerCase());
|
|
1009
|
-
}
|
|
1010
|
-
function detectPrototypePollution(obj, maxDepth = 10) {
|
|
1011
|
-
if (maxDepth <= 0) return false;
|
|
1012
|
-
if (obj === null || typeof obj !== "object") return false;
|
|
1013
|
-
if (Array.isArray(obj)) {
|
|
1014
|
-
return obj.some((item) => detectPrototypePollution(item, maxDepth - 1));
|
|
1015
|
-
}
|
|
1016
|
-
for (const key of Object.keys(obj)) {
|
|
1017
|
-
if (DANGEROUS_PROTO_KEYS.has(key.toLowerCase())) {
|
|
1018
|
-
return true;
|
|
1019
|
-
}
|
|
1020
|
-
const value = obj[key];
|
|
1021
|
-
if (typeof value === "object" && value !== null) {
|
|
1022
|
-
if (detectPrototypePollution(value, maxDepth - 1)) {
|
|
1023
|
-
return true;
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
return false;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
895
|
// src/sanitizers/ssti.ts
|
|
1031
896
|
var SSTI_DETECT_PATTERNS = [
|
|
1032
897
|
/** Jinja2 / Twig / Nunjucks: {{ ... }} */
|
|
@@ -1189,6 +1054,208 @@ function detectXxe(input) {
|
|
|
1189
1054
|
return false;
|
|
1190
1055
|
}
|
|
1191
1056
|
|
|
1057
|
+
// src/sanitizers/sanitize.ts
|
|
1058
|
+
function sanitizeString(value, options = {}) {
|
|
1059
|
+
if (typeof value !== "string") return value;
|
|
1060
|
+
const maxSize = options.maxSize ?? INPUT.DEFAULT_MAX_SIZE;
|
|
1061
|
+
if (value.length > maxSize) {
|
|
1062
|
+
throw new InputTooLargeError(maxSize, value.length);
|
|
1063
|
+
}
|
|
1064
|
+
const reject = options.mode === "reject";
|
|
1065
|
+
let result = value;
|
|
1066
|
+
if (options.sql !== false) {
|
|
1067
|
+
if (reject) {
|
|
1068
|
+
if (detectSql(result)) {
|
|
1069
|
+
throw new SecurityThreatError("sql_injection", "SQL pattern detected in input");
|
|
1070
|
+
}
|
|
1071
|
+
} else {
|
|
1072
|
+
result = sanitizeSql(result);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (options.path !== false) {
|
|
1076
|
+
result = sanitizePath(result);
|
|
1077
|
+
}
|
|
1078
|
+
if (options.command !== false) {
|
|
1079
|
+
if (reject) {
|
|
1080
|
+
if (detectCommandInjection(result)) {
|
|
1081
|
+
throw new SecurityThreatError("command_injection", "Shell metacharacter detected in input");
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
result = sanitizeCommand(result);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (options.xss !== false) {
|
|
1088
|
+
result = sanitizeXss(result, false, options.htmlEncode ?? false);
|
|
1089
|
+
}
|
|
1090
|
+
return result;
|
|
1091
|
+
}
|
|
1092
|
+
function sanitizeObject(obj, options = {}) {
|
|
1093
|
+
if (obj === null || obj === void 0) return obj;
|
|
1094
|
+
if (typeof obj === "string") return sanitizeString(obj, options);
|
|
1095
|
+
if (typeof obj !== "object") return obj;
|
|
1096
|
+
if (Array.isArray(obj)) return obj.map((item) => sanitizeObject(item, options));
|
|
1097
|
+
const result = sanitizeObjectDepth(obj, options, 0);
|
|
1098
|
+
return options.freeze ? Object.freeze(result) : result;
|
|
1099
|
+
}
|
|
1100
|
+
function sanitizeObjectDepth(obj, options, depth) {
|
|
1101
|
+
if (depth >= INPUT.MAX_RECURSION_DEPTH) return obj;
|
|
1102
|
+
const result = {};
|
|
1103
|
+
for (const key of Object.keys(obj)) {
|
|
1104
|
+
if (options.proto !== false && DANGEROUS_PROTO_KEYS.has(key.toLowerCase())) {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
if (options.nosql !== false && NOSQL_DANGEROUS_KEYS.has(key)) {
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
const sanitizedKey = sanitizeString(key, options);
|
|
1111
|
+
const value = obj[key];
|
|
1112
|
+
if (value === null || value === void 0) {
|
|
1113
|
+
result[sanitizedKey] = value;
|
|
1114
|
+
} else if (typeof value === "string") {
|
|
1115
|
+
result[sanitizedKey] = sanitizeString(value, options);
|
|
1116
|
+
} else if (Array.isArray(value)) {
|
|
1117
|
+
result[sanitizedKey] = value.map((item) => sanitizeObject(item, options));
|
|
1118
|
+
} else if (typeof value === "object") {
|
|
1119
|
+
result[sanitizedKey] = sanitizeObjectDepth(value, options, depth + 1);
|
|
1120
|
+
} else {
|
|
1121
|
+
result[sanitizedKey] = value;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return result;
|
|
1125
|
+
}
|
|
1126
|
+
function scanThreats(data, depth = 0) {
|
|
1127
|
+
if (depth > INPUT.MAX_RECURSION_DEPTH) return null;
|
|
1128
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
1129
|
+
for (const key of Object.keys(data)) {
|
|
1130
|
+
const lower = key.toLowerCase();
|
|
1131
|
+
if (DANGEROUS_PROTO_KEYS.has(lower)) {
|
|
1132
|
+
return { vector: "prototype", rule: "prototype/match", matchedPattern: key };
|
|
1133
|
+
}
|
|
1134
|
+
if (NOSQL_DANGEROUS_KEYS.has(key)) {
|
|
1135
|
+
return { vector: "nosql", rule: "nosql/match", matchedPattern: key };
|
|
1136
|
+
}
|
|
1137
|
+
const inner = scanThreats(data[key], depth + 1);
|
|
1138
|
+
if (inner) return inner;
|
|
1139
|
+
}
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
if (Array.isArray(data)) {
|
|
1143
|
+
for (const item of data) {
|
|
1144
|
+
const inner = scanThreats(item, depth + 1);
|
|
1145
|
+
if (inner) return inner;
|
|
1146
|
+
}
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
if (typeof data !== "string") return null;
|
|
1150
|
+
const sample = data.slice(0, 80);
|
|
1151
|
+
if (detectXss(data)) {
|
|
1152
|
+
return { vector: "xss", rule: "xss/match", matchedPattern: sample };
|
|
1153
|
+
}
|
|
1154
|
+
if (detectSsti(data)) {
|
|
1155
|
+
return { vector: "ssti", rule: "ssti/match", matchedPattern: sample };
|
|
1156
|
+
}
|
|
1157
|
+
if (detectXxe(data)) {
|
|
1158
|
+
return { vector: "xxe", rule: "xxe/match", matchedPattern: sample };
|
|
1159
|
+
}
|
|
1160
|
+
if (detectSql(data)) {
|
|
1161
|
+
return { vector: "sql", rule: "sql/match", matchedPattern: sample };
|
|
1162
|
+
}
|
|
1163
|
+
if (detectPathTraversal(data)) {
|
|
1164
|
+
return { vector: "path", rule: "path/match", matchedPattern: sample };
|
|
1165
|
+
}
|
|
1166
|
+
if (detectCommandInjection(data)) {
|
|
1167
|
+
return { vector: "command", rule: "command/match", matchedPattern: sample };
|
|
1168
|
+
}
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
function createSanitizer(options = {}) {
|
|
1172
|
+
return (req, res, next) => {
|
|
1173
|
+
try {
|
|
1174
|
+
if (options.block) {
|
|
1175
|
+
const hit = scanThreats(req.body) || scanThreats(req.query) || scanThreats(req.params) || scanThreats(req.path);
|
|
1176
|
+
if (hit) {
|
|
1177
|
+
req.__arcis = {
|
|
1178
|
+
vector: hit.vector,
|
|
1179
|
+
rule: hit.rule,
|
|
1180
|
+
severity: "high",
|
|
1181
|
+
matchedPattern: hit.matchedPattern,
|
|
1182
|
+
reason: `${hit.vector} pattern detected in request`,
|
|
1183
|
+
decision: "deny"
|
|
1184
|
+
};
|
|
1185
|
+
res.status(403).json({
|
|
1186
|
+
error: "Request blocked for security reasons",
|
|
1187
|
+
code: "SECURITY_THREAT",
|
|
1188
|
+
vector: hit.vector
|
|
1189
|
+
});
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (req.body && typeof req.body === "object") {
|
|
1194
|
+
req.body = sanitizeObject(req.body, options);
|
|
1195
|
+
}
|
|
1196
|
+
if (req.query && typeof req.query === "object") {
|
|
1197
|
+
const sanitizedQuery = sanitizeObject(req.query, options);
|
|
1198
|
+
Object.defineProperty(req, "query", { value: sanitizedQuery, writable: true, configurable: true });
|
|
1199
|
+
}
|
|
1200
|
+
if (req.params && typeof req.params === "object") {
|
|
1201
|
+
const sanitizedParams = sanitizeObject(req.params, options);
|
|
1202
|
+
Object.defineProperty(req, "params", { value: sanitizedParams, writable: true, configurable: true });
|
|
1203
|
+
}
|
|
1204
|
+
next();
|
|
1205
|
+
} catch (err) {
|
|
1206
|
+
next(err);
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// src/sanitizers/nosql.ts
|
|
1212
|
+
function isDangerousNoSqlKey(key) {
|
|
1213
|
+
return NOSQL_DANGEROUS_KEYS.has(key);
|
|
1214
|
+
}
|
|
1215
|
+
function detectNoSqlInjection(obj, maxDepth = 10) {
|
|
1216
|
+
if (maxDepth <= 0) return false;
|
|
1217
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
1218
|
+
if (Array.isArray(obj)) {
|
|
1219
|
+
return obj.some((item) => detectNoSqlInjection(item, maxDepth - 1));
|
|
1220
|
+
}
|
|
1221
|
+
for (const key of Object.keys(obj)) {
|
|
1222
|
+
if (isDangerousNoSqlKey(key)) {
|
|
1223
|
+
return true;
|
|
1224
|
+
}
|
|
1225
|
+
const value = obj[key];
|
|
1226
|
+
if (typeof value === "object" && value !== null) {
|
|
1227
|
+
if (detectNoSqlInjection(value, maxDepth - 1)) {
|
|
1228
|
+
return true;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// src/sanitizers/prototype.ts
|
|
1236
|
+
function isDangerousProtoKey(key) {
|
|
1237
|
+
return DANGEROUS_PROTO_KEYS.has(key.toLowerCase());
|
|
1238
|
+
}
|
|
1239
|
+
function detectPrototypePollution(obj, maxDepth = 10) {
|
|
1240
|
+
if (maxDepth <= 0) return false;
|
|
1241
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
1242
|
+
if (Array.isArray(obj)) {
|
|
1243
|
+
return obj.some((item) => detectPrototypePollution(item, maxDepth - 1));
|
|
1244
|
+
}
|
|
1245
|
+
for (const key of Object.keys(obj)) {
|
|
1246
|
+
if (DANGEROUS_PROTO_KEYS.has(key.toLowerCase())) {
|
|
1247
|
+
return true;
|
|
1248
|
+
}
|
|
1249
|
+
const value = obj[key];
|
|
1250
|
+
if (typeof value === "object" && value !== null) {
|
|
1251
|
+
if (detectPrototypePollution(value, maxDepth - 1)) {
|
|
1252
|
+
return true;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1192
1259
|
// src/sanitizers/jsonp.ts
|
|
1193
1260
|
var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/;
|
|
1194
1261
|
var DANGEROUS_CALLBACK_PATTERNS = [
|
|
@@ -2408,11 +2475,16 @@ var MAX_BATCH_SIZE = 500;
|
|
|
2408
2475
|
var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
|
|
2409
2476
|
var MIN_FLUSH_INTERVAL_MS = 500;
|
|
2410
2477
|
var FLUSH_TIMEOUT_MS = 1e4;
|
|
2478
|
+
var DEFAULT_MAX_QUEUE_SIZE = 1e4;
|
|
2411
2479
|
var TelemetryClient = class {
|
|
2412
2480
|
constructor(options) {
|
|
2413
2481
|
this.queue = [];
|
|
2414
2482
|
this.flushing = false;
|
|
2415
2483
|
this.closed = false;
|
|
2484
|
+
// Counts events dropped since the last successful flush. Resets to 0
|
|
2485
|
+
// each flush so onQueueOverflow callbacks see "drops in this window"
|
|
2486
|
+
// rather than a monotonic lifetime counter.
|
|
2487
|
+
this.droppedSinceLastFlush = 0;
|
|
2416
2488
|
if (!options.endpoint || typeof options.endpoint !== "string") {
|
|
2417
2489
|
throw new TypeError("TelemetryClient: `endpoint` is required");
|
|
2418
2490
|
}
|
|
@@ -2428,8 +2500,14 @@ var TelemetryClient = class {
|
|
|
2428
2500
|
options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
2429
2501
|
MIN_FLUSH_INTERVAL_MS
|
|
2430
2502
|
);
|
|
2503
|
+
this.maxQueueSize = Math.max(
|
|
2504
|
+
options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
|
|
2505
|
+
this.batchSize
|
|
2506
|
+
);
|
|
2431
2507
|
this.onError = options.onError ?? (() => {
|
|
2432
2508
|
});
|
|
2509
|
+
this.onQueueOverflow = options.onQueueOverflow ?? (() => {
|
|
2510
|
+
});
|
|
2433
2511
|
this.startTimer();
|
|
2434
2512
|
}
|
|
2435
2513
|
/**
|
|
@@ -2439,6 +2517,15 @@ var TelemetryClient = class {
|
|
|
2439
2517
|
record(event) {
|
|
2440
2518
|
if (this.closed) return;
|
|
2441
2519
|
this.queue.push(event);
|
|
2520
|
+
if (this.queue.length > this.maxQueueSize) {
|
|
2521
|
+
const drop = this.queue.length - this.maxQueueSize;
|
|
2522
|
+
this.queue.splice(0, drop);
|
|
2523
|
+
this.droppedSinceLastFlush += drop;
|
|
2524
|
+
try {
|
|
2525
|
+
this.onQueueOverflow(this.droppedSinceLastFlush);
|
|
2526
|
+
} catch {
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2442
2529
|
if (this.queue.length >= this.batchSize) {
|
|
2443
2530
|
void this.flush();
|
|
2444
2531
|
}
|
|
@@ -2455,6 +2542,7 @@ var TelemetryClient = class {
|
|
|
2455
2542
|
try {
|
|
2456
2543
|
const batch = this.queue.splice(0, this.batchSize);
|
|
2457
2544
|
await this.send(batch);
|
|
2545
|
+
this.droppedSinceLastFlush = 0;
|
|
2458
2546
|
} catch (err) {
|
|
2459
2547
|
this.safeNotify(err);
|
|
2460
2548
|
} finally {
|
|
@@ -2599,7 +2687,10 @@ function arcis(options = {}) {
|
|
|
2599
2687
|
cleanupFns.push(() => rateLimiter.close());
|
|
2600
2688
|
}
|
|
2601
2689
|
if (options.sanitize !== false) {
|
|
2602
|
-
const sanitizeOpts = typeof options.sanitize === "object" ? options.sanitize : {};
|
|
2690
|
+
const sanitizeOpts = typeof options.sanitize === "object" ? { ...options.sanitize } : {};
|
|
2691
|
+
if (options.block && sanitizeOpts.block === void 0) {
|
|
2692
|
+
sanitizeOpts.block = true;
|
|
2693
|
+
}
|
|
2603
2694
|
const sanitizer = createSanitizer(sanitizeOpts);
|
|
2604
2695
|
middlewares.push(telemetryClient ? tapSanitizerThreats(sanitizer) : sanitizer);
|
|
2605
2696
|
}
|
|
@@ -3140,6 +3231,13 @@ function botProtection(options = {}) {
|
|
|
3140
3231
|
return next();
|
|
3141
3232
|
}
|
|
3142
3233
|
if (denySet.has(result.category)) {
|
|
3234
|
+
req.__arcis = {
|
|
3235
|
+
vector: "bot",
|
|
3236
|
+
rule: `bot/${result.category.toLowerCase()}`,
|
|
3237
|
+
severity: "medium",
|
|
3238
|
+
reason: result.name ? `Bot detected: ${result.name}` : "Bot detected",
|
|
3239
|
+
decision: "deny"
|
|
3240
|
+
};
|
|
3143
3241
|
if (onDetected) {
|
|
3144
3242
|
return onDetected(req, res, result);
|
|
3145
3243
|
}
|
|
@@ -3147,6 +3245,13 @@ function botProtection(options = {}) {
|
|
|
3147
3245
|
return;
|
|
3148
3246
|
}
|
|
3149
3247
|
if (defaultAction === "deny") {
|
|
3248
|
+
req.__arcis = {
|
|
3249
|
+
vector: "bot",
|
|
3250
|
+
rule: "bot/uncategorized",
|
|
3251
|
+
severity: "medium",
|
|
3252
|
+
reason: "Uncategorized bot under defaultAction=deny",
|
|
3253
|
+
decision: "deny"
|
|
3254
|
+
};
|
|
3150
3255
|
if (onDetected) {
|
|
3151
3256
|
return onDetected(req, res, result);
|
|
3152
3257
|
}
|
|
@@ -3200,7 +3305,14 @@ function csrfProtection(options = {}) {
|
|
|
3200
3305
|
sameSite: options.cookie?.sameSite ?? "Lax",
|
|
3201
3306
|
domain: options.cookie?.domain
|
|
3202
3307
|
};
|
|
3203
|
-
const defaultOnError = (
|
|
3308
|
+
const defaultOnError = (req, res, _next) => {
|
|
3309
|
+
req.__arcis = {
|
|
3310
|
+
vector: "csrf",
|
|
3311
|
+
rule: "csrf/token-mismatch",
|
|
3312
|
+
severity: "high",
|
|
3313
|
+
reason: "CSRF token missing or invalid",
|
|
3314
|
+
decision: "deny"
|
|
3315
|
+
};
|
|
3204
3316
|
res.status(403).json({
|
|
3205
3317
|
error: "CSRF token validation failed",
|
|
3206
3318
|
message: "Invalid or missing CSRF token. Include the token from the cookie in the X-CSRF-Token header."
|