exonware-xwsystem 0.0.1.411__py3-none-any.whl → 0.1.0.3__py3-none-any.whl
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.
- exonware/__init__.py +2 -1
- exonware/conf.py +2 -2
- exonware/xwsystem/__init__.py +115 -43
- exonware/xwsystem/base.py +30 -0
- exonware/xwsystem/caching/__init__.py +39 -13
- exonware/xwsystem/caching/base.py +24 -6
- exonware/xwsystem/caching/bloom_cache.py +2 -2
- exonware/xwsystem/caching/cache_manager.py +2 -1
- exonware/xwsystem/caching/conditional.py +2 -2
- exonware/xwsystem/caching/contracts.py +85 -139
- exonware/xwsystem/caching/decorators.py +6 -19
- exonware/xwsystem/caching/defs.py +2 -1
- exonware/xwsystem/caching/disk_cache.py +2 -1
- exonware/xwsystem/caching/distributed.py +2 -1
- exonware/xwsystem/caching/errors.py +2 -1
- exonware/xwsystem/caching/events.py +110 -27
- exonware/xwsystem/caching/eviction_strategies.py +2 -2
- exonware/xwsystem/caching/external_caching_python.py +701 -0
- exonware/xwsystem/caching/facade.py +253 -0
- exonware/xwsystem/caching/factory.py +300 -0
- exonware/xwsystem/caching/fluent.py +14 -12
- exonware/xwsystem/caching/integrity.py +21 -6
- exonware/xwsystem/caching/lfu_cache.py +2 -1
- exonware/xwsystem/caching/lfu_optimized.py +18 -6
- exonware/xwsystem/caching/lru_cache.py +7 -4
- exonware/xwsystem/caching/memory_bounded.py +2 -2
- exonware/xwsystem/caching/metrics_exporter.py +2 -2
- exonware/xwsystem/caching/observable_cache.py +2 -2
- exonware/xwsystem/caching/pluggable_cache.py +2 -2
- exonware/xwsystem/caching/rate_limiter.py +2 -2
- exonware/xwsystem/caching/read_through.py +2 -2
- exonware/xwsystem/caching/secure_cache.py +81 -28
- exonware/xwsystem/caching/serializable.py +9 -7
- exonware/xwsystem/caching/stats.py +2 -2
- exonware/xwsystem/caching/tagging.py +2 -2
- exonware/xwsystem/caching/ttl_cache.py +4 -3
- exonware/xwsystem/caching/two_tier_cache.py +6 -3
- exonware/xwsystem/caching/utils.py +30 -12
- exonware/xwsystem/caching/validation.py +2 -2
- exonware/xwsystem/caching/warming.py +6 -3
- exonware/xwsystem/caching/write_behind.py +15 -6
- exonware/xwsystem/config/__init__.py +11 -17
- exonware/xwsystem/config/base.py +5 -5
- exonware/xwsystem/config/contracts.py +93 -153
- exonware/xwsystem/config/defaults.py +3 -2
- exonware/xwsystem/config/defs.py +3 -2
- exonware/xwsystem/config/errors.py +2 -5
- exonware/xwsystem/config/logging.py +12 -8
- exonware/xwsystem/config/logging_setup.py +3 -2
- exonware/xwsystem/config/performance.py +73 -391
- exonware/xwsystem/config/performance_modes.py +9 -8
- exonware/xwsystem/config/version_manager.py +1 -0
- exonware/xwsystem/config.py +27 -0
- exonware/xwsystem/console/__init__.py +53 -0
- exonware/xwsystem/console/base.py +133 -0
- exonware/xwsystem/console/cli/__init__.py +61 -0
- exonware/xwsystem/{cli → console/cli}/args.py +27 -24
- exonware/xwsystem/{cli → console/cli}/base.py +18 -87
- exonware/xwsystem/{cli → console/cli}/colors.py +15 -13
- exonware/xwsystem/console/cli/console.py +98 -0
- exonware/xwsystem/{cli → console/cli}/contracts.py +51 -69
- exonware/xwsystem/console/cli/defs.py +87 -0
- exonware/xwsystem/console/cli/encoding.py +69 -0
- exonware/xwsystem/{cli → console/cli}/errors.py +8 -3
- exonware/xwsystem/console/cli/event_logger.py +166 -0
- exonware/xwsystem/{cli → console/cli}/progress.py +25 -21
- exonware/xwsystem/{cli → console/cli}/prompts.py +3 -2
- exonware/xwsystem/{cli → console/cli}/tables.py +27 -24
- exonware/xwsystem/console/contracts.py +113 -0
- exonware/xwsystem/console/defs.py +154 -0
- exonware/xwsystem/console/errors.py +34 -0
- exonware/xwsystem/console/event_logger.py +385 -0
- exonware/xwsystem/console/writer.py +132 -0
- exonware/xwsystem/contracts.py +28 -0
- exonware/xwsystem/data_structures/__init__.py +23 -0
- exonware/xwsystem/data_structures/trie.py +34 -0
- exonware/xwsystem/data_structures/union_find.py +144 -0
- exonware/xwsystem/defs.py +17 -0
- exonware/xwsystem/errors.py +23 -0
- exonware/xwsystem/facade.py +62 -0
- exonware/xwsystem/http_client/__init__.py +22 -1
- exonware/xwsystem/http_client/advanced_client.py +8 -5
- exonware/xwsystem/http_client/base.py +3 -2
- exonware/xwsystem/http_client/client.py +7 -4
- exonware/xwsystem/http_client/contracts.py +42 -56
- exonware/xwsystem/http_client/defs.py +2 -1
- exonware/xwsystem/http_client/errors.py +2 -1
- exonware/xwsystem/http_client/facade.py +156 -0
- exonware/xwsystem/io/__init__.py +22 -3
- exonware/xwsystem/io/archive/__init__.py +8 -2
- exonware/xwsystem/io/archive/archive.py +1 -1
- exonware/xwsystem/io/archive/archive_files.py +4 -7
- exonware/xwsystem/io/archive/archivers.py +120 -10
- exonware/xwsystem/io/archive/base.py +4 -5
- exonware/xwsystem/io/archive/codec_integration.py +1 -2
- exonware/xwsystem/io/archive/compression.py +1 -2
- exonware/xwsystem/io/archive/facade.py +263 -0
- exonware/xwsystem/io/archive/formats/__init__.py +2 -3
- exonware/xwsystem/io/archive/formats/brotli_format.py +20 -7
- exonware/xwsystem/io/archive/formats/lz4_format.py +20 -7
- exonware/xwsystem/io/archive/formats/rar.py +11 -5
- exonware/xwsystem/io/archive/formats/sevenzip.py +12 -6
- exonware/xwsystem/io/archive/formats/squashfs_format.py +1 -2
- exonware/xwsystem/io/archive/formats/tar.py +52 -7
- exonware/xwsystem/io/archive/formats/wim_format.py +11 -5
- exonware/xwsystem/io/archive/formats/zip.py +1 -2
- exonware/xwsystem/io/archive/formats/zpaq_format.py +1 -2
- exonware/xwsystem/io/archive/formats/zstandard.py +20 -7
- exonware/xwsystem/io/base.py +119 -115
- exonware/xwsystem/io/codec/__init__.py +4 -2
- exonware/xwsystem/io/codec/base.py +19 -13
- exonware/xwsystem/io/codec/contracts.py +59 -2
- exonware/xwsystem/io/codec/registry.py +67 -21
- exonware/xwsystem/io/common/__init__.py +1 -1
- exonware/xwsystem/io/common/atomic.py +29 -16
- exonware/xwsystem/io/common/base.py +11 -10
- exonware/xwsystem/io/common/lock.py +6 -5
- exonware/xwsystem/io/common/path_manager.py +2 -1
- exonware/xwsystem/io/common/watcher.py +1 -2
- exonware/xwsystem/io/contracts.py +301 -433
- exonware/xwsystem/io/contracts_1.py +1180 -0
- exonware/xwsystem/io/data_operations.py +279 -14
- exonware/xwsystem/io/defs.py +4 -3
- exonware/xwsystem/io/errors.py +3 -2
- exonware/xwsystem/io/facade.py +87 -61
- exonware/xwsystem/io/file/__init__.py +1 -1
- exonware/xwsystem/io/file/base.py +8 -9
- exonware/xwsystem/io/file/conversion.py +2 -3
- exonware/xwsystem/io/file/file.py +61 -18
- exonware/xwsystem/io/file/paged_source.py +8 -8
- exonware/xwsystem/io/file/paging/__init__.py +1 -2
- exonware/xwsystem/io/file/paging/byte_paging.py +4 -5
- exonware/xwsystem/io/file/paging/line_paging.py +2 -3
- exonware/xwsystem/io/file/paging/record_paging.py +2 -3
- exonware/xwsystem/io/file/paging/registry.py +1 -2
- exonware/xwsystem/io/file/source.py +13 -17
- exonware/xwsystem/io/filesystem/__init__.py +1 -1
- exonware/xwsystem/io/filesystem/base.py +1 -2
- exonware/xwsystem/io/filesystem/local.py +3 -4
- exonware/xwsystem/io/folder/__init__.py +1 -1
- exonware/xwsystem/io/folder/base.py +1 -2
- exonware/xwsystem/io/folder/folder.py +16 -7
- exonware/xwsystem/io/indexing/__init__.py +14 -0
- exonware/xwsystem/io/indexing/facade.py +443 -0
- exonware/xwsystem/io/path_parser.py +98 -0
- exonware/xwsystem/io/serialization/__init__.py +21 -3
- exonware/xwsystem/io/serialization/auto_serializer.py +146 -20
- exonware/xwsystem/io/serialization/base.py +84 -34
- exonware/xwsystem/io/serialization/contracts.py +50 -73
- exonware/xwsystem/io/serialization/defs.py +2 -1
- exonware/xwsystem/io/serialization/errors.py +2 -1
- exonware/xwsystem/io/serialization/flyweight.py +154 -7
- exonware/xwsystem/io/serialization/format_detector.py +15 -14
- exonware/xwsystem/io/serialization/formats/__init__.py +8 -5
- exonware/xwsystem/io/serialization/formats/binary/bson.py +15 -6
- exonware/xwsystem/io/serialization/formats/binary/cbor.py +5 -5
- exonware/xwsystem/io/serialization/formats/binary/marshal.py +5 -5
- exonware/xwsystem/io/serialization/formats/binary/msgpack.py +5 -5
- exonware/xwsystem/io/serialization/formats/binary/pickle.py +5 -5
- exonware/xwsystem/io/serialization/formats/binary/plistlib.py +5 -5
- exonware/xwsystem/io/serialization/formats/database/dbm.py +7 -7
- exonware/xwsystem/io/serialization/formats/database/shelve.py +7 -7
- exonware/xwsystem/io/serialization/formats/database/sqlite3.py +7 -7
- exonware/xwsystem/io/serialization/formats/tabular/__init__.py +27 -0
- exonware/xwsystem/io/serialization/formats/tabular/base.py +89 -0
- exonware/xwsystem/io/serialization/formats/tabular/csv.py +319 -0
- exonware/xwsystem/io/serialization/formats/tabular/df.py +249 -0
- exonware/xwsystem/io/serialization/formats/tabular/excel.py +291 -0
- exonware/xwsystem/io/serialization/formats/tabular/googlesheets.py +374 -0
- exonware/xwsystem/io/serialization/formats/text/__init__.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/append_only_log.py +199 -0
- exonware/xwsystem/io/serialization/formats/text/configparser.py +5 -5
- exonware/xwsystem/io/serialization/formats/text/csv.py +7 -5
- exonware/xwsystem/io/serialization/formats/text/formdata.py +5 -5
- exonware/xwsystem/io/serialization/formats/text/json.py +65 -33
- exonware/xwsystem/io/serialization/formats/text/json5.py +8 -4
- exonware/xwsystem/io/serialization/formats/text/jsonlines.py +113 -25
- exonware/xwsystem/io/serialization/formats/text/multipart.py +5 -5
- exonware/xwsystem/io/serialization/formats/text/toml.py +8 -6
- exonware/xwsystem/io/serialization/formats/text/xml.py +25 -20
- exonware/xwsystem/io/serialization/formats/text/yaml.py +8 -6
- exonware/xwsystem/io/serialization/parsers/__init__.py +16 -0
- exonware/xwsystem/io/serialization/parsers/base.py +60 -0
- exonware/xwsystem/io/serialization/parsers/hybrid_parser.py +62 -0
- exonware/xwsystem/io/serialization/parsers/msgspec_parser.py +48 -0
- exonware/xwsystem/io/serialization/parsers/orjson_direct_parser.py +54 -0
- exonware/xwsystem/io/serialization/parsers/orjson_parser.py +62 -0
- exonware/xwsystem/io/serialization/parsers/pysimdjson_parser.py +55 -0
- exonware/xwsystem/io/serialization/parsers/rapidjson_parser.py +53 -0
- exonware/xwsystem/io/serialization/parsers/registry.py +91 -0
- exonware/xwsystem/io/serialization/parsers/standard.py +44 -0
- exonware/xwsystem/io/serialization/parsers/ujson_parser.py +53 -0
- exonware/xwsystem/io/serialization/registry.py +4 -4
- exonware/xwsystem/io/serialization/serializer.py +168 -79
- exonware/xwsystem/io/serialization/universal_options.py +367 -0
- exonware/xwsystem/io/serialization/utils/__init__.py +1 -2
- exonware/xwsystem/io/serialization/utils/path_ops.py +5 -6
- exonware/xwsystem/io/source_reader.py +223 -0
- exonware/xwsystem/io/stream/__init__.py +1 -1
- exonware/xwsystem/io/stream/async_operations.py +61 -14
- exonware/xwsystem/io/stream/base.py +1 -2
- exonware/xwsystem/io/stream/codec_io.py +6 -7
- exonware/xwsystem/ipc/__init__.py +1 -0
- exonware/xwsystem/ipc/async_fabric.py +4 -4
- exonware/xwsystem/ipc/base.py +6 -5
- exonware/xwsystem/ipc/contracts.py +41 -66
- exonware/xwsystem/ipc/defs.py +2 -1
- exonware/xwsystem/ipc/errors.py +2 -1
- exonware/xwsystem/ipc/message_queue.py +5 -2
- exonware/xwsystem/ipc/pipes.py +70 -34
- exonware/xwsystem/ipc/process_manager.py +7 -5
- exonware/xwsystem/ipc/process_pool.py +6 -5
- exonware/xwsystem/ipc/shared_memory.py +64 -11
- exonware/xwsystem/monitoring/__init__.py +7 -0
- exonware/xwsystem/monitoring/base.py +11 -8
- exonware/xwsystem/monitoring/contracts.py +86 -144
- exonware/xwsystem/monitoring/defs.py +2 -1
- exonware/xwsystem/monitoring/error_recovery.py +16 -3
- exonware/xwsystem/monitoring/errors.py +2 -1
- exonware/xwsystem/monitoring/facade.py +183 -0
- exonware/xwsystem/monitoring/memory_monitor.py +1 -0
- exonware/xwsystem/monitoring/metrics.py +1 -0
- exonware/xwsystem/monitoring/performance_manager_generic.py +7 -7
- exonware/xwsystem/monitoring/performance_monitor.py +1 -0
- exonware/xwsystem/monitoring/performance_validator.py +1 -0
- exonware/xwsystem/monitoring/system_monitor.py +6 -5
- exonware/xwsystem/monitoring/tracing.py +18 -16
- exonware/xwsystem/monitoring/tracker.py +2 -1
- exonware/xwsystem/operations/__init__.py +5 -50
- exonware/xwsystem/operations/base.py +3 -44
- exonware/xwsystem/operations/contracts.py +25 -15
- exonware/xwsystem/operations/defs.py +1 -1
- exonware/xwsystem/operations/diff.py +5 -4
- exonware/xwsystem/operations/errors.py +1 -1
- exonware/xwsystem/operations/merge.py +6 -4
- exonware/xwsystem/operations/patch.py +5 -4
- exonware/xwsystem/patterns/__init__.py +1 -0
- exonware/xwsystem/patterns/base.py +2 -1
- exonware/xwsystem/patterns/context_manager.py +2 -1
- exonware/xwsystem/patterns/contracts.py +215 -256
- exonware/xwsystem/patterns/defs.py +2 -1
- exonware/xwsystem/patterns/dynamic_facade.py +1 -0
- exonware/xwsystem/patterns/errors.py +2 -4
- exonware/xwsystem/patterns/handler_factory.py +2 -3
- exonware/xwsystem/patterns/import_registry.py +1 -0
- exonware/xwsystem/patterns/object_pool.py +1 -0
- exonware/xwsystem/patterns/registry.py +4 -43
- exonware/xwsystem/plugins/__init__.py +2 -1
- exonware/xwsystem/plugins/base.py +6 -5
- exonware/xwsystem/plugins/contracts.py +94 -158
- exonware/xwsystem/plugins/defs.py +2 -1
- exonware/xwsystem/plugins/errors.py +2 -1
- exonware/xwsystem/py.typed +3 -0
- exonware/xwsystem/query/__init__.py +36 -0
- exonware/xwsystem/query/contracts.py +56 -0
- exonware/xwsystem/query/errors.py +22 -0
- exonware/xwsystem/query/registry.py +128 -0
- exonware/xwsystem/runtime/__init__.py +2 -1
- exonware/xwsystem/runtime/base.py +4 -3
- exonware/xwsystem/runtime/contracts.py +39 -60
- exonware/xwsystem/runtime/defs.py +2 -1
- exonware/xwsystem/runtime/env.py +11 -9
- exonware/xwsystem/runtime/errors.py +2 -1
- exonware/xwsystem/runtime/reflection.py +3 -2
- exonware/xwsystem/security/__init__.py +68 -11
- exonware/xwsystem/security/audit.py +167 -0
- exonware/xwsystem/security/base.py +121 -24
- exonware/xwsystem/security/contracts.py +91 -146
- exonware/xwsystem/security/crypto.py +17 -16
- exonware/xwsystem/security/defs.py +2 -1
- exonware/xwsystem/security/errors.py +2 -1
- exonware/xwsystem/security/facade.py +321 -0
- exonware/xwsystem/security/file_security.py +330 -0
- exonware/xwsystem/security/hazmat.py +11 -8
- exonware/xwsystem/security/monitor.py +372 -0
- exonware/xwsystem/security/path_validator.py +140 -18
- exonware/xwsystem/security/policy.py +357 -0
- exonware/xwsystem/security/resource_limits.py +1 -0
- exonware/xwsystem/security/validator.py +455 -0
- exonware/xwsystem/shared/__init__.py +14 -1
- exonware/xwsystem/shared/base.py +285 -2
- exonware/xwsystem/shared/contracts.py +415 -126
- exonware/xwsystem/shared/defs.py +2 -1
- exonware/xwsystem/shared/errors.py +2 -2
- exonware/xwsystem/shared/xwobject.py +316 -0
- exonware/xwsystem/structures/__init__.py +1 -0
- exonware/xwsystem/structures/base.py +3 -2
- exonware/xwsystem/structures/circular_detector.py +15 -14
- exonware/xwsystem/structures/contracts.py +53 -76
- exonware/xwsystem/structures/defs.py +2 -1
- exonware/xwsystem/structures/errors.py +2 -1
- exonware/xwsystem/structures/tree_walker.py +2 -1
- exonware/xwsystem/threading/__init__.py +21 -4
- exonware/xwsystem/threading/async_primitives.py +6 -5
- exonware/xwsystem/threading/base.py +3 -2
- exonware/xwsystem/threading/contracts.py +87 -143
- exonware/xwsystem/threading/defs.py +2 -1
- exonware/xwsystem/threading/errors.py +2 -1
- exonware/xwsystem/threading/facade.py +175 -0
- exonware/xwsystem/threading/locks.py +1 -0
- exonware/xwsystem/threading/safe_factory.py +1 -0
- exonware/xwsystem/utils/__init__.py +40 -0
- exonware/xwsystem/utils/base.py +22 -21
- exonware/xwsystem/utils/contracts.py +50 -73
- exonware/xwsystem/utils/dt/__init__.py +19 -3
- exonware/xwsystem/utils/dt/base.py +5 -4
- exonware/xwsystem/utils/dt/contracts.py +22 -29
- exonware/xwsystem/utils/dt/defs.py +2 -1
- exonware/xwsystem/utils/dt/errors.py +2 -5
- exonware/xwsystem/utils/dt/formatting.py +88 -2
- exonware/xwsystem/utils/dt/humanize.py +10 -9
- exonware/xwsystem/utils/dt/parsing.py +56 -5
- exonware/xwsystem/utils/dt/timezone_utils.py +2 -24
- exonware/xwsystem/utils/errors.py +2 -4
- exonware/xwsystem/utils/paths.py +1 -0
- exonware/xwsystem/utils/string.py +49 -0
- exonware/xwsystem/utils/test_runner.py +139 -480
- exonware/xwsystem/utils/utils_contracts.py +2 -1
- exonware/xwsystem/utils/web.py +110 -0
- exonware/xwsystem/validation/__init__.py +25 -1
- exonware/xwsystem/validation/base.py +6 -5
- exonware/xwsystem/validation/contracts.py +29 -41
- exonware/xwsystem/validation/data_validator.py +1 -0
- exonware/xwsystem/validation/declarative.py +11 -8
- exonware/xwsystem/validation/defs.py +2 -1
- exonware/xwsystem/validation/errors.py +2 -1
- exonware/xwsystem/validation/facade.py +198 -0
- exonware/xwsystem/validation/fluent_validator.py +22 -19
- exonware/xwsystem/validation/schema_discovery.py +210 -0
- exonware/xwsystem/validation/type_safety.py +2 -1
- exonware/xwsystem/version.py +4 -4
- {exonware_xwsystem-0.0.1.411.dist-info → exonware_xwsystem-0.1.0.3.dist-info}/METADATA +71 -4
- exonware_xwsystem-0.1.0.3.dist-info/RECORD +337 -0
- exonware/xwsystem/caching/USAGE_GUIDE.md +0 -779
- exonware/xwsystem/cli/__init__.py +0 -43
- exonware/xwsystem/cli/console.py +0 -113
- exonware/xwsystem/cli/defs.py +0 -134
- exonware/xwsystem/conf.py +0 -44
- exonware/xwsystem/security/auth.py +0 -484
- exonware_xwsystem-0.0.1.411.dist-info/RECORD +0 -274
- {exonware_xwsystem-0.0.1.411.dist-info → exonware_xwsystem-0.1.0.3.dist-info}/WHEEL +0 -0
- {exonware_xwsystem-0.0.1.411.dist-info → exonware_xwsystem-0.1.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/security/monitor.py
|
|
3
|
+
"""
|
|
4
|
+
Company: eXonware.com
|
|
5
|
+
Author: Eng. Muhammad AlShehri
|
|
6
|
+
Email: connect@exonware.com
|
|
7
|
+
Version: 0.1.0.3
|
|
8
|
+
Generation Date: 07-Jan-2025
|
|
9
|
+
|
|
10
|
+
Security monitor implementation for XWSystem.
|
|
11
|
+
Implements ISecurityMonitor protocol for security monitoring and threat detection.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import time
|
|
15
|
+
from collections import defaultdict, deque
|
|
16
|
+
from typing import Any, Optional
|
|
17
|
+
from datetime import datetime, timedelta
|
|
18
|
+
|
|
19
|
+
from .base import ASecurityMonitorBase
|
|
20
|
+
from .contracts import ISecurityMonitor
|
|
21
|
+
from .defs import SecurityLevel
|
|
22
|
+
from ..config.logging_setup import get_logger
|
|
23
|
+
|
|
24
|
+
logger = get_logger("xwsystem.security.monitor")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SecurityMonitor(ASecurityMonitorBase, ISecurityMonitor):
|
|
28
|
+
"""
|
|
29
|
+
Security monitor implementation.
|
|
30
|
+
|
|
31
|
+
Provides comprehensive security monitoring including:
|
|
32
|
+
- Intrusion detection
|
|
33
|
+
- Failed login monitoring
|
|
34
|
+
- Anomaly detection
|
|
35
|
+
- Security alerts
|
|
36
|
+
- Threat level management
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, security_level: SecurityLevel = SecurityLevel.MEDIUM):
|
|
40
|
+
"""
|
|
41
|
+
Initialize security monitor.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
security_level: Security level for monitoring
|
|
45
|
+
"""
|
|
46
|
+
super().__init__()
|
|
47
|
+
self.security_level = security_level
|
|
48
|
+
self._threat_level = SecurityLevel.MEDIUM
|
|
49
|
+
self._security_alerts: list[dict[str, Any]] = []
|
|
50
|
+
self._failed_logins: dict[str, deque] = defaultdict(lambda: deque(maxlen=100))
|
|
51
|
+
self._event_history: deque = deque(maxlen=1000)
|
|
52
|
+
self._anomaly_threshold = 5.0 # Standard deviations for anomaly detection
|
|
53
|
+
|
|
54
|
+
# Intrusion detection patterns
|
|
55
|
+
self._intrusion_patterns = [
|
|
56
|
+
"multiple_failed_logins",
|
|
57
|
+
"unusual_access_pattern",
|
|
58
|
+
"privilege_escalation_attempt",
|
|
59
|
+
"data_exfiltration_pattern",
|
|
60
|
+
"command_injection_attempt",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
def detect_intrusion(self, event_data: dict[str, Any]) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Detect intrusion attempts.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
event_data: Event data to analyze
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if intrusion detected
|
|
72
|
+
"""
|
|
73
|
+
event_type = event_data.get("type", "")
|
|
74
|
+
user = event_data.get("user", "")
|
|
75
|
+
resource = event_data.get("resource", "")
|
|
76
|
+
timestamp = event_data.get("timestamp", time.time())
|
|
77
|
+
|
|
78
|
+
# Check for multiple failed logins
|
|
79
|
+
if event_type == "failed_login":
|
|
80
|
+
if self.monitor_failed_logins(user, max_attempts=5):
|
|
81
|
+
self._add_alert("intrusion", f"Multiple failed logins detected for user: {user}")
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
# Check for unusual access patterns
|
|
85
|
+
if event_type == "access":
|
|
86
|
+
if self._check_unusual_access(user, resource, timestamp):
|
|
87
|
+
self._add_alert("intrusion", f"Unusual access pattern detected for user: {user}")
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
# Check for privilege escalation attempts
|
|
91
|
+
if event_type == "privilege_escalation":
|
|
92
|
+
self._add_alert("intrusion", f"Privilege escalation attempt detected for user: {user}")
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
# Check for command injection patterns
|
|
96
|
+
if "command" in event_data:
|
|
97
|
+
command = str(event_data["command"])
|
|
98
|
+
suspicious_patterns = [";", "|", "&&", "||", "`", "$(", "<", ">"]
|
|
99
|
+
if any(pattern in command for pattern in suspicious_patterns):
|
|
100
|
+
self._add_alert("intrusion", f"Command injection pattern detected for user: {user}")
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
# Check for data exfiltration patterns
|
|
104
|
+
if event_type == "data_access":
|
|
105
|
+
data_size = event_data.get("data_size", 0)
|
|
106
|
+
if data_size > 100 * 1024 * 1024: # 100MB threshold
|
|
107
|
+
self._add_alert("intrusion", f"Large data access detected for user: {user}")
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def monitor_failed_logins(self, user: str, max_attempts: int = 5) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Monitor failed login attempts.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
user: User identifier
|
|
118
|
+
max_attempts: Maximum allowed attempts
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if threshold exceeded
|
|
122
|
+
"""
|
|
123
|
+
now = time.time()
|
|
124
|
+
user_logins = self._failed_logins[user]
|
|
125
|
+
|
|
126
|
+
# Add current failed login
|
|
127
|
+
user_logins.append(now)
|
|
128
|
+
|
|
129
|
+
# Check attempts in last 15 minutes
|
|
130
|
+
cutoff_time = now - 900 # 15 minutes
|
|
131
|
+
recent_attempts = [t for t in user_logins if t > cutoff_time]
|
|
132
|
+
|
|
133
|
+
if len(recent_attempts) >= max_attempts:
|
|
134
|
+
self._add_alert("failed_login", f"User {user} exceeded {max_attempts} failed login attempts")
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def detect_anomaly(self, behavior_data: dict[str, Any]) -> bool:
|
|
140
|
+
"""
|
|
141
|
+
Detect anomalous behavior.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
behavior_data: Behavior data to analyze
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if anomaly detected
|
|
148
|
+
"""
|
|
149
|
+
behavior_type = behavior_data.get("type", "")
|
|
150
|
+
user = behavior_data.get("user", "")
|
|
151
|
+
value = behavior_data.get("value", 0)
|
|
152
|
+
|
|
153
|
+
# Store event for pattern analysis
|
|
154
|
+
self._event_history.append({
|
|
155
|
+
"type": behavior_type,
|
|
156
|
+
"user": user,
|
|
157
|
+
"value": value,
|
|
158
|
+
"timestamp": time.time()
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
# Simple statistical anomaly detection
|
|
162
|
+
if len(self._event_history) < 10:
|
|
163
|
+
return False # Need more data
|
|
164
|
+
|
|
165
|
+
# Get recent values for this behavior type
|
|
166
|
+
recent_values = [
|
|
167
|
+
e["value"] for e in self._event_history
|
|
168
|
+
if e["type"] == behavior_type and isinstance(e["value"], (int, float))
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
if len(recent_values) < 5:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
# Calculate mean and standard deviation
|
|
175
|
+
mean = sum(recent_values) / len(recent_values)
|
|
176
|
+
variance = sum((x - mean) ** 2 for x in recent_values) / len(recent_values)
|
|
177
|
+
std_dev = variance ** 0.5
|
|
178
|
+
|
|
179
|
+
if std_dev == 0:
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
# Check if current value is anomaly (more than threshold standard deviations)
|
|
183
|
+
z_score = abs((value - mean) / std_dev)
|
|
184
|
+
|
|
185
|
+
if z_score > self._anomaly_threshold:
|
|
186
|
+
self._add_alert("anomaly", f"Anomalous behavior detected: {behavior_type} for user: {user}")
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
def get_security_alerts(self) -> list[dict[str, Any]]:
|
|
192
|
+
"""
|
|
193
|
+
Get security alerts.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List of security alerts
|
|
197
|
+
"""
|
|
198
|
+
return self._security_alerts.copy()
|
|
199
|
+
|
|
200
|
+
def clear_security_alerts(self) -> None:
|
|
201
|
+
"""Clear security alerts."""
|
|
202
|
+
self._security_alerts.clear()
|
|
203
|
+
|
|
204
|
+
def get_threat_level(self) -> SecurityLevel:
|
|
205
|
+
"""
|
|
206
|
+
Get current threat level.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Current threat level
|
|
210
|
+
"""
|
|
211
|
+
return self._threat_level
|
|
212
|
+
|
|
213
|
+
def set_threat_level(self, level: SecurityLevel) -> None:
|
|
214
|
+
"""
|
|
215
|
+
Set threat level.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
level: Threat level to set
|
|
219
|
+
"""
|
|
220
|
+
self._threat_level = level
|
|
221
|
+
logger.info(f"Threat level set to: {level.value}")
|
|
222
|
+
|
|
223
|
+
def get_security_metrics(self) -> dict[str, Any]:
|
|
224
|
+
"""
|
|
225
|
+
Get security metrics.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Security metrics dictionary
|
|
229
|
+
"""
|
|
230
|
+
now = time.time()
|
|
231
|
+
one_hour_ago = now - 3600
|
|
232
|
+
one_day_ago = now - 86400
|
|
233
|
+
|
|
234
|
+
# Count alerts by severity
|
|
235
|
+
alert_counts = defaultdict(int)
|
|
236
|
+
for alert in self._security_alerts:
|
|
237
|
+
alert_type = alert.get("type", "unknown")
|
|
238
|
+
alert_counts[alert_type] += 1
|
|
239
|
+
|
|
240
|
+
# Count failed logins
|
|
241
|
+
recent_failed_logins = sum(
|
|
242
|
+
len([t for t in logins if t > one_hour_ago])
|
|
243
|
+
for logins in self._failed_logins.values()
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Count events
|
|
247
|
+
recent_events = len([e for e in self._event_history if e.get("timestamp", 0) > one_hour_ago])
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
"threat_level": self._threat_level.value,
|
|
251
|
+
"security_level": self.security_level.value,
|
|
252
|
+
"total_alerts": len(self._security_alerts),
|
|
253
|
+
"alert_counts": dict(alert_counts),
|
|
254
|
+
"failed_logins_last_hour": recent_failed_logins,
|
|
255
|
+
"events_last_hour": recent_events,
|
|
256
|
+
"total_failed_login_users": len(self._failed_logins),
|
|
257
|
+
"monitoring_active": True,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
def _check_unusual_access(self, user: str, resource: str, timestamp: float) -> bool:
|
|
261
|
+
"""
|
|
262
|
+
Check for unusual access patterns.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
user: User identifier
|
|
266
|
+
resource: Resource identifier
|
|
267
|
+
timestamp: Access timestamp
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
True if unusual access detected
|
|
271
|
+
"""
|
|
272
|
+
# Get user's access history
|
|
273
|
+
user_accesses = [
|
|
274
|
+
e for e in self._event_history
|
|
275
|
+
if e.get("user") == user and e.get("type") == "access"
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
if len(user_accesses) < 3:
|
|
279
|
+
return False # Not enough history
|
|
280
|
+
|
|
281
|
+
# Check for access outside normal hours (simple heuristic)
|
|
282
|
+
access_time = datetime.fromtimestamp(timestamp)
|
|
283
|
+
hour = access_time.hour
|
|
284
|
+
|
|
285
|
+
# Normal hours: 8 AM to 8 PM
|
|
286
|
+
if hour < 8 or hour > 20:
|
|
287
|
+
# Check if this is unusual for this user
|
|
288
|
+
normal_hour_accesses = [
|
|
289
|
+
e for e in user_accesses
|
|
290
|
+
if 8 <= datetime.fromtimestamp(e.get("timestamp", 0)).hour <= 20
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
if len(normal_hour_accesses) > len(user_accesses) * 0.8:
|
|
294
|
+
# User usually accesses during normal hours
|
|
295
|
+
return True
|
|
296
|
+
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
def _add_alert(self, alert_type: str, message: str) -> None:
|
|
300
|
+
"""
|
|
301
|
+
Add security alert.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
alert_type: Alert type
|
|
305
|
+
message: Alert message
|
|
306
|
+
"""
|
|
307
|
+
alert = {
|
|
308
|
+
"type": alert_type,
|
|
309
|
+
"message": message,
|
|
310
|
+
"timestamp": time.time(),
|
|
311
|
+
"datetime": datetime.now().isoformat(),
|
|
312
|
+
"severity": self._get_alert_severity(alert_type),
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
self._security_alerts.append(alert)
|
|
316
|
+
|
|
317
|
+
# Keep only last 1000 alerts
|
|
318
|
+
if len(self._security_alerts) > 1000:
|
|
319
|
+
self._security_alerts = self._security_alerts[-1000:]
|
|
320
|
+
|
|
321
|
+
logger.warning(f"Security alert [{alert_type}]: {message}")
|
|
322
|
+
|
|
323
|
+
# Update threat level based on alerts
|
|
324
|
+
self._update_threat_level()
|
|
325
|
+
|
|
326
|
+
def _get_alert_severity(self, alert_type: str) -> str:
|
|
327
|
+
"""
|
|
328
|
+
Get alert severity.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
alert_type: Alert type
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Severity level
|
|
335
|
+
"""
|
|
336
|
+
severity_map = {
|
|
337
|
+
"intrusion": "high",
|
|
338
|
+
"failed_login": "medium",
|
|
339
|
+
"anomaly": "medium",
|
|
340
|
+
"privilege_escalation": "critical",
|
|
341
|
+
"data_exfiltration": "high",
|
|
342
|
+
}
|
|
343
|
+
return severity_map.get(alert_type, "low")
|
|
344
|
+
|
|
345
|
+
def _update_threat_level(self) -> None:
|
|
346
|
+
"""Update threat level based on recent alerts."""
|
|
347
|
+
if not self._security_alerts:
|
|
348
|
+
self._threat_level = SecurityLevel.MEDIUM
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
# Get alerts from last hour
|
|
352
|
+
now = time.time()
|
|
353
|
+
one_hour_ago = now - 3600
|
|
354
|
+
recent_alerts = [
|
|
355
|
+
a for a in self._security_alerts
|
|
356
|
+
if a.get("timestamp", 0) > one_hour_ago
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
# Count critical/high severity alerts
|
|
360
|
+
critical_count = sum(
|
|
361
|
+
1 for a in recent_alerts
|
|
362
|
+
if a.get("severity") in ["critical", "high"]
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
if critical_count >= 5:
|
|
366
|
+
self._threat_level = SecurityLevel.CRITICAL
|
|
367
|
+
elif critical_count >= 2:
|
|
368
|
+
self._threat_level = SecurityLevel.HIGH
|
|
369
|
+
elif critical_count >= 1:
|
|
370
|
+
self._threat_level = SecurityLevel.MEDIUM
|
|
371
|
+
else:
|
|
372
|
+
self._threat_level = SecurityLevel.LOW
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/security/path_validator.py
|
|
1
2
|
"""
|
|
2
3
|
Enhanced path validation and security utilities.
|
|
3
4
|
"""
|
|
4
5
|
|
|
5
6
|
import logging
|
|
6
7
|
import os
|
|
8
|
+
import platform
|
|
7
9
|
import stat
|
|
10
|
+
import sys
|
|
8
11
|
import tempfile
|
|
9
12
|
from pathlib import Path
|
|
10
|
-
from typing import Optional
|
|
13
|
+
from typing import Optional
|
|
11
14
|
|
|
12
15
|
logger = logging.getLogger(__name__)
|
|
13
16
|
|
|
@@ -24,7 +27,7 @@ class PathValidator:
|
|
|
24
27
|
and other path-based attacks.
|
|
25
28
|
"""
|
|
26
29
|
|
|
27
|
-
# Dangerous patterns that
|
|
30
|
+
# Dangerous patterns that are blocked
|
|
28
31
|
DANGEROUS_PATTERNS = [
|
|
29
32
|
"..", # Directory traversal
|
|
30
33
|
"~", # Home directory
|
|
@@ -39,7 +42,7 @@ class PathValidator:
|
|
|
39
42
|
">", # Redirects
|
|
40
43
|
]
|
|
41
44
|
|
|
42
|
-
# System paths that
|
|
45
|
+
# System paths that are protected
|
|
43
46
|
PROTECTED_PATHS = [
|
|
44
47
|
"/etc/",
|
|
45
48
|
"/bin/",
|
|
@@ -57,33 +60,70 @@ class PathValidator:
|
|
|
57
60
|
|
|
58
61
|
def __init__(
|
|
59
62
|
self,
|
|
60
|
-
base_path: Optional[
|
|
63
|
+
base_path: Optional[str | Path] = None,
|
|
61
64
|
allow_absolute: bool = False,
|
|
62
|
-
max_path_length: int =
|
|
65
|
+
max_path_length: Optional[int] = None,
|
|
63
66
|
check_existence: bool = True,
|
|
67
|
+
enable_cache: bool = True,
|
|
68
|
+
max_cache_size: int = 10000,
|
|
64
69
|
):
|
|
65
70
|
"""
|
|
66
|
-
Initialize path validator.
|
|
71
|
+
Initialize path validator with xwsystem LRUCache for O(1) validation.
|
|
67
72
|
|
|
68
73
|
Args:
|
|
69
74
|
base_path: Base directory to restrict operations to
|
|
70
75
|
allow_absolute: Whether to allow absolute paths
|
|
71
76
|
max_path_length: Maximum allowed path length
|
|
72
77
|
check_existence: Whether to check if paths exist
|
|
78
|
+
enable_cache: Enable validation result caching (default: True)
|
|
79
|
+
max_cache_size: Maximum number of cached paths (default: 10000)
|
|
73
80
|
"""
|
|
74
81
|
self.base_path = Path(base_path).resolve() if base_path else None
|
|
75
82
|
self.allow_absolute = allow_absolute
|
|
76
|
-
|
|
83
|
+
# Platform-aware path length limits using Python's native platform module
|
|
84
|
+
if max_path_length is None:
|
|
85
|
+
system = platform.system()
|
|
86
|
+
if system == 'Windows':
|
|
87
|
+
self.max_path_length = 260 # Windows MAX_PATH (extended paths can be 32767)
|
|
88
|
+
elif system == 'Darwin':
|
|
89
|
+
self.max_path_length = 1024 # macOS typical limit
|
|
90
|
+
else:
|
|
91
|
+
self.max_path_length = 4096 # Linux PATH_MAX
|
|
92
|
+
else:
|
|
93
|
+
self.max_path_length = max_path_length
|
|
77
94
|
self.check_existence = check_existence
|
|
95
|
+
|
|
96
|
+
# OPTIMIZATION: Use xwsystem's production-grade LRUCache
|
|
97
|
+
# Benefits: Automatic LRU eviction, thread-safe RLock, statistics, battle-tested
|
|
98
|
+
self.enable_cache = enable_cache
|
|
99
|
+
self.max_cache_size = max_cache_size
|
|
100
|
+
|
|
101
|
+
if enable_cache:
|
|
102
|
+
# Lazy import to avoid circular dependencies
|
|
103
|
+
from ..caching import create_cache
|
|
104
|
+
# Use flexible create_cache() to allow configuration via environment/settings
|
|
105
|
+
# Defaults to FunctoolsLRUCache (fastest Python cache)
|
|
106
|
+
self._cache = create_cache(capacity=max_cache_size, namespace='xwsystem.security', name="PathValidator")
|
|
107
|
+
else:
|
|
108
|
+
self._cache = None
|
|
109
|
+
|
|
110
|
+
logger.debug(
|
|
111
|
+
f"PathValidator initialized with "
|
|
112
|
+
f"caching={'enabled (xwsystem LRUCache)' if enable_cache else 'disabled'}, "
|
|
113
|
+
f"capacity={max_cache_size}"
|
|
114
|
+
)
|
|
78
115
|
|
|
79
116
|
def validate_path(
|
|
80
117
|
self,
|
|
81
|
-
path:
|
|
118
|
+
path: str | Path,
|
|
82
119
|
for_writing: bool = False,
|
|
83
120
|
create_dirs: bool = False,
|
|
84
121
|
) -> Path:
|
|
85
122
|
"""
|
|
86
|
-
Validate a path for security and constraints.
|
|
123
|
+
Validate a path for security and constraints with O(1) caching.
|
|
124
|
+
|
|
125
|
+
First call: validates and caches (10-50μs)
|
|
126
|
+
Subsequent calls: cache lookup (< 1μs) ✅
|
|
87
127
|
|
|
88
128
|
Args:
|
|
89
129
|
path: Path to validate
|
|
@@ -100,6 +140,16 @@ class PathValidator:
|
|
|
100
140
|
if not path:
|
|
101
141
|
raise PathSecurityError("Empty path provided")
|
|
102
142
|
|
|
143
|
+
# FAST PATH: Check xwsystem LRUCache first (O(1) with automatic LRU eviction)
|
|
144
|
+
if self.enable_cache:
|
|
145
|
+
cache_key = f"{self.base_path}:{path}:{for_writing}:{create_dirs}"
|
|
146
|
+
|
|
147
|
+
# xwsystem LRUCache is thread-safe with RLock (reentrant)
|
|
148
|
+
cached_result = self._cache.get(cache_key)
|
|
149
|
+
if cached_result is not None:
|
|
150
|
+
return cached_result # O(1) cache hit! ✅
|
|
151
|
+
|
|
152
|
+
# SLOW PATH: Validate and cache
|
|
103
153
|
path_obj = Path(path)
|
|
104
154
|
original_path = str(path)
|
|
105
155
|
|
|
@@ -148,6 +198,11 @@ class PathValidator:
|
|
|
148
198
|
if self.check_existence or for_writing:
|
|
149
199
|
self._check_permissions(resolved_path, for_writing, create_dirs)
|
|
150
200
|
|
|
201
|
+
# CACHE RESULT: Store in xwsystem LRUCache (automatic eviction!)
|
|
202
|
+
if self.enable_cache:
|
|
203
|
+
# xwsystem LRUCache handles eviction automatically (no manual pruning needed!)
|
|
204
|
+
self._cache.put(cache_key, resolved_path)
|
|
205
|
+
|
|
151
206
|
return resolved_path
|
|
152
207
|
|
|
153
208
|
def _check_dangerous_patterns(self, path: str) -> None:
|
|
@@ -224,6 +279,8 @@ class PathValidator:
|
|
|
224
279
|
def is_safe_filename(self, filename: str) -> bool:
|
|
225
280
|
"""
|
|
226
281
|
Check if a filename is safe (no path components).
|
|
282
|
+
|
|
283
|
+
Uses Python's native pathlib for cross-platform validation.
|
|
227
284
|
|
|
228
285
|
Args:
|
|
229
286
|
filename: Filename to check
|
|
@@ -234,19 +291,44 @@ class PathValidator:
|
|
|
234
291
|
if not filename:
|
|
235
292
|
return False
|
|
236
293
|
|
|
237
|
-
#
|
|
238
|
-
|
|
239
|
-
return False
|
|
240
|
-
|
|
241
|
-
# Check for dangerous patterns
|
|
294
|
+
# Use pathlib to check if it's a simple filename (no path components)
|
|
295
|
+
# Path.parts will have more than 1 element if there are path separators
|
|
242
296
|
try:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
297
|
+
path_obj = Path(filename)
|
|
298
|
+
# If filename contains path separators, parts will have multiple elements
|
|
299
|
+
if len(path_obj.parts) > 1:
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
# Get just the filename part (handles cross-platform separators automatically)
|
|
303
|
+
name_only = path_obj.name
|
|
304
|
+
|
|
305
|
+
# Check for Windows reserved filenames using native pathlib
|
|
306
|
+
# Python's pathlib doesn't validate reserved names, but we can check using native methods
|
|
307
|
+
if platform.system() == 'Windows':
|
|
308
|
+
# Windows reserved names (filesystem limitation, not Python limitation)
|
|
309
|
+
# Python doesn't provide native validation, but pathlib.stem gives us the base name
|
|
310
|
+
WINDOWS_RESERVED_NAMES = {
|
|
311
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
312
|
+
*[f'COM{i}' for i in range(1, 10)],
|
|
313
|
+
*[f'LPT{i}' for i in range(1, 10)]
|
|
314
|
+
}
|
|
315
|
+
# Use pathlib's native stem property (name without extension)
|
|
316
|
+
name_base = path_obj.stem.upper()
|
|
317
|
+
if name_base in WINDOWS_RESERVED_NAMES:
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
# Check for dangerous patterns
|
|
321
|
+
try:
|
|
322
|
+
self._check_dangerous_patterns(name_only)
|
|
323
|
+
return True
|
|
324
|
+
except PathSecurityError:
|
|
325
|
+
return False
|
|
326
|
+
except (ValueError, OSError):
|
|
327
|
+
# Invalid path or OS error - not safe
|
|
246
328
|
return False
|
|
247
329
|
|
|
248
330
|
def get_safe_path(
|
|
249
|
-
self, base_dir:
|
|
331
|
+
self, base_dir: str | Path, filename: str, ensure_unique: bool = True
|
|
250
332
|
) -> Path:
|
|
251
333
|
"""
|
|
252
334
|
Generate a safe path within a base directory.
|
|
@@ -311,3 +393,43 @@ class PathValidator:
|
|
|
311
393
|
else:
|
|
312
394
|
temp_dir = tempfile.mkdtemp(prefix=prefix, suffix=suffix, dir=base_dir)
|
|
313
395
|
return Path(temp_dir)
|
|
396
|
+
|
|
397
|
+
def clear_cache(self) -> int:
|
|
398
|
+
"""
|
|
399
|
+
Clear validation cache.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Number of cached items cleared
|
|
403
|
+
"""
|
|
404
|
+
if not self.enable_cache:
|
|
405
|
+
return 0
|
|
406
|
+
|
|
407
|
+
# xwsystem LRUCache provides size() method
|
|
408
|
+
count = self._cache.size()
|
|
409
|
+
self._cache.clear()
|
|
410
|
+
logger.debug(f"PathValidator cache cleared ({count} items)")
|
|
411
|
+
return count
|
|
412
|
+
|
|
413
|
+
def get_cache_stats(self) -> dict:
|
|
414
|
+
"""
|
|
415
|
+
Get cache statistics from xwsystem LRUCache.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
Dictionary with cache stats including hits/misses/evictions
|
|
419
|
+
"""
|
|
420
|
+
if not self.enable_cache:
|
|
421
|
+
return {'enabled': False}
|
|
422
|
+
|
|
423
|
+
# xwsystem LRUCache provides comprehensive stats!
|
|
424
|
+
stats = self._cache.stats()
|
|
425
|
+
return {
|
|
426
|
+
'enabled': True,
|
|
427
|
+
'name': 'PathValidator',
|
|
428
|
+
'size': stats['size'],
|
|
429
|
+
'capacity': stats['capacity'],
|
|
430
|
+
'hits': stats['hits'],
|
|
431
|
+
'misses': stats['misses'],
|
|
432
|
+
'evictions': stats['evictions'],
|
|
433
|
+
'hit_rate': stats['hit_rate'],
|
|
434
|
+
'utilization': stats['size'] / stats['capacity'] if stats['capacity'] > 0 else 0
|
|
435
|
+
}
|