pompelmi 0.33.0 → 0.34.0

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.
Files changed (42) hide show
  1. package/README.md +351 -987
  2. package/dist/pompelmi.audit.cjs +130 -0
  3. package/dist/pompelmi.audit.cjs.map +1 -0
  4. package/dist/pompelmi.audit.esm.js +109 -0
  5. package/dist/pompelmi.audit.esm.js.map +1 -0
  6. package/dist/pompelmi.browser.cjs +1455 -0
  7. package/dist/pompelmi.browser.cjs.map +1 -0
  8. package/dist/pompelmi.browser.esm.js +1429 -0
  9. package/dist/pompelmi.browser.esm.js.map +1 -0
  10. package/dist/pompelmi.cjs +1333 -3044
  11. package/dist/pompelmi.cjs.map +1 -1
  12. package/dist/pompelmi.esm.js +1327 -3042
  13. package/dist/pompelmi.esm.js.map +1 -1
  14. package/dist/pompelmi.hooks.cjs +75 -0
  15. package/dist/pompelmi.hooks.cjs.map +1 -0
  16. package/dist/pompelmi.hooks.esm.js +72 -0
  17. package/dist/pompelmi.hooks.esm.js.map +1 -0
  18. package/dist/pompelmi.policy-packs.cjs +239 -0
  19. package/dist/pompelmi.policy-packs.cjs.map +1 -0
  20. package/dist/pompelmi.policy-packs.esm.js +231 -0
  21. package/dist/pompelmi.policy-packs.esm.js.map +1 -0
  22. package/dist/pompelmi.quarantine.cjs +315 -0
  23. package/dist/pompelmi.quarantine.cjs.map +1 -0
  24. package/dist/pompelmi.quarantine.esm.js +291 -0
  25. package/dist/pompelmi.quarantine.esm.js.map +1 -0
  26. package/dist/pompelmi.react.cjs +1486 -0
  27. package/dist/pompelmi.react.cjs.map +1 -0
  28. package/dist/pompelmi.react.esm.js +1459 -0
  29. package/dist/pompelmi.react.esm.js.map +1 -0
  30. package/dist/types/audit.d.ts +84 -0
  31. package/dist/types/browser-index.d.ts +28 -2
  32. package/dist/types/config.d.ts +3 -2
  33. package/dist/types/hooks.d.ts +89 -0
  34. package/dist/types/index.d.ts +17 -9
  35. package/dist/types/policy-packs.d.ts +98 -0
  36. package/dist/types/quarantine/index.d.ts +18 -0
  37. package/dist/types/quarantine/storage.d.ts +77 -0
  38. package/dist/types/quarantine/types.d.ts +78 -0
  39. package/dist/types/quarantine/workflow.d.ts +97 -0
  40. package/dist/types/react-index.d.ts +13 -0
  41. package/dist/types/types.d.ts +0 -1
  42. package/package.json +54 -3
@@ -245,32 +245,6 @@ function createPresetScanner(preset, opts = {}) {
245
245
  }
246
246
  return composeScanners(...scanners);
247
247
  }
248
- // Preset configurations
249
- const PRESET_CONFIGS = {
250
- 'basic': {
251
- timeout: 10000
252
- },
253
- 'advanced': {
254
- timeout: 30000,
255
- enableDecompilation: false
256
- },
257
- 'malware-analysis': {
258
- timeout: 60000,
259
- enableDecompilation: true,
260
- decompilationEngine: 'both',
261
- decompilationDepth: 'deep'
262
- },
263
- 'decompilation-basic': {
264
- timeout: 30000,
265
- enableDecompilation: true,
266
- decompilationDepth: 'basic'
267
- },
268
- 'decompilation-deep': {
269
- timeout: 120000,
270
- enableDecompilation: true,
271
- decompilationDepth: 'deep'
272
- }
273
- };
274
248
 
275
249
  /**
276
250
  * Performance monitoring utilities for pompelmi scans
@@ -848,1900 +822,6 @@ function validateFile(file) {
848
822
  return { valid: true };
849
823
  }
850
824
 
851
- var react = {exports: {}};
852
-
853
- var react_production = {};
854
-
855
- /**
856
- * @license React
857
- * react.production.js
858
- *
859
- * Copyright (c) Meta Platforms, Inc. and affiliates.
860
- *
861
- * This source code is licensed under the MIT license found in the
862
- * LICENSE file in the root directory of this source tree.
863
- */
864
-
865
- var hasRequiredReact_production;
866
-
867
- function requireReact_production () {
868
- if (hasRequiredReact_production) return react_production;
869
- hasRequiredReact_production = 1;
870
- var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"),
871
- REACT_PORTAL_TYPE = Symbol.for("react.portal"),
872
- REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"),
873
- REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"),
874
- REACT_PROFILER_TYPE = Symbol.for("react.profiler"),
875
- REACT_CONSUMER_TYPE = Symbol.for("react.consumer"),
876
- REACT_CONTEXT_TYPE = Symbol.for("react.context"),
877
- REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"),
878
- REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"),
879
- REACT_MEMO_TYPE = Symbol.for("react.memo"),
880
- REACT_LAZY_TYPE = Symbol.for("react.lazy"),
881
- REACT_ACTIVITY_TYPE = Symbol.for("react.activity"),
882
- MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
883
- function getIteratorFn(maybeIterable) {
884
- if (null === maybeIterable || "object" !== typeof maybeIterable) return null;
885
- maybeIterable =
886
- (MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL]) ||
887
- maybeIterable["@@iterator"];
888
- return "function" === typeof maybeIterable ? maybeIterable : null;
889
- }
890
- var ReactNoopUpdateQueue = {
891
- isMounted: function () {
892
- return false;
893
- },
894
- enqueueForceUpdate: function () {},
895
- enqueueReplaceState: function () {},
896
- enqueueSetState: function () {}
897
- },
898
- assign = Object.assign,
899
- emptyObject = {};
900
- function Component(props, context, updater) {
901
- this.props = props;
902
- this.context = context;
903
- this.refs = emptyObject;
904
- this.updater = updater || ReactNoopUpdateQueue;
905
- }
906
- Component.prototype.isReactComponent = {};
907
- Component.prototype.setState = function (partialState, callback) {
908
- if (
909
- "object" !== typeof partialState &&
910
- "function" !== typeof partialState &&
911
- null != partialState
912
- )
913
- throw Error(
914
- "takes an object of state variables to update or a function which returns an object of state variables."
915
- );
916
- this.updater.enqueueSetState(this, partialState, callback, "setState");
917
- };
918
- Component.prototype.forceUpdate = function (callback) {
919
- this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
920
- };
921
- function ComponentDummy() {}
922
- ComponentDummy.prototype = Component.prototype;
923
- function PureComponent(props, context, updater) {
924
- this.props = props;
925
- this.context = context;
926
- this.refs = emptyObject;
927
- this.updater = updater || ReactNoopUpdateQueue;
928
- }
929
- var pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
930
- pureComponentPrototype.constructor = PureComponent;
931
- assign(pureComponentPrototype, Component.prototype);
932
- pureComponentPrototype.isPureReactComponent = true;
933
- var isArrayImpl = Array.isArray;
934
- function noop() {}
935
- var ReactSharedInternals = { H: null, A: null, T: null, S: null },
936
- hasOwnProperty = Object.prototype.hasOwnProperty;
937
- function ReactElement(type, key, props) {
938
- var refProp = props.ref;
939
- return {
940
- $$typeof: REACT_ELEMENT_TYPE,
941
- type: type,
942
- key: key,
943
- ref: void 0 !== refProp ? refProp : null,
944
- props: props
945
- };
946
- }
947
- function cloneAndReplaceKey(oldElement, newKey) {
948
- return ReactElement(oldElement.type, newKey, oldElement.props);
949
- }
950
- function isValidElement(object) {
951
- return (
952
- "object" === typeof object &&
953
- null !== object &&
954
- object.$$typeof === REACT_ELEMENT_TYPE
955
- );
956
- }
957
- function escape(key) {
958
- var escaperLookup = { "=": "=0", ":": "=2" };
959
- return (
960
- "$" +
961
- key.replace(/[=:]/g, function (match) {
962
- return escaperLookup[match];
963
- })
964
- );
965
- }
966
- var userProvidedKeyEscapeRegex = /\/+/g;
967
- function getElementKey(element, index) {
968
- return "object" === typeof element && null !== element && null != element.key
969
- ? escape("" + element.key)
970
- : index.toString(36);
971
- }
972
- function resolveThenable(thenable) {
973
- switch (thenable.status) {
974
- case "fulfilled":
975
- return thenable.value;
976
- case "rejected":
977
- throw thenable.reason;
978
- default:
979
- switch (
980
- ("string" === typeof thenable.status
981
- ? thenable.then(noop, noop)
982
- : ((thenable.status = "pending"),
983
- thenable.then(
984
- function (fulfilledValue) {
985
- "pending" === thenable.status &&
986
- ((thenable.status = "fulfilled"),
987
- (thenable.value = fulfilledValue));
988
- },
989
- function (error) {
990
- "pending" === thenable.status &&
991
- ((thenable.status = "rejected"), (thenable.reason = error));
992
- }
993
- )),
994
- thenable.status)
995
- ) {
996
- case "fulfilled":
997
- return thenable.value;
998
- case "rejected":
999
- throw thenable.reason;
1000
- }
1001
- }
1002
- throw thenable;
1003
- }
1004
- function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) {
1005
- var type = typeof children;
1006
- if ("undefined" === type || "boolean" === type) children = null;
1007
- var invokeCallback = false;
1008
- if (null === children) invokeCallback = true;
1009
- else
1010
- switch (type) {
1011
- case "bigint":
1012
- case "string":
1013
- case "number":
1014
- invokeCallback = true;
1015
- break;
1016
- case "object":
1017
- switch (children.$$typeof) {
1018
- case REACT_ELEMENT_TYPE:
1019
- case REACT_PORTAL_TYPE:
1020
- invokeCallback = true;
1021
- break;
1022
- case REACT_LAZY_TYPE:
1023
- return (
1024
- (invokeCallback = children._init),
1025
- mapIntoArray(
1026
- invokeCallback(children._payload),
1027
- array,
1028
- escapedPrefix,
1029
- nameSoFar,
1030
- callback
1031
- )
1032
- );
1033
- }
1034
- }
1035
- if (invokeCallback)
1036
- return (
1037
- (callback = callback(children)),
1038
- (invokeCallback =
1039
- "" === nameSoFar ? "." + getElementKey(children, 0) : nameSoFar),
1040
- isArrayImpl(callback)
1041
- ? ((escapedPrefix = ""),
1042
- null != invokeCallback &&
1043
- (escapedPrefix =
1044
- invokeCallback.replace(userProvidedKeyEscapeRegex, "$&/") + "/"),
1045
- mapIntoArray(callback, array, escapedPrefix, "", function (c) {
1046
- return c;
1047
- }))
1048
- : null != callback &&
1049
- (isValidElement(callback) &&
1050
- (callback = cloneAndReplaceKey(
1051
- callback,
1052
- escapedPrefix +
1053
- (null == callback.key ||
1054
- (children && children.key === callback.key)
1055
- ? ""
1056
- : ("" + callback.key).replace(
1057
- userProvidedKeyEscapeRegex,
1058
- "$&/"
1059
- ) + "/") +
1060
- invokeCallback
1061
- )),
1062
- array.push(callback)),
1063
- 1
1064
- );
1065
- invokeCallback = 0;
1066
- var nextNamePrefix = "" === nameSoFar ? "." : nameSoFar + ":";
1067
- if (isArrayImpl(children))
1068
- for (var i = 0; i < children.length; i++)
1069
- (nameSoFar = children[i]),
1070
- (type = nextNamePrefix + getElementKey(nameSoFar, i)),
1071
- (invokeCallback += mapIntoArray(
1072
- nameSoFar,
1073
- array,
1074
- escapedPrefix,
1075
- type,
1076
- callback
1077
- ));
1078
- else if (((i = getIteratorFn(children)), "function" === typeof i))
1079
- for (
1080
- children = i.call(children), i = 0;
1081
- !(nameSoFar = children.next()).done;
1082
-
1083
- )
1084
- (nameSoFar = nameSoFar.value),
1085
- (type = nextNamePrefix + getElementKey(nameSoFar, i++)),
1086
- (invokeCallback += mapIntoArray(
1087
- nameSoFar,
1088
- array,
1089
- escapedPrefix,
1090
- type,
1091
- callback
1092
- ));
1093
- else if ("object" === type) {
1094
- if ("function" === typeof children.then)
1095
- return mapIntoArray(
1096
- resolveThenable(children),
1097
- array,
1098
- escapedPrefix,
1099
- nameSoFar,
1100
- callback
1101
- );
1102
- array = String(children);
1103
- throw Error(
1104
- "Objects are not valid as a React child (found: " +
1105
- ("[object Object]" === array
1106
- ? "object with keys {" + Object.keys(children).join(", ") + "}"
1107
- : array) +
1108
- "). If you meant to render a collection of children, use an array instead."
1109
- );
1110
- }
1111
- return invokeCallback;
1112
- }
1113
- function mapChildren(children, func, context) {
1114
- if (null == children) return children;
1115
- var result = [],
1116
- count = 0;
1117
- mapIntoArray(children, result, "", "", function (child) {
1118
- return func.call(context, child, count++);
1119
- });
1120
- return result;
1121
- }
1122
- function lazyInitializer(payload) {
1123
- if (-1 === payload._status) {
1124
- var ctor = payload._result;
1125
- ctor = ctor();
1126
- ctor.then(
1127
- function (moduleObject) {
1128
- if (0 === payload._status || -1 === payload._status)
1129
- (payload._status = 1), (payload._result = moduleObject);
1130
- },
1131
- function (error) {
1132
- if (0 === payload._status || -1 === payload._status)
1133
- (payload._status = 2), (payload._result = error);
1134
- }
1135
- );
1136
- -1 === payload._status && ((payload._status = 0), (payload._result = ctor));
1137
- }
1138
- if (1 === payload._status) return payload._result.default;
1139
- throw payload._result;
1140
- }
1141
- var reportGlobalError =
1142
- "function" === typeof reportError
1143
- ? reportError
1144
- : function (error) {
1145
- if (
1146
- "object" === typeof window &&
1147
- "function" === typeof window.ErrorEvent
1148
- ) {
1149
- var event = new window.ErrorEvent("error", {
1150
- bubbles: true,
1151
- cancelable: true,
1152
- message:
1153
- "object" === typeof error &&
1154
- null !== error &&
1155
- "string" === typeof error.message
1156
- ? String(error.message)
1157
- : String(error),
1158
- error: error
1159
- });
1160
- if (!window.dispatchEvent(event)) return;
1161
- } else if (
1162
- "object" === typeof process &&
1163
- "function" === typeof process.emit
1164
- ) {
1165
- process.emit("uncaughtException", error);
1166
- return;
1167
- }
1168
- console.error(error);
1169
- },
1170
- Children = {
1171
- map: mapChildren,
1172
- forEach: function (children, forEachFunc, forEachContext) {
1173
- mapChildren(
1174
- children,
1175
- function () {
1176
- forEachFunc.apply(this, arguments);
1177
- },
1178
- forEachContext
1179
- );
1180
- },
1181
- count: function (children) {
1182
- var n = 0;
1183
- mapChildren(children, function () {
1184
- n++;
1185
- });
1186
- return n;
1187
- },
1188
- toArray: function (children) {
1189
- return (
1190
- mapChildren(children, function (child) {
1191
- return child;
1192
- }) || []
1193
- );
1194
- },
1195
- only: function (children) {
1196
- if (!isValidElement(children))
1197
- throw Error(
1198
- "React.Children.only expected to receive a single React element child."
1199
- );
1200
- return children;
1201
- }
1202
- };
1203
- react_production.Activity = REACT_ACTIVITY_TYPE;
1204
- react_production.Children = Children;
1205
- react_production.Component = Component;
1206
- react_production.Fragment = REACT_FRAGMENT_TYPE;
1207
- react_production.Profiler = REACT_PROFILER_TYPE;
1208
- react_production.PureComponent = PureComponent;
1209
- react_production.StrictMode = REACT_STRICT_MODE_TYPE;
1210
- react_production.Suspense = REACT_SUSPENSE_TYPE;
1211
- react_production.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE =
1212
- ReactSharedInternals;
1213
- react_production.__COMPILER_RUNTIME = {
1214
- __proto__: null,
1215
- c: function (size) {
1216
- return ReactSharedInternals.H.useMemoCache(size);
1217
- }
1218
- };
1219
- react_production.cache = function (fn) {
1220
- return function () {
1221
- return fn.apply(null, arguments);
1222
- };
1223
- };
1224
- react_production.cacheSignal = function () {
1225
- return null;
1226
- };
1227
- react_production.cloneElement = function (element, config, children) {
1228
- if (null === element || void 0 === element)
1229
- throw Error(
1230
- "The argument must be a React element, but you passed " + element + "."
1231
- );
1232
- var props = assign({}, element.props),
1233
- key = element.key;
1234
- if (null != config)
1235
- for (propName in (void 0 !== config.key && (key = "" + config.key), config))
1236
- !hasOwnProperty.call(config, propName) ||
1237
- "key" === propName ||
1238
- "__self" === propName ||
1239
- "__source" === propName ||
1240
- ("ref" === propName && void 0 === config.ref) ||
1241
- (props[propName] = config[propName]);
1242
- var propName = arguments.length - 2;
1243
- if (1 === propName) props.children = children;
1244
- else if (1 < propName) {
1245
- for (var childArray = Array(propName), i = 0; i < propName; i++)
1246
- childArray[i] = arguments[i + 2];
1247
- props.children = childArray;
1248
- }
1249
- return ReactElement(element.type, key, props);
1250
- };
1251
- react_production.createContext = function (defaultValue) {
1252
- defaultValue = {
1253
- $$typeof: REACT_CONTEXT_TYPE,
1254
- _currentValue: defaultValue,
1255
- _currentValue2: defaultValue,
1256
- _threadCount: 0,
1257
- Provider: null,
1258
- Consumer: null
1259
- };
1260
- defaultValue.Provider = defaultValue;
1261
- defaultValue.Consumer = {
1262
- $$typeof: REACT_CONSUMER_TYPE,
1263
- _context: defaultValue
1264
- };
1265
- return defaultValue;
1266
- };
1267
- react_production.createElement = function (type, config, children) {
1268
- var propName,
1269
- props = {},
1270
- key = null;
1271
- if (null != config)
1272
- for (propName in (void 0 !== config.key && (key = "" + config.key), config))
1273
- hasOwnProperty.call(config, propName) &&
1274
- "key" !== propName &&
1275
- "__self" !== propName &&
1276
- "__source" !== propName &&
1277
- (props[propName] = config[propName]);
1278
- var childrenLength = arguments.length - 2;
1279
- if (1 === childrenLength) props.children = children;
1280
- else if (1 < childrenLength) {
1281
- for (var childArray = Array(childrenLength), i = 0; i < childrenLength; i++)
1282
- childArray[i] = arguments[i + 2];
1283
- props.children = childArray;
1284
- }
1285
- if (type && type.defaultProps)
1286
- for (propName in ((childrenLength = type.defaultProps), childrenLength))
1287
- void 0 === props[propName] &&
1288
- (props[propName] = childrenLength[propName]);
1289
- return ReactElement(type, key, props);
1290
- };
1291
- react_production.createRef = function () {
1292
- return { current: null };
1293
- };
1294
- react_production.forwardRef = function (render) {
1295
- return { $$typeof: REACT_FORWARD_REF_TYPE, render: render };
1296
- };
1297
- react_production.isValidElement = isValidElement;
1298
- react_production.lazy = function (ctor) {
1299
- return {
1300
- $$typeof: REACT_LAZY_TYPE,
1301
- _payload: { _status: -1, _result: ctor },
1302
- _init: lazyInitializer
1303
- };
1304
- };
1305
- react_production.memo = function (type, compare) {
1306
- return {
1307
- $$typeof: REACT_MEMO_TYPE,
1308
- type: type,
1309
- compare: void 0 === compare ? null : compare
1310
- };
1311
- };
1312
- react_production.startTransition = function (scope) {
1313
- var prevTransition = ReactSharedInternals.T,
1314
- currentTransition = {};
1315
- ReactSharedInternals.T = currentTransition;
1316
- try {
1317
- var returnValue = scope(),
1318
- onStartTransitionFinish = ReactSharedInternals.S;
1319
- null !== onStartTransitionFinish &&
1320
- onStartTransitionFinish(currentTransition, returnValue);
1321
- "object" === typeof returnValue &&
1322
- null !== returnValue &&
1323
- "function" === typeof returnValue.then &&
1324
- returnValue.then(noop, reportGlobalError);
1325
- } catch (error) {
1326
- reportGlobalError(error);
1327
- } finally {
1328
- null !== prevTransition &&
1329
- null !== currentTransition.types &&
1330
- (prevTransition.types = currentTransition.types),
1331
- (ReactSharedInternals.T = prevTransition);
1332
- }
1333
- };
1334
- react_production.unstable_useCacheRefresh = function () {
1335
- return ReactSharedInternals.H.useCacheRefresh();
1336
- };
1337
- react_production.use = function (usable) {
1338
- return ReactSharedInternals.H.use(usable);
1339
- };
1340
- react_production.useActionState = function (action, initialState, permalink) {
1341
- return ReactSharedInternals.H.useActionState(action, initialState, permalink);
1342
- };
1343
- react_production.useCallback = function (callback, deps) {
1344
- return ReactSharedInternals.H.useCallback(callback, deps);
1345
- };
1346
- react_production.useContext = function (Context) {
1347
- return ReactSharedInternals.H.useContext(Context);
1348
- };
1349
- react_production.useDebugValue = function () {};
1350
- react_production.useDeferredValue = function (value, initialValue) {
1351
- return ReactSharedInternals.H.useDeferredValue(value, initialValue);
1352
- };
1353
- react_production.useEffect = function (create, deps) {
1354
- return ReactSharedInternals.H.useEffect(create, deps);
1355
- };
1356
- react_production.useEffectEvent = function (callback) {
1357
- return ReactSharedInternals.H.useEffectEvent(callback);
1358
- };
1359
- react_production.useId = function () {
1360
- return ReactSharedInternals.H.useId();
1361
- };
1362
- react_production.useImperativeHandle = function (ref, create, deps) {
1363
- return ReactSharedInternals.H.useImperativeHandle(ref, create, deps);
1364
- };
1365
- react_production.useInsertionEffect = function (create, deps) {
1366
- return ReactSharedInternals.H.useInsertionEffect(create, deps);
1367
- };
1368
- react_production.useLayoutEffect = function (create, deps) {
1369
- return ReactSharedInternals.H.useLayoutEffect(create, deps);
1370
- };
1371
- react_production.useMemo = function (create, deps) {
1372
- return ReactSharedInternals.H.useMemo(create, deps);
1373
- };
1374
- react_production.useOptimistic = function (passthrough, reducer) {
1375
- return ReactSharedInternals.H.useOptimistic(passthrough, reducer);
1376
- };
1377
- react_production.useReducer = function (reducer, initialArg, init) {
1378
- return ReactSharedInternals.H.useReducer(reducer, initialArg, init);
1379
- };
1380
- react_production.useRef = function (initialValue) {
1381
- return ReactSharedInternals.H.useRef(initialValue);
1382
- };
1383
- react_production.useState = function (initialState) {
1384
- return ReactSharedInternals.H.useState(initialState);
1385
- };
1386
- react_production.useSyncExternalStore = function (
1387
- subscribe,
1388
- getSnapshot,
1389
- getServerSnapshot
1390
- ) {
1391
- return ReactSharedInternals.H.useSyncExternalStore(
1392
- subscribe,
1393
- getSnapshot,
1394
- getServerSnapshot
1395
- );
1396
- };
1397
- react_production.useTransition = function () {
1398
- return ReactSharedInternals.H.useTransition();
1399
- };
1400
- react_production.version = "19.2.0";
1401
- return react_production;
1402
- }
1403
-
1404
- var react_development = {exports: {}};
1405
-
1406
- /**
1407
- * @license React
1408
- * react.development.js
1409
- *
1410
- * Copyright (c) Meta Platforms, Inc. and affiliates.
1411
- *
1412
- * This source code is licensed under the MIT license found in the
1413
- * LICENSE file in the root directory of this source tree.
1414
- */
1415
- react_development.exports;
1416
-
1417
- var hasRequiredReact_development;
1418
-
1419
- function requireReact_development () {
1420
- if (hasRequiredReact_development) return react_development.exports;
1421
- hasRequiredReact_development = 1;
1422
- (function (module, exports) {
1423
- "production" !== process.env.NODE_ENV &&
1424
- (function () {
1425
- function defineDeprecationWarning(methodName, info) {
1426
- Object.defineProperty(Component.prototype, methodName, {
1427
- get: function () {
1428
- console.warn(
1429
- "%s(...) is deprecated in plain JavaScript React classes. %s",
1430
- info[0],
1431
- info[1]
1432
- );
1433
- }
1434
- });
1435
- }
1436
- function getIteratorFn(maybeIterable) {
1437
- if (null === maybeIterable || "object" !== typeof maybeIterable)
1438
- return null;
1439
- maybeIterable =
1440
- (MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL]) ||
1441
- maybeIterable["@@iterator"];
1442
- return "function" === typeof maybeIterable ? maybeIterable : null;
1443
- }
1444
- function warnNoop(publicInstance, callerName) {
1445
- publicInstance =
1446
- ((publicInstance = publicInstance.constructor) &&
1447
- (publicInstance.displayName || publicInstance.name)) ||
1448
- "ReactClass";
1449
- var warningKey = publicInstance + "." + callerName;
1450
- didWarnStateUpdateForUnmountedComponent[warningKey] ||
1451
- (console.error(
1452
- "Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the %s component.",
1453
- callerName,
1454
- publicInstance
1455
- ),
1456
- (didWarnStateUpdateForUnmountedComponent[warningKey] = true));
1457
- }
1458
- function Component(props, context, updater) {
1459
- this.props = props;
1460
- this.context = context;
1461
- this.refs = emptyObject;
1462
- this.updater = updater || ReactNoopUpdateQueue;
1463
- }
1464
- function ComponentDummy() {}
1465
- function PureComponent(props, context, updater) {
1466
- this.props = props;
1467
- this.context = context;
1468
- this.refs = emptyObject;
1469
- this.updater = updater || ReactNoopUpdateQueue;
1470
- }
1471
- function noop() {}
1472
- function testStringCoercion(value) {
1473
- return "" + value;
1474
- }
1475
- function checkKeyStringCoercion(value) {
1476
- try {
1477
- testStringCoercion(value);
1478
- var JSCompiler_inline_result = !1;
1479
- } catch (e) {
1480
- JSCompiler_inline_result = true;
1481
- }
1482
- if (JSCompiler_inline_result) {
1483
- JSCompiler_inline_result = console;
1484
- var JSCompiler_temp_const = JSCompiler_inline_result.error;
1485
- var JSCompiler_inline_result$jscomp$0 =
1486
- ("function" === typeof Symbol &&
1487
- Symbol.toStringTag &&
1488
- value[Symbol.toStringTag]) ||
1489
- value.constructor.name ||
1490
- "Object";
1491
- JSCompiler_temp_const.call(
1492
- JSCompiler_inline_result,
1493
- "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
1494
- JSCompiler_inline_result$jscomp$0
1495
- );
1496
- return testStringCoercion(value);
1497
- }
1498
- }
1499
- function getComponentNameFromType(type) {
1500
- if (null == type) return null;
1501
- if ("function" === typeof type)
1502
- return type.$$typeof === REACT_CLIENT_REFERENCE
1503
- ? null
1504
- : type.displayName || type.name || null;
1505
- if ("string" === typeof type) return type;
1506
- switch (type) {
1507
- case REACT_FRAGMENT_TYPE:
1508
- return "Fragment";
1509
- case REACT_PROFILER_TYPE:
1510
- return "Profiler";
1511
- case REACT_STRICT_MODE_TYPE:
1512
- return "StrictMode";
1513
- case REACT_SUSPENSE_TYPE:
1514
- return "Suspense";
1515
- case REACT_SUSPENSE_LIST_TYPE:
1516
- return "SuspenseList";
1517
- case REACT_ACTIVITY_TYPE:
1518
- return "Activity";
1519
- }
1520
- if ("object" === typeof type)
1521
- switch (
1522
- ("number" === typeof type.tag &&
1523
- console.error(
1524
- "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
1525
- ),
1526
- type.$$typeof)
1527
- ) {
1528
- case REACT_PORTAL_TYPE:
1529
- return "Portal";
1530
- case REACT_CONTEXT_TYPE:
1531
- return type.displayName || "Context";
1532
- case REACT_CONSUMER_TYPE:
1533
- return (type._context.displayName || "Context") + ".Consumer";
1534
- case REACT_FORWARD_REF_TYPE:
1535
- var innerType = type.render;
1536
- type = type.displayName;
1537
- type ||
1538
- ((type = innerType.displayName || innerType.name || ""),
1539
- (type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef"));
1540
- return type;
1541
- case REACT_MEMO_TYPE:
1542
- return (
1543
- (innerType = type.displayName || null),
1544
- null !== innerType
1545
- ? innerType
1546
- : getComponentNameFromType(type.type) || "Memo"
1547
- );
1548
- case REACT_LAZY_TYPE:
1549
- innerType = type._payload;
1550
- type = type._init;
1551
- try {
1552
- return getComponentNameFromType(type(innerType));
1553
- } catch (x) {}
1554
- }
1555
- return null;
1556
- }
1557
- function getTaskName(type) {
1558
- if (type === REACT_FRAGMENT_TYPE) return "<>";
1559
- if (
1560
- "object" === typeof type &&
1561
- null !== type &&
1562
- type.$$typeof === REACT_LAZY_TYPE
1563
- )
1564
- return "<...>";
1565
- try {
1566
- var name = getComponentNameFromType(type);
1567
- return name ? "<" + name + ">" : "<...>";
1568
- } catch (x) {
1569
- return "<...>";
1570
- }
1571
- }
1572
- function getOwner() {
1573
- var dispatcher = ReactSharedInternals.A;
1574
- return null === dispatcher ? null : dispatcher.getOwner();
1575
- }
1576
- function UnknownOwner() {
1577
- return Error("react-stack-top-frame");
1578
- }
1579
- function hasValidKey(config) {
1580
- if (hasOwnProperty.call(config, "key")) {
1581
- var getter = Object.getOwnPropertyDescriptor(config, "key").get;
1582
- if (getter && getter.isReactWarning) return false;
1583
- }
1584
- return void 0 !== config.key;
1585
- }
1586
- function defineKeyPropWarningGetter(props, displayName) {
1587
- function warnAboutAccessingKey() {
1588
- specialPropKeyWarningShown ||
1589
- ((specialPropKeyWarningShown = true),
1590
- console.error(
1591
- "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
1592
- displayName
1593
- ));
1594
- }
1595
- warnAboutAccessingKey.isReactWarning = true;
1596
- Object.defineProperty(props, "key", {
1597
- get: warnAboutAccessingKey,
1598
- configurable: true
1599
- });
1600
- }
1601
- function elementRefGetterWithDeprecationWarning() {
1602
- var componentName = getComponentNameFromType(this.type);
1603
- didWarnAboutElementRef[componentName] ||
1604
- ((didWarnAboutElementRef[componentName] = true),
1605
- console.error(
1606
- "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
1607
- ));
1608
- componentName = this.props.ref;
1609
- return void 0 !== componentName ? componentName : null;
1610
- }
1611
- function ReactElement(type, key, props, owner, debugStack, debugTask) {
1612
- var refProp = props.ref;
1613
- type = {
1614
- $$typeof: REACT_ELEMENT_TYPE,
1615
- type: type,
1616
- key: key,
1617
- props: props,
1618
- _owner: owner
1619
- };
1620
- null !== (void 0 !== refProp ? refProp : null)
1621
- ? Object.defineProperty(type, "ref", {
1622
- enumerable: false,
1623
- get: elementRefGetterWithDeprecationWarning
1624
- })
1625
- : Object.defineProperty(type, "ref", { enumerable: false, value: null });
1626
- type._store = {};
1627
- Object.defineProperty(type._store, "validated", {
1628
- configurable: false,
1629
- enumerable: false,
1630
- writable: true,
1631
- value: 0
1632
- });
1633
- Object.defineProperty(type, "_debugInfo", {
1634
- configurable: false,
1635
- enumerable: false,
1636
- writable: true,
1637
- value: null
1638
- });
1639
- Object.defineProperty(type, "_debugStack", {
1640
- configurable: false,
1641
- enumerable: false,
1642
- writable: true,
1643
- value: debugStack
1644
- });
1645
- Object.defineProperty(type, "_debugTask", {
1646
- configurable: false,
1647
- enumerable: false,
1648
- writable: true,
1649
- value: debugTask
1650
- });
1651
- Object.freeze && (Object.freeze(type.props), Object.freeze(type));
1652
- return type;
1653
- }
1654
- function cloneAndReplaceKey(oldElement, newKey) {
1655
- newKey = ReactElement(
1656
- oldElement.type,
1657
- newKey,
1658
- oldElement.props,
1659
- oldElement._owner,
1660
- oldElement._debugStack,
1661
- oldElement._debugTask
1662
- );
1663
- oldElement._store &&
1664
- (newKey._store.validated = oldElement._store.validated);
1665
- return newKey;
1666
- }
1667
- function validateChildKeys(node) {
1668
- isValidElement(node)
1669
- ? node._store && (node._store.validated = 1)
1670
- : "object" === typeof node &&
1671
- null !== node &&
1672
- node.$$typeof === REACT_LAZY_TYPE &&
1673
- ("fulfilled" === node._payload.status
1674
- ? isValidElement(node._payload.value) &&
1675
- node._payload.value._store &&
1676
- (node._payload.value._store.validated = 1)
1677
- : node._store && (node._store.validated = 1));
1678
- }
1679
- function isValidElement(object) {
1680
- return (
1681
- "object" === typeof object &&
1682
- null !== object &&
1683
- object.$$typeof === REACT_ELEMENT_TYPE
1684
- );
1685
- }
1686
- function escape(key) {
1687
- var escaperLookup = { "=": "=0", ":": "=2" };
1688
- return (
1689
- "$" +
1690
- key.replace(/[=:]/g, function (match) {
1691
- return escaperLookup[match];
1692
- })
1693
- );
1694
- }
1695
- function getElementKey(element, index) {
1696
- return "object" === typeof element &&
1697
- null !== element &&
1698
- null != element.key
1699
- ? (checkKeyStringCoercion(element.key), escape("" + element.key))
1700
- : index.toString(36);
1701
- }
1702
- function resolveThenable(thenable) {
1703
- switch (thenable.status) {
1704
- case "fulfilled":
1705
- return thenable.value;
1706
- case "rejected":
1707
- throw thenable.reason;
1708
- default:
1709
- switch (
1710
- ("string" === typeof thenable.status
1711
- ? thenable.then(noop, noop)
1712
- : ((thenable.status = "pending"),
1713
- thenable.then(
1714
- function (fulfilledValue) {
1715
- "pending" === thenable.status &&
1716
- ((thenable.status = "fulfilled"),
1717
- (thenable.value = fulfilledValue));
1718
- },
1719
- function (error) {
1720
- "pending" === thenable.status &&
1721
- ((thenable.status = "rejected"),
1722
- (thenable.reason = error));
1723
- }
1724
- )),
1725
- thenable.status)
1726
- ) {
1727
- case "fulfilled":
1728
- return thenable.value;
1729
- case "rejected":
1730
- throw thenable.reason;
1731
- }
1732
- }
1733
- throw thenable;
1734
- }
1735
- function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) {
1736
- var type = typeof children;
1737
- if ("undefined" === type || "boolean" === type) children = null;
1738
- var invokeCallback = false;
1739
- if (null === children) invokeCallback = true;
1740
- else
1741
- switch (type) {
1742
- case "bigint":
1743
- case "string":
1744
- case "number":
1745
- invokeCallback = true;
1746
- break;
1747
- case "object":
1748
- switch (children.$$typeof) {
1749
- case REACT_ELEMENT_TYPE:
1750
- case REACT_PORTAL_TYPE:
1751
- invokeCallback = true;
1752
- break;
1753
- case REACT_LAZY_TYPE:
1754
- return (
1755
- (invokeCallback = children._init),
1756
- mapIntoArray(
1757
- invokeCallback(children._payload),
1758
- array,
1759
- escapedPrefix,
1760
- nameSoFar,
1761
- callback
1762
- )
1763
- );
1764
- }
1765
- }
1766
- if (invokeCallback) {
1767
- invokeCallback = children;
1768
- callback = callback(invokeCallback);
1769
- var childKey =
1770
- "" === nameSoFar ? "." + getElementKey(invokeCallback, 0) : nameSoFar;
1771
- isArrayImpl(callback)
1772
- ? ((escapedPrefix = ""),
1773
- null != childKey &&
1774
- (escapedPrefix =
1775
- childKey.replace(userProvidedKeyEscapeRegex, "$&/") + "/"),
1776
- mapIntoArray(callback, array, escapedPrefix, "", function (c) {
1777
- return c;
1778
- }))
1779
- : null != callback &&
1780
- (isValidElement(callback) &&
1781
- (null != callback.key &&
1782
- ((invokeCallback && invokeCallback.key === callback.key) ||
1783
- checkKeyStringCoercion(callback.key)),
1784
- (escapedPrefix = cloneAndReplaceKey(
1785
- callback,
1786
- escapedPrefix +
1787
- (null == callback.key ||
1788
- (invokeCallback && invokeCallback.key === callback.key)
1789
- ? ""
1790
- : ("" + callback.key).replace(
1791
- userProvidedKeyEscapeRegex,
1792
- "$&/"
1793
- ) + "/") +
1794
- childKey
1795
- )),
1796
- "" !== nameSoFar &&
1797
- null != invokeCallback &&
1798
- isValidElement(invokeCallback) &&
1799
- null == invokeCallback.key &&
1800
- invokeCallback._store &&
1801
- !invokeCallback._store.validated &&
1802
- (escapedPrefix._store.validated = 2),
1803
- (callback = escapedPrefix)),
1804
- array.push(callback));
1805
- return 1;
1806
- }
1807
- invokeCallback = 0;
1808
- childKey = "" === nameSoFar ? "." : nameSoFar + ":";
1809
- if (isArrayImpl(children))
1810
- for (var i = 0; i < children.length; i++)
1811
- (nameSoFar = children[i]),
1812
- (type = childKey + getElementKey(nameSoFar, i)),
1813
- (invokeCallback += mapIntoArray(
1814
- nameSoFar,
1815
- array,
1816
- escapedPrefix,
1817
- type,
1818
- callback
1819
- ));
1820
- else if (((i = getIteratorFn(children)), "function" === typeof i))
1821
- for (
1822
- i === children.entries &&
1823
- (didWarnAboutMaps ||
1824
- console.warn(
1825
- "Using Maps as children is not supported. Use an array of keyed ReactElements instead."
1826
- ),
1827
- (didWarnAboutMaps = true)),
1828
- children = i.call(children),
1829
- i = 0;
1830
- !(nameSoFar = children.next()).done;
1831
-
1832
- )
1833
- (nameSoFar = nameSoFar.value),
1834
- (type = childKey + getElementKey(nameSoFar, i++)),
1835
- (invokeCallback += mapIntoArray(
1836
- nameSoFar,
1837
- array,
1838
- escapedPrefix,
1839
- type,
1840
- callback
1841
- ));
1842
- else if ("object" === type) {
1843
- if ("function" === typeof children.then)
1844
- return mapIntoArray(
1845
- resolveThenable(children),
1846
- array,
1847
- escapedPrefix,
1848
- nameSoFar,
1849
- callback
1850
- );
1851
- array = String(children);
1852
- throw Error(
1853
- "Objects are not valid as a React child (found: " +
1854
- ("[object Object]" === array
1855
- ? "object with keys {" + Object.keys(children).join(", ") + "}"
1856
- : array) +
1857
- "). If you meant to render a collection of children, use an array instead."
1858
- );
1859
- }
1860
- return invokeCallback;
1861
- }
1862
- function mapChildren(children, func, context) {
1863
- if (null == children) return children;
1864
- var result = [],
1865
- count = 0;
1866
- mapIntoArray(children, result, "", "", function (child) {
1867
- return func.call(context, child, count++);
1868
- });
1869
- return result;
1870
- }
1871
- function lazyInitializer(payload) {
1872
- if (-1 === payload._status) {
1873
- var ioInfo = payload._ioInfo;
1874
- null != ioInfo && (ioInfo.start = ioInfo.end = performance.now());
1875
- ioInfo = payload._result;
1876
- var thenable = ioInfo();
1877
- thenable.then(
1878
- function (moduleObject) {
1879
- if (0 === payload._status || -1 === payload._status) {
1880
- payload._status = 1;
1881
- payload._result = moduleObject;
1882
- var _ioInfo = payload._ioInfo;
1883
- null != _ioInfo && (_ioInfo.end = performance.now());
1884
- void 0 === thenable.status &&
1885
- ((thenable.status = "fulfilled"),
1886
- (thenable.value = moduleObject));
1887
- }
1888
- },
1889
- function (error) {
1890
- if (0 === payload._status || -1 === payload._status) {
1891
- payload._status = 2;
1892
- payload._result = error;
1893
- var _ioInfo2 = payload._ioInfo;
1894
- null != _ioInfo2 && (_ioInfo2.end = performance.now());
1895
- void 0 === thenable.status &&
1896
- ((thenable.status = "rejected"), (thenable.reason = error));
1897
- }
1898
- }
1899
- );
1900
- ioInfo = payload._ioInfo;
1901
- if (null != ioInfo) {
1902
- ioInfo.value = thenable;
1903
- var displayName = thenable.displayName;
1904
- "string" === typeof displayName && (ioInfo.name = displayName);
1905
- }
1906
- -1 === payload._status &&
1907
- ((payload._status = 0), (payload._result = thenable));
1908
- }
1909
- if (1 === payload._status)
1910
- return (
1911
- (ioInfo = payload._result),
1912
- void 0 === ioInfo &&
1913
- console.error(
1914
- "lazy: Expected the result of a dynamic import() call. Instead received: %s\n\nYour code should look like: \n const MyComponent = lazy(() => import('./MyComponent'))\n\nDid you accidentally put curly braces around the import?",
1915
- ioInfo
1916
- ),
1917
- "default" in ioInfo ||
1918
- console.error(
1919
- "lazy: Expected the result of a dynamic import() call. Instead received: %s\n\nYour code should look like: \n const MyComponent = lazy(() => import('./MyComponent'))",
1920
- ioInfo
1921
- ),
1922
- ioInfo.default
1923
- );
1924
- throw payload._result;
1925
- }
1926
- function resolveDispatcher() {
1927
- var dispatcher = ReactSharedInternals.H;
1928
- null === dispatcher &&
1929
- console.error(
1930
- "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem."
1931
- );
1932
- return dispatcher;
1933
- }
1934
- function releaseAsyncTransition() {
1935
- ReactSharedInternals.asyncTransitions--;
1936
- }
1937
- function enqueueTask(task) {
1938
- if (null === enqueueTaskImpl)
1939
- try {
1940
- var requireString = ("require" + Math.random()).slice(0, 7);
1941
- enqueueTaskImpl = (module && module[requireString]).call(
1942
- module,
1943
- "timers"
1944
- ).setImmediate;
1945
- } catch (_err) {
1946
- enqueueTaskImpl = function (callback) {
1947
- false === didWarnAboutMessageChannel &&
1948
- ((didWarnAboutMessageChannel = true),
1949
- "undefined" === typeof MessageChannel &&
1950
- console.error(
1951
- "This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."
1952
- ));
1953
- var channel = new MessageChannel();
1954
- channel.port1.onmessage = callback;
1955
- channel.port2.postMessage(void 0);
1956
- };
1957
- }
1958
- return enqueueTaskImpl(task);
1959
- }
1960
- function aggregateErrors(errors) {
1961
- return 1 < errors.length && "function" === typeof AggregateError
1962
- ? new AggregateError(errors)
1963
- : errors[0];
1964
- }
1965
- function popActScope(prevActQueue, prevActScopeDepth) {
1966
- prevActScopeDepth !== actScopeDepth - 1 &&
1967
- console.error(
1968
- "You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "
1969
- );
1970
- actScopeDepth = prevActScopeDepth;
1971
- }
1972
- function recursivelyFlushAsyncActWork(returnValue, resolve, reject) {
1973
- var queue = ReactSharedInternals.actQueue;
1974
- if (null !== queue)
1975
- if (0 !== queue.length)
1976
- try {
1977
- flushActQueue(queue);
1978
- enqueueTask(function () {
1979
- return recursivelyFlushAsyncActWork(returnValue, resolve, reject);
1980
- });
1981
- return;
1982
- } catch (error) {
1983
- ReactSharedInternals.thrownErrors.push(error);
1984
- }
1985
- else ReactSharedInternals.actQueue = null;
1986
- 0 < ReactSharedInternals.thrownErrors.length
1987
- ? ((queue = aggregateErrors(ReactSharedInternals.thrownErrors)),
1988
- (ReactSharedInternals.thrownErrors.length = 0),
1989
- reject(queue))
1990
- : resolve(returnValue);
1991
- }
1992
- function flushActQueue(queue) {
1993
- if (!isFlushing) {
1994
- isFlushing = true;
1995
- var i = 0;
1996
- try {
1997
- for (; i < queue.length; i++) {
1998
- var callback = queue[i];
1999
- do {
2000
- ReactSharedInternals.didUsePromise = !1;
2001
- var continuation = callback(!1);
2002
- if (null !== continuation) {
2003
- if (ReactSharedInternals.didUsePromise) {
2004
- queue[i] = callback;
2005
- queue.splice(0, i);
2006
- return;
2007
- }
2008
- callback = continuation;
2009
- } else break;
2010
- } while (1);
2011
- }
2012
- queue.length = 0;
2013
- } catch (error) {
2014
- queue.splice(0, i + 1), ReactSharedInternals.thrownErrors.push(error);
2015
- } finally {
2016
- isFlushing = false;
2017
- }
2018
- }
2019
- }
2020
- "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
2021
- "function" ===
2022
- typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart &&
2023
- __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
2024
- var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"),
2025
- REACT_PORTAL_TYPE = Symbol.for("react.portal"),
2026
- REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"),
2027
- REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"),
2028
- REACT_PROFILER_TYPE = Symbol.for("react.profiler"),
2029
- REACT_CONSUMER_TYPE = Symbol.for("react.consumer"),
2030
- REACT_CONTEXT_TYPE = Symbol.for("react.context"),
2031
- REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"),
2032
- REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"),
2033
- REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"),
2034
- REACT_MEMO_TYPE = Symbol.for("react.memo"),
2035
- REACT_LAZY_TYPE = Symbol.for("react.lazy"),
2036
- REACT_ACTIVITY_TYPE = Symbol.for("react.activity"),
2037
- MAYBE_ITERATOR_SYMBOL = Symbol.iterator,
2038
- didWarnStateUpdateForUnmountedComponent = {},
2039
- ReactNoopUpdateQueue = {
2040
- isMounted: function () {
2041
- return false;
2042
- },
2043
- enqueueForceUpdate: function (publicInstance) {
2044
- warnNoop(publicInstance, "forceUpdate");
2045
- },
2046
- enqueueReplaceState: function (publicInstance) {
2047
- warnNoop(publicInstance, "replaceState");
2048
- },
2049
- enqueueSetState: function (publicInstance) {
2050
- warnNoop(publicInstance, "setState");
2051
- }
2052
- },
2053
- assign = Object.assign,
2054
- emptyObject = {};
2055
- Object.freeze(emptyObject);
2056
- Component.prototype.isReactComponent = {};
2057
- Component.prototype.setState = function (partialState, callback) {
2058
- if (
2059
- "object" !== typeof partialState &&
2060
- "function" !== typeof partialState &&
2061
- null != partialState
2062
- )
2063
- throw Error(
2064
- "takes an object of state variables to update or a function which returns an object of state variables."
2065
- );
2066
- this.updater.enqueueSetState(this, partialState, callback, "setState");
2067
- };
2068
- Component.prototype.forceUpdate = function (callback) {
2069
- this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
2070
- };
2071
- var deprecatedAPIs = {
2072
- isMounted: [
2073
- "isMounted",
2074
- "Instead, make sure to clean up subscriptions and pending requests in componentWillUnmount to prevent memory leaks."
2075
- ],
2076
- replaceState: [
2077
- "replaceState",
2078
- "Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236)."
2079
- ]
2080
- };
2081
- for (fnName in deprecatedAPIs)
2082
- deprecatedAPIs.hasOwnProperty(fnName) &&
2083
- defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
2084
- ComponentDummy.prototype = Component.prototype;
2085
- deprecatedAPIs = PureComponent.prototype = new ComponentDummy();
2086
- deprecatedAPIs.constructor = PureComponent;
2087
- assign(deprecatedAPIs, Component.prototype);
2088
- deprecatedAPIs.isPureReactComponent = true;
2089
- var isArrayImpl = Array.isArray,
2090
- REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"),
2091
- ReactSharedInternals = {
2092
- H: null,
2093
- A: null,
2094
- T: null,
2095
- S: null,
2096
- actQueue: null,
2097
- asyncTransitions: 0,
2098
- isBatchingLegacy: false,
2099
- didScheduleLegacyUpdate: false,
2100
- didUsePromise: false,
2101
- thrownErrors: [],
2102
- getCurrentStack: null,
2103
- recentlyCreatedOwnerStacks: 0
2104
- },
2105
- hasOwnProperty = Object.prototype.hasOwnProperty,
2106
- createTask = console.createTask
2107
- ? console.createTask
2108
- : function () {
2109
- return null;
2110
- };
2111
- deprecatedAPIs = {
2112
- react_stack_bottom_frame: function (callStackForError) {
2113
- return callStackForError();
2114
- }
2115
- };
2116
- var specialPropKeyWarningShown, didWarnAboutOldJSXRuntime;
2117
- var didWarnAboutElementRef = {};
2118
- var unknownOwnerDebugStack = deprecatedAPIs.react_stack_bottom_frame.bind(
2119
- deprecatedAPIs,
2120
- UnknownOwner
2121
- )();
2122
- var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
2123
- var didWarnAboutMaps = false,
2124
- userProvidedKeyEscapeRegex = /\/+/g,
2125
- reportGlobalError =
2126
- "function" === typeof reportError
2127
- ? reportError
2128
- : function (error) {
2129
- if (
2130
- "object" === typeof window &&
2131
- "function" === typeof window.ErrorEvent
2132
- ) {
2133
- var event = new window.ErrorEvent("error", {
2134
- bubbles: true,
2135
- cancelable: true,
2136
- message:
2137
- "object" === typeof error &&
2138
- null !== error &&
2139
- "string" === typeof error.message
2140
- ? String(error.message)
2141
- : String(error),
2142
- error: error
2143
- });
2144
- if (!window.dispatchEvent(event)) return;
2145
- } else if (
2146
- "object" === typeof process &&
2147
- "function" === typeof process.emit
2148
- ) {
2149
- process.emit("uncaughtException", error);
2150
- return;
2151
- }
2152
- console.error(error);
2153
- },
2154
- didWarnAboutMessageChannel = false,
2155
- enqueueTaskImpl = null,
2156
- actScopeDepth = 0,
2157
- didWarnNoAwaitAct = false,
2158
- isFlushing = false,
2159
- queueSeveralMicrotasks =
2160
- "function" === typeof queueMicrotask
2161
- ? function (callback) {
2162
- queueMicrotask(function () {
2163
- return queueMicrotask(callback);
2164
- });
2165
- }
2166
- : enqueueTask;
2167
- deprecatedAPIs = Object.freeze({
2168
- __proto__: null,
2169
- c: function (size) {
2170
- return resolveDispatcher().useMemoCache(size);
2171
- }
2172
- });
2173
- var fnName = {
2174
- map: mapChildren,
2175
- forEach: function (children, forEachFunc, forEachContext) {
2176
- mapChildren(
2177
- children,
2178
- function () {
2179
- forEachFunc.apply(this, arguments);
2180
- },
2181
- forEachContext
2182
- );
2183
- },
2184
- count: function (children) {
2185
- var n = 0;
2186
- mapChildren(children, function () {
2187
- n++;
2188
- });
2189
- return n;
2190
- },
2191
- toArray: function (children) {
2192
- return (
2193
- mapChildren(children, function (child) {
2194
- return child;
2195
- }) || []
2196
- );
2197
- },
2198
- only: function (children) {
2199
- if (!isValidElement(children))
2200
- throw Error(
2201
- "React.Children.only expected to receive a single React element child."
2202
- );
2203
- return children;
2204
- }
2205
- };
2206
- exports.Activity = REACT_ACTIVITY_TYPE;
2207
- exports.Children = fnName;
2208
- exports.Component = Component;
2209
- exports.Fragment = REACT_FRAGMENT_TYPE;
2210
- exports.Profiler = REACT_PROFILER_TYPE;
2211
- exports.PureComponent = PureComponent;
2212
- exports.StrictMode = REACT_STRICT_MODE_TYPE;
2213
- exports.Suspense = REACT_SUSPENSE_TYPE;
2214
- exports.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE =
2215
- ReactSharedInternals;
2216
- exports.__COMPILER_RUNTIME = deprecatedAPIs;
2217
- exports.act = function (callback) {
2218
- var prevActQueue = ReactSharedInternals.actQueue,
2219
- prevActScopeDepth = actScopeDepth;
2220
- actScopeDepth++;
2221
- var queue = (ReactSharedInternals.actQueue =
2222
- null !== prevActQueue ? prevActQueue : []),
2223
- didAwaitActCall = false;
2224
- try {
2225
- var result = callback();
2226
- } catch (error) {
2227
- ReactSharedInternals.thrownErrors.push(error);
2228
- }
2229
- if (0 < ReactSharedInternals.thrownErrors.length)
2230
- throw (
2231
- (popActScope(prevActQueue, prevActScopeDepth),
2232
- (callback = aggregateErrors(ReactSharedInternals.thrownErrors)),
2233
- (ReactSharedInternals.thrownErrors.length = 0),
2234
- callback)
2235
- );
2236
- if (
2237
- null !== result &&
2238
- "object" === typeof result &&
2239
- "function" === typeof result.then
2240
- ) {
2241
- var thenable = result;
2242
- queueSeveralMicrotasks(function () {
2243
- didAwaitActCall ||
2244
- didWarnNoAwaitAct ||
2245
- ((didWarnNoAwaitAct = true),
2246
- console.error(
2247
- "You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"
2248
- ));
2249
- });
2250
- return {
2251
- then: function (resolve, reject) {
2252
- didAwaitActCall = true;
2253
- thenable.then(
2254
- function (returnValue) {
2255
- popActScope(prevActQueue, prevActScopeDepth);
2256
- if (0 === prevActScopeDepth) {
2257
- try {
2258
- flushActQueue(queue),
2259
- enqueueTask(function () {
2260
- return recursivelyFlushAsyncActWork(
2261
- returnValue,
2262
- resolve,
2263
- reject
2264
- );
2265
- });
2266
- } catch (error$0) {
2267
- ReactSharedInternals.thrownErrors.push(error$0);
2268
- }
2269
- if (0 < ReactSharedInternals.thrownErrors.length) {
2270
- var _thrownError = aggregateErrors(
2271
- ReactSharedInternals.thrownErrors
2272
- );
2273
- ReactSharedInternals.thrownErrors.length = 0;
2274
- reject(_thrownError);
2275
- }
2276
- } else resolve(returnValue);
2277
- },
2278
- function (error) {
2279
- popActScope(prevActQueue, prevActScopeDepth);
2280
- 0 < ReactSharedInternals.thrownErrors.length
2281
- ? ((error = aggregateErrors(
2282
- ReactSharedInternals.thrownErrors
2283
- )),
2284
- (ReactSharedInternals.thrownErrors.length = 0),
2285
- reject(error))
2286
- : reject(error);
2287
- }
2288
- );
2289
- }
2290
- };
2291
- }
2292
- var returnValue$jscomp$0 = result;
2293
- popActScope(prevActQueue, prevActScopeDepth);
2294
- 0 === prevActScopeDepth &&
2295
- (flushActQueue(queue),
2296
- 0 !== queue.length &&
2297
- queueSeveralMicrotasks(function () {
2298
- didAwaitActCall ||
2299
- didWarnNoAwaitAct ||
2300
- ((didWarnNoAwaitAct = true),
2301
- console.error(
2302
- "A component suspended inside an `act` scope, but the `act` call was not awaited. When testing React components that depend on asynchronous data, you must await the result:\n\nawait act(() => ...)"
2303
- ));
2304
- }),
2305
- (ReactSharedInternals.actQueue = null));
2306
- if (0 < ReactSharedInternals.thrownErrors.length)
2307
- throw (
2308
- ((callback = aggregateErrors(ReactSharedInternals.thrownErrors)),
2309
- (ReactSharedInternals.thrownErrors.length = 0),
2310
- callback)
2311
- );
2312
- return {
2313
- then: function (resolve, reject) {
2314
- didAwaitActCall = true;
2315
- 0 === prevActScopeDepth
2316
- ? ((ReactSharedInternals.actQueue = queue),
2317
- enqueueTask(function () {
2318
- return recursivelyFlushAsyncActWork(
2319
- returnValue$jscomp$0,
2320
- resolve,
2321
- reject
2322
- );
2323
- }))
2324
- : resolve(returnValue$jscomp$0);
2325
- }
2326
- };
2327
- };
2328
- exports.cache = function (fn) {
2329
- return function () {
2330
- return fn.apply(null, arguments);
2331
- };
2332
- };
2333
- exports.cacheSignal = function () {
2334
- return null;
2335
- };
2336
- exports.captureOwnerStack = function () {
2337
- var getCurrentStack = ReactSharedInternals.getCurrentStack;
2338
- return null === getCurrentStack ? null : getCurrentStack();
2339
- };
2340
- exports.cloneElement = function (element, config, children) {
2341
- if (null === element || void 0 === element)
2342
- throw Error(
2343
- "The argument must be a React element, but you passed " +
2344
- element +
2345
- "."
2346
- );
2347
- var props = assign({}, element.props),
2348
- key = element.key,
2349
- owner = element._owner;
2350
- if (null != config) {
2351
- var JSCompiler_inline_result;
2352
- a: {
2353
- if (
2354
- hasOwnProperty.call(config, "ref") &&
2355
- (JSCompiler_inline_result = Object.getOwnPropertyDescriptor(
2356
- config,
2357
- "ref"
2358
- ).get) &&
2359
- JSCompiler_inline_result.isReactWarning
2360
- ) {
2361
- JSCompiler_inline_result = false;
2362
- break a;
2363
- }
2364
- JSCompiler_inline_result = void 0 !== config.ref;
2365
- }
2366
- JSCompiler_inline_result && (owner = getOwner());
2367
- hasValidKey(config) &&
2368
- (checkKeyStringCoercion(config.key), (key = "" + config.key));
2369
- for (propName in config)
2370
- !hasOwnProperty.call(config, propName) ||
2371
- "key" === propName ||
2372
- "__self" === propName ||
2373
- "__source" === propName ||
2374
- ("ref" === propName && void 0 === config.ref) ||
2375
- (props[propName] = config[propName]);
2376
- }
2377
- var propName = arguments.length - 2;
2378
- if (1 === propName) props.children = children;
2379
- else if (1 < propName) {
2380
- JSCompiler_inline_result = Array(propName);
2381
- for (var i = 0; i < propName; i++)
2382
- JSCompiler_inline_result[i] = arguments[i + 2];
2383
- props.children = JSCompiler_inline_result;
2384
- }
2385
- props = ReactElement(
2386
- element.type,
2387
- key,
2388
- props,
2389
- owner,
2390
- element._debugStack,
2391
- element._debugTask
2392
- );
2393
- for (key = 2; key < arguments.length; key++)
2394
- validateChildKeys(arguments[key]);
2395
- return props;
2396
- };
2397
- exports.createContext = function (defaultValue) {
2398
- defaultValue = {
2399
- $$typeof: REACT_CONTEXT_TYPE,
2400
- _currentValue: defaultValue,
2401
- _currentValue2: defaultValue,
2402
- _threadCount: 0,
2403
- Provider: null,
2404
- Consumer: null
2405
- };
2406
- defaultValue.Provider = defaultValue;
2407
- defaultValue.Consumer = {
2408
- $$typeof: REACT_CONSUMER_TYPE,
2409
- _context: defaultValue
2410
- };
2411
- defaultValue._currentRenderer = null;
2412
- defaultValue._currentRenderer2 = null;
2413
- return defaultValue;
2414
- };
2415
- exports.createElement = function (type, config, children) {
2416
- for (var i = 2; i < arguments.length; i++)
2417
- validateChildKeys(arguments[i]);
2418
- i = {};
2419
- var key = null;
2420
- if (null != config)
2421
- for (propName in (didWarnAboutOldJSXRuntime ||
2422
- !("__self" in config) ||
2423
- "key" in config ||
2424
- ((didWarnAboutOldJSXRuntime = true),
2425
- console.warn(
2426
- "Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance: https://react.dev/link/new-jsx-transform"
2427
- )),
2428
- hasValidKey(config) &&
2429
- (checkKeyStringCoercion(config.key), (key = "" + config.key)),
2430
- config))
2431
- hasOwnProperty.call(config, propName) &&
2432
- "key" !== propName &&
2433
- "__self" !== propName &&
2434
- "__source" !== propName &&
2435
- (i[propName] = config[propName]);
2436
- var childrenLength = arguments.length - 2;
2437
- if (1 === childrenLength) i.children = children;
2438
- else if (1 < childrenLength) {
2439
- for (
2440
- var childArray = Array(childrenLength), _i = 0;
2441
- _i < childrenLength;
2442
- _i++
2443
- )
2444
- childArray[_i] = arguments[_i + 2];
2445
- Object.freeze && Object.freeze(childArray);
2446
- i.children = childArray;
2447
- }
2448
- if (type && type.defaultProps)
2449
- for (propName in ((childrenLength = type.defaultProps), childrenLength))
2450
- void 0 === i[propName] && (i[propName] = childrenLength[propName]);
2451
- key &&
2452
- defineKeyPropWarningGetter(
2453
- i,
2454
- "function" === typeof type
2455
- ? type.displayName || type.name || "Unknown"
2456
- : type
2457
- );
2458
- var propName = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;
2459
- return ReactElement(
2460
- type,
2461
- key,
2462
- i,
2463
- getOwner(),
2464
- propName ? Error("react-stack-top-frame") : unknownOwnerDebugStack,
2465
- propName ? createTask(getTaskName(type)) : unknownOwnerDebugTask
2466
- );
2467
- };
2468
- exports.createRef = function () {
2469
- var refObject = { current: null };
2470
- Object.seal(refObject);
2471
- return refObject;
2472
- };
2473
- exports.forwardRef = function (render) {
2474
- null != render && render.$$typeof === REACT_MEMO_TYPE
2475
- ? console.error(
2476
- "forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."
2477
- )
2478
- : "function" !== typeof render
2479
- ? console.error(
2480
- "forwardRef requires a render function but was given %s.",
2481
- null === render ? "null" : typeof render
2482
- )
2483
- : 0 !== render.length &&
2484
- 2 !== render.length &&
2485
- console.error(
2486
- "forwardRef render functions accept exactly two parameters: props and ref. %s",
2487
- 1 === render.length
2488
- ? "Did you forget to use the ref parameter?"
2489
- : "Any additional parameter will be undefined."
2490
- );
2491
- null != render &&
2492
- null != render.defaultProps &&
2493
- console.error(
2494
- "forwardRef render functions do not support defaultProps. Did you accidentally pass a React component?"
2495
- );
2496
- var elementType = { $$typeof: REACT_FORWARD_REF_TYPE, render: render },
2497
- ownName;
2498
- Object.defineProperty(elementType, "displayName", {
2499
- enumerable: false,
2500
- configurable: true,
2501
- get: function () {
2502
- return ownName;
2503
- },
2504
- set: function (name) {
2505
- ownName = name;
2506
- render.name ||
2507
- render.displayName ||
2508
- (Object.defineProperty(render, "name", { value: name }),
2509
- (render.displayName = name));
2510
- }
2511
- });
2512
- return elementType;
2513
- };
2514
- exports.isValidElement = isValidElement;
2515
- exports.lazy = function (ctor) {
2516
- ctor = { _status: -1, _result: ctor };
2517
- var lazyType = {
2518
- $$typeof: REACT_LAZY_TYPE,
2519
- _payload: ctor,
2520
- _init: lazyInitializer
2521
- },
2522
- ioInfo = {
2523
- name: "lazy",
2524
- start: -1,
2525
- end: -1,
2526
- value: null,
2527
- owner: null,
2528
- debugStack: Error("react-stack-top-frame"),
2529
- debugTask: console.createTask ? console.createTask("lazy()") : null
2530
- };
2531
- ctor._ioInfo = ioInfo;
2532
- lazyType._debugInfo = [{ awaited: ioInfo }];
2533
- return lazyType;
2534
- };
2535
- exports.memo = function (type, compare) {
2536
- null == type &&
2537
- console.error(
2538
- "memo: The first argument must be a component. Instead received: %s",
2539
- null === type ? "null" : typeof type
2540
- );
2541
- compare = {
2542
- $$typeof: REACT_MEMO_TYPE,
2543
- type: type,
2544
- compare: void 0 === compare ? null : compare
2545
- };
2546
- var ownName;
2547
- Object.defineProperty(compare, "displayName", {
2548
- enumerable: false,
2549
- configurable: true,
2550
- get: function () {
2551
- return ownName;
2552
- },
2553
- set: function (name) {
2554
- ownName = name;
2555
- type.name ||
2556
- type.displayName ||
2557
- (Object.defineProperty(type, "name", { value: name }),
2558
- (type.displayName = name));
2559
- }
2560
- });
2561
- return compare;
2562
- };
2563
- exports.startTransition = function (scope) {
2564
- var prevTransition = ReactSharedInternals.T,
2565
- currentTransition = {};
2566
- currentTransition._updatedFibers = new Set();
2567
- ReactSharedInternals.T = currentTransition;
2568
- try {
2569
- var returnValue = scope(),
2570
- onStartTransitionFinish = ReactSharedInternals.S;
2571
- null !== onStartTransitionFinish &&
2572
- onStartTransitionFinish(currentTransition, returnValue);
2573
- "object" === typeof returnValue &&
2574
- null !== returnValue &&
2575
- "function" === typeof returnValue.then &&
2576
- (ReactSharedInternals.asyncTransitions++,
2577
- returnValue.then(releaseAsyncTransition, releaseAsyncTransition),
2578
- returnValue.then(noop, reportGlobalError));
2579
- } catch (error) {
2580
- reportGlobalError(error);
2581
- } finally {
2582
- null === prevTransition &&
2583
- currentTransition._updatedFibers &&
2584
- ((scope = currentTransition._updatedFibers.size),
2585
- currentTransition._updatedFibers.clear(),
2586
- 10 < scope &&
2587
- console.warn(
2588
- "Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table."
2589
- )),
2590
- null !== prevTransition &&
2591
- null !== currentTransition.types &&
2592
- (null !== prevTransition.types &&
2593
- prevTransition.types !== currentTransition.types &&
2594
- console.error(
2595
- "We expected inner Transitions to have transferred the outer types set and that you cannot add to the outer Transition while inside the inner.This is a bug in React."
2596
- ),
2597
- (prevTransition.types = currentTransition.types)),
2598
- (ReactSharedInternals.T = prevTransition);
2599
- }
2600
- };
2601
- exports.unstable_useCacheRefresh = function () {
2602
- return resolveDispatcher().useCacheRefresh();
2603
- };
2604
- exports.use = function (usable) {
2605
- return resolveDispatcher().use(usable);
2606
- };
2607
- exports.useActionState = function (action, initialState, permalink) {
2608
- return resolveDispatcher().useActionState(
2609
- action,
2610
- initialState,
2611
- permalink
2612
- );
2613
- };
2614
- exports.useCallback = function (callback, deps) {
2615
- return resolveDispatcher().useCallback(callback, deps);
2616
- };
2617
- exports.useContext = function (Context) {
2618
- var dispatcher = resolveDispatcher();
2619
- Context.$$typeof === REACT_CONSUMER_TYPE &&
2620
- console.error(
2621
- "Calling useContext(Context.Consumer) is not supported and will cause bugs. Did you mean to call useContext(Context) instead?"
2622
- );
2623
- return dispatcher.useContext(Context);
2624
- };
2625
- exports.useDebugValue = function (value, formatterFn) {
2626
- return resolveDispatcher().useDebugValue(value, formatterFn);
2627
- };
2628
- exports.useDeferredValue = function (value, initialValue) {
2629
- return resolveDispatcher().useDeferredValue(value, initialValue);
2630
- };
2631
- exports.useEffect = function (create, deps) {
2632
- null == create &&
2633
- console.warn(
2634
- "React Hook useEffect requires an effect callback. Did you forget to pass a callback to the hook?"
2635
- );
2636
- return resolveDispatcher().useEffect(create, deps);
2637
- };
2638
- exports.useEffectEvent = function (callback) {
2639
- return resolveDispatcher().useEffectEvent(callback);
2640
- };
2641
- exports.useId = function () {
2642
- return resolveDispatcher().useId();
2643
- };
2644
- exports.useImperativeHandle = function (ref, create, deps) {
2645
- return resolveDispatcher().useImperativeHandle(ref, create, deps);
2646
- };
2647
- exports.useInsertionEffect = function (create, deps) {
2648
- null == create &&
2649
- console.warn(
2650
- "React Hook useInsertionEffect requires an effect callback. Did you forget to pass a callback to the hook?"
2651
- );
2652
- return resolveDispatcher().useInsertionEffect(create, deps);
2653
- };
2654
- exports.useLayoutEffect = function (create, deps) {
2655
- null == create &&
2656
- console.warn(
2657
- "React Hook useLayoutEffect requires an effect callback. Did you forget to pass a callback to the hook?"
2658
- );
2659
- return resolveDispatcher().useLayoutEffect(create, deps);
2660
- };
2661
- exports.useMemo = function (create, deps) {
2662
- return resolveDispatcher().useMemo(create, deps);
2663
- };
2664
- exports.useOptimistic = function (passthrough, reducer) {
2665
- return resolveDispatcher().useOptimistic(passthrough, reducer);
2666
- };
2667
- exports.useReducer = function (reducer, initialArg, init) {
2668
- return resolveDispatcher().useReducer(reducer, initialArg, init);
2669
- };
2670
- exports.useRef = function (initialValue) {
2671
- return resolveDispatcher().useRef(initialValue);
2672
- };
2673
- exports.useState = function (initialState) {
2674
- return resolveDispatcher().useState(initialState);
2675
- };
2676
- exports.useSyncExternalStore = function (
2677
- subscribe,
2678
- getSnapshot,
2679
- getServerSnapshot
2680
- ) {
2681
- return resolveDispatcher().useSyncExternalStore(
2682
- subscribe,
2683
- getSnapshot,
2684
- getServerSnapshot
2685
- );
2686
- };
2687
- exports.useTransition = function () {
2688
- return resolveDispatcher().useTransition();
2689
- };
2690
- exports.version = "19.2.0";
2691
- "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
2692
- "function" ===
2693
- typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
2694
- __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
2695
- })();
2696
- } (react_development, react_development.exports));
2697
- return react_development.exports;
2698
- }
2699
-
2700
- var hasRequiredReact;
2701
-
2702
- function requireReact () {
2703
- if (hasRequiredReact) return react.exports;
2704
- hasRequiredReact = 1;
2705
-
2706
- if (process.env.NODE_ENV === 'production') {
2707
- react.exports = requireReact_production();
2708
- } else {
2709
- react.exports = requireReact_development();
2710
- }
2711
- return react.exports;
2712
- }
2713
-
2714
- var reactExports = requireReact();
2715
-
2716
- /**
2717
- * React Hook: handles <input type="file" onChange> with validation + scanning.
2718
- */
2719
- function useFileScanner() {
2720
- const [results, setResults] = reactExports.useState([]);
2721
- const [errors, setErrors] = reactExports.useState([]);
2722
- const onChange = reactExports.useCallback(async (e) => {
2723
- const fileList = Array.from(e.target.files || []);
2724
- const good = [];
2725
- const bad = [];
2726
- for (const file of fileList) {
2727
- const { valid, error } = validateFile(file);
2728
- if (valid)
2729
- good.push(file);
2730
- else
2731
- bad.push({ file, error: error });
2732
- }
2733
- setErrors(bad);
2734
- if (good.length) {
2735
- const scanned = await scanFiles(good);
2736
- setResults(scanned.map((r, i) => ({ file: good[i], report: r })));
2737
- }
2738
- else {
2739
- setResults([]);
2740
- }
2741
- }, []);
2742
- return { results, errors, onChange };
2743
- }
2744
-
2745
825
  async function createRemoteEngine(opts) {
2746
826
  const { endpoint, headers = {}, rulesField = 'rules', fileField = 'file', mode = 'multipart', rulesAsBase64 = false, } = opts;
2747
827
  const engine = {
@@ -2830,6 +910,355 @@ async function scanFilesWithRemoteYara(files, rulesSource, remote) {
2830
910
  return results;
2831
911
  }
2832
912
 
913
+ const SIG_CEN = 0x02014b50;
914
+ const DEFAULTS = {
915
+ maxEntries: 1000,
916
+ maxTotalUncompressedBytes: 500 * 1024 * 1024,
917
+ maxEntryNameLength: 255,
918
+ maxCompressionRatio: 1000,
919
+ eocdSearchWindow: 70000,
920
+ };
921
+ function r16(buf, off) {
922
+ return buf.readUInt16LE(off);
923
+ }
924
+ function r32(buf, off) {
925
+ return buf.readUInt32LE(off);
926
+ }
927
+ function isZipLike(buf) {
928
+ // local file header at start is common
929
+ return buf.length >= 4 && buf[0] === 0x50 && buf[1] === 0x4b && buf[2] === 0x03 && buf[3] === 0x04;
930
+ }
931
+ function lastIndexOfEOCD(buf, window) {
932
+ const sig = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
933
+ const start = Math.max(0, buf.length - window);
934
+ const idx = buf.lastIndexOf(sig, Math.min(buf.length - sig.length, buf.length - 1));
935
+ return idx >= start ? idx : -1;
936
+ }
937
+ function hasTraversal(name) {
938
+ return name.includes('../') || name.includes('..\\') || name.startsWith('/') || /^[A-Za-z]:/.test(name);
939
+ }
940
+ function createZipBombGuard(opts = {}) {
941
+ const cfg = { ...DEFAULTS, ...opts };
942
+ return {
943
+ async scan(input) {
944
+ const buf = Buffer.from(input);
945
+ const matches = [];
946
+ if (!isZipLike(buf))
947
+ return matches;
948
+ // Find EOCD near the end
949
+ const eocdPos = lastIndexOfEOCD(buf, cfg.eocdSearchWindow);
950
+ if (eocdPos < 0 || eocdPos + 22 > buf.length) {
951
+ // ZIP but no EOCD — malformed or polyglot → suspicious
952
+ matches.push({ rule: 'zip_eocd_not_found', severity: 'medium' });
953
+ return matches;
954
+ }
955
+ const totalEntries = r16(buf, eocdPos + 10);
956
+ const cdSize = r32(buf, eocdPos + 12);
957
+ const cdOffset = r32(buf, eocdPos + 16);
958
+ // Bounds check
959
+ if (cdOffset + cdSize > buf.length) {
960
+ matches.push({ rule: 'zip_cd_out_of_bounds', severity: 'medium' });
961
+ return matches;
962
+ }
963
+ // Iterate central directory entries
964
+ let ptr = cdOffset;
965
+ let seen = 0;
966
+ let sumComp = 0;
967
+ let sumUnc = 0;
968
+ while (ptr + 46 <= cdOffset + cdSize && seen < totalEntries) {
969
+ const sig = r32(buf, ptr);
970
+ if (sig !== SIG_CEN)
971
+ break; // stop if structure breaks
972
+ const compSize = r32(buf, ptr + 20);
973
+ const uncSize = r32(buf, ptr + 24);
974
+ const fnLen = r16(buf, ptr + 28);
975
+ const exLen = r16(buf, ptr + 30);
976
+ const cmLen = r16(buf, ptr + 32);
977
+ const nameStart = ptr + 46;
978
+ const nameEnd = nameStart + fnLen;
979
+ if (nameEnd > buf.length)
980
+ break;
981
+ const name = buf.toString('utf8', nameStart, nameEnd);
982
+ sumComp += compSize;
983
+ sumUnc += uncSize;
984
+ seen++;
985
+ if (name.length > cfg.maxEntryNameLength) {
986
+ matches.push({ rule: 'zip_entry_name_too_long', severity: 'medium', meta: { name, length: name.length } });
987
+ }
988
+ if (hasTraversal(name)) {
989
+ matches.push({ rule: 'zip_path_traversal_entry', severity: 'medium', meta: { name } });
990
+ }
991
+ // move to next entry
992
+ ptr = nameEnd + exLen + cmLen;
993
+ }
994
+ if (seen !== totalEntries) {
995
+ // central dir truncated/odd, still report what we found
996
+ matches.push({ rule: 'zip_cd_truncated', severity: 'medium', meta: { seen, totalEntries } });
997
+ }
998
+ // Heuristics thresholds
999
+ if (seen > cfg.maxEntries) {
1000
+ matches.push({ rule: 'zip_too_many_entries', severity: 'medium', meta: { seen, limit: cfg.maxEntries } });
1001
+ }
1002
+ if (sumUnc > cfg.maxTotalUncompressedBytes) {
1003
+ matches.push({
1004
+ rule: 'zip_total_uncompressed_too_large',
1005
+ severity: 'medium',
1006
+ meta: { totalUncompressed: sumUnc, limit: cfg.maxTotalUncompressedBytes }
1007
+ });
1008
+ }
1009
+ if (sumComp === 0 && sumUnc > 0) {
1010
+ matches.push({ rule: 'zip_suspicious_ratio', severity: 'medium', meta: { ratio: Infinity } });
1011
+ }
1012
+ else if (sumComp > 0) {
1013
+ const ratio = sumUnc / Math.max(1, sumComp);
1014
+ if (ratio >= cfg.maxCompressionRatio) {
1015
+ matches.push({ rule: 'zip_suspicious_ratio', severity: 'medium', meta: { ratio, limit: cfg.maxCompressionRatio } });
1016
+ }
1017
+ }
1018
+ return matches;
1019
+ }
1020
+ };
1021
+ }
1022
+
1023
+ const MB$1 = 1024 * 1024;
1024
+ const DEFAULT_POLICY = {
1025
+ includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf'],
1026
+ allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
1027
+ maxFileSizeBytes: 20 * MB$1,
1028
+ timeoutMs: 5000,
1029
+ concurrency: 4,
1030
+ failClosed: true
1031
+ };
1032
+ function definePolicy(input = {}) {
1033
+ const p = { ...DEFAULT_POLICY, ...input };
1034
+ if (!Array.isArray(p.includeExtensions))
1035
+ throw new TypeError('includeExtensions must be string[]');
1036
+ if (!Array.isArray(p.allowedMimeTypes))
1037
+ throw new TypeError('allowedMimeTypes must be string[]');
1038
+ if (!(Number.isFinite(p.maxFileSizeBytes) && p.maxFileSizeBytes > 0))
1039
+ throw new TypeError('maxFileSizeBytes must be > 0');
1040
+ if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0))
1041
+ throw new TypeError('timeoutMs must be > 0');
1042
+ if (!(Number.isInteger(p.concurrency) && p.concurrency > 0))
1043
+ throw new TypeError('concurrency must be > 0');
1044
+ return p;
1045
+ }
1046
+
1047
+ /**
1048
+ * Policy packs for Pompelmi.
1049
+ *
1050
+ * Pre-configured, named policies for common upload scenarios. Each pack
1051
+ * defines the file type allowlist, size limits, and timeout appropriate for
1052
+ * its use case.
1053
+ *
1054
+ * All packs are built on `definePolicy` and are fully overridable:
1055
+ *
1056
+ * ```ts
1057
+ * import { POLICY_PACKS } from 'pompelmi/policy-packs';
1058
+ *
1059
+ * // Use a pack as-is:
1060
+ * const policy = POLICY_PACKS['images-only'];
1061
+ *
1062
+ * // Or override individual fields:
1063
+ * import { definePolicy } from 'pompelmi';
1064
+ * const custom = definePolicy({ ...POLICY_PACKS['documents-only'], maxFileSizeBytes: 5 * 1024 * 1024 });
1065
+ * ```
1066
+ *
1067
+ * These packs are *deterministic* and *descriptor-based* — they do not
1068
+ * depend on any external threat intelligence feed.
1069
+ *
1070
+ * @module policy-packs
1071
+ */
1072
+ const KB = 1024;
1073
+ const MB = 1024 * KB;
1074
+ // ── Policy packs ──────────────────────────────────────────────────────────────
1075
+ /**
1076
+ * Documents-only policy.
1077
+ *
1078
+ * Appropriate for: document management APIs, PDF/Office file upload endpoints,
1079
+ * data import pipelines.
1080
+ *
1081
+ * Allowed: PDF, Word (.docx/.doc), Excel (.xlsx/.xls), PowerPoint (.pptx/.ppt),
1082
+ * CSV, plain text, JSON, YAML, ODT/ODS/ODP (OpenDocument).
1083
+ * Max size: 25 MB.
1084
+ */
1085
+ const DOCUMENTS_ONLY = definePolicy({
1086
+ includeExtensions: [
1087
+ 'pdf',
1088
+ 'doc', 'docx',
1089
+ 'xls', 'xlsx',
1090
+ 'ppt', 'pptx',
1091
+ 'odt', 'ods', 'odp',
1092
+ 'csv',
1093
+ 'txt',
1094
+ 'json',
1095
+ 'yaml', 'yml',
1096
+ 'md',
1097
+ ],
1098
+ allowedMimeTypes: [
1099
+ 'application/pdf',
1100
+ 'application/msword',
1101
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1102
+ 'application/vnd.ms-excel',
1103
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1104
+ 'application/vnd.ms-powerpoint',
1105
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
1106
+ 'application/vnd.oasis.opendocument.text',
1107
+ 'application/vnd.oasis.opendocument.spreadsheet',
1108
+ 'application/vnd.oasis.opendocument.presentation',
1109
+ 'text/csv',
1110
+ 'text/plain',
1111
+ 'application/json',
1112
+ 'text/yaml',
1113
+ 'text/markdown',
1114
+ ],
1115
+ maxFileSizeBytes: 25 * MB,
1116
+ timeoutMs: 10000,
1117
+ concurrency: 4,
1118
+ failClosed: true,
1119
+ });
1120
+ /**
1121
+ * Images-only policy.
1122
+ *
1123
+ * Appropriate for: avatar uploads, product image APIs, content platforms with
1124
+ * user-generated imagery.
1125
+ *
1126
+ * Allowed: JPEG, PNG, GIF, WebP, AVIF, TIFF, BMP, ICO.
1127
+ * Max size: 10 MB.
1128
+ * Note: SVG is intentionally excluded — inline SVGs can contain scripts.
1129
+ */
1130
+ const IMAGES_ONLY = definePolicy({
1131
+ includeExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'tiff', 'tif', 'bmp', 'ico'],
1132
+ allowedMimeTypes: [
1133
+ 'image/jpeg',
1134
+ 'image/png',
1135
+ 'image/gif',
1136
+ 'image/webp',
1137
+ 'image/avif',
1138
+ 'image/tiff',
1139
+ 'image/bmp',
1140
+ 'image/x-icon',
1141
+ 'image/vnd.microsoft.icon',
1142
+ ],
1143
+ maxFileSizeBytes: 10 * MB,
1144
+ timeoutMs: 5000,
1145
+ concurrency: 8,
1146
+ failClosed: true,
1147
+ });
1148
+ /**
1149
+ * Strict public-upload policy.
1150
+ *
1151
+ * Appropriate for: anonymous or low-trust upload endpoints, public APIs,
1152
+ * any surface exposed to untrusted users.
1153
+ *
1154
+ * Aggressive size limit (5 MB), short timeout, fail-closed, narrow MIME
1155
+ * allowlist. Only allows plain images and PDF.
1156
+ */
1157
+ const STRICT_PUBLIC_UPLOAD = definePolicy({
1158
+ includeExtensions: ['jpg', 'jpeg', 'png', 'webp', 'pdf'],
1159
+ allowedMimeTypes: [
1160
+ 'image/jpeg',
1161
+ 'image/png',
1162
+ 'image/webp',
1163
+ 'application/pdf',
1164
+ ],
1165
+ maxFileSizeBytes: 5 * MB,
1166
+ timeoutMs: 4000,
1167
+ concurrency: 2,
1168
+ failClosed: true,
1169
+ });
1170
+ /**
1171
+ * Conservative default policy.
1172
+ *
1173
+ * A hardened version of the built-in `DEFAULT_POLICY` suitable for
1174
+ * production without further customisation. Stricter size limit and
1175
+ * shorter timeout than the permissive default.
1176
+ */
1177
+ const CONSERVATIVE_DEFAULT = definePolicy({
1178
+ includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt', 'csv', 'docx', 'xlsx'],
1179
+ allowedMimeTypes: [
1180
+ 'application/zip',
1181
+ 'image/png',
1182
+ 'image/jpeg',
1183
+ 'application/pdf',
1184
+ 'text/plain',
1185
+ 'text/csv',
1186
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1187
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1188
+ ],
1189
+ maxFileSizeBytes: 10 * MB,
1190
+ timeoutMs: 8000,
1191
+ concurrency: 4,
1192
+ failClosed: true,
1193
+ });
1194
+ /**
1195
+ * Archives policy.
1196
+ *
1197
+ * Appropriate for: endpoints that accept ZIP, tar, or compressed archives.
1198
+ * Combines a generous size allowance with a longer timeout for deep inspection.
1199
+ *
1200
+ * NOTE: Pair this policy with `createZipBombGuard()` to defend against
1201
+ * decompression-bomb attacks:
1202
+ *
1203
+ * ```ts
1204
+ * import { composeScanners, createZipBombGuard, CommonHeuristicsScanner } from 'pompelmi';
1205
+ * const scanner = composeScanners(
1206
+ * [['zipGuard', createZipBombGuard()], ['heuristics', CommonHeuristicsScanner]]
1207
+ * );
1208
+ * ```
1209
+ */
1210
+ const ARCHIVES = definePolicy({
1211
+ includeExtensions: ['zip', 'tar', 'gz', 'tgz', 'bz2', 'xz', '7z', 'rar'],
1212
+ allowedMimeTypes: [
1213
+ 'application/zip',
1214
+ 'application/x-tar',
1215
+ 'application/gzip',
1216
+ 'application/x-bzip2',
1217
+ 'application/x-xz',
1218
+ 'application/x-7z-compressed',
1219
+ 'application/x-rar-compressed',
1220
+ ],
1221
+ maxFileSizeBytes: 100 * MB,
1222
+ timeoutMs: 30000,
1223
+ concurrency: 2,
1224
+ failClosed: true,
1225
+ });
1226
+ /**
1227
+ * Named map of all built-in policy packs.
1228
+ *
1229
+ * ```ts
1230
+ * import { POLICY_PACKS } from 'pompelmi/policy-packs';
1231
+ * const policy = POLICY_PACKS['strict-public-upload'];
1232
+ * ```
1233
+ */
1234
+ const POLICY_PACKS = {
1235
+ 'documents-only': DOCUMENTS_ONLY,
1236
+ 'images-only': IMAGES_ONLY,
1237
+ 'strict-public-upload': STRICT_PUBLIC_UPLOAD,
1238
+ 'conservative-default': CONSERVATIVE_DEFAULT,
1239
+ 'archives': ARCHIVES,
1240
+ };
1241
+ /**
1242
+ * Look up a policy pack by name.
1243
+ * Throws if the name is not recognised.
1244
+ */
1245
+ function getPolicyPack(name) {
1246
+ const policy = POLICY_PACKS[name];
1247
+ if (!policy)
1248
+ throw new Error(`Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(', ')}`);
1249
+ return policy;
1250
+ }
1251
+
1252
+ function mapMatchesToVerdict(matches = []) {
1253
+ if (!matches.length)
1254
+ return 'clean';
1255
+ const malHints = ['trojan', 'ransom', 'worm', 'spy', 'rootkit', 'keylog', 'botnet'];
1256
+ const tagSet = new Set(matches.flatMap(m => (m.tags ?? []).map(t => t.toLowerCase())));
1257
+ const nameHit = (r) => malHints.some(h => r.toLowerCase().includes(h));
1258
+ const isMal = matches.some(m => nameHit(m.rule)) || tagSet.has('malware') || tagSet.has('critical');
1259
+ return isMal ? 'malicious' : 'suspicious';
1260
+ }
1261
+
2833
1262
  /** Decompilation-specific types for Pompelmi */
2834
1263
  const SUSPICIOUS_PATTERNS = [
2835
1264
  {
@@ -2865,1279 +1294,1135 @@ const SUSPICIOUS_PATTERNS = [
2865
1294
  ];
2866
1295
 
2867
1296
  /**
2868
- * HIPAA Compliance Module for Pompelmi
2869
- *
2870
- * This module provides comprehensive HIPAA compliance features for healthcare environments
2871
- * where Pompelmi is used to analyze potentially compromised systems containing PHI.
2872
- *
2873
- * Key protections:
2874
- * - Data sanitization and redaction
2875
- * - Secure temporary file handling
2876
- * - Audit logging
2877
- * - Memory protection
2878
- * - Error message sanitization
1297
+ * Batch scanning with concurrency control
1298
+ * @module utils/batch-scanner
2879
1299
  */
2880
- class HipaaComplianceManager {
2881
- constructor(config) {
2882
- this.auditEvents = [];
2883
- this.config = {
2884
- sanitizeErrors: true,
2885
- sanitizeFilenames: true,
2886
- encryptTempFiles: true,
2887
- memoryProtection: true,
2888
- requireSecureTransport: true,
2889
- ...config,
2890
- enabled: config.enabled !== undefined ? config.enabled : true
1300
+ /**
1301
+ * Batch file scanner with concurrency control and progress tracking
1302
+ */
1303
+ class BatchScanner {
1304
+ constructor(options = {}) {
1305
+ this.options = {
1306
+ concurrency: 5,
1307
+ continueOnError: true,
1308
+ ...options,
2891
1309
  };
2892
- this.sessionId = this.generateSessionId();
2893
1310
  }
2894
1311
  /**
2895
- * Sanitize filename to prevent PHI leakage in logs
1312
+ * Scan multiple files with controlled concurrency
2896
1313
  */
2897
- sanitizeFilename(filename) {
2898
- if (!this.config.enabled || !this.config.sanitizeFilenames || !filename) {
2899
- return filename || 'unknown';
1314
+ async scanBatch(tasks) {
1315
+ const startTime = Date.now();
1316
+ const results = new Array(tasks.length);
1317
+ const errors = [];
1318
+ let successCount = 0;
1319
+ let errorCount = 0;
1320
+ let completedCount = 0;
1321
+ const concurrency = this.options.concurrency ?? 5;
1322
+ // Process tasks in chunks with controlled concurrency
1323
+ const processingQueue = [];
1324
+ let currentIndex = 0;
1325
+ const processTask = async (index) => {
1326
+ try {
1327
+ const task = tasks[index];
1328
+ const report = await scanBytes(task.content, {
1329
+ ...this.options,
1330
+ ctx: task.context,
1331
+ });
1332
+ results[index] = report;
1333
+ successCount++;
1334
+ completedCount++;
1335
+ if (this.options.onProgress) {
1336
+ this.options.onProgress(completedCount, tasks.length, report);
1337
+ }
1338
+ }
1339
+ catch (error) {
1340
+ errorCount++;
1341
+ completedCount++;
1342
+ const err = error instanceof Error ? error : new Error(String(error));
1343
+ if (this.options.onError) {
1344
+ this.options.onError(err, index);
1345
+ }
1346
+ errors.push({ index, error: err });
1347
+ if (!this.options.continueOnError) {
1348
+ throw err;
1349
+ }
1350
+ results[index] = null;
1351
+ }
1352
+ };
1353
+ // Start initial batch of concurrent tasks
1354
+ while (currentIndex < tasks.length) {
1355
+ while (processingQueue.length < concurrency && currentIndex < tasks.length) {
1356
+ const promise = processTask(currentIndex);
1357
+ processingQueue.push(promise);
1358
+ currentIndex++;
1359
+ // Remove completed promises from queue
1360
+ promise.finally(() => {
1361
+ const idx = processingQueue.indexOf(promise);
1362
+ if (idx > -1)
1363
+ processingQueue.splice(idx, 1);
1364
+ });
1365
+ }
1366
+ // Wait for at least one task to complete before continuing
1367
+ if (processingQueue.length >= concurrency) {
1368
+ await Promise.race(processingQueue);
1369
+ }
2900
1370
  }
2901
- // Remove potentially sensitive path information
2902
- const basename = path.basename(filename);
2903
- // Hash the filename to create a consistent but non-revealing identifier
2904
- const hash = crypto.createHash('sha256').update(basename).digest('hex').substring(0, 8);
2905
- // Preserve file extension for analysis purposes
2906
- const ext = path.extname(basename);
2907
- return `file_${hash}${ext}`;
1371
+ // Wait for all remaining tasks
1372
+ await Promise.all(processingQueue);
1373
+ const totalDurationMs = Date.now() - startTime;
1374
+ return {
1375
+ reports: results,
1376
+ successCount,
1377
+ errorCount,
1378
+ totalDurationMs,
1379
+ errors,
1380
+ };
2908
1381
  }
2909
1382
  /**
2910
- * Sanitize error messages to prevent PHI exposure
1383
+ * Scan files from File objects (browser environment)
2911
1384
  */
2912
- sanitizeError(error) {
2913
- if (!this.config.enabled || !this.config.sanitizeErrors) {
2914
- return typeof error === 'string' ? error : error.message;
2915
- }
2916
- const message = typeof error === 'string' ? error : error.message;
2917
- // Remove common patterns that might contain PHI
2918
- let sanitized = message
2919
- // Remove file paths
2920
- .replace(/[A-Za-z]:\\\\[^\\s]+/g, '[REDACTED_PATH]')
2921
- .replace(/\/[^\\s]+/g, '[REDACTED_PATH]')
2922
- // Remove potential patient identifiers (numbers that could be MRNs, SSNs)
2923
- .replace(/\\b\\d{3}-?\\d{2}-?\\d{4}\\b/g, '[REDACTED_ID]')
2924
- .replace(/\\b\\d{6,}\\b/g, '[REDACTED_ID]')
2925
- // Remove email addresses
2926
- .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g, '[REDACTED_EMAIL]')
2927
- // Remove potential names (capitalize words in error messages)
2928
- .replace(/\\b[A-Z][a-z]+\\s+[A-Z][a-z]+\\b/g, '[REDACTED_NAME]')
2929
- // Remove IP addresses
2930
- .replace(/\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g, '[REDACTED_IP]');
2931
- return sanitized;
1385
+ async scanFiles(files) {
1386
+ const tasks = await Promise.all(files.map(async (file) => ({
1387
+ content: new Uint8Array(await file.arrayBuffer()),
1388
+ context: {
1389
+ filename: file.name,
1390
+ mimeType: file.type,
1391
+ size: file.size,
1392
+ },
1393
+ })));
1394
+ return this.scanBatch(tasks);
2932
1395
  }
2933
1396
  /**
2934
- * Create secure temporary file path with encryption if enabled
1397
+ * Scan files from file paths (Node.js environment)
2935
1398
  */
2936
- createSecureTempPath(prefix = 'pompelmi') {
2937
- if (!this.config.enabled) {
2938
- return path.join(os.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2939
- }
2940
- // Use cryptographically secure random names
2941
- const randomId = crypto.randomBytes(16).toString('hex');
2942
- const timestamp = Date.now();
2943
- // Create path in secure temp directory
2944
- const secureTempDir = this.getSecureTempDir();
2945
- const tempPath = path.join(secureTempDir, `${prefix}-${timestamp}-${randomId}`);
2946
- this.auditLog('temp_file_created', {
2947
- action: 'create_temp_file',
2948
- success: true,
2949
- metadata: { path: this.sanitizeFilename(tempPath) }
1399
+ async scanFilePaths(filePaths) {
1400
+ const fs = await import('fs/promises');
1401
+ const path = await import('path');
1402
+ const tasks = await Promise.all(filePaths.map(async (filePath) => {
1403
+ const [content, stats] = await Promise.all([
1404
+ fs.readFile(filePath),
1405
+ fs.stat(filePath),
1406
+ ]);
1407
+ return {
1408
+ content: new Uint8Array(content),
1409
+ context: {
1410
+ filename: path.basename(filePath),
1411
+ size: stats.size,
1412
+ },
1413
+ };
1414
+ }));
1415
+ return this.scanBatch(tasks);
1416
+ }
1417
+ }
1418
+ /**
1419
+ * Quick helper for batch scanning with default options
1420
+ */
1421
+ async function batchScan(tasks, options) {
1422
+ const scanner = new BatchScanner(options);
1423
+ return scanner.scanBatch(tasks);
1424
+ }
1425
+
1426
+ /**
1427
+ * Threat intelligence integration and enhanced detection
1428
+ * @module utils/threat-intelligence
1429
+ */
1430
+ /**
1431
+ * Built-in threat intelligence - known malware hashes
1432
+ * In production, this would connect to real threat intel APIs
1433
+ */
1434
+ class LocalThreatIntelligence {
1435
+ constructor() {
1436
+ this.name = 'Local Database';
1437
+ this.knownThreats = new Map();
1438
+ // Initialize with some example known threats (in production, load from database)
1439
+ this.initializeKnownThreats();
1440
+ }
1441
+ initializeKnownThreats() {
1442
+ // Example: EICAR test file hash
1443
+ this.knownThreats.set('275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f', {
1444
+ threatLevel: 100,
1445
+ category: 'test-malware',
1446
+ source: 'local',
1447
+ metadata: { name: 'EICAR Test File' },
2950
1448
  });
2951
- return tempPath;
1449
+ }
1450
+ async checkHash(hash) {
1451
+ return this.knownThreats.get(hash.toLowerCase()) || null;
2952
1452
  }
2953
1453
  /**
2954
- * Get or create secure temporary directory with restricted permissions
1454
+ * Add a known threat to the local database
2955
1455
  */
2956
- getSecureTempDir() {
2957
- const secureTempPath = path.join(os.tmpdir(), 'pompelmi-secure');
2958
- try {
2959
- const fs = require('fs');
2960
- if (!fs.existsSync(secureTempPath)) {
2961
- fs.mkdirSync(secureTempPath, { mode: 0o700 }); // Owner read/write/execute only
2962
- }
1456
+ addThreat(hash, info) {
1457
+ this.knownThreats.set(hash.toLowerCase(), info);
1458
+ }
1459
+ /**
1460
+ * Remove a threat from the local database
1461
+ */
1462
+ removeThreat(hash) {
1463
+ return this.knownThreats.delete(hash.toLowerCase());
1464
+ }
1465
+ /**
1466
+ * Get all known threats
1467
+ */
1468
+ getAllThreats() {
1469
+ return new Map(this.knownThreats);
1470
+ }
1471
+ }
1472
+ /**
1473
+ * Threat intelligence aggregator
1474
+ */
1475
+ class ThreatIntelligenceAggregator {
1476
+ constructor(sources) {
1477
+ this.sources = [];
1478
+ if (sources) {
1479
+ this.sources = sources;
2963
1480
  }
2964
- catch (error) {
2965
- // Fallback to system temp
2966
- return os.tmpdir();
1481
+ else {
1482
+ // Default to local intelligence
1483
+ this.sources = [new LocalThreatIntelligence()];
2967
1484
  }
2968
- return secureTempPath;
2969
1485
  }
2970
1486
  /**
2971
- * Secure file cleanup with multiple overwrite passes
1487
+ * Add a threat intelligence source
2972
1488
  */
2973
- async secureFileCleanup(filePath) {
2974
- if (!this.config.enabled) {
2975
- try {
2976
- const fs = await import('fs/promises');
2977
- await fs.unlink(filePath);
2978
- }
2979
- catch {
2980
- // Ignore cleanup errors
1489
+ addSource(source) {
1490
+ this.sources.push(source);
1491
+ }
1492
+ /**
1493
+ * Check file hash against all sources
1494
+ */
1495
+ async checkHash(hash) {
1496
+ const results = await Promise.allSettled(this.sources.map(source => source.checkHash(hash)));
1497
+ const threats = [];
1498
+ for (const result of results) {
1499
+ if (result.status === 'fulfilled' && result.value) {
1500
+ threats.push(result.value);
2981
1501
  }
2982
- return;
2983
1502
  }
2984
- try {
2985
- const fs = await import('fs/promises');
2986
- const stats = await fs.stat(filePath);
2987
- if (this.config.memoryProtection) {
2988
- // Overwrite file with random data multiple times (DoD 5220.22-M standard)
2989
- const fileSize = stats.size;
2990
- const buffer = crypto.randomBytes(Math.min(fileSize, 64 * 1024)); // 64KB chunks
2991
- for (let pass = 0; pass < 3; pass++) {
2992
- const handle = await fs.open(filePath, 'r+');
2993
- try {
2994
- for (let offset = 0; offset < fileSize; offset += buffer.length) {
2995
- const chunk = offset + buffer.length > fileSize
2996
- ? buffer.subarray(0, fileSize - offset)
2997
- : buffer;
2998
- await handle.write(chunk, 0, chunk.length, offset);
2999
- }
3000
- await handle.sync();
3001
- }
3002
- finally {
3003
- await handle.close();
3004
- }
3005
- }
3006
- }
3007
- // Final deletion
3008
- await fs.unlink(filePath);
3009
- this.auditLog('temp_file_deleted', {
3010
- action: 'secure_delete',
3011
- success: true,
3012
- metadata: {
3013
- path: this.sanitizeFilename(filePath),
3014
- overwritePasses: this.config.memoryProtection ? 3 : 0
3015
- }
3016
- });
3017
- }
3018
- catch (error) {
3019
- this.auditLog('temp_file_deleted', {
3020
- action: 'secure_delete',
3021
- success: false,
3022
- sanitizedError: this.sanitizeError(error),
3023
- metadata: { path: this.sanitizeFilename(filePath) }
3024
- });
3025
- }
3026
- }
3027
- /**
3028
- * Calculate secure file hash for audit purposes
3029
- */
3030
- calculateFileHash(data) {
3031
- return crypto.createHash('sha256').update(data).digest('hex');
1503
+ return threats;
3032
1504
  }
3033
1505
  /**
3034
- * Log audit event
1506
+ * Enhance scan report with threat intelligence
3035
1507
  */
3036
- auditLog(eventType, details) {
3037
- if (!this.config.enabled)
3038
- return;
3039
- const event = {
3040
- timestamp: new Date().toISOString(),
3041
- eventType,
3042
- sessionId: this.sessionId,
3043
- details: {
3044
- action: details.action || 'unknown',
3045
- success: details.success ?? true,
3046
- ...details
3047
- }
1508
+ async enhanceScanReport(content, report) {
1509
+ // Calculate file hash
1510
+ const hash = createHash('sha256').update(content).digest('hex');
1511
+ // Check threat intelligence
1512
+ const threatIntel = await this.checkHash(hash);
1513
+ // Calculate risk score
1514
+ const riskScore = this.calculateRiskScore(report, threatIntel);
1515
+ return {
1516
+ ...report,
1517
+ fileHash: hash,
1518
+ threatIntel: threatIntel.length > 0 ? threatIntel : undefined,
1519
+ riskScore,
3048
1520
  };
3049
- this.auditEvents.push(event);
3050
- // Write to audit log file if configured
3051
- if (this.config.auditLogPath) {
3052
- this.writeAuditLog(event).catch(() => {
3053
- // Silent failure to prevent error loops
3054
- });
3055
- }
3056
- }
3057
- /**
3058
- * Write audit event to file
3059
- */
3060
- async writeAuditLog(event) {
3061
- if (!this.config.auditLogPath)
3062
- return;
3063
- try {
3064
- const fs = await import('fs/promises');
3065
- const logLine = JSON.stringify(event) + '\\n';
3066
- await fs.appendFile(this.config.auditLogPath, logLine, { flag: 'a' });
3067
- }
3068
- catch {
3069
- // Silent failure
3070
- }
3071
- }
3072
- /**
3073
- * Generate cryptographically secure session ID
3074
- */
3075
- generateSessionId() {
3076
- return crypto.randomBytes(16).toString('hex');
3077
- }
3078
- /**
3079
- * Get current audit events for this session
3080
- */
3081
- getAuditEvents() {
3082
- return [...this.auditEvents];
3083
- }
3084
- /**
3085
- * Clear sensitive data from memory
3086
- */
3087
- clearSensitiveData() {
3088
- if (!this.config.enabled || !this.config.memoryProtection)
3089
- return;
3090
- // Clear audit events
3091
- this.auditEvents.length = 0;
3092
- // Force garbage collection if available
3093
- if (global.gc) {
3094
- global.gc();
3095
- }
3096
1521
  }
3097
1522
  /**
3098
- * Validate transport security
1523
+ * Calculate overall risk score based on scan results and threat intel
3099
1524
  */
3100
- validateTransportSecurity(url) {
3101
- if (!this.config.enabled || !this.config.requireSecureTransport) {
3102
- return true;
3103
- }
3104
- if (!url)
3105
- return true;
3106
- try {
3107
- const urlObj = new URL(url);
3108
- const isSecure = urlObj.protocol === 'https:' || urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1';
3109
- if (!isSecure) {
3110
- this.auditLog('security_violation', {
3111
- action: 'insecure_transport',
3112
- success: false,
3113
- metadata: { protocol: urlObj.protocol, hostname: urlObj.hostname }
3114
- });
3115
- }
3116
- return isSecure;
1525
+ calculateRiskScore(report, threats) {
1526
+ let score = 0;
1527
+ // Base score from verdict
1528
+ switch (report.verdict) {
1529
+ case 'malicious':
1530
+ score += 70;
1531
+ break;
1532
+ case 'suspicious':
1533
+ score += 40;
1534
+ break;
1535
+ case 'clean':
1536
+ score += 0;
1537
+ break;
3117
1538
  }
3118
- catch {
3119
- return false;
1539
+ // Add points for number of matches
1540
+ score += Math.min(report.matches.length * 5, 20);
1541
+ // Add points from threat intelligence
1542
+ if (threats.length > 0) {
1543
+ const maxThreat = Math.max(...threats.map(t => t.threatLevel));
1544
+ score = Math.max(score, maxThreat);
3120
1545
  }
1546
+ return Math.min(score, 100);
3121
1547
  }
3122
1548
  }
3123
- // Global HIPAA compliance instance
3124
- let hipaaManager = null;
3125
1549
  /**
3126
- * Initialize HIPAA compliance
1550
+ * Create default threat intelligence aggregator
3127
1551
  */
3128
- function initializeHipaaCompliance(config) {
3129
- hipaaManager = new HipaaComplianceManager(config);
3130
- return hipaaManager;
1552
+ function createThreatIntelligence() {
1553
+ return new ThreatIntelligenceAggregator();
3131
1554
  }
3132
1555
  /**
3133
- * Get current HIPAA compliance manager
1556
+ * Helper to get file hash
3134
1557
  */
3135
- function getHipaaManager() {
3136
- return hipaaManager;
1558
+ function getFileHash(content) {
1559
+ return createHash('sha256').update(content).digest('hex');
3137
1560
  }
1561
+
3138
1562
  /**
3139
- * HIPAA-compliant error wrapper
1563
+ * Export utilities for scan results
1564
+ * @module utils/export
3140
1565
  */
3141
- function createHipaaError(error, context) {
3142
- const manager = getHipaaManager();
3143
- if (!manager) {
3144
- return typeof error === 'string' ? new Error(error) : error;
3145
- }
3146
- const sanitizedMessage = manager.sanitizeError(error);
3147
- const hipaaError = new Error(sanitizedMessage);
3148
- manager.auditLog('error_occurred', {
3149
- action: context || 'error',
3150
- success: false,
3151
- sanitizedError: sanitizedMessage
3152
- });
3153
- return hipaaError;
3154
- }
3155
1566
  /**
3156
- * HIPAA-compliant temporary file utilities
1567
+ * Export scan results to various formats
3157
1568
  */
3158
- const HipaaTemp = {
3159
- createPath: (prefix) => {
3160
- const manager = getHipaaManager();
3161
- return manager ? manager.createSecureTempPath(prefix) : path.join(os.tmpdir(), `${prefix || 'pompelmi'}-${Date.now()}`);
3162
- },
3163
- cleanup: async (filePath) => {
3164
- const manager = getHipaaManager();
3165
- if (manager) {
3166
- await manager.secureFileCleanup(filePath);
1569
+ class ScanResultExporter {
1570
+ /**
1571
+ * Export to JSON format
1572
+ */
1573
+ toJSON(reports, options = {}) {
1574
+ const data = Array.isArray(reports) ? reports : [reports];
1575
+ if (!options.includeDetails) {
1576
+ // Simplified output
1577
+ const simplified = data.map(r => ({
1578
+ verdict: r.verdict,
1579
+ file: r.file?.name,
1580
+ matches: r.matches.length,
1581
+ durationMs: r.durationMs,
1582
+ }));
1583
+ return options.prettyPrint
1584
+ ? JSON.stringify(simplified, null, 2)
1585
+ : JSON.stringify(simplified);
3167
1586
  }
3168
- else {
3169
- try {
3170
- const fs = await import('fs/promises');
3171
- await fs.unlink(filePath);
3172
- }
3173
- catch {
3174
- // Ignore errors
3175
- }
1587
+ return options.prettyPrint
1588
+ ? JSON.stringify(data, null, 2)
1589
+ : JSON.stringify(data);
1590
+ }
1591
+ /**
1592
+ * Export to CSV format
1593
+ */
1594
+ toCSV(reports, options = {}) {
1595
+ const data = Array.isArray(reports) ? reports : [reports];
1596
+ const headers = [
1597
+ 'filename',
1598
+ 'verdict',
1599
+ 'matches_count',
1600
+ 'file_size',
1601
+ 'mime_type',
1602
+ 'duration_ms',
1603
+ 'engine',
1604
+ ];
1605
+ if (options.includeDetails) {
1606
+ headers.push('reasons', 'match_rules');
3176
1607
  }
1608
+ const rows = data.map(report => {
1609
+ const row = [
1610
+ this.escapeCsv(report.file?.name || 'unknown'),
1611
+ report.verdict,
1612
+ report.matches.length.toString(),
1613
+ (report.file?.size || 0).toString(),
1614
+ this.escapeCsv(report.file?.mimeType || 'unknown'),
1615
+ (report.durationMs || 0).toString(),
1616
+ report.engine || 'unknown',
1617
+ ];
1618
+ if (options.includeDetails) {
1619
+ row.push(this.escapeCsv((report.reasons || []).join('; ')), this.escapeCsv(report.matches.map(m => m.rule).join('; ')));
1620
+ }
1621
+ return row.join(',');
1622
+ });
1623
+ return [headers.join(','), ...rows].join('\n');
3177
1624
  }
3178
- };
3179
-
3180
- function mapMatchesToVerdict(matches = []) {
3181
- if (!matches.length)
3182
- return 'clean';
3183
- const malHints = ['trojan', 'ransom', 'worm', 'spy', 'rootkit', 'keylog', 'botnet'];
3184
- const tagSet = new Set(matches.flatMap(m => (m.tags ?? []).map(t => t.toLowerCase())));
3185
- const nameHit = (r) => malHints.some(h => r.toLowerCase().includes(h));
3186
- const isMal = matches.some(m => nameHit(m.rule)) || tagSet.has('malware') || tagSet.has('critical');
3187
- return isMal ? 'malicious' : 'suspicious';
3188
- }
3189
-
3190
- const SIG_CEN = 0x02014b50;
3191
- const DEFAULTS = {
3192
- maxEntries: 1000,
3193
- maxTotalUncompressedBytes: 500 * 1024 * 1024,
3194
- maxEntryNameLength: 255,
3195
- maxCompressionRatio: 1000,
3196
- eocdSearchWindow: 70000,
3197
- };
3198
- function r16(buf, off) {
3199
- return buf.readUInt16LE(off);
3200
- }
3201
- function r32(buf, off) {
3202
- return buf.readUInt32LE(off);
3203
- }
3204
- function isZipLike(buf) {
3205
- // local file header at start is common
3206
- return buf.length >= 4 && buf[0] === 0x50 && buf[1] === 0x4b && buf[2] === 0x03 && buf[3] === 0x04;
3207
- }
3208
- function lastIndexOfEOCD(buf, window) {
3209
- const sig = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
3210
- const start = Math.max(0, buf.length - window);
3211
- const idx = buf.lastIndexOf(sig, Math.min(buf.length - sig.length, buf.length - 1));
3212
- return idx >= start ? idx : -1;
3213
- }
3214
- function hasTraversal(name) {
3215
- return name.includes('../') || name.includes('..\\') || name.startsWith('/') || /^[A-Za-z]:/.test(name);
3216
- }
3217
- function createZipBombGuard(opts = {}) {
3218
- const cfg = { ...DEFAULTS, ...opts };
3219
- return {
3220
- async scan(input) {
3221
- const buf = Buffer.from(input);
3222
- const matches = [];
3223
- if (!isZipLike(buf))
3224
- return matches;
3225
- // Find EOCD near the end
3226
- const eocdPos = lastIndexOfEOCD(buf, cfg.eocdSearchWindow);
3227
- if (eocdPos < 0 || eocdPos + 22 > buf.length) {
3228
- // ZIP but no EOCD — malformed or polyglot → suspicious
3229
- matches.push({ rule: 'zip_eocd_not_found', severity: 'medium' });
3230
- return matches;
3231
- }
3232
- const totalEntries = r16(buf, eocdPos + 10);
3233
- const cdSize = r32(buf, eocdPos + 12);
3234
- const cdOffset = r32(buf, eocdPos + 16);
3235
- // Bounds check
3236
- if (cdOffset + cdSize > buf.length) {
3237
- matches.push({ rule: 'zip_cd_out_of_bounds', severity: 'medium' });
3238
- return matches;
3239
- }
3240
- // Iterate central directory entries
3241
- let ptr = cdOffset;
3242
- let seen = 0;
3243
- let sumComp = 0;
3244
- let sumUnc = 0;
3245
- while (ptr + 46 <= cdOffset + cdSize && seen < totalEntries) {
3246
- const sig = r32(buf, ptr);
3247
- if (sig !== SIG_CEN)
3248
- break; // stop if structure breaks
3249
- const compSize = r32(buf, ptr + 20);
3250
- const uncSize = r32(buf, ptr + 24);
3251
- const fnLen = r16(buf, ptr + 28);
3252
- const exLen = r16(buf, ptr + 30);
3253
- const cmLen = r16(buf, ptr + 32);
3254
- const nameStart = ptr + 46;
3255
- const nameEnd = nameStart + fnLen;
3256
- if (nameEnd > buf.length)
3257
- break;
3258
- const name = buf.toString('utf8', nameStart, nameEnd);
3259
- sumComp += compSize;
3260
- sumUnc += uncSize;
3261
- seen++;
3262
- if (name.length > cfg.maxEntryNameLength) {
3263
- matches.push({ rule: 'zip_entry_name_too_long', severity: 'medium', meta: { name, length: name.length } });
3264
- }
3265
- if (hasTraversal(name)) {
3266
- matches.push({ rule: 'zip_path_traversal_entry', severity: 'medium', meta: { name } });
1625
+ /**
1626
+ * Export to Markdown format
1627
+ */
1628
+ toMarkdown(reports, options = {}) {
1629
+ const data = Array.isArray(reports) ? reports : [reports];
1630
+ let md = '# Scan Results\n\n';
1631
+ md += `**Total Scans:** ${data.length}\n\n`;
1632
+ const clean = data.filter(r => r.verdict === 'clean').length;
1633
+ const suspicious = data.filter(r => r.verdict === 'suspicious').length;
1634
+ const malicious = data.filter(r => r.verdict === 'malicious').length;
1635
+ md += '## Summary\n\n';
1636
+ md += `- ✅ Clean: ${clean}\n`;
1637
+ md += `- ⚠️ Suspicious: ${suspicious}\n`;
1638
+ md += `- ❌ Malicious: ${malicious}\n\n`;
1639
+ md += '## Detailed Results\n\n';
1640
+ for (const report of data) {
1641
+ const icon = report.verdict === 'clean' ? '✅' : report.verdict === 'suspicious' ? '⚠️' : '❌';
1642
+ md += `### ${icon} ${report.file?.name || 'Unknown'}\n\n`;
1643
+ md += `- **Verdict:** ${report.verdict}\n`;
1644
+ md += `- **Size:** ${this.formatBytes(report.file?.size || 0)}\n`;
1645
+ md += `- **MIME Type:** ${report.file?.mimeType || 'unknown'}\n`;
1646
+ md += `- **Duration:** ${report.durationMs || 0}ms\n`;
1647
+ md += `- **Matches:** ${report.matches.length}\n`;
1648
+ if (options.includeDetails && report.matches.length > 0) {
1649
+ md += '\n**Match Details:**\n';
1650
+ for (const match of report.matches) {
1651
+ md += `- ${match.rule}`;
1652
+ if (match.tags && match.tags.length > 0) {
1653
+ md += ` (${match.tags.join(', ')})`;
1654
+ }
1655
+ md += '\n';
3267
1656
  }
3268
- // move to next entry
3269
- ptr = nameEnd + exLen + cmLen;
3270
- }
3271
- if (seen !== totalEntries) {
3272
- // central dir truncated/odd, still report what we found
3273
- matches.push({ rule: 'zip_cd_truncated', severity: 'medium', meta: { seen, totalEntries } });
3274
- }
3275
- // Heuristics thresholds
3276
- if (seen > cfg.maxEntries) {
3277
- matches.push({ rule: 'zip_too_many_entries', severity: 'medium', meta: { seen, limit: cfg.maxEntries } });
3278
- }
3279
- if (sumUnc > cfg.maxTotalUncompressedBytes) {
3280
- matches.push({
3281
- rule: 'zip_total_uncompressed_too_large',
3282
- severity: 'medium',
3283
- meta: { totalUncompressed: sumUnc, limit: cfg.maxTotalUncompressedBytes }
3284
- });
3285
1657
  }
3286
- if (sumComp === 0 && sumUnc > 0) {
3287
- matches.push({ rule: 'zip_suspicious_ratio', severity: 'medium', meta: { ratio: Infinity } });
3288
- }
3289
- else if (sumComp > 0) {
3290
- const ratio = sumUnc / Math.max(1, sumComp);
3291
- if (ratio >= cfg.maxCompressionRatio) {
3292
- matches.push({ rule: 'zip_suspicious_ratio', severity: 'medium', meta: { ratio, limit: cfg.maxCompressionRatio } });
1658
+ md += '\n';
1659
+ }
1660
+ return md;
1661
+ }
1662
+ /**
1663
+ * Export to SARIF format (Static Analysis Results Interchange Format)
1664
+ * Useful for CI/CD integration
1665
+ */
1666
+ toSARIF(reports, options = {}) {
1667
+ const data = Array.isArray(reports) ? reports : [reports];
1668
+ const results = data.flatMap(report => {
1669
+ if (report.verdict === 'clean')
1670
+ return [];
1671
+ return report.matches.map(match => ({
1672
+ ruleId: match.rule,
1673
+ level: report.verdict === 'malicious' ? 'error' : 'warning',
1674
+ message: {
1675
+ text: `${match.rule} detected in ${report.file?.name || 'unknown file'}`,
1676
+ },
1677
+ locations: [
1678
+ {
1679
+ physicalLocation: {
1680
+ artifactLocation: {
1681
+ uri: report.file?.name || 'unknown',
1682
+ },
1683
+ },
1684
+ },
1685
+ ],
1686
+ properties: {
1687
+ tags: match.tags,
1688
+ metadata: match.meta,
1689
+ },
1690
+ }));
1691
+ });
1692
+ const sarif = {
1693
+ version: '2.1.0',
1694
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
1695
+ runs: [
1696
+ {
1697
+ tool: {
1698
+ driver: {
1699
+ name: 'Pompelmi',
1700
+ version: '0.29.0',
1701
+ informationUri: 'https://pompelmi.github.io/pompelmi/',
1702
+ },
1703
+ },
1704
+ results,
1705
+ },
1706
+ ],
1707
+ };
1708
+ return options.prettyPrint
1709
+ ? JSON.stringify(sarif, null, 2)
1710
+ : JSON.stringify(sarif);
1711
+ }
1712
+ /**
1713
+ * Export to HTML format
1714
+ */
1715
+ toHTML(reports, options = {}) {
1716
+ const data = Array.isArray(reports) ? reports : [reports];
1717
+ const clean = data.filter(r => r.verdict === 'clean').length;
1718
+ const suspicious = data.filter(r => r.verdict === 'suspicious').length;
1719
+ const malicious = data.filter(r => r.verdict === 'malicious').length;
1720
+ let html = `<!DOCTYPE html>
1721
+ <html lang="en">
1722
+ <head>
1723
+ <meta charset="UTF-8">
1724
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1725
+ <title>Pompelmi Scan Results</title>
1726
+ <style>
1727
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
1728
+ .summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin: 20px 0; }
1729
+ .card { padding: 20px; border-radius: 8px; text-align: center; }
1730
+ .clean { background: #d4edda; color: #155724; }
1731
+ .suspicious { background: #fff3cd; color: #856404; }
1732
+ .malicious { background: #f8d7da; color: #721c24; }
1733
+ .result { border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin: 10px 0; }
1734
+ .result h3 { margin-top: 0; }
1735
+ .badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.8em; margin: 2px; }
1736
+ table { width: 100%; border-collapse: collapse; }
1737
+ th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
1738
+ </style>
1739
+ </head>
1740
+ <body>
1741
+ <h1>🛡️ Pompelmi Scan Results</h1>
1742
+ <div class="summary">
1743
+ <div class="card clean"><h2>${clean}</h2><p>Clean Files</p></div>
1744
+ <div class="card suspicious"><h2>${suspicious}</h2><p>Suspicious Files</p></div>
1745
+ <div class="card malicious"><h2>${malicious}</h2><p>Malicious Files</p></div>
1746
+ </div>
1747
+ <h2>Detailed Results</h2>`;
1748
+ for (const report of data) {
1749
+ const statusClass = report.verdict;
1750
+ html += `<div class="result ${statusClass}">`;
1751
+ html += `<h3>${this.escapeHtml(report.file?.name || 'Unknown')}</h3>`;
1752
+ html += `<table>`;
1753
+ html += `<tr><th>Verdict</th><td>${report.verdict.toUpperCase()}</td></tr>`;
1754
+ html += `<tr><th>Size</th><td>${this.formatBytes(report.file?.size || 0)}</td></tr>`;
1755
+ html += `<tr><th>MIME Type</th><td>${this.escapeHtml(report.file?.mimeType || 'unknown')}</td></tr>`;
1756
+ html += `<tr><th>Duration</th><td>${report.durationMs || 0}ms</td></tr>`;
1757
+ html += `<tr><th>Matches</th><td>${report.matches.length}</td></tr>`;
1758
+ html += `</table>`;
1759
+ if (options.includeDetails && report.matches.length > 0) {
1760
+ html += `<h4>Match Details:</h4><ul>`;
1761
+ for (const match of report.matches) {
1762
+ html += `<li><strong>${this.escapeHtml(match.rule)}</strong>`;
1763
+ if (match.tags && match.tags.length > 0) {
1764
+ html += ` ${match.tags.map(tag => `<span class="badge">${this.escapeHtml(tag)}</span>`).join('')}`;
1765
+ }
1766
+ html += `</li>`;
3293
1767
  }
1768
+ html += `</ul>`;
3294
1769
  }
3295
- return matches;
1770
+ html += `</div>`;
1771
+ }
1772
+ html += `</body></html>`;
1773
+ return html;
1774
+ }
1775
+ /**
1776
+ * Export to specified format
1777
+ */
1778
+ export(reports, format, options = {}) {
1779
+ switch (format) {
1780
+ case 'json':
1781
+ return this.toJSON(reports, options);
1782
+ case 'csv':
1783
+ return this.toCSV(reports, options);
1784
+ case 'markdown':
1785
+ return this.toMarkdown(reports, options);
1786
+ case 'html':
1787
+ return this.toHTML(reports, options);
1788
+ case 'sarif':
1789
+ return this.toSARIF(reports, options);
1790
+ default:
1791
+ throw new Error(`Unsupported export format: ${format}`);
3296
1792
  }
3297
- };
1793
+ }
1794
+ escapeCsv(value) {
1795
+ if (value.includes(',') || value.includes('"') || value.includes('\n')) {
1796
+ return `"${value.replace(/"/g, '""')}"`;
1797
+ }
1798
+ return value;
1799
+ }
1800
+ escapeHtml(value) {
1801
+ return value
1802
+ .replace(/&/g, '&amp;')
1803
+ .replace(/</g, '&lt;')
1804
+ .replace(/>/g, '&gt;')
1805
+ .replace(/"/g, '&quot;')
1806
+ .replace(/'/g, '&#039;');
1807
+ }
1808
+ formatBytes(bytes) {
1809
+ if (bytes === 0)
1810
+ return '0 Bytes';
1811
+ const k = 1024;
1812
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
1813
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1814
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
1815
+ }
3298
1816
  }
3299
-
3300
- const MB = 1024 * 1024;
3301
- const DEFAULT_POLICY = {
3302
- includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf'],
3303
- allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
3304
- maxFileSizeBytes: 20 * MB,
3305
- timeoutMs: 5000,
3306
- concurrency: 4,
3307
- failClosed: true
3308
- };
3309
- function definePolicy(input = {}) {
3310
- const p = { ...DEFAULT_POLICY, ...input };
3311
- if (!Array.isArray(p.includeExtensions))
3312
- throw new TypeError('includeExtensions must be string[]');
3313
- if (!Array.isArray(p.allowedMimeTypes))
3314
- throw new TypeError('allowedMimeTypes must be string[]');
3315
- if (!(Number.isFinite(p.maxFileSizeBytes) && p.maxFileSizeBytes > 0))
3316
- throw new TypeError('maxFileSizeBytes must be > 0');
3317
- if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0))
3318
- throw new TypeError('timeoutMs must be > 0');
3319
- if (!(Number.isInteger(p.concurrency) && p.concurrency > 0))
3320
- throw new TypeError('concurrency must be > 0');
3321
- return p;
1817
+ /**
1818
+ * Quick export helper
1819
+ */
1820
+ function exportScanResults(reports, format, options) {
1821
+ const exporter = new ScanResultExporter();
1822
+ return exporter.export(reports, format, options);
3322
1823
  }
3323
1824
 
3324
1825
  /**
3325
- * Batch scanning with concurrency control
3326
- * @module utils/batch-scanner
1826
+ * Advanced configuration system for pompelmi
1827
+ * @module config
3327
1828
  */
3328
1829
  /**
3329
- * Batch file scanner with concurrency control and progress tracking
1830
+ * Default configuration
3330
1831
  */
3331
- class BatchScanner {
3332
- constructor(options = {}) {
3333
- this.options = {
3334
- concurrency: 5,
3335
- continueOnError: true,
3336
- ...options,
3337
- };
3338
- }
3339
- /**
3340
- * Scan multiple files with controlled concurrency
3341
- */
3342
- async scanBatch(tasks) {
3343
- const startTime = Date.now();
3344
- const results = new Array(tasks.length);
3345
- const errors = [];
3346
- let successCount = 0;
3347
- let errorCount = 0;
3348
- let completedCount = 0;
3349
- const concurrency = this.options.concurrency ?? 5;
3350
- // Process tasks in chunks with controlled concurrency
3351
- const processingQueue = [];
3352
- let currentIndex = 0;
3353
- const processTask = async (index) => {
3354
- try {
3355
- const task = tasks[index];
3356
- const report = await scanBytes(task.content, {
3357
- ...this.options,
3358
- ctx: task.context,
3359
- });
3360
- results[index] = report;
3361
- successCount++;
3362
- completedCount++;
3363
- if (this.options.onProgress) {
3364
- this.options.onProgress(completedCount, tasks.length, report);
3365
- }
3366
- }
3367
- catch (error) {
3368
- errorCount++;
3369
- completedCount++;
3370
- const err = error instanceof Error ? error : new Error(String(error));
3371
- if (this.options.onError) {
3372
- this.options.onError(err, index);
3373
- }
3374
- errors.push({ index, error: err });
3375
- if (!this.options.continueOnError) {
3376
- throw err;
3377
- }
3378
- results[index] = null;
3379
- }
3380
- };
3381
- // Start initial batch of concurrent tasks
3382
- while (currentIndex < tasks.length) {
3383
- while (processingQueue.length < concurrency && currentIndex < tasks.length) {
3384
- const promise = processTask(currentIndex);
3385
- processingQueue.push(promise);
3386
- currentIndex++;
3387
- // Remove completed promises from queue
3388
- promise.finally(() => {
3389
- const idx = processingQueue.indexOf(promise);
3390
- if (idx > -1)
3391
- processingQueue.splice(idx, 1);
3392
- });
3393
- }
3394
- // Wait for at least one task to complete before continuing
3395
- if (processingQueue.length >= concurrency) {
3396
- await Promise.race(processingQueue);
3397
- }
3398
- }
3399
- // Wait for all remaining tasks
3400
- await Promise.all(processingQueue);
3401
- const totalDurationMs = Date.now() - startTime;
3402
- return {
3403
- reports: results,
3404
- successCount,
3405
- errorCount,
3406
- totalDurationMs,
3407
- errors,
3408
- };
1832
+ const DEFAULT_CONFIG = {
1833
+ defaultPreset: 'zip-basic',
1834
+ performance: {
1835
+ enableCache: false,
1836
+ enablePerformanceTracking: false,
1837
+ enableParallel: true,
1838
+ maxConcurrency: 5,
1839
+ cacheOptions: {
1840
+ maxSize: 1000,
1841
+ ttl: 3600000, // 1 hour
1842
+ enableLRU: true,
1843
+ enableStats: false,
1844
+ },
1845
+ },
1846
+ security: {
1847
+ maxFileSize: 100 * 1024 * 1024, // 100MB
1848
+ enableThreatIntel: false,
1849
+ scanTimeout: 30000, // 30 seconds
1850
+ strictMode: false,
1851
+ },
1852
+ advanced: {
1853
+ enablePolyglotDetection: true,
1854
+ enableObfuscationDetection: true,
1855
+ enableNestedArchiveAnalysis: true,
1856
+ maxArchiveDepth: 5,
1857
+ },
1858
+ logging: {
1859
+ verbose: false,
1860
+ level: 'info',
1861
+ enableStats: false,
1862
+ },
1863
+ };
1864
+ /**
1865
+ * Configuration presets for common use cases
1866
+ */
1867
+ const CONFIG_PRESETS = {
1868
+ /** Fast scanning with minimal features */
1869
+ fast: {
1870
+ defaultPreset: 'basic',
1871
+ performance: {
1872
+ enableCache: true,
1873
+ enablePerformanceTracking: false,
1874
+ maxConcurrency: 10,
1875
+ },
1876
+ advanced: {
1877
+ enablePolyglotDetection: false,
1878
+ enableObfuscationDetection: false,
1879
+ enableNestedArchiveAnalysis: false,
1880
+ },
1881
+ },
1882
+ /** Balanced scanning (recommended) */
1883
+ balanced: DEFAULT_CONFIG,
1884
+ /** Thorough scanning with all features */
1885
+ thorough: {
1886
+ defaultPreset: 'advanced',
1887
+ performance: {
1888
+ enableCache: true,
1889
+ enablePerformanceTracking: true,
1890
+ maxConcurrency: 3,
1891
+ },
1892
+ security: {
1893
+ maxFileSize: 500 * 1024 * 1024, // 500MB
1894
+ enableThreatIntel: true,
1895
+ scanTimeout: 60000, // 60 seconds
1896
+ strictMode: true,
1897
+ },
1898
+ advanced: {
1899
+ enablePolyglotDetection: true,
1900
+ enableObfuscationDetection: true,
1901
+ enableNestedArchiveAnalysis: true,
1902
+ maxArchiveDepth: 10,
1903
+ },
1904
+ logging: {
1905
+ verbose: true,
1906
+ level: 'debug',
1907
+ enableStats: true,
1908
+ },
1909
+ },
1910
+ /** Production-ready configuration */
1911
+ production: {
1912
+ defaultPreset: 'advanced',
1913
+ performance: {
1914
+ enableCache: true,
1915
+ enablePerformanceTracking: true,
1916
+ maxConcurrency: 5,
1917
+ cacheOptions: {
1918
+ maxSize: 5000,
1919
+ ttl: 7200000, // 2 hours
1920
+ enableLRU: true,
1921
+ enableStats: true,
1922
+ },
1923
+ },
1924
+ security: {
1925
+ maxFileSize: 200 * 1024 * 1024, // 200MB
1926
+ enableThreatIntel: true,
1927
+ scanTimeout: 45000,
1928
+ strictMode: false,
1929
+ },
1930
+ advanced: {
1931
+ enablePolyglotDetection: true,
1932
+ enableObfuscationDetection: true,
1933
+ enableNestedArchiveAnalysis: true,
1934
+ maxArchiveDepth: 7,
1935
+ },
1936
+ logging: {
1937
+ verbose: false,
1938
+ level: 'warn',
1939
+ enableStats: true,
1940
+ },
1941
+ },
1942
+ /** Development configuration */
1943
+ development: {
1944
+ defaultPreset: 'basic',
1945
+ performance: {
1946
+ enableCache: false,
1947
+ enablePerformanceTracking: true,
1948
+ maxConcurrency: 3,
1949
+ },
1950
+ security: {
1951
+ maxFileSize: 50 * 1024 * 1024, // 50MB
1952
+ scanTimeout: 15000,
1953
+ strictMode: false,
1954
+ },
1955
+ logging: {
1956
+ verbose: true,
1957
+ level: 'debug',
1958
+ enableStats: true,
1959
+ },
1960
+ },
1961
+ };
1962
+ /**
1963
+ * Configuration manager
1964
+ */
1965
+ class ConfigManager {
1966
+ constructor(initialConfig) {
1967
+ this.config = this.mergeConfig(DEFAULT_CONFIG, initialConfig || {});
3409
1968
  }
3410
1969
  /**
3411
- * Scan files from File objects (browser environment)
1970
+ * Get current configuration
3412
1971
  */
3413
- async scanFiles(files) {
3414
- const tasks = await Promise.all(files.map(async (file) => ({
3415
- content: new Uint8Array(await file.arrayBuffer()),
3416
- context: {
3417
- filename: file.name,
3418
- mimeType: file.type,
3419
- size: file.size,
3420
- },
3421
- })));
3422
- return this.scanBatch(tasks);
1972
+ getConfig() {
1973
+ return { ...this.config };
3423
1974
  }
3424
1975
  /**
3425
- * Scan files from file paths (Node.js environment)
1976
+ * Update configuration
3426
1977
  */
3427
- async scanFilePaths(filePaths) {
3428
- const fs = await import('fs/promises');
3429
- const path = await import('path');
3430
- const tasks = await Promise.all(filePaths.map(async (filePath) => {
3431
- const [content, stats] = await Promise.all([
3432
- fs.readFile(filePath),
3433
- fs.stat(filePath),
3434
- ]);
3435
- return {
3436
- content: new Uint8Array(content),
3437
- context: {
3438
- filename: path.basename(filePath),
3439
- size: stats.size,
3440
- },
3441
- };
3442
- }));
3443
- return this.scanBatch(tasks);
3444
- }
3445
- }
3446
- /**
3447
- * Quick helper for batch scanning with default options
3448
- */
3449
- async function batchScan(tasks, options) {
3450
- const scanner = new BatchScanner(options);
3451
- return scanner.scanBatch(tasks);
3452
- }
3453
-
3454
- /**
3455
- * Threat intelligence integration and enhanced detection
3456
- * @module utils/threat-intelligence
3457
- */
3458
- /**
3459
- * Built-in threat intelligence - known malware hashes
3460
- * In production, this would connect to real threat intel APIs
3461
- */
3462
- class LocalThreatIntelligence {
3463
- constructor() {
3464
- this.name = 'Local Database';
3465
- this.knownThreats = new Map();
3466
- // Initialize with some example known threats (in production, load from database)
3467
- this.initializeKnownThreats();
3468
- }
3469
- initializeKnownThreats() {
3470
- // Example: EICAR test file hash
3471
- this.knownThreats.set('275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f', {
3472
- threatLevel: 100,
3473
- category: 'test-malware',
3474
- source: 'local',
3475
- metadata: { name: 'EICAR Test File' },
3476
- });
3477
- }
3478
- async checkHash(hash) {
3479
- return this.knownThreats.get(hash.toLowerCase()) || null;
1978
+ updateConfig(updates) {
1979
+ this.config = this.mergeConfig(this.config, updates);
3480
1980
  }
3481
1981
  /**
3482
- * Add a known threat to the local database
1982
+ * Load a preset configuration
3483
1983
  */
3484
- addThreat(hash, info) {
3485
- this.knownThreats.set(hash.toLowerCase(), info);
1984
+ loadPreset(preset) {
1985
+ const presetConfig = CONFIG_PRESETS[preset];
1986
+ this.config = this.mergeConfig(DEFAULT_CONFIG, presetConfig);
3486
1987
  }
3487
1988
  /**
3488
- * Remove a threat from the local database
1989
+ * Reset to default configuration
3489
1990
  */
3490
- removeThreat(hash) {
3491
- return this.knownThreats.delete(hash.toLowerCase());
1991
+ reset() {
1992
+ this.config = { ...DEFAULT_CONFIG };
3492
1993
  }
3493
1994
  /**
3494
- * Get all known threats
1995
+ * Get a specific configuration value
3495
1996
  */
3496
- getAllThreats() {
3497
- return new Map(this.knownThreats);
3498
- }
3499
- }
3500
- /**
3501
- * Threat intelligence aggregator
3502
- */
3503
- class ThreatIntelligenceAggregator {
3504
- constructor(sources) {
3505
- this.sources = [];
3506
- if (sources) {
3507
- this.sources = sources;
3508
- }
3509
- else {
3510
- // Default to local intelligence
3511
- this.sources = [new LocalThreatIntelligence()];
3512
- }
1997
+ get(key) {
1998
+ return this.config[key];
3513
1999
  }
3514
2000
  /**
3515
- * Add a threat intelligence source
2001
+ * Set a specific configuration value
3516
2002
  */
3517
- addSource(source) {
3518
- this.sources.push(source);
2003
+ set(key, value) {
2004
+ this.config[key] = value;
3519
2005
  }
3520
2006
  /**
3521
- * Check file hash against all sources
2007
+ * Validate configuration
3522
2008
  */
3523
- async checkHash(hash) {
3524
- const results = await Promise.allSettled(this.sources.map(source => source.checkHash(hash)));
3525
- const threats = [];
3526
- for (const result of results) {
3527
- if (result.status === 'fulfilled' && result.value) {
3528
- threats.push(result.value);
2009
+ validate() {
2010
+ const errors = [];
2011
+ // Validate performance settings
2012
+ if (this.config.performance?.maxConcurrency !== undefined) {
2013
+ if (this.config.performance.maxConcurrency < 1) {
2014
+ errors.push('maxConcurrency must be at least 1');
2015
+ }
2016
+ if (this.config.performance.maxConcurrency > 50) {
2017
+ errors.push('maxConcurrency should not exceed 50');
3529
2018
  }
3530
2019
  }
3531
- return threats;
2020
+ // Validate security settings
2021
+ if (this.config.security?.maxFileSize !== undefined) {
2022
+ if (this.config.security.maxFileSize < 1024) {
2023
+ errors.push('maxFileSize must be at least 1KB');
2024
+ }
2025
+ }
2026
+ if (this.config.security?.scanTimeout !== undefined) {
2027
+ if (this.config.security.scanTimeout < 1000) {
2028
+ errors.push('scanTimeout must be at least 1000ms');
2029
+ }
2030
+ }
2031
+ // Validate advanced settings
2032
+ if (this.config.advanced?.maxArchiveDepth !== undefined) {
2033
+ if (this.config.advanced.maxArchiveDepth < 1) {
2034
+ errors.push('maxArchiveDepth must be at least 1');
2035
+ }
2036
+ if (this.config.advanced.maxArchiveDepth > 20) {
2037
+ errors.push('maxArchiveDepth should not exceed 20');
2038
+ }
2039
+ }
2040
+ return {
2041
+ valid: errors.length === 0,
2042
+ errors,
2043
+ };
3532
2044
  }
3533
2045
  /**
3534
- * Enhance scan report with threat intelligence
2046
+ * Deep merge configuration objects
3535
2047
  */
3536
- async enhanceScanReport(content, report) {
3537
- // Calculate file hash
3538
- const hash = createHash('sha256').update(content).digest('hex');
3539
- // Check threat intelligence
3540
- const threatIntel = await this.checkHash(hash);
3541
- // Calculate risk score
3542
- const riskScore = this.calculateRiskScore(report, threatIntel);
2048
+ mergeConfig(base, updates) {
3543
2049
  return {
3544
- ...report,
3545
- fileHash: hash,
3546
- threatIntel: threatIntel.length > 0 ? threatIntel : undefined,
3547
- riskScore,
2050
+ ...base,
2051
+ ...updates,
2052
+ performance: {
2053
+ ...base.performance,
2054
+ ...updates.performance,
2055
+ cacheOptions: {
2056
+ ...base.performance?.cacheOptions,
2057
+ ...updates.performance?.cacheOptions,
2058
+ },
2059
+ },
2060
+ security: {
2061
+ ...base.security,
2062
+ ...updates.security,
2063
+ },
2064
+ advanced: {
2065
+ ...base.advanced,
2066
+ ...updates.advanced,
2067
+ },
2068
+ logging: {
2069
+ ...base.logging,
2070
+ ...updates.logging,
2071
+ },
2072
+ callbacks: {
2073
+ ...base.callbacks,
2074
+ ...updates.callbacks,
2075
+ },
2076
+ presetOptions: {
2077
+ ...base.presetOptions,
2078
+ ...updates.presetOptions,
2079
+ },
3548
2080
  };
3549
2081
  }
3550
2082
  /**
3551
- * Calculate overall risk score based on scan results and threat intel
2083
+ * Export configuration as JSON
3552
2084
  */
3553
- calculateRiskScore(report, threats) {
3554
- let score = 0;
3555
- // Base score from verdict
3556
- switch (report.verdict) {
3557
- case 'malicious':
3558
- score += 70;
3559
- break;
3560
- case 'suspicious':
3561
- score += 40;
3562
- break;
3563
- case 'clean':
3564
- score += 0;
3565
- break;
2085
+ toJSON() {
2086
+ return JSON.stringify(this.config, null, 2);
2087
+ }
2088
+ /**
2089
+ * Load configuration from JSON
2090
+ */
2091
+ fromJSON(json) {
2092
+ try {
2093
+ const parsed = JSON.parse(json);
2094
+ this.config = this.mergeConfig(DEFAULT_CONFIG, parsed);
3566
2095
  }
3567
- // Add points for number of matches
3568
- score += Math.min(report.matches.length * 5, 20);
3569
- // Add points from threat intelligence
3570
- if (threats.length > 0) {
3571
- const maxThreat = Math.max(...threats.map(t => t.threatLevel));
3572
- score = Math.max(score, maxThreat);
2096
+ catch (error) {
2097
+ throw new Error(`Failed to parse configuration JSON: ${error}`);
3573
2098
  }
3574
- return Math.min(score, 100);
3575
2099
  }
3576
2100
  }
3577
2101
  /**
3578
- * Create default threat intelligence aggregator
3579
- */
3580
- function createThreatIntelligence() {
3581
- return new ThreatIntelligenceAggregator();
2102
+ * Create a new configuration manager
2103
+ */
2104
+ function createConfig(config) {
2105
+ return new ConfigManager(config);
3582
2106
  }
3583
2107
  /**
3584
- * Helper to get file hash
2108
+ * Get a preset configuration
3585
2109
  */
3586
- function getFileHash(content) {
3587
- return createHash('sha256').update(content).digest('hex');
2110
+ function getPresetConfig(preset) {
2111
+ return { ...DEFAULT_CONFIG, ...CONFIG_PRESETS[preset] };
3588
2112
  }
3589
2113
 
3590
2114
  /**
3591
- * Export utilities for scan results
3592
- * @module utils/export
3593
- */
3594
- /**
3595
- * Export scan results to various formats
2115
+ * HIPAA Compliance Module for Pompelmi
2116
+ *
2117
+ * This module provides comprehensive HIPAA compliance features for healthcare environments
2118
+ * where Pompelmi is used to analyze potentially compromised systems containing PHI.
2119
+ *
2120
+ * Key protections:
2121
+ * - Data sanitization and redaction
2122
+ * - Secure temporary file handling
2123
+ * - Audit logging
2124
+ * - Memory protection
2125
+ * - Error message sanitization
3596
2126
  */
3597
- class ScanResultExporter {
3598
- /**
3599
- * Export to JSON format
3600
- */
3601
- toJSON(reports, options = {}) {
3602
- const data = Array.isArray(reports) ? reports : [reports];
3603
- if (!options.includeDetails) {
3604
- // Simplified output
3605
- const simplified = data.map(r => ({
3606
- verdict: r.verdict,
3607
- file: r.file?.name,
3608
- matches: r.matches.length,
3609
- durationMs: r.durationMs,
3610
- }));
3611
- return options.prettyPrint
3612
- ? JSON.stringify(simplified, null, 2)
3613
- : JSON.stringify(simplified);
3614
- }
3615
- return options.prettyPrint
3616
- ? JSON.stringify(data, null, 2)
3617
- : JSON.stringify(data);
3618
- }
3619
- /**
3620
- * Export to CSV format
3621
- */
3622
- toCSV(reports, options = {}) {
3623
- const data = Array.isArray(reports) ? reports : [reports];
3624
- const headers = [
3625
- 'filename',
3626
- 'verdict',
3627
- 'matches_count',
3628
- 'file_size',
3629
- 'mime_type',
3630
- 'duration_ms',
3631
- 'engine',
3632
- ];
3633
- if (options.includeDetails) {
3634
- headers.push('reasons', 'match_rules');
3635
- }
3636
- const rows = data.map(report => {
3637
- const row = [
3638
- this.escapeCsv(report.file?.name || 'unknown'),
3639
- report.verdict,
3640
- report.matches.length.toString(),
3641
- (report.file?.size || 0).toString(),
3642
- this.escapeCsv(report.file?.mimeType || 'unknown'),
3643
- (report.durationMs || 0).toString(),
3644
- report.engine || 'unknown',
3645
- ];
3646
- if (options.includeDetails) {
3647
- row.push(this.escapeCsv((report.reasons || []).join('; ')), this.escapeCsv(report.matches.map(m => m.rule).join('; ')));
3648
- }
3649
- return row.join(',');
3650
- });
3651
- return [headers.join(','), ...rows].join('\n');
2127
+ class HipaaComplianceManager {
2128
+ constructor(config) {
2129
+ this.auditEvents = [];
2130
+ this.config = {
2131
+ sanitizeErrors: true,
2132
+ sanitizeFilenames: true,
2133
+ encryptTempFiles: true,
2134
+ memoryProtection: true,
2135
+ requireSecureTransport: true,
2136
+ ...config,
2137
+ enabled: config.enabled !== undefined ? config.enabled : true
2138
+ };
2139
+ this.sessionId = this.generateSessionId();
3652
2140
  }
3653
2141
  /**
3654
- * Export to Markdown format
2142
+ * Sanitize filename to prevent PHI leakage in logs
3655
2143
  */
3656
- toMarkdown(reports, options = {}) {
3657
- const data = Array.isArray(reports) ? reports : [reports];
3658
- let md = '# Scan Results\n\n';
3659
- md += `**Total Scans:** ${data.length}\n\n`;
3660
- const clean = data.filter(r => r.verdict === 'clean').length;
3661
- const suspicious = data.filter(r => r.verdict === 'suspicious').length;
3662
- const malicious = data.filter(r => r.verdict === 'malicious').length;
3663
- md += '## Summary\n\n';
3664
- md += `- ✅ Clean: ${clean}\n`;
3665
- md += `- ⚠️ Suspicious: ${suspicious}\n`;
3666
- md += `- ❌ Malicious: ${malicious}\n\n`;
3667
- md += '## Detailed Results\n\n';
3668
- for (const report of data) {
3669
- const icon = report.verdict === 'clean' ? '✅' : report.verdict === 'suspicious' ? '⚠️' : '❌';
3670
- md += `### ${icon} ${report.file?.name || 'Unknown'}\n\n`;
3671
- md += `- **Verdict:** ${report.verdict}\n`;
3672
- md += `- **Size:** ${this.formatBytes(report.file?.size || 0)}\n`;
3673
- md += `- **MIME Type:** ${report.file?.mimeType || 'unknown'}\n`;
3674
- md += `- **Duration:** ${report.durationMs || 0}ms\n`;
3675
- md += `- **Matches:** ${report.matches.length}\n`;
3676
- if (options.includeDetails && report.matches.length > 0) {
3677
- md += '\n**Match Details:**\n';
3678
- for (const match of report.matches) {
3679
- md += `- ${match.rule}`;
3680
- if (match.tags && match.tags.length > 0) {
3681
- md += ` (${match.tags.join(', ')})`;
3682
- }
3683
- md += '\n';
3684
- }
3685
- }
3686
- md += '\n';
2144
+ sanitizeFilename(filename) {
2145
+ if (!this.config.enabled || !this.config.sanitizeFilenames || !filename) {
2146
+ return filename || 'unknown';
3687
2147
  }
3688
- return md;
3689
- }
3690
- /**
3691
- * Export to SARIF format (Static Analysis Results Interchange Format)
3692
- * Useful for CI/CD integration
3693
- */
3694
- toSARIF(reports, options = {}) {
3695
- const data = Array.isArray(reports) ? reports : [reports];
3696
- const results = data.flatMap(report => {
3697
- if (report.verdict === 'clean')
3698
- return [];
3699
- return report.matches.map(match => ({
3700
- ruleId: match.rule,
3701
- level: report.verdict === 'malicious' ? 'error' : 'warning',
3702
- message: {
3703
- text: `${match.rule} detected in ${report.file?.name || 'unknown file'}`,
3704
- },
3705
- locations: [
3706
- {
3707
- physicalLocation: {
3708
- artifactLocation: {
3709
- uri: report.file?.name || 'unknown',
3710
- },
3711
- },
3712
- },
3713
- ],
3714
- properties: {
3715
- tags: match.tags,
3716
- metadata: match.meta,
3717
- },
3718
- }));
3719
- });
3720
- const sarif = {
3721
- version: '2.1.0',
3722
- $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
3723
- runs: [
3724
- {
3725
- tool: {
3726
- driver: {
3727
- name: 'Pompelmi',
3728
- version: '0.29.0',
3729
- informationUri: 'https://pompelmi.github.io/pompelmi/',
3730
- },
3731
- },
3732
- results,
3733
- },
3734
- ],
3735
- };
3736
- return options.prettyPrint
3737
- ? JSON.stringify(sarif, null, 2)
3738
- : JSON.stringify(sarif);
2148
+ // Remove potentially sensitive path information
2149
+ const basename = path.basename(filename);
2150
+ // Hash the filename to create a consistent but non-revealing identifier
2151
+ const hash = crypto.createHash('sha256').update(basename).digest('hex').substring(0, 8);
2152
+ // Preserve file extension for analysis purposes
2153
+ const ext = path.extname(basename);
2154
+ return `file_${hash}${ext}`;
3739
2155
  }
3740
2156
  /**
3741
- * Export to HTML format
2157
+ * Sanitize error messages to prevent PHI exposure
3742
2158
  */
3743
- toHTML(reports, options = {}) {
3744
- const data = Array.isArray(reports) ? reports : [reports];
3745
- const clean = data.filter(r => r.verdict === 'clean').length;
3746
- const suspicious = data.filter(r => r.verdict === 'suspicious').length;
3747
- const malicious = data.filter(r => r.verdict === 'malicious').length;
3748
- let html = `<!DOCTYPE html>
3749
- <html lang="en">
3750
- <head>
3751
- <meta charset="UTF-8">
3752
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
3753
- <title>Pompelmi Scan Results</title>
3754
- <style>
3755
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
3756
- .summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin: 20px 0; }
3757
- .card { padding: 20px; border-radius: 8px; text-align: center; }
3758
- .clean { background: #d4edda; color: #155724; }
3759
- .suspicious { background: #fff3cd; color: #856404; }
3760
- .malicious { background: #f8d7da; color: #721c24; }
3761
- .result { border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin: 10px 0; }
3762
- .result h3 { margin-top: 0; }
3763
- .badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.8em; margin: 2px; }
3764
- table { width: 100%; border-collapse: collapse; }
3765
- th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
3766
- </style>
3767
- </head>
3768
- <body>
3769
- <h1>🛡️ Pompelmi Scan Results</h1>
3770
- <div class="summary">
3771
- <div class="card clean"><h2>${clean}</h2><p>Clean Files</p></div>
3772
- <div class="card suspicious"><h2>${suspicious}</h2><p>Suspicious Files</p></div>
3773
- <div class="card malicious"><h2>${malicious}</h2><p>Malicious Files</p></div>
3774
- </div>
3775
- <h2>Detailed Results</h2>`;
3776
- for (const report of data) {
3777
- const statusClass = report.verdict;
3778
- html += `<div class="result ${statusClass}">`;
3779
- html += `<h3>${this.escapeHtml(report.file?.name || 'Unknown')}</h3>`;
3780
- html += `<table>`;
3781
- html += `<tr><th>Verdict</th><td>${report.verdict.toUpperCase()}</td></tr>`;
3782
- html += `<tr><th>Size</th><td>${this.formatBytes(report.file?.size || 0)}</td></tr>`;
3783
- html += `<tr><th>MIME Type</th><td>${this.escapeHtml(report.file?.mimeType || 'unknown')}</td></tr>`;
3784
- html += `<tr><th>Duration</th><td>${report.durationMs || 0}ms</td></tr>`;
3785
- html += `<tr><th>Matches</th><td>${report.matches.length}</td></tr>`;
3786
- html += `</table>`;
3787
- if (options.includeDetails && report.matches.length > 0) {
3788
- html += `<h4>Match Details:</h4><ul>`;
3789
- for (const match of report.matches) {
3790
- html += `<li><strong>${this.escapeHtml(match.rule)}</strong>`;
3791
- if (match.tags && match.tags.length > 0) {
3792
- html += ` ${match.tags.map(tag => `<span class="badge">${this.escapeHtml(tag)}</span>`).join('')}`;
3793
- }
3794
- html += `</li>`;
3795
- }
3796
- html += `</ul>`;
3797
- }
3798
- html += `</div>`;
2159
+ sanitizeError(error) {
2160
+ if (!this.config.enabled || !this.config.sanitizeErrors) {
2161
+ return typeof error === 'string' ? error : error.message;
3799
2162
  }
3800
- html += `</body></html>`;
3801
- return html;
2163
+ const message = typeof error === 'string' ? error : error.message;
2164
+ // Remove common patterns that might contain PHI
2165
+ let sanitized = message
2166
+ // Remove file paths
2167
+ .replace(/[A-Za-z]:\\\\[^\\s]+/g, '[REDACTED_PATH]')
2168
+ .replace(/\/[^\\s]+/g, '[REDACTED_PATH]')
2169
+ // Remove potential patient identifiers (numbers that could be MRNs, SSNs)
2170
+ .replace(/\\b\\d{3}-?\\d{2}-?\\d{4}\\b/g, '[REDACTED_ID]')
2171
+ .replace(/\\b\\d{6,}\\b/g, '[REDACTED_ID]')
2172
+ // Remove email addresses
2173
+ .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g, '[REDACTED_EMAIL]')
2174
+ // Remove potential names (capitalize words in error messages)
2175
+ .replace(/\\b[A-Z][a-z]+\\s+[A-Z][a-z]+\\b/g, '[REDACTED_NAME]')
2176
+ // Remove IP addresses
2177
+ .replace(/\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g, '[REDACTED_IP]');
2178
+ return sanitized;
3802
2179
  }
3803
2180
  /**
3804
- * Export to specified format
2181
+ * Create secure temporary file path with encryption if enabled
3805
2182
  */
3806
- export(reports, format, options = {}) {
3807
- switch (format) {
3808
- case 'json':
3809
- return this.toJSON(reports, options);
3810
- case 'csv':
3811
- return this.toCSV(reports, options);
3812
- case 'markdown':
3813
- return this.toMarkdown(reports, options);
3814
- case 'html':
3815
- return this.toHTML(reports, options);
3816
- case 'sarif':
3817
- return this.toSARIF(reports, options);
3818
- default:
3819
- throw new Error(`Unsupported export format: ${format}`);
2183
+ createSecureTempPath(prefix = 'pompelmi') {
2184
+ if (!this.config.enabled) {
2185
+ return path.join(os.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
3820
2186
  }
2187
+ // Use cryptographically secure random names
2188
+ const randomId = crypto.randomBytes(16).toString('hex');
2189
+ const timestamp = Date.now();
2190
+ // Create path in secure temp directory
2191
+ const secureTempDir = this.getSecureTempDir();
2192
+ const tempPath = path.join(secureTempDir, `${prefix}-${timestamp}-${randomId}`);
2193
+ this.auditLog('temp_file_created', {
2194
+ action: 'create_temp_file',
2195
+ success: true,
2196
+ metadata: { path: this.sanitizeFilename(tempPath) }
2197
+ });
2198
+ return tempPath;
3821
2199
  }
3822
- escapeCsv(value) {
3823
- if (value.includes(',') || value.includes('"') || value.includes('\n')) {
3824
- return `"${value.replace(/"/g, '""')}"`;
2200
+ /**
2201
+ * Get or create secure temporary directory with restricted permissions
2202
+ */
2203
+ getSecureTempDir() {
2204
+ const secureTempPath = path.join(os.tmpdir(), 'pompelmi-secure');
2205
+ try {
2206
+ const fs = require('fs');
2207
+ if (!fs.existsSync(secureTempPath)) {
2208
+ fs.mkdirSync(secureTempPath, { mode: 0o700 }); // Owner read/write/execute only
2209
+ }
3825
2210
  }
3826
- return value;
3827
- }
3828
- escapeHtml(value) {
3829
- return value
3830
- .replace(/&/g, '&amp;')
3831
- .replace(/</g, '&lt;')
3832
- .replace(/>/g, '&gt;')
3833
- .replace(/"/g, '&quot;')
3834
- .replace(/'/g, '&#039;');
3835
- }
3836
- formatBytes(bytes) {
3837
- if (bytes === 0)
3838
- return '0 Bytes';
3839
- const k = 1024;
3840
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
3841
- const i = Math.floor(Math.log(bytes) / Math.log(k));
3842
- return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
3843
- }
3844
- }
3845
- /**
3846
- * Quick export helper
3847
- */
3848
- function exportScanResults(reports, format, options) {
3849
- const exporter = new ScanResultExporter();
3850
- return exporter.export(reports, format, options);
3851
- }
3852
-
3853
- /**
3854
- * Advanced configuration system for pompelmi
3855
- * @module config
3856
- */
3857
- /**
3858
- * Default configuration
3859
- */
3860
- const DEFAULT_CONFIG = {
3861
- defaultPreset: 'zip-basic',
3862
- performance: {
3863
- enableCache: false,
3864
- enablePerformanceTracking: false,
3865
- enableParallel: true,
3866
- maxConcurrency: 5,
3867
- cacheOptions: {
3868
- maxSize: 1000,
3869
- ttl: 3600000, // 1 hour
3870
- enableLRU: true,
3871
- enableStats: false,
3872
- },
3873
- },
3874
- security: {
3875
- maxFileSize: 100 * 1024 * 1024, // 100MB
3876
- enableThreatIntel: false,
3877
- scanTimeout: 30000, // 30 seconds
3878
- strictMode: false,
3879
- },
3880
- advanced: {
3881
- enablePolyglotDetection: true,
3882
- enableObfuscationDetection: true,
3883
- enableNestedArchiveAnalysis: true,
3884
- maxArchiveDepth: 5,
3885
- },
3886
- logging: {
3887
- verbose: false,
3888
- level: 'info',
3889
- enableStats: false,
3890
- },
3891
- };
3892
- /**
3893
- * Configuration presets for common use cases
3894
- */
3895
- const CONFIG_PRESETS = {
3896
- /** Fast scanning with minimal features */
3897
- fast: {
3898
- defaultPreset: 'basic',
3899
- performance: {
3900
- enableCache: true,
3901
- enablePerformanceTracking: false,
3902
- maxConcurrency: 10,
3903
- },
3904
- advanced: {
3905
- enablePolyglotDetection: false,
3906
- enableObfuscationDetection: false,
3907
- enableNestedArchiveAnalysis: false,
3908
- },
3909
- },
3910
- /** Balanced scanning (recommended) */
3911
- balanced: DEFAULT_CONFIG,
3912
- /** Thorough scanning with all features */
3913
- thorough: {
3914
- defaultPreset: 'advanced',
3915
- performance: {
3916
- enableCache: true,
3917
- enablePerformanceTracking: true,
3918
- maxConcurrency: 3,
3919
- },
3920
- security: {
3921
- maxFileSize: 500 * 1024 * 1024, // 500MB
3922
- enableThreatIntel: true,
3923
- scanTimeout: 60000, // 60 seconds
3924
- strictMode: true,
3925
- },
3926
- advanced: {
3927
- enablePolyglotDetection: true,
3928
- enableObfuscationDetection: true,
3929
- enableNestedArchiveAnalysis: true,
3930
- maxArchiveDepth: 10,
3931
- },
3932
- logging: {
3933
- verbose: true,
3934
- level: 'debug',
3935
- enableStats: true,
3936
- },
3937
- },
3938
- /** Production-ready configuration */
3939
- production: {
3940
- defaultPreset: 'advanced',
3941
- performance: {
3942
- enableCache: true,
3943
- enablePerformanceTracking: true,
3944
- maxConcurrency: 5,
3945
- cacheOptions: {
3946
- maxSize: 5000,
3947
- ttl: 7200000, // 2 hours
3948
- enableLRU: true,
3949
- enableStats: true,
3950
- },
3951
- },
3952
- security: {
3953
- maxFileSize: 200 * 1024 * 1024, // 200MB
3954
- enableThreatIntel: true,
3955
- scanTimeout: 45000,
3956
- strictMode: false,
3957
- },
3958
- advanced: {
3959
- enablePolyglotDetection: true,
3960
- enableObfuscationDetection: true,
3961
- enableNestedArchiveAnalysis: true,
3962
- maxArchiveDepth: 7,
3963
- },
3964
- logging: {
3965
- verbose: false,
3966
- level: 'warn',
3967
- enableStats: true,
3968
- },
3969
- },
3970
- /** Development configuration */
3971
- development: {
3972
- defaultPreset: 'basic',
3973
- performance: {
3974
- enableCache: false,
3975
- enablePerformanceTracking: true,
3976
- maxConcurrency: 3,
3977
- },
3978
- security: {
3979
- maxFileSize: 50 * 1024 * 1024, // 50MB
3980
- scanTimeout: 15000,
3981
- strictMode: false,
3982
- },
3983
- logging: {
3984
- verbose: true,
3985
- level: 'debug',
3986
- enableStats: true,
3987
- },
3988
- },
3989
- };
3990
- /**
3991
- * Configuration manager
3992
- */
3993
- class ConfigManager {
3994
- constructor(initialConfig) {
3995
- this.config = this.mergeConfig(DEFAULT_CONFIG, initialConfig || {});
2211
+ catch (error) {
2212
+ // Fallback to system temp
2213
+ return os.tmpdir();
2214
+ }
2215
+ return secureTempPath;
3996
2216
  }
3997
2217
  /**
3998
- * Get current configuration
2218
+ * Secure file cleanup with multiple overwrite passes
3999
2219
  */
4000
- getConfig() {
4001
- return { ...this.config };
2220
+ async secureFileCleanup(filePath) {
2221
+ if (!this.config.enabled) {
2222
+ try {
2223
+ const fs = await import('fs/promises');
2224
+ await fs.unlink(filePath);
2225
+ }
2226
+ catch {
2227
+ // Ignore cleanup errors
2228
+ }
2229
+ return;
2230
+ }
2231
+ try {
2232
+ const fs = await import('fs/promises');
2233
+ const stats = await fs.stat(filePath);
2234
+ if (this.config.memoryProtection) {
2235
+ // Overwrite file with random data multiple times (DoD 5220.22-M standard)
2236
+ const fileSize = stats.size;
2237
+ const buffer = crypto.randomBytes(Math.min(fileSize, 64 * 1024)); // 64KB chunks
2238
+ for (let pass = 0; pass < 3; pass++) {
2239
+ const handle = await fs.open(filePath, 'r+');
2240
+ try {
2241
+ for (let offset = 0; offset < fileSize; offset += buffer.length) {
2242
+ const chunk = offset + buffer.length > fileSize
2243
+ ? buffer.subarray(0, fileSize - offset)
2244
+ : buffer;
2245
+ await handle.write(chunk, 0, chunk.length, offset);
2246
+ }
2247
+ await handle.sync();
2248
+ }
2249
+ finally {
2250
+ await handle.close();
2251
+ }
2252
+ }
2253
+ }
2254
+ // Final deletion
2255
+ await fs.unlink(filePath);
2256
+ this.auditLog('temp_file_deleted', {
2257
+ action: 'secure_delete',
2258
+ success: true,
2259
+ metadata: {
2260
+ path: this.sanitizeFilename(filePath),
2261
+ overwritePasses: this.config.memoryProtection ? 3 : 0
2262
+ }
2263
+ });
2264
+ }
2265
+ catch (error) {
2266
+ this.auditLog('temp_file_deleted', {
2267
+ action: 'secure_delete',
2268
+ success: false,
2269
+ sanitizedError: this.sanitizeError(error),
2270
+ metadata: { path: this.sanitizeFilename(filePath) }
2271
+ });
2272
+ }
4002
2273
  }
4003
2274
  /**
4004
- * Update configuration
2275
+ * Calculate secure file hash for audit purposes
4005
2276
  */
4006
- updateConfig(updates) {
4007
- this.config = this.mergeConfig(this.config, updates);
2277
+ calculateFileHash(data) {
2278
+ return crypto.createHash('sha256').update(data).digest('hex');
4008
2279
  }
4009
2280
  /**
4010
- * Load a preset configuration
2281
+ * Log audit event
4011
2282
  */
4012
- loadPreset(preset) {
4013
- const presetConfig = CONFIG_PRESETS[preset];
4014
- this.config = this.mergeConfig(DEFAULT_CONFIG, presetConfig);
2283
+ auditLog(eventType, details) {
2284
+ if (!this.config.enabled)
2285
+ return;
2286
+ const event = {
2287
+ timestamp: new Date().toISOString(),
2288
+ eventType,
2289
+ sessionId: this.sessionId,
2290
+ details: {
2291
+ action: details.action || 'unknown',
2292
+ success: details.success ?? true,
2293
+ ...details
2294
+ }
2295
+ };
2296
+ this.auditEvents.push(event);
2297
+ // Write to audit log file if configured
2298
+ if (this.config.auditLogPath) {
2299
+ this.writeAuditLog(event).catch(() => {
2300
+ // Silent failure to prevent error loops
2301
+ });
2302
+ }
4015
2303
  }
4016
2304
  /**
4017
- * Reset to default configuration
2305
+ * Write audit event to file
4018
2306
  */
4019
- reset() {
4020
- this.config = { ...DEFAULT_CONFIG };
2307
+ async writeAuditLog(event) {
2308
+ if (!this.config.auditLogPath)
2309
+ return;
2310
+ try {
2311
+ const fs = await import('fs/promises');
2312
+ const logLine = JSON.stringify(event) + '\\n';
2313
+ await fs.appendFile(this.config.auditLogPath, logLine, { flag: 'a' });
2314
+ }
2315
+ catch {
2316
+ // Silent failure
2317
+ }
4021
2318
  }
4022
2319
  /**
4023
- * Get a specific configuration value
2320
+ * Generate cryptographically secure session ID
4024
2321
  */
4025
- get(key) {
4026
- return this.config[key];
2322
+ generateSessionId() {
2323
+ return crypto.randomBytes(16).toString('hex');
4027
2324
  }
4028
2325
  /**
4029
- * Set a specific configuration value
2326
+ * Get current audit events for this session
4030
2327
  */
4031
- set(key, value) {
4032
- this.config[key] = value;
2328
+ getAuditEvents() {
2329
+ return [...this.auditEvents];
4033
2330
  }
4034
2331
  /**
4035
- * Validate configuration
2332
+ * Clear sensitive data from memory
4036
2333
  */
4037
- validate() {
4038
- const errors = [];
4039
- // Validate performance settings
4040
- if (this.config.performance?.maxConcurrency !== undefined) {
4041
- if (this.config.performance.maxConcurrency < 1) {
4042
- errors.push('maxConcurrency must be at least 1');
4043
- }
4044
- if (this.config.performance.maxConcurrency > 50) {
4045
- errors.push('maxConcurrency should not exceed 50');
4046
- }
4047
- }
4048
- // Validate security settings
4049
- if (this.config.security?.maxFileSize !== undefined) {
4050
- if (this.config.security.maxFileSize < 1024) {
4051
- errors.push('maxFileSize must be at least 1KB');
4052
- }
4053
- }
4054
- if (this.config.security?.scanTimeout !== undefined) {
4055
- if (this.config.security.scanTimeout < 1000) {
4056
- errors.push('scanTimeout must be at least 1000ms');
4057
- }
4058
- }
4059
- // Validate advanced settings
4060
- if (this.config.advanced?.maxArchiveDepth !== undefined) {
4061
- if (this.config.advanced.maxArchiveDepth < 1) {
4062
- errors.push('maxArchiveDepth must be at least 1');
4063
- }
4064
- if (this.config.advanced.maxArchiveDepth > 20) {
4065
- errors.push('maxArchiveDepth should not exceed 20');
4066
- }
2334
+ clearSensitiveData() {
2335
+ if (!this.config.enabled || !this.config.memoryProtection)
2336
+ return;
2337
+ // Clear audit events
2338
+ this.auditEvents.length = 0;
2339
+ // Force garbage collection if available
2340
+ if (global.gc) {
2341
+ global.gc();
4067
2342
  }
4068
- return {
4069
- valid: errors.length === 0,
4070
- errors,
4071
- };
4072
- }
4073
- /**
4074
- * Deep merge configuration objects
4075
- */
4076
- mergeConfig(base, updates) {
4077
- return {
4078
- ...base,
4079
- ...updates,
4080
- performance: {
4081
- ...base.performance,
4082
- ...updates.performance,
4083
- cacheOptions: {
4084
- ...base.performance?.cacheOptions,
4085
- ...updates.performance?.cacheOptions,
4086
- },
4087
- },
4088
- security: {
4089
- ...base.security,
4090
- ...updates.security,
4091
- },
4092
- advanced: {
4093
- ...base.advanced,
4094
- ...updates.advanced,
4095
- },
4096
- logging: {
4097
- ...base.logging,
4098
- ...updates.logging,
4099
- },
4100
- callbacks: {
4101
- ...base.callbacks,
4102
- ...updates.callbacks,
4103
- },
4104
- presetOptions: {
4105
- ...base.presetOptions,
4106
- ...updates.presetOptions,
4107
- },
4108
- };
4109
- }
4110
- /**
4111
- * Export configuration as JSON
4112
- */
4113
- toJSON() {
4114
- return JSON.stringify(this.config, null, 2);
4115
2343
  }
4116
2344
  /**
4117
- * Load configuration from JSON
2345
+ * Validate transport security
4118
2346
  */
4119
- fromJSON(json) {
2347
+ validateTransportSecurity(url) {
2348
+ if (!this.config.enabled || !this.config.requireSecureTransport) {
2349
+ return true;
2350
+ }
2351
+ if (!url)
2352
+ return true;
4120
2353
  try {
4121
- const parsed = JSON.parse(json);
4122
- this.config = this.mergeConfig(DEFAULT_CONFIG, parsed);
2354
+ const urlObj = new URL(url);
2355
+ const isSecure = urlObj.protocol === 'https:' || urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1';
2356
+ if (!isSecure) {
2357
+ this.auditLog('security_violation', {
2358
+ action: 'insecure_transport',
2359
+ success: false,
2360
+ metadata: { protocol: urlObj.protocol, hostname: urlObj.hostname }
2361
+ });
2362
+ }
2363
+ return isSecure;
4123
2364
  }
4124
- catch (error) {
4125
- throw new Error(`Failed to parse configuration JSON: ${error}`);
2365
+ catch {
2366
+ return false;
4126
2367
  }
4127
2368
  }
4128
2369
  }
2370
+ // Global HIPAA compliance instance
2371
+ let hipaaManager = null;
4129
2372
  /**
4130
- * Create a new configuration manager
2373
+ * Initialize HIPAA compliance
4131
2374
  */
4132
- function createConfig(config) {
4133
- return new ConfigManager(config);
2375
+ function initializeHipaaCompliance(config) {
2376
+ hipaaManager = new HipaaComplianceManager(config);
2377
+ return hipaaManager;
4134
2378
  }
4135
2379
  /**
4136
- * Get a preset configuration
2380
+ * Get current HIPAA compliance manager
4137
2381
  */
4138
- function getPresetConfig(preset) {
4139
- return { ...DEFAULT_CONFIG, ...CONFIG_PRESETS[preset] };
2382
+ function getHipaaManager() {
2383
+ return hipaaManager;
2384
+ }
2385
+ /**
2386
+ * HIPAA-compliant error wrapper
2387
+ */
2388
+ function createHipaaError(error, context) {
2389
+ const manager = getHipaaManager();
2390
+ if (!manager) {
2391
+ return typeof error === 'string' ? new Error(error) : error;
2392
+ }
2393
+ const sanitizedMessage = manager.sanitizeError(error);
2394
+ const hipaaError = new Error(sanitizedMessage);
2395
+ manager.auditLog('error_occurred', {
2396
+ action: context || 'error',
2397
+ success: false,
2398
+ sanitizedError: sanitizedMessage
2399
+ });
2400
+ return hipaaError;
4140
2401
  }
2402
+ /**
2403
+ * HIPAA-compliant temporary file utilities
2404
+ */
2405
+ const HipaaTemp = {
2406
+ createPath: (prefix) => {
2407
+ const manager = getHipaaManager();
2408
+ return manager ? manager.createSecureTempPath(prefix) : path.join(os.tmpdir(), `${prefix || 'pompelmi'}-${Date.now()}`);
2409
+ },
2410
+ cleanup: async (filePath) => {
2411
+ const manager = getHipaaManager();
2412
+ if (manager) {
2413
+ await manager.secureFileCleanup(filePath);
2414
+ }
2415
+ else {
2416
+ try {
2417
+ const fs = await import('fs/promises');
2418
+ await fs.unlink(filePath);
2419
+ }
2420
+ catch {
2421
+ // Ignore errors
2422
+ }
2423
+ }
2424
+ }
2425
+ };
4141
2426
 
4142
- export { BatchScanner, CONFIG_PRESETS, CommonHeuristicsScanner, ConfigManager, DEFAULT_CONFIG, DEFAULT_POLICY, HipaaComplianceManager, HipaaTemp, LocalThreatIntelligence, PRESET_CONFIGS, PerformanceTracker, SUSPICIOUS_PATTERNS, ScanCacheManager, ScanResultExporter, ThreatIntelligenceAggregator, aggregateScanStats, analyzeNestedArchives, batchScan, composeScanners, createConfig, createHipaaError, createPresetScanner, createThreatIntelligence, createZipBombGuard, definePolicy, detectObfuscatedScripts, detectPolyglot, exportScanResults, getDefaultCache, getFileHash, getHipaaManager, getPresetConfig, initializeHipaaCompliance, mapMatchesToVerdict, resetDefaultCache, scanBytes, scanFile, scanFiles, scanFilesWithRemoteYara, useFileScanner, validateFile };
2427
+ export { ARCHIVES, BatchScanner, CONFIG_PRESETS, CONSERVATIVE_DEFAULT, CommonHeuristicsScanner, ConfigManager, DEFAULT_CONFIG, DEFAULT_POLICY, DOCUMENTS_ONLY, HipaaTemp, IMAGES_ONLY, LocalThreatIntelligence, POLICY_PACKS, PerformanceTracker, STRICT_PUBLIC_UPLOAD, SUSPICIOUS_PATTERNS, ScanCacheManager, ScanResultExporter, ThreatIntelligenceAggregator, aggregateScanStats, analyzeNestedArchives, batchScan, composeScanners, createConfig, createHipaaError, createPresetScanner, createThreatIntelligence, createZipBombGuard, definePolicy, detectObfuscatedScripts, detectPolyglot, exportScanResults, getDefaultCache, getFileHash, getHipaaManager, getPolicyPack, getPresetConfig, initializeHipaaCompliance, mapMatchesToVerdict, resetDefaultCache, scanBytes, scanFile, scanFiles, scanFilesWithRemoteYara, validateFile };
4143
2428
  //# sourceMappingURL=pompelmi.esm.js.map