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