@nest-omni/core 4.1.3-2 → 4.1.3-22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/audit/audit.module.d.ts +11 -0
- package/audit/audit.module.js +65 -1
- package/audit/controllers/audit.controller.d.ts +81 -1
- package/audit/controllers/audit.controller.js +67 -0
- package/audit/decorators/audit-action.decorator.d.ts +74 -0
- package/audit/decorators/audit-action.decorator.js +42 -0
- package/audit/decorators/audit-controller.decorator.d.ts +9 -1
- package/audit/decorators/audit-controller.decorator.js +11 -2
- package/audit/decorators/audit-operation.decorator.d.ts +38 -0
- package/audit/decorators/audit-operation.decorator.js +42 -0
- package/audit/decorators/entity-audit.decorator.d.ts +85 -1
- package/audit/decorators/entity-audit.decorator.js +153 -3
- package/audit/decorators/index.d.ts +2 -0
- package/audit/decorators/index.js +2 -0
- package/audit/dto/audit-action-query.dto.d.ts +13 -0
- package/audit/dto/audit-action-query.dto.js +77 -0
- package/audit/dto/audit-log-query.dto.d.ts +3 -0
- package/audit/dto/audit-log-query.dto.js +3 -0
- package/audit/dto/begin-transaction.dto.d.ts +3 -0
- package/audit/dto/begin-transaction.dto.js +3 -0
- package/audit/dto/compare-entities.dto.d.ts +3 -0
- package/audit/dto/compare-entities.dto.js +3 -0
- package/audit/dto/index.d.ts +1 -0
- package/audit/dto/index.js +1 -0
- package/audit/dto/pre-check-restore.dto.d.ts +3 -0
- package/audit/dto/pre-check-restore.dto.js +3 -0
- package/audit/dto/restore-entity.dto.d.ts +3 -0
- package/audit/dto/restore-entity.dto.js +3 -0
- package/audit/entities/audit-action-summary.entity.d.ts +23 -0
- package/audit/entities/audit-action-summary.entity.js +101 -0
- package/audit/entities/entity-audit-log.entity.d.ts +10 -2
- package/audit/entities/entity-audit-log.entity.js +48 -9
- package/audit/entities/entity-transaction.entity.d.ts +11 -2
- package/audit/entities/entity-transaction.entity.js +42 -3
- package/audit/entities/index.d.ts +3 -0
- package/audit/entities/index.js +3 -0
- package/audit/entities/manual-operation-log.entity.d.ts +4 -2
- package/audit/entities/manual-operation-log.entity.js +12 -9
- package/audit/entities/operation-template.entity.d.ts +4 -0
- package/audit/entities/operation-template.entity.js +4 -0
- package/audit/enums/audit.enums.d.ts +29 -6
- package/audit/enums/audit.enums.js +31 -7
- package/audit/examples/decorator-value-mapping.example.d.ts +70 -0
- package/audit/examples/decorator-value-mapping.example.js +414 -0
- package/audit/index.d.ts +5 -1
- package/audit/index.js +38 -1
- package/audit/interceptors/audit-action.interceptor.d.ts +38 -0
- package/audit/interceptors/audit-action.interceptor.js +215 -0
- package/audit/interceptors/audit.interceptor.d.ts +16 -0
- package/audit/interceptors/audit.interceptor.js +41 -11
- package/audit/interceptors/index.d.ts +1 -0
- package/audit/interceptors/index.js +1 -0
- package/audit/interfaces/audit.interfaces.d.ts +174 -4
- package/audit/services/audit-action.service.d.ts +141 -0
- package/audit/services/audit-action.service.js +244 -0
- package/audit/services/audit-context.service.d.ts +106 -0
- package/audit/services/audit-context.service.js +185 -0
- package/audit/services/audit-strategy.service.d.ts +6 -0
- package/audit/services/audit-strategy.service.js +13 -0
- package/audit/services/entity-audit.service.d.ts +273 -5
- package/audit/services/entity-audit.service.js +840 -60
- package/audit/services/index.d.ts +3 -0
- package/audit/services/index.js +3 -0
- package/audit/services/manual-audit-log.service.d.ts +133 -9
- package/audit/services/manual-audit-log.service.js +157 -42
- package/audit/services/multi-database.service.d.ts +9 -2
- package/audit/services/multi-database.service.js +9 -21
- package/audit/services/operation-description.service.d.ts +71 -2
- package/audit/services/operation-description.service.js +231 -20
- package/audit/services/transaction-audit.service.d.ts +30 -0
- package/audit/services/transaction-audit.service.js +53 -5
- package/audit/subscribers/entity-audit.subscriber.d.ts +20 -0
- package/audit/subscribers/entity-audit.subscriber.js +98 -6
- package/cache/cache-metrics.service.d.ts +67 -0
- package/cache/cache-metrics.service.js +68 -4
- package/cache/cache-serialization.service.d.ts +31 -0
- package/cache/cache-serialization.service.js +25 -0
- package/cache/cache.constants.d.ts +9 -0
- package/cache/cache.constants.js +9 -0
- package/cache/cache.health.d.ts +26 -0
- package/cache/cache.health.js +30 -0
- package/cache/cache.module.d.ts +87 -2
- package/cache/cache.module.js +84 -11
- package/cache/cache.service.d.ts +143 -3
- package/cache/cache.service.js +173 -4
- package/cache/cache.warmup.service.d.ts +39 -0
- package/cache/cache.warmup.service.js +32 -0
- package/cache/decorators/cache-evict.decorator.d.ts +47 -0
- package/cache/decorators/cache-evict.decorator.js +56 -0
- package/cache/decorators/cache-put.decorator.d.ts +34 -0
- package/cache/decorators/cache-put.decorator.js +39 -0
- package/cache/decorators/cacheable.decorator.d.ts +40 -0
- package/cache/decorators/cacheable.decorator.js +55 -0
- package/cache/dependencies/callback.dependency.d.ts +33 -0
- package/cache/dependencies/callback.dependency.js +39 -1
- package/cache/dependencies/chain.dependency.d.ts +28 -0
- package/cache/dependencies/chain.dependency.js +34 -0
- package/cache/dependencies/db.dependency.d.ts +83 -7
- package/cache/dependencies/db.dependency.js +89 -14
- package/cache/dependencies/file.dependency.d.ts +32 -0
- package/cache/dependencies/file.dependency.js +34 -0
- package/cache/dependencies/tag.dependency.d.ts +75 -4
- package/cache/dependencies/tag.dependency.js +145 -11
- package/cache/dependencies/time.dependency.d.ts +43 -0
- package/cache/dependencies/time.dependency.js +43 -0
- package/cache/entities/index.d.ts +1 -0
- package/cache/entities/index.js +17 -0
- package/cache/entities/typeorm-cache.entity.d.ts +71 -0
- package/cache/entities/typeorm-cache.entity.js +110 -0
- package/cache/examples/basic-usage.d.ts +15 -0
- package/cache/examples/basic-usage.js +62 -8
- package/cache/index.d.ts +2 -1
- package/cache/index.js +28 -2
- package/cache/interfaces/cache-dependency.interface.d.ts +53 -0
- package/cache/interfaces/cache-options.interface.d.ts +89 -0
- package/cache/interfaces/cache-options.interface.js +6 -0
- package/cache/interfaces/cache-provider.interface.d.ts +78 -0
- package/cache/providers/base-cache.provider.d.ts +14 -0
- package/cache/providers/base-cache.provider.js +16 -0
- package/cache/providers/cls-cache.provider.d.ts +20 -0
- package/cache/providers/cls-cache.provider.js +28 -0
- package/cache/providers/index.d.ts +2 -1
- package/cache/providers/index.js +2 -1
- package/cache/providers/lrucache.provider.d.ts +76 -0
- package/cache/providers/lrucache.provider.js +226 -0
- package/cache/providers/redis-cache.provider.d.ts +26 -0
- package/cache/providers/redis-cache.provider.js +29 -0
- package/cache/providers/typeorm-cache.provider.d.ts +211 -0
- package/cache/providers/typeorm-cache.provider.js +483 -0
- package/cache/utils/dependency-manager.util.d.ts +52 -0
- package/cache/utils/dependency-manager.util.js +59 -0
- package/cache/utils/key-generator.util.d.ts +42 -0
- package/cache/utils/key-generator.util.js +53 -1
- package/common/abstract.entity.d.ts +14 -0
- package/common/abstract.entity.js +14 -0
- package/common/boilerplate.polyfill.d.ts +143 -0
- package/common/boilerplate.polyfill.js +35 -1
- package/common/dto/dto-container.d.ts +16 -0
- package/common/dto/dto-container.js +20 -0
- package/common/dto/dto-decorators.d.ts +18 -0
- package/common/dto/dto-decorators.js +14 -0
- package/common/dto/dto-extensions.d.ts +11 -0
- package/common/dto/dto-extensions.js +9 -0
- package/common/dto/dto-service-accessor.d.ts +17 -0
- package/common/dto/dto-service-accessor.js +18 -0
- package/common/dto/dto-transformer.d.ts +12 -0
- package/common/dto/dto-transformer.js +9 -0
- package/common/dto/index.js +2 -0
- package/common/examples/paginate-and-map.example.d.ts +6 -0
- package/common/examples/paginate-and-map.example.js +26 -0
- package/common/helpers/validation-metadata-helper.d.ts +55 -0
- package/common/helpers/validation-metadata-helper.js +60 -0
- package/common/index.d.ts +1 -0
- package/common/index.js +4 -0
- package/common/utils.d.ts +15 -0
- package/common/utils.js +15 -0
- package/constants/language-code.js +1 -0
- package/decorators/field.decorators.d.ts +72 -3
- package/decorators/field.decorators.js +155 -19
- package/decorators/property.decorators.js +1 -0
- package/decorators/public-route.decorator.js +1 -0
- package/decorators/transform.decorators.d.ts +27 -2
- package/decorators/transform.decorators.js +29 -23
- package/decorators/translate.decorator.js +1 -0
- package/decorators/user.decorator.js +1 -0
- package/decorators/validator.decorators.d.ts +8 -18
- package/decorators/validator.decorators.js +22 -190
- package/file-upload/controllers/file-access.controller.d.ts +23 -0
- package/file-upload/controllers/file-access.controller.js +128 -0
- package/file-upload/decorators/column.decorator.d.ts +151 -0
- package/file-upload/decorators/column.decorator.js +273 -0
- package/file-upload/decorators/csv-data.decorator.d.ts +30 -0
- package/file-upload/decorators/csv-data.decorator.js +85 -0
- package/file-upload/decorators/csv-import.decorator.d.ts +34 -0
- package/file-upload/decorators/csv-import.decorator.js +24 -0
- package/file-upload/decorators/examples/column-mapping.example.d.ts +76 -0
- package/file-upload/decorators/examples/column-mapping.example.js +122 -0
- package/file-upload/decorators/excel-data.decorator.d.ts +30 -0
- package/file-upload/decorators/excel-data.decorator.js +85 -0
- package/file-upload/decorators/file-upload.decorator.d.ts +83 -0
- package/file-upload/decorators/file-upload.decorator.js +172 -0
- package/file-upload/decorators/index.d.ts +5 -0
- package/file-upload/decorators/index.js +38 -0
- package/file-upload/decorators/process.decorator.d.ts +40 -0
- package/file-upload/decorators/process.decorator.js +52 -0
- package/file-upload/decorators/validate-data.decorator.d.ts +91 -0
- package/file-upload/decorators/validate-data.decorator.js +39 -0
- package/file-upload/dto/create-file.dto.d.ts +24 -0
- package/file-upload/dto/create-file.dto.js +112 -0
- package/file-upload/dto/find-files.dto.d.ts +15 -0
- package/file-upload/dto/find-files.dto.js +76 -0
- package/file-upload/dto/index.d.ts +4 -0
- package/file-upload/dto/index.js +20 -0
- package/file-upload/dto/pagination.dto.d.ts +7 -0
- package/file-upload/dto/pagination.dto.js +39 -0
- package/file-upload/dto/update-file.dto.d.ts +15 -0
- package/file-upload/dto/update-file.dto.js +67 -0
- package/file-upload/entities/file-metadata.entity.d.ts +25 -0
- package/file-upload/entities/file-metadata.entity.js +76 -0
- package/file-upload/entities/file.entity.d.ts +114 -0
- package/file-upload/entities/file.entity.js +350 -0
- package/file-upload/entities/index.d.ts +2 -0
- package/file-upload/entities/index.js +18 -0
- package/file-upload/enums/file-type.enum.d.ts +72 -0
- package/file-upload/enums/file-type.enum.js +212 -0
- package/file-upload/exceptions/file-upload.exception.d.ts +57 -0
- package/file-upload/exceptions/file-upload.exception.js +120 -0
- package/file-upload/exceptions/index.d.ts +1 -0
- package/file-upload/exceptions/index.js +17 -0
- package/file-upload/file-upload.module.d.ts +89 -0
- package/file-upload/file-upload.module.js +292 -0
- package/file-upload/index.d.ts +37 -0
- package/file-upload/index.js +77 -0
- package/file-upload/interceptors/file-upload.interceptor.d.ts +101 -0
- package/file-upload/interceptors/file-upload.interceptor.js +594 -0
- package/file-upload/interceptors/index.d.ts +1 -0
- package/file-upload/interceptors/index.js +17 -0
- package/file-upload/interfaces/custom-file-type.interface.d.ts +72 -0
- package/file-upload/interfaces/custom-file-type.interface.js +2 -0
- package/file-upload/interfaces/file-buffer.interface.d.ts +72 -0
- package/file-upload/interfaces/file-buffer.interface.js +2 -0
- package/file-upload/interfaces/file-entity.interface.d.ts +142 -0
- package/file-upload/interfaces/file-entity.interface.js +28 -0
- package/file-upload/interfaces/file-metadata.interface.d.ts +21 -0
- package/file-upload/interfaces/file-metadata.interface.js +2 -0
- package/file-upload/interfaces/file-processor.interface.d.ts +93 -0
- package/file-upload/interfaces/file-processor.interface.js +2 -0
- package/file-upload/interfaces/file-upload-options.interface.d.ts +74 -0
- package/file-upload/interfaces/file-upload-options.interface.js +5 -0
- package/file-upload/interfaces/index.d.ts +7 -0
- package/file-upload/interfaces/index.js +24 -0
- package/file-upload/interfaces/processor-options.interface.d.ts +102 -0
- package/file-upload/interfaces/processor-options.interface.js +2 -0
- package/file-upload/interfaces/storage-provider.interface.d.ts +239 -0
- package/file-upload/interfaces/storage-provider.interface.js +2 -0
- package/file-upload/interfaces/upload-options.interface.d.ts +19 -0
- package/file-upload/interfaces/upload-options.interface.js +2 -0
- package/file-upload/processors/csv.processor.d.ts +98 -0
- package/file-upload/processors/csv.processor.js +391 -0
- package/file-upload/processors/excel.processor.d.ts +130 -0
- package/file-upload/processors/excel.processor.js +547 -0
- package/file-upload/processors/image.processor.d.ts +199 -0
- package/file-upload/processors/image.processor.js +377 -0
- package/file-upload/providers/index.d.ts +2 -0
- package/file-upload/providers/index.js +18 -0
- package/file-upload/providers/local-storage.provider.d.ts +98 -0
- package/file-upload/providers/local-storage.provider.js +484 -0
- package/file-upload/providers/s3-storage.provider.d.ts +87 -0
- package/file-upload/providers/s3-storage.provider.js +455 -0
- package/file-upload/services/file-signature-validator.service.d.ts +118 -0
- package/file-upload/services/file-signature-validator.service.js +376 -0
- package/file-upload/services/file.service.d.ts +193 -0
- package/file-upload/services/file.service.js +638 -0
- package/file-upload/services/index.d.ts +4 -0
- package/file-upload/services/index.js +20 -0
- package/file-upload/services/malicious-file-detector.service.d.ts +300 -0
- package/file-upload/services/malicious-file-detector.service.js +1234 -0
- package/file-upload/services/mime-registry.service.d.ts +47 -0
- package/file-upload/services/mime-registry.service.js +167 -0
- package/file-upload/utils/checksum.util.d.ts +28 -0
- package/file-upload/utils/checksum.util.js +65 -0
- package/file-upload/utils/dynamic-import.util.d.ts +54 -0
- package/file-upload/utils/dynamic-import.util.js +156 -0
- package/file-upload/utils/filename.util.d.ts +59 -0
- package/file-upload/utils/filename.util.js +184 -0
- package/file-upload/utils/filepath.util.d.ts +70 -0
- package/file-upload/utils/filepath.util.js +152 -0
- package/file-upload/utils/index.d.ts +4 -0
- package/file-upload/utils/index.js +20 -0
- package/filters/bad-request.filter.js +19 -4
- package/filters/constraint-errors.js +1 -0
- package/helpers/common.helper.d.ts +13 -0
- package/helpers/common.helper.js +13 -0
- package/http-client/config/http-client.config.d.ts +20 -0
- package/http-client/config/http-client.config.js +48 -21
- package/http-client/decorators/http-client.decorators.d.ts +55 -14
- package/http-client/decorators/http-client.decorators.js +154 -78
- package/http-client/entities/http-log.entity.d.ts +217 -8
- package/http-client/entities/http-log.entity.js +7 -22
- package/http-client/errors/http-client.errors.d.ts +57 -0
- package/http-client/errors/http-client.errors.js +58 -0
- package/http-client/examples/advanced-usage.example.d.ts +40 -0
- package/http-client/examples/advanced-usage.example.js +53 -61
- package/http-client/examples/auth-with-waiting-lock.example.d.ts +31 -0
- package/http-client/examples/auth-with-waiting-lock.example.js +52 -5
- package/http-client/examples/basic-usage.example.d.ts +60 -0
- package/http-client/examples/basic-usage.example.js +60 -0
- package/http-client/examples/multi-api-configuration.example.d.ts +60 -0
- package/http-client/examples/multi-api-configuration.example.js +76 -5
- package/http-client/examples/proxy-from-environment.example.d.ts +133 -0
- package/http-client/examples/proxy-from-environment.example.js +409 -0
- package/http-client/http-client.module.d.ts +48 -2
- package/http-client/http-client.module.js +147 -68
- package/http-client/index.d.ts +1 -1
- package/http-client/index.js +8 -0
- package/http-client/interfaces/api-client-config.interface.d.ts +80 -45
- package/http-client/interfaces/api-client-config.interface.js +3 -0
- package/http-client/interfaces/http-client-config.interface.d.ts +109 -52
- package/http-client/services/api-client-registry.service.d.ts +50 -11
- package/http-client/services/api-client-registry.service.js +90 -250
- package/http-client/services/circuit-breaker.service.d.ts +115 -2
- package/http-client/services/circuit-breaker.service.js +237 -7
- package/http-client/services/http-client.service.d.ts +124 -14
- package/http-client/services/http-client.service.js +437 -148
- package/http-client/services/http-log-query.service.d.ts +83 -0
- package/http-client/services/http-log-query.service.js +121 -13
- package/http-client/services/http-replay.service.d.ts +101 -0
- package/http-client/services/http-replay.service.js +86 -0
- package/http-client/services/index.d.ts +0 -1
- package/http-client/services/index.js +0 -1
- package/http-client/services/log-cleanup.service.d.ts +63 -0
- package/http-client/services/log-cleanup.service.js +54 -2
- package/http-client/services/logging.service.d.ts +116 -7
- package/http-client/services/logging.service.js +349 -86
- package/http-client/utils/call-stack-extractor.util.d.ts +63 -0
- package/http-client/utils/call-stack-extractor.util.js +83 -0
- package/http-client/utils/context-extractor.util.d.ts +49 -0
- package/http-client/utils/context-extractor.util.js +54 -0
- package/http-client/utils/curl-generator.util.d.ts +21 -0
- package/http-client/utils/curl-generator.util.js +44 -3
- package/http-client/utils/index.d.ts +1 -0
- package/http-client/utils/index.js +1 -0
- package/http-client/utils/proxy-environment.util.d.ts +42 -0
- package/http-client/utils/proxy-environment.util.js +148 -0
- package/http-client/utils/request-id.util.d.ts +18 -0
- package/http-client/utils/request-id.util.js +20 -0
- package/http-client/utils/retry-recorder.util.d.ts +42 -0
- package/http-client/utils/retry-recorder.util.js +44 -0
- package/http-client/utils/security-validator.util.d.ts +118 -0
- package/http-client/utils/security-validator.util.js +352 -0
- package/index.d.ts +3 -1
- package/index.js +12 -1
- package/interceptors/translation-interceptor.service.js +5 -0
- package/ip-filter/constants.d.ts +21 -0
- package/ip-filter/constants.js +24 -0
- package/ip-filter/decorators/index.d.ts +1 -0
- package/ip-filter/decorators/index.js +17 -0
- package/ip-filter/decorators/ip-filter.decorator.d.ts +58 -0
- package/ip-filter/decorators/ip-filter.decorator.js +79 -0
- package/ip-filter/guards/index.d.ts +1 -0
- package/ip-filter/guards/index.js +17 -0
- package/ip-filter/guards/ip-filter.guard.d.ts +62 -0
- package/ip-filter/guards/ip-filter.guard.js +174 -0
- package/ip-filter/index.d.ts +7 -0
- package/ip-filter/index.js +23 -0
- package/ip-filter/interfaces/index.d.ts +4 -0
- package/ip-filter/interfaces/index.js +20 -0
- package/ip-filter/interfaces/ip-filter-async-options.interface.d.ts +15 -0
- package/ip-filter/interfaces/ip-filter-async-options.interface.js +2 -0
- package/ip-filter/interfaces/ip-filter-metadata.interface.d.ts +26 -0
- package/ip-filter/interfaces/ip-filter-metadata.interface.js +2 -0
- package/ip-filter/interfaces/ip-filter-options.interface.d.ts +34 -0
- package/ip-filter/interfaces/ip-filter-options.interface.js +2 -0
- package/ip-filter/interfaces/ip-rule.interface.d.ts +36 -0
- package/ip-filter/interfaces/ip-rule.interface.js +2 -0
- package/ip-filter/ip-filter.module.d.ts +55 -0
- package/ip-filter/ip-filter.module.js +105 -0
- package/ip-filter/services/index.d.ts +1 -0
- package/ip-filter/services/index.js +17 -0
- package/ip-filter/services/ip-filter.service.d.ts +92 -0
- package/ip-filter/services/ip-filter.service.js +238 -0
- package/ip-filter/utils/index.d.ts +1 -0
- package/ip-filter/utils/index.js +17 -0
- package/ip-filter/utils/ip-utils.d.ts +61 -0
- package/ip-filter/utils/ip-utils.js +162 -0
- package/package.json +32 -29
- package/providers/context.provider.d.ts +9 -0
- package/providers/context.provider.js +15 -0
- package/providers/generator.provider.d.ts +4 -0
- package/providers/generator.provider.js +4 -0
- package/redis-lock/comprehensive-lock-cleanup.service.d.ts +94 -0
- package/redis-lock/comprehensive-lock-cleanup.service.js +253 -0
- package/redis-lock/examples/lock-strategy.examples.d.ts +89 -0
- package/redis-lock/examples/lock-strategy.examples.js +130 -15
- package/redis-lock/index.d.ts +2 -0
- package/redis-lock/index.js +8 -1
- package/redis-lock/lock-heartbeat.service.d.ts +80 -0
- package/redis-lock/lock-heartbeat.service.js +232 -0
- package/redis-lock/redis-lock.decorator.d.ts +101 -0
- package/redis-lock/redis-lock.decorator.js +120 -0
- package/redis-lock/redis-lock.module.d.ts +66 -0
- package/redis-lock/redis-lock.module.js +175 -70
- package/redis-lock/redis-lock.service.d.ts +282 -0
- package/redis-lock/redis-lock.service.js +343 -20
- package/setup/bootstrap.setup.d.ts +2 -1
- package/setup/bootstrap.setup.js +22 -1
- package/setup/index.d.ts +1 -0
- package/setup/index.js +1 -0
- package/setup/mode.setup.d.ts +44 -0
- package/setup/mode.setup.js +44 -0
- package/setup/run-in-mode.decorator.d.ts +56 -0
- package/setup/run-in-mode.decorator.js +92 -0
- package/setup/schedule.decorator.d.ts +227 -0
- package/setup/schedule.decorator.js +240 -12
- package/setup/worker.decorator.d.ts +86 -0
- package/setup/worker.decorator.js +97 -0
- package/shared/index.d.ts +1 -1
- package/shared/index.js +1 -1
- package/shared/{serviceRegistryModule.js → service-registry.module.js} +19 -18
- package/shared/services/api-config.service.d.ts +3 -0
- package/shared/services/api-config.service.js +21 -9
- package/shared/services/index.d.ts +0 -1
- package/shared/services/index.js +0 -1
- package/validator-json/decorators.d.ts +17 -0
- package/validator-json/decorators.js +17 -2
- package/validator-json/default.d.ts +6 -0
- package/validator-json/default.js +30 -2
- package/validator-json/defaultConverters.js +1 -0
- package/validator-json/options.d.ts +23 -0
- package/validators/common-validators.d.ts +143 -0
- package/validators/common-validators.js +249 -0
- package/validators/custom-validate.examples.d.ts +23 -0
- package/validators/custom-validate.examples.js +78 -6
- package/validators/custom-validate.validator.d.ts +108 -0
- package/validators/custom-validate.validator.js +85 -0
- package/validators/file-mimetype.validator.d.ts +0 -2
- package/validators/file-mimetype.validator.js +4 -6
- package/validators/index.d.ts +1 -0
- package/validators/index.js +1 -0
- package/validators/is-exists.validator.d.ts +26 -6
- package/validators/is-exists.validator.js +30 -7
- package/validators/is-unique.validator.d.ts +33 -7
- package/validators/is-unique.validator.js +59 -17
- package/validators/skip-empty.validator.d.ts +5 -0
- package/validators/skip-empty.validator.js +5 -0
- package/vault/interfaces/vault-options.interface.d.ts +9 -0
- package/vault/vault-config.loader.d.ts +30 -0
- package/vault/vault-config.loader.js +48 -1
- package/vault/vault-config.service.d.ts +53 -0
- package/vault/vault-config.service.js +57 -0
- package/vault/vault.module.d.ts +4 -0
- package/vault/vault.module.js +4 -0
- package/cache/providers/memory-cache.provider.d.ts +0 -26
- package/cache/providers/memory-cache.provider.js +0 -171
- package/decorators/examples/validation-decorators.example.d.ts +0 -69
- package/decorators/examples/validation-decorators.example.js +0 -331
- package/http-client/services/cache.service.d.ts +0 -24
- package/http-client/services/cache.service.js +0 -264
- package/shared/services/validator.service.d.ts +0 -3
- package/shared/services/validator.service.js +0 -20
- /package/shared/{serviceRegistryModule.d.ts → service-registry.module.d.ts} +0 -0
|
@@ -0,0 +1,1234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
15
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
16
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
17
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
18
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
19
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
20
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
var MaliciousFileDetector_1;
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.MaliciousFileDetector = void 0;
|
|
26
|
+
const common_1 = require("@nestjs/common");
|
|
27
|
+
const fs = require("fs-extra");
|
|
28
|
+
const path = require("path");
|
|
29
|
+
const child_process_1 = require("child_process");
|
|
30
|
+
const util_1 = require("util");
|
|
31
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
32
|
+
/**
|
|
33
|
+
* 恶意文件检测服务
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* 恶意文件检测服务(增强版)
|
|
37
|
+
*
|
|
38
|
+
* 功能:
|
|
39
|
+
* 1. 多编码检测 - 支持 UTF-8, UTF-16LE, Latin1, ASCII, Base64
|
|
40
|
+
* 2. 文件结构分析 - 检测嵌套压缩、可执行内容、Office宏、高熵值、多态性
|
|
41
|
+
* 3. ClamAV集成 - 支持本地命令行和远程服务两种模式
|
|
42
|
+
*
|
|
43
|
+
* 使用示例:
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // 方式1: 本地ClamAV(需要在当前机器安装)
|
|
46
|
+
* FileUploadModule.forRoot({
|
|
47
|
+
* maliciousDetection: {
|
|
48
|
+
* enableClamAV: true,
|
|
49
|
+
* clamavMode: 'local', // 本地模式
|
|
50
|
+
* clamavCommand: 'clamdscan', // 或 'clamscan'
|
|
51
|
+
* },
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* // 方式2: 远程ClamAV服务(推荐用于生产环境)
|
|
55
|
+
* FileUploadModule.forRoot({
|
|
56
|
+
* maliciousDetection: {
|
|
57
|
+
* enableClamAV: true,
|
|
58
|
+
* clamavMode: 'remote', // 远程模式
|
|
59
|
+
* clamavRemote: {
|
|
60
|
+
* host: 'clamav-server.com', // ClamAV服务器地址
|
|
61
|
+
* port: 3310, // ClamAV守护进程端口
|
|
62
|
+
* timeout: 30000, // 超时时间(ms)
|
|
63
|
+
* },
|
|
64
|
+
* },
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* // 方式3: 仅使用多编码检测和结构分析(无需安装ClamAV)
|
|
68
|
+
* FileUploadModule.forRoot({
|
|
69
|
+
* maliciousDetection: {
|
|
70
|
+
* enableClamAV: false, // 禁用ClamAV
|
|
71
|
+
* enableMultiEncoding: true, // 启用多编码检测
|
|
72
|
+
* enableStructureAnalysis: true, // 启用结构分析
|
|
73
|
+
* riskThreshold: 50, // 风险阈值
|
|
74
|
+
* },
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* // 直接使用服务
|
|
78
|
+
* const result = await maliciousDetector.scanFile('/path/to/file');
|
|
79
|
+
* console.log('Is Safe:', result.safe);
|
|
80
|
+
* console.log('Risk Score:', result.riskScore);
|
|
81
|
+
* console.log('Threats:', result.threats);
|
|
82
|
+
* console.log('Details:', result.details);
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* 远程ClamAV服务部署示例:
|
|
86
|
+
* ```bash
|
|
87
|
+
* # Docker部署ClamAV服务
|
|
88
|
+
* docker run -d -p 3310:3310 \
|
|
89
|
+
* --name clamav \
|
|
90
|
+
* clamav/clamav:latest
|
|
91
|
+
*
|
|
92
|
+
* # 配置clamd.conf允许远程连接
|
|
93
|
+
* TCPSocket 3310
|
|
94
|
+
* TCPAddr 0.0.0.0
|
|
95
|
+
* StreamMaxLength 100M
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* 检测结果示例:
|
|
99
|
+
* ```json
|
|
100
|
+
* {
|
|
101
|
+
* "safe": false,
|
|
102
|
+
* "isMalware": true,
|
|
103
|
+
* "riskScore": 85,
|
|
104
|
+
* "threats": [
|
|
105
|
+
* "PE executable header (Windows)",
|
|
106
|
+
* "Script tag injection (base64 encoded)",
|
|
107
|
+
* "Executable content detected"
|
|
108
|
+
* ],
|
|
109
|
+
* "details": {
|
|
110
|
+
* "encodingThreats": ["Script tag injection (base64 encoded)"],
|
|
111
|
+
* "structureAnalysis": {
|
|
112
|
+
* "anomalies": ["Executable content detected"],
|
|
113
|
+
* "riskScore": 60,
|
|
114
|
+
* "characteristics": {
|
|
115
|
+
* "hasExecutableContent": true,
|
|
116
|
+
* "entropy": 6.8
|
|
117
|
+
* }
|
|
118
|
+
* },
|
|
119
|
+
* "clamavResult": {
|
|
120
|
+
* "scanned": true,
|
|
121
|
+
* "infected": true,
|
|
122
|
+
* "virusName": "Win.Trojan.Agent"
|
|
123
|
+
* }
|
|
124
|
+
* }
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
let MaliciousFileDetector = MaliciousFileDetector_1 = class MaliciousFileDetector {
|
|
129
|
+
constructor(options) {
|
|
130
|
+
this.options = options;
|
|
131
|
+
this.logger = new common_1.Logger(MaliciousFileDetector_1.name);
|
|
132
|
+
// 并发控制:当前正在扫描的文件数
|
|
133
|
+
this.activeScanCount = 0;
|
|
134
|
+
// 支持的编码列表
|
|
135
|
+
this.encodings = ['utf8', 'utf16le', 'latin1', 'ascii', 'base64'];
|
|
136
|
+
// 恶意模式库(多编码)
|
|
137
|
+
this.maliciousPatterns = [
|
|
138
|
+
// 脚本注入模式
|
|
139
|
+
{
|
|
140
|
+
pattern: /<script[^>]*>.*?<\/script>/gi,
|
|
141
|
+
description: 'Script tag injection',
|
|
142
|
+
riskScore: 60,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
pattern: /<iframe[^>]*>/gi,
|
|
146
|
+
description: 'Iframe injection',
|
|
147
|
+
riskScore: 50,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
pattern: /on\w+\s*=\s*["'][^"']*["']/gi,
|
|
151
|
+
description: 'Event handler injection',
|
|
152
|
+
riskScore: 55,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
pattern: /javascript:\s*[^\s]+/gi,
|
|
156
|
+
description: 'JavaScript protocol',
|
|
157
|
+
riskScore: 50,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
pattern: /vbscript:\s*[^\s]+/gi,
|
|
161
|
+
description: 'VBScript protocol',
|
|
162
|
+
riskScore: 50,
|
|
163
|
+
},
|
|
164
|
+
// Shell命令模式
|
|
165
|
+
{
|
|
166
|
+
pattern: /\$\([^)]+\)/g,
|
|
167
|
+
description: 'Shell command substitution',
|
|
168
|
+
riskScore: 65,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
pattern: /`[^`]+`/g,
|
|
172
|
+
description: 'Backtick command execution',
|
|
173
|
+
riskScore: 65,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
pattern: /eval\s*\(/gi,
|
|
177
|
+
description: 'Eval function call',
|
|
178
|
+
riskScore: 70,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
pattern: /exec\s*\(/gi,
|
|
182
|
+
description: 'Exec function call',
|
|
183
|
+
riskScore: 70,
|
|
184
|
+
},
|
|
185
|
+
// SQL注入模式
|
|
186
|
+
{
|
|
187
|
+
pattern: /union\s+select/gi,
|
|
188
|
+
description: 'SQL injection pattern',
|
|
189
|
+
riskScore: 45,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
pattern: /';\s*drop\s+table/gi,
|
|
193
|
+
description: 'SQL drop table pattern',
|
|
194
|
+
riskScore: 80,
|
|
195
|
+
},
|
|
196
|
+
// 二进制注入标记
|
|
197
|
+
{ pattern: /\x00/g, description: 'Null byte injection', riskScore: 40 },
|
|
198
|
+
];
|
|
199
|
+
this.suspiciousSignatures = [
|
|
200
|
+
// 可执行文件
|
|
201
|
+
{
|
|
202
|
+
pattern: [0x4d, 0x5a],
|
|
203
|
+
description: 'PE executable header (Windows)',
|
|
204
|
+
riskScore: 70,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
pattern: [0x7f, 0x45, 0x4c, 0x46],
|
|
208
|
+
description: 'ELF executable header (Linux)',
|
|
209
|
+
riskScore: 70,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
pattern: [0xfe, 0xed, 0xfa, 0xce],
|
|
213
|
+
description: 'Mach-O executable header (macOS)',
|
|
214
|
+
riskScore: 70,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
pattern: [0xfe, 0xed, 0xfa, 0xcf],
|
|
218
|
+
description: 'Mach-O 64-bit executable (macOS)',
|
|
219
|
+
riskScore: 70,
|
|
220
|
+
},
|
|
221
|
+
// 脚本内容
|
|
222
|
+
{
|
|
223
|
+
pattern: '<script',
|
|
224
|
+
type: 'string',
|
|
225
|
+
description: 'Script tag detected',
|
|
226
|
+
riskScore: 40,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
pattern: 'javascript:',
|
|
230
|
+
type: 'string',
|
|
231
|
+
description: 'JavaScript protocol',
|
|
232
|
+
riskScore: 50,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
pattern: 'vbscript:',
|
|
236
|
+
type: 'string',
|
|
237
|
+
description: 'VBScript protocol',
|
|
238
|
+
riskScore: 50,
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
pattern: 'data:text/html',
|
|
242
|
+
type: 'string',
|
|
243
|
+
description: 'Data URI with HTML',
|
|
244
|
+
riskScore: 45,
|
|
245
|
+
},
|
|
246
|
+
// 宏病毒相关
|
|
247
|
+
{
|
|
248
|
+
pattern: 'vbaProject',
|
|
249
|
+
type: 'string',
|
|
250
|
+
description: 'VBA Project marker',
|
|
251
|
+
riskScore: 60,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
pattern: 'Auto_Open',
|
|
255
|
+
type: 'string',
|
|
256
|
+
description: 'Auto-execute macro',
|
|
257
|
+
riskScore: 40,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
pattern: 'Workbook_Open',
|
|
261
|
+
type: 'string',
|
|
262
|
+
description: 'Auto-execute macro',
|
|
263
|
+
riskScore: 40,
|
|
264
|
+
},
|
|
265
|
+
// 压缩炸弹检测
|
|
266
|
+
{
|
|
267
|
+
pattern: [0x50, 0x4b, 0x03, 0x04],
|
|
268
|
+
description: 'ZIP header',
|
|
269
|
+
checkRatio: true,
|
|
270
|
+
riskScore: 0, // 需要进一步检查压缩比
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
this.options = Object.assign({ enabled: true, enableClamAV: false, clamavMode: 'local', clamavCommand: 'clamdscan', enableMultiEncoding: true, enableStructureAnalysis: true, riskThreshold: 50 }, options);
|
|
274
|
+
// 初始化并发控制参数
|
|
275
|
+
this.maxConcurrentScans = this.options.maxConcurrentScans || 5;
|
|
276
|
+
this.maxScanSize = this.options.maxScanSize || 10 * 1024 * 1024; // 默认 10MB
|
|
277
|
+
this.scanTimeout = this.options.scanTimeout || 60000; // 默认 60 秒
|
|
278
|
+
this.logger.debug(`MaliciousFileDetector initialized - maxConcurrent: ${this.maxConcurrentScans}, maxSize: ${this.maxScanSize}, timeout: ${this.scanTimeout}`);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 扫描文件(增强版 - 带并发控制和流式处理)
|
|
282
|
+
*/
|
|
283
|
+
scanFile(filePath, buffer) {
|
|
284
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
285
|
+
// 检查是否启用
|
|
286
|
+
if (this.options.enabled === false) {
|
|
287
|
+
this.logger.debug('Malicious detection is disabled');
|
|
288
|
+
return {
|
|
289
|
+
safe: true,
|
|
290
|
+
threats: [],
|
|
291
|
+
riskScore: 0,
|
|
292
|
+
isMalware: false,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// 如果没有文件路径且没有buffer,返回安全
|
|
296
|
+
if (!filePath && !buffer) {
|
|
297
|
+
this.logger.debug('No file path or buffer provided for scanning');
|
|
298
|
+
return {
|
|
299
|
+
safe: true,
|
|
300
|
+
threats: [],
|
|
301
|
+
riskScore: 0,
|
|
302
|
+
isMalware: false,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
// 并发控制:等待直到有可用的扫描槽位
|
|
306
|
+
yield this.waitForScanSlot();
|
|
307
|
+
try {
|
|
308
|
+
// 增加活跃扫描计数
|
|
309
|
+
this.activeScanCount++;
|
|
310
|
+
this.logger.debug(`MaliciousFileDetector: Starting scan (${this.activeScanCount}/${this.maxConcurrentScans}): ${filePath || '(buffer)'}`);
|
|
311
|
+
// 设置超时保护
|
|
312
|
+
const scanPromise = this.performScan(filePath, buffer);
|
|
313
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
314
|
+
setTimeout(() => {
|
|
315
|
+
reject(new Error(`Scan timeout after ${this.scanTimeout}ms`));
|
|
316
|
+
}, this.scanTimeout);
|
|
317
|
+
});
|
|
318
|
+
// 等待扫描完成或超时
|
|
319
|
+
return yield Promise.race([scanPromise, timeoutPromise]);
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
// 释放扫描槽位
|
|
323
|
+
this.activeScanCount--;
|
|
324
|
+
this.logger.debug(`MaliciousFileDetector: Scan completed (${this.activeScanCount}/${this.maxConcurrentScans})`);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* 等待直到有可用的扫描槽位
|
|
330
|
+
*/
|
|
331
|
+
waitForScanSlot() {
|
|
332
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
333
|
+
while (this.activeScanCount >= this.maxConcurrentScans) {
|
|
334
|
+
this.logger.debug(`MaliciousFileDetector: Waiting for scan slot (${this.activeScanCount}/${this.maxConcurrentScans})`);
|
|
335
|
+
// 等待 100ms 后重试
|
|
336
|
+
yield new Promise((resolve) => setTimeout(resolve, 100));
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* 执行实际的扫描操作
|
|
342
|
+
*/
|
|
343
|
+
performScan(filePath, inputBuffer) {
|
|
344
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
345
|
+
const threats = [];
|
|
346
|
+
let riskScore = 0;
|
|
347
|
+
const details = {};
|
|
348
|
+
try {
|
|
349
|
+
let scanBuffer;
|
|
350
|
+
let fileSize;
|
|
351
|
+
if (inputBuffer) {
|
|
352
|
+
// Use provided buffer directly
|
|
353
|
+
scanBuffer = inputBuffer;
|
|
354
|
+
fileSize = inputBuffer.length;
|
|
355
|
+
this.logger.debug(`MaliciousFileDetector: Using provided buffer - size: ${fileSize}`);
|
|
356
|
+
}
|
|
357
|
+
else if (filePath) {
|
|
358
|
+
// Read file from disk
|
|
359
|
+
const stats = yield fs.stat(filePath);
|
|
360
|
+
fileSize = stats.size;
|
|
361
|
+
this.logger.debug(`MaliciousFileDetector: File stats - size: ${stats.size}`);
|
|
362
|
+
// Skip malicious file detection for very small files (< 100 bytes)
|
|
363
|
+
// These are usually test placeholders or empty files
|
|
364
|
+
if (fileSize < 100) {
|
|
365
|
+
this.logger.debug(`MaliciousFileDetector: Skipping scan for very small file (${fileSize} bytes)`);
|
|
366
|
+
return {
|
|
367
|
+
safe: true,
|
|
368
|
+
threats: [],
|
|
369
|
+
riskScore: 0,
|
|
370
|
+
isMalware: false,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
// 使用流式处理读取文件(防止大文件占用过多内存)
|
|
374
|
+
scanBuffer = yield this.readFileStream(filePath, stats.size);
|
|
375
|
+
this.logger.debug(`MaliciousFileDetector: File read successfully, buffer size: ${scanBuffer.length}`);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// No input provided
|
|
379
|
+
return {
|
|
380
|
+
safe: true,
|
|
381
|
+
threats: [],
|
|
382
|
+
riskScore: 0,
|
|
383
|
+
isMalware: false,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
// Skip malicious file detection for very small buffers (< 100 bytes)
|
|
387
|
+
if (scanBuffer.length < 100) {
|
|
388
|
+
this.logger.debug(`MaliciousFileDetector: Skipping scan for very small buffer (${scanBuffer.length} bytes)`);
|
|
389
|
+
return {
|
|
390
|
+
safe: true,
|
|
391
|
+
threats: [],
|
|
392
|
+
riskScore: 0,
|
|
393
|
+
isMalware: false,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
// 1. 检查可疑签名
|
|
397
|
+
for (const signature of this.suspiciousSignatures) {
|
|
398
|
+
if (this.checkSignature(scanBuffer, signature)) {
|
|
399
|
+
threats.push(signature.description);
|
|
400
|
+
riskScore += signature.riskScore;
|
|
401
|
+
this.logger.debug(`MaliciousFileDetector: Found suspicious signature: ${signature.description}, risk: ${signature.riskScore}`);
|
|
402
|
+
// 如果需要检查压缩比
|
|
403
|
+
if (signature.checkRatio && filePath) {
|
|
404
|
+
const ratio = yield this.checkCompressionRatio(filePath, scanBuffer);
|
|
405
|
+
if (ratio > 100) {
|
|
406
|
+
threats.push('Suspicious compression ratio (possible zip bomb)');
|
|
407
|
+
riskScore += 50;
|
|
408
|
+
this.logger.debug(`MaliciousFileDetector: Found suspicious compression ratio: ${ratio}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// 2. 多编码检测(新增)
|
|
414
|
+
if (this.options.enableMultiEncoding) {
|
|
415
|
+
const encodingResult = this.detectMultiEncodingThreats(scanBuffer);
|
|
416
|
+
if (encodingResult.threats.length > 0) {
|
|
417
|
+
threats.push(...encodingResult.threats);
|
|
418
|
+
riskScore += encodingResult.riskScore;
|
|
419
|
+
details.encodingThreats = encodingResult.threats;
|
|
420
|
+
this.logger.debug(`MaliciousFileDetector: Multi-encoding threats found: ${encodingResult.threats.join(', ')}, risk: ${encodingResult.riskScore}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// 3. 文件结构分析(新增)
|
|
424
|
+
if (this.options.enableStructureAnalysis) {
|
|
425
|
+
const structureResult = yield this.analyzeFileStructure(filePath, scanBuffer);
|
|
426
|
+
if (structureResult.anomalies.length > 0) {
|
|
427
|
+
threats.push(...structureResult.anomalies);
|
|
428
|
+
riskScore += structureResult.riskScore;
|
|
429
|
+
details.structureAnalysis = structureResult;
|
|
430
|
+
this.logger.debug(`MaliciousFileDetector: Structure analysis threats found: ${structureResult.anomalies.join(', ')}, risk: ${structureResult.riskScore}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// 4. ClamAV扫描(新增,可选)
|
|
434
|
+
if (this.options.enableClamAV && filePath) {
|
|
435
|
+
const clamavResult = yield this.scanWithClamAV(filePath);
|
|
436
|
+
details.clamavResult = clamavResult;
|
|
437
|
+
if (clamavResult.infected) {
|
|
438
|
+
threats.push(`Virus detected: ${clamavResult.virusName}`);
|
|
439
|
+
riskScore += 100; // 直接判定为恶意
|
|
440
|
+
this.logger.debug(`MaliciousFileDetector: ClamAV detected virus: ${clamavResult.virusName}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// 5. 检查文件名
|
|
444
|
+
if (filePath) {
|
|
445
|
+
const fileName = path.basename(filePath);
|
|
446
|
+
if (this.hasSuspiciousName(fileName)) {
|
|
447
|
+
threats.push('Suspicious filename pattern');
|
|
448
|
+
riskScore += 20;
|
|
449
|
+
this.logger.debug(`MaliciousFileDetector: Suspicious filename pattern detected: ${fileName}`);
|
|
450
|
+
}
|
|
451
|
+
// 6. 检查双扩展名
|
|
452
|
+
if (this.hasDoubleExtension(fileName)) {
|
|
453
|
+
threats.push('Double extension detected');
|
|
454
|
+
riskScore += 30;
|
|
455
|
+
this.logger.debug(`MaliciousFileDetector: Double extension detected: ${fileName}`);
|
|
456
|
+
}
|
|
457
|
+
// 7. 检查隐藏扩展名技巧
|
|
458
|
+
if (this.hasHiddenExtension(fileName)) {
|
|
459
|
+
threats.push('Hidden extension trick detected');
|
|
460
|
+
riskScore += 40;
|
|
461
|
+
this.logger.debug(`MaliciousFileDetector: Hidden extension trick detected: ${fileName}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// 8. 检查文件大小异常
|
|
465
|
+
if (fileSize > 500 * 1024 * 1024) {
|
|
466
|
+
// 大于500MB
|
|
467
|
+
threats.push('Unusually large file size');
|
|
468
|
+
riskScore += 15;
|
|
469
|
+
this.logger.debug(`MaliciousFileDetector: Unusually large file size: ${fileSize}`);
|
|
470
|
+
}
|
|
471
|
+
// 9. 动态阈值调整(小文件更宽松)
|
|
472
|
+
let threshold = this.options.riskThreshold || 50;
|
|
473
|
+
if (fileSize < 1024) {
|
|
474
|
+
// 小于1KB的文件,提高阈值(减少误报)
|
|
475
|
+
threshold = Math.max(threshold, 80);
|
|
476
|
+
this.logger.debug(`MaliciousFileDetector: Using increased threshold for small file (${fileSize} bytes): ${threshold}`);
|
|
477
|
+
}
|
|
478
|
+
else if (fileSize < 10 * 1024) {
|
|
479
|
+
// 小于10KB的文件,适当提高阈值
|
|
480
|
+
threshold = Math.max(threshold, 65);
|
|
481
|
+
this.logger.debug(`MaliciousFileDetector: Using increased threshold for medium-small file (${fileSize} bytes): ${threshold}`);
|
|
482
|
+
}
|
|
483
|
+
const isSafe = riskScore < threshold;
|
|
484
|
+
this.logger.debug(`MaliciousFileDetector: Final result - safe: ${isSafe}, riskScore: ${riskScore}, threshold: ${threshold}, threats: ${threats.join(', ')}`);
|
|
485
|
+
return {
|
|
486
|
+
safe: isSafe,
|
|
487
|
+
threats,
|
|
488
|
+
riskScore: Math.min(riskScore, 100),
|
|
489
|
+
isMalware: riskScore >= 70,
|
|
490
|
+
details: Object.keys(details).length > 0 ? details : undefined,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
this.logger.error(`Error scanning file: ${error.message}`);
|
|
495
|
+
return {
|
|
496
|
+
safe: false,
|
|
497
|
+
threats: ['Unable to scan file: ' + error.message],
|
|
498
|
+
riskScore: 100,
|
|
499
|
+
isMalware: true,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* 使用流式处理读取文件(防止大文件占用过多内存)
|
|
506
|
+
*/
|
|
507
|
+
readFileStream(filePath, fileSize) {
|
|
508
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
509
|
+
// 限制读取大小
|
|
510
|
+
const readSize = Math.min(fileSize, this.maxScanSize);
|
|
511
|
+
// 如果文件较小,直接读取
|
|
512
|
+
if (fileSize <= 1024 * 1024) {
|
|
513
|
+
// 1MB 以下直接读取
|
|
514
|
+
return fs.readFile(filePath);
|
|
515
|
+
}
|
|
516
|
+
// 大文件使用流式处理
|
|
517
|
+
return new Promise((resolve, reject) => {
|
|
518
|
+
const chunks = [];
|
|
519
|
+
let totalRead = 0;
|
|
520
|
+
const stream = fs.createReadStream(filePath, {
|
|
521
|
+
highWaterMark: 64 * 1024, // 每次读取 64KB
|
|
522
|
+
end: readSize - 1, // 只读取前 N 字节
|
|
523
|
+
});
|
|
524
|
+
stream.on('data', (chunk) => {
|
|
525
|
+
chunks.push(chunk);
|
|
526
|
+
totalRead += chunk.length;
|
|
527
|
+
// 额外的安全检查:如果超过限制,立即停止
|
|
528
|
+
if (totalRead > this.maxScanSize) {
|
|
529
|
+
stream.destroy();
|
|
530
|
+
reject(new Error(`File size exceeds scan limit: ${this.maxScanSize}`));
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
stream.on('end', () => {
|
|
534
|
+
const buffer = Buffer.concat(chunks);
|
|
535
|
+
this.logger.debug(`Read ${buffer.length} bytes from ${filePath} using stream`);
|
|
536
|
+
resolve(buffer);
|
|
537
|
+
});
|
|
538
|
+
stream.on('error', (error) => {
|
|
539
|
+
reject(error);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* 检查签名
|
|
546
|
+
*/
|
|
547
|
+
checkSignature(buffer, signature) {
|
|
548
|
+
if (signature.type === 'string') {
|
|
549
|
+
const content = buffer.toString('ascii').toLowerCase();
|
|
550
|
+
return content.includes(signature.pattern.toLowerCase());
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
return this.bufferContains(buffer, signature.pattern);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* 缓冲区包含检查
|
|
558
|
+
*/
|
|
559
|
+
bufferContains(buffer, pattern) {
|
|
560
|
+
for (let i = 0; i <= buffer.length - pattern.length; i++) {
|
|
561
|
+
let match = true;
|
|
562
|
+
for (let j = 0; j < pattern.length; j++) {
|
|
563
|
+
if (buffer[i + j] !== pattern[j]) {
|
|
564
|
+
match = false;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (match)
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 检查压缩比(简单实现)
|
|
575
|
+
*/
|
|
576
|
+
checkCompressionRatio(filePath, buffer) {
|
|
577
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
578
|
+
// 简化版本:检查ZIP文件的压缩信息
|
|
579
|
+
// 完整实现需要解析ZIP结构
|
|
580
|
+
const compressedSize = (yield fs.stat(filePath)).size;
|
|
581
|
+
// 读取ZIP中央目录来估算未压缩大小
|
|
582
|
+
// 这里使用简单的启发式:如果文件很小但声称很大,可能是炸弹
|
|
583
|
+
if (compressedSize < 10 * 1024 && buffer.length < 10 * 1024) {
|
|
584
|
+
// 小于10KB的文件,假设正常
|
|
585
|
+
return 10;
|
|
586
|
+
}
|
|
587
|
+
// 保守估计:返回较低的比率避免误报
|
|
588
|
+
return 10;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* 检查可疑文件名
|
|
593
|
+
*/
|
|
594
|
+
hasSuspiciousName(fileName) {
|
|
595
|
+
const suspiciousPatterns = [
|
|
596
|
+
/\.exe$/i,
|
|
597
|
+
/\.scr$/i,
|
|
598
|
+
/\.bat$/i,
|
|
599
|
+
/\.cmd$/i,
|
|
600
|
+
/\.vbs$/i,
|
|
601
|
+
/\.js$/i,
|
|
602
|
+
/\.jar$/i,
|
|
603
|
+
/\.com$/i,
|
|
604
|
+
/\.pif$/i,
|
|
605
|
+
/\.application$/i,
|
|
606
|
+
/\.gadget$/i,
|
|
607
|
+
/\.msi$/i,
|
|
608
|
+
/\.msp$/i,
|
|
609
|
+
/\.cpl$/i,
|
|
610
|
+
/\.dll$/i,
|
|
611
|
+
/\.hta$/i,
|
|
612
|
+
/\.ws$/i,
|
|
613
|
+
/\.wsf$/i,
|
|
614
|
+
];
|
|
615
|
+
return suspiciousPatterns.some((pattern) => pattern.test(fileName));
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* 检查双扩展名
|
|
619
|
+
*/
|
|
620
|
+
hasDoubleExtension(fileName) {
|
|
621
|
+
// 例如: document.pdf.exe, photo.jpg.scr
|
|
622
|
+
const dangerousExtensions = [
|
|
623
|
+
'.exe',
|
|
624
|
+
'.scr',
|
|
625
|
+
'.bat',
|
|
626
|
+
'.cmd',
|
|
627
|
+
'.vbs',
|
|
628
|
+
'.com',
|
|
629
|
+
];
|
|
630
|
+
const lowerName = fileName.toLowerCase();
|
|
631
|
+
const parts = lowerName.split('.');
|
|
632
|
+
if (parts.length < 3) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
// 检查倒数第二个扩展名是否是安全的,最后一个是危险的
|
|
636
|
+
const lastExt = '.' + parts[parts.length - 1];
|
|
637
|
+
const secondLastExt = '.' + parts[parts.length - 2];
|
|
638
|
+
const safeExtensions = [
|
|
639
|
+
'.jpg',
|
|
640
|
+
'.jpeg',
|
|
641
|
+
'.png',
|
|
642
|
+
'.gif',
|
|
643
|
+
'.pdf',
|
|
644
|
+
'.doc',
|
|
645
|
+
'.docx',
|
|
646
|
+
'.txt',
|
|
647
|
+
];
|
|
648
|
+
return (safeExtensions.includes(secondLastExt) &&
|
|
649
|
+
dangerousExtensions.includes(lastExt));
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* 检查隐藏扩展名技巧
|
|
653
|
+
*/
|
|
654
|
+
hasHiddenExtension(fileName) {
|
|
655
|
+
// 检查是否使用空格或特殊字符隐藏真实扩展名
|
|
656
|
+
// 例如: "document.pdf .exe"
|
|
657
|
+
// 检查文件名中是否有多个连续空格
|
|
658
|
+
if (/\s{5,}/.test(fileName)) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
// 检查是否使用了右到左覆盖字符 (RTLO attack)
|
|
662
|
+
// U+202E
|
|
663
|
+
if (fileName.includes('\u202E')) {
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
// 检查是否使用了零宽字符
|
|
667
|
+
if (/[\u200B-\u200D\uFEFF]/.test(fileName)) {
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* 多编码检测(新增)
|
|
674
|
+
* 通过多种编码方式解析文件内容,检测隐藏的恶意模式
|
|
675
|
+
*/
|
|
676
|
+
detectMultiEncodingThreats(buffer) {
|
|
677
|
+
const threats = [];
|
|
678
|
+
let riskScore = 0;
|
|
679
|
+
// 遍历所有编码方式
|
|
680
|
+
for (const encoding of this.encodings) {
|
|
681
|
+
try {
|
|
682
|
+
let content;
|
|
683
|
+
// 特殊处理base64编码
|
|
684
|
+
if (encoding === 'base64') {
|
|
685
|
+
// 尝试将buffer解码为base64
|
|
686
|
+
const base64Pattern = /[A-Za-z0-9+/]{20,}={0,2}/g;
|
|
687
|
+
const asciiContent = buffer.toString('ascii');
|
|
688
|
+
const matches = asciiContent.match(base64Pattern);
|
|
689
|
+
if (matches) {
|
|
690
|
+
for (const match of matches) {
|
|
691
|
+
try {
|
|
692
|
+
const decoded = Buffer.from(match, 'base64').toString('utf8');
|
|
693
|
+
content = decoded;
|
|
694
|
+
// 检查解码后的内容
|
|
695
|
+
for (const pattern of this.maliciousPatterns) {
|
|
696
|
+
if (pattern.pattern.test(content)) {
|
|
697
|
+
threats.push(`${pattern.description} (base64 encoded)`);
|
|
698
|
+
riskScore += pattern.riskScore;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
catch (_a) {
|
|
703
|
+
// 解码失败,跳过
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
// 其他编码方式
|
|
710
|
+
content = buffer.toString(encoding).toLowerCase();
|
|
711
|
+
// 检查所有恶意模式
|
|
712
|
+
for (const pattern of this.maliciousPatterns) {
|
|
713
|
+
if (pattern.pattern.test(content)) {
|
|
714
|
+
const threatDesc = encoding === 'utf8'
|
|
715
|
+
? pattern.description
|
|
716
|
+
: `${pattern.description} (${encoding} encoding)`;
|
|
717
|
+
// 避免重复添加相同威胁
|
|
718
|
+
if (!threats.includes(threatDesc)) {
|
|
719
|
+
threats.push(threatDesc);
|
|
720
|
+
riskScore += pattern.riskScore;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
catch (error) {
|
|
726
|
+
// 编码转换失败,跳过该编码
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return { threats, riskScore };
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* 文件结构分析(新增)
|
|
733
|
+
* 分析文件的深层结构,检测异常特征
|
|
734
|
+
*/
|
|
735
|
+
analyzeFileStructure(filePath, buffer) {
|
|
736
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
737
|
+
const anomalies = [];
|
|
738
|
+
let riskScore = 0;
|
|
739
|
+
const characteristics = {};
|
|
740
|
+
// 1. 检测嵌套压缩文件
|
|
741
|
+
const hasNestedArchive = this.detectNestedArchive(buffer);
|
|
742
|
+
if (hasNestedArchive) {
|
|
743
|
+
anomalies.push('Nested archive detected');
|
|
744
|
+
riskScore += 35;
|
|
745
|
+
characteristics.hasNestedArchive = true;
|
|
746
|
+
}
|
|
747
|
+
// 2. 检测可执行内容
|
|
748
|
+
const hasExecutable = this.detectExecutableContent(buffer);
|
|
749
|
+
if (hasExecutable) {
|
|
750
|
+
anomalies.push('Executable content detected');
|
|
751
|
+
riskScore += 60;
|
|
752
|
+
characteristics.hasExecutableContent = true;
|
|
753
|
+
}
|
|
754
|
+
// 3. 检测Office宏
|
|
755
|
+
const hasMacros = this.detectOfficeMacros(buffer);
|
|
756
|
+
if (hasMacros) {
|
|
757
|
+
anomalies.push('Office macros detected');
|
|
758
|
+
riskScore += 50;
|
|
759
|
+
characteristics.hasMacros = true;
|
|
760
|
+
}
|
|
761
|
+
// 4. 计算文件熵值(检测加密/混淆)
|
|
762
|
+
const entropy = this.calculateEntropy(buffer);
|
|
763
|
+
characteristics.entropy = entropy;
|
|
764
|
+
if (entropy > 7.5) {
|
|
765
|
+
// 高熵值可能表示加密或压缩
|
|
766
|
+
anomalies.push('High entropy detected (possible encryption/obfuscation)');
|
|
767
|
+
riskScore += 30;
|
|
768
|
+
}
|
|
769
|
+
// 5. 检测多态性(文件头与内容不匹配)
|
|
770
|
+
const hasPolymorphism = yield this.detectPolymorphism(buffer);
|
|
771
|
+
if (hasPolymorphism) {
|
|
772
|
+
anomalies.push('Polymorphic file structure detected');
|
|
773
|
+
riskScore += 45;
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
anomalies,
|
|
777
|
+
riskScore,
|
|
778
|
+
characteristics,
|
|
779
|
+
};
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* ClamAV病毒扫描(增强版 - 支持本地/远程)
|
|
784
|
+
*/
|
|
785
|
+
scanWithClamAV(filePath) {
|
|
786
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
787
|
+
if (this.options.clamavMode === 'remote') {
|
|
788
|
+
return this.scanWithRemoteClamAV(filePath);
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
return this.scanWithLocalClamAV(filePath);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* 本地ClamAV扫描(增强安全性)
|
|
797
|
+
*/
|
|
798
|
+
scanWithLocalClamAV(filePath) {
|
|
799
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
800
|
+
try {
|
|
801
|
+
// 1. 验证 ClamAV 命令(仅允许白名单命令)
|
|
802
|
+
this.validateClamAVCommand();
|
|
803
|
+
// 2. 验证文件路径安全性
|
|
804
|
+
this.validateFilePath(filePath);
|
|
805
|
+
// 3. 验证文件存在且可读
|
|
806
|
+
const absoluteFilePath = path.resolve(filePath);
|
|
807
|
+
if (!fs.existsSync(absoluteFilePath)) {
|
|
808
|
+
throw new Error(`File does not exist: ${filePath}`);
|
|
809
|
+
}
|
|
810
|
+
// 使用 spawn 和参数数组,避免命令注入
|
|
811
|
+
const { spawn } = yield Promise.resolve().then(() => require('child_process'));
|
|
812
|
+
return new Promise((resolve, reject) => {
|
|
813
|
+
const args = ['--no-summary', absoluteFilePath];
|
|
814
|
+
// 使用安全选项启动进程
|
|
815
|
+
const child = spawn(this.options.clamavCommand, args, {
|
|
816
|
+
timeout: 30000, // 进程级超时
|
|
817
|
+
windowsHide: true, // Windows 下隐藏窗口
|
|
818
|
+
shell: false, // 禁用 shell,防止 shell 注入
|
|
819
|
+
});
|
|
820
|
+
let stdout = '';
|
|
821
|
+
let stderr = '';
|
|
822
|
+
let killed = false;
|
|
823
|
+
// 限制输出大小,防止内存溢出
|
|
824
|
+
const MAX_OUTPUT_SIZE = 1024 * 1024; // 1MB
|
|
825
|
+
child.stdout.on('data', (data) => {
|
|
826
|
+
if (stdout.length + data.length > MAX_OUTPUT_SIZE) {
|
|
827
|
+
this.logger.warn('ClamAV output size exceeded limit');
|
|
828
|
+
child.kill('SIGKILL');
|
|
829
|
+
killed = true;
|
|
830
|
+
reject(new Error('ClamAV output size exceeded limit'));
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
stdout += data.toString();
|
|
834
|
+
});
|
|
835
|
+
child.stderr.on('data', (data) => {
|
|
836
|
+
if (stderr.length + data.length > MAX_OUTPUT_SIZE) {
|
|
837
|
+
this.logger.warn('ClamAV error output size exceeded limit');
|
|
838
|
+
child.kill('SIGKILL');
|
|
839
|
+
killed = true;
|
|
840
|
+
reject(new Error('ClamAV error output size exceeded limit'));
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
stderr += data.toString();
|
|
844
|
+
});
|
|
845
|
+
// 设置超时
|
|
846
|
+
const timeout = setTimeout(() => {
|
|
847
|
+
if (!killed && !child.killed) {
|
|
848
|
+
this.logger.warn('ClamAV scan timeout, killing process');
|
|
849
|
+
child.kill('SIGKILL');
|
|
850
|
+
killed = true;
|
|
851
|
+
}
|
|
852
|
+
}, 30000);
|
|
853
|
+
child.on('close', (code) => {
|
|
854
|
+
clearTimeout(timeout);
|
|
855
|
+
// 如果已经被 killed,不再处理
|
|
856
|
+
if (killed) {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
// ClamAV 退出码:0=无病毒, 1=发现病毒, 其他=错误
|
|
860
|
+
if (code !== 0 && code !== 1) {
|
|
861
|
+
reject(new Error(`ClamAV process exited with code ${code}: ${stderr}`));
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
// 解析ClamAV输出
|
|
865
|
+
const output = stdout + stderr;
|
|
866
|
+
if (output.includes('FOUND')) {
|
|
867
|
+
// 提取病毒名称(限制长度)
|
|
868
|
+
const match = output.match(/: ([^:]+) FOUND/);
|
|
869
|
+
const virusName = match ? match[1].substring(0, 100) : 'Unknown';
|
|
870
|
+
resolve({
|
|
871
|
+
scanned: true,
|
|
872
|
+
infected: true,
|
|
873
|
+
virusName,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
resolve({
|
|
878
|
+
scanned: true,
|
|
879
|
+
infected: false,
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
child.on('error', (error) => {
|
|
884
|
+
clearTimeout(timeout);
|
|
885
|
+
if (!killed) {
|
|
886
|
+
// 尝试清理进程
|
|
887
|
+
try {
|
|
888
|
+
if (!child.killed) {
|
|
889
|
+
child.kill('SIGKILL');
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
catch (killError) {
|
|
893
|
+
this.logger.error(`Failed to kill ClamAV process: ${killError.message}`);
|
|
894
|
+
}
|
|
895
|
+
reject(error);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
catch (error) {
|
|
901
|
+
// ClamAV不可用或扫描失败
|
|
902
|
+
this.logger.warn(`Local ClamAV scan failed: ${error.message}`);
|
|
903
|
+
return {
|
|
904
|
+
scanned: false,
|
|
905
|
+
infected: false,
|
|
906
|
+
error: error.message,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* 验证 ClamAV 命令(仅允许白名单)
|
|
913
|
+
*/
|
|
914
|
+
validateClamAVCommand() {
|
|
915
|
+
const allowedCommands = ['clamscan', 'clamdscan'];
|
|
916
|
+
const commandBasename = path.basename(this.options.clamavCommand);
|
|
917
|
+
if (!allowedCommands.includes(commandBasename)) {
|
|
918
|
+
throw new Error(`Invalid ClamAV command: ${commandBasename}. Allowed: ${allowedCommands.join(', ')}`);
|
|
919
|
+
}
|
|
920
|
+
// 检查命令路径不包含危险字符
|
|
921
|
+
const dangerousChars = [';', '&', '|', '$', '`', '\n', '\r'];
|
|
922
|
+
for (const char of dangerousChars) {
|
|
923
|
+
if (this.options.clamavCommand.includes(char)) {
|
|
924
|
+
throw new Error(`Dangerous character in ClamAV command path: ${char}`);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* 验证文件路径,防止命令注入
|
|
930
|
+
*/
|
|
931
|
+
validateFilePath(filePath) {
|
|
932
|
+
// 检查是否包含危险字符
|
|
933
|
+
const dangerousChars = [
|
|
934
|
+
'"',
|
|
935
|
+
"'",
|
|
936
|
+
'`',
|
|
937
|
+
';',
|
|
938
|
+
'&',
|
|
939
|
+
'|',
|
|
940
|
+
'$',
|
|
941
|
+
'(',
|
|
942
|
+
')',
|
|
943
|
+
'<',
|
|
944
|
+
'>',
|
|
945
|
+
'\n',
|
|
946
|
+
'\r',
|
|
947
|
+
];
|
|
948
|
+
for (const char of dangerousChars) {
|
|
949
|
+
if (filePath.includes(char)) {
|
|
950
|
+
throw new Error(`Dangerous character detected in file path: ${char}`);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
// 检查路径遍历
|
|
954
|
+
if (filePath.includes('..')) {
|
|
955
|
+
throw new Error('Path traversal not allowed in file path');
|
|
956
|
+
}
|
|
957
|
+
// 检查空字节注入
|
|
958
|
+
if (filePath.includes('\0')) {
|
|
959
|
+
throw new Error('Null byte detected in file path');
|
|
960
|
+
}
|
|
961
|
+
// 检查是否为绝对路径(根据业务需求决定是否允许)
|
|
962
|
+
if (path.isAbsolute(filePath)) {
|
|
963
|
+
// 可以根据需要抛出错误或允许
|
|
964
|
+
// throw new Error('Absolute paths not allowed');
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* 远程ClamAV扫描(通过ClamAV守护进程的网络接口)
|
|
969
|
+
*/
|
|
970
|
+
scanWithRemoteClamAV(filePath) {
|
|
971
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
972
|
+
try {
|
|
973
|
+
const { host, port, timeout = 30000 } = this.options.clamavRemote || {};
|
|
974
|
+
if (!host || !port) {
|
|
975
|
+
throw new Error('Remote ClamAV host and port must be configured');
|
|
976
|
+
}
|
|
977
|
+
// 读取文件内容
|
|
978
|
+
const fileBuffer = yield fs.readFile(filePath);
|
|
979
|
+
// 使用Node.js net模块连接到ClamAV守护进程
|
|
980
|
+
const net = yield Promise.resolve().then(() => require('net'));
|
|
981
|
+
// 响应大小限制(1MB)
|
|
982
|
+
const MAX_RESPONSE_SIZE = 1024 * 1024;
|
|
983
|
+
return new Promise((resolve, reject) => {
|
|
984
|
+
const client = new net.Socket();
|
|
985
|
+
let response = '';
|
|
986
|
+
let responseSize = 0;
|
|
987
|
+
// 设置超时
|
|
988
|
+
const timeoutId = setTimeout(() => {
|
|
989
|
+
client.destroy();
|
|
990
|
+
reject(new Error('Remote ClamAV scan timeout'));
|
|
991
|
+
}, timeout);
|
|
992
|
+
client.connect(port, host, () => {
|
|
993
|
+
// 发送INSTREAM命令
|
|
994
|
+
client.write('zINSTREAM\0');
|
|
995
|
+
// 发送文件大小(4字节,网络字节序)
|
|
996
|
+
const sizeBuffer = Buffer.alloc(4);
|
|
997
|
+
sizeBuffer.writeUInt32BE(fileBuffer.length, 0);
|
|
998
|
+
client.write(sizeBuffer);
|
|
999
|
+
// 发送文件内容
|
|
1000
|
+
client.write(fileBuffer);
|
|
1001
|
+
// 发送结束标记(长度为0)
|
|
1002
|
+
const endBuffer = Buffer.alloc(4);
|
|
1003
|
+
endBuffer.writeUInt32BE(0, 0);
|
|
1004
|
+
client.write(endBuffer);
|
|
1005
|
+
});
|
|
1006
|
+
client.on('data', (data) => {
|
|
1007
|
+
responseSize += data.length;
|
|
1008
|
+
// 防止响应过大
|
|
1009
|
+
if (responseSize > MAX_RESPONSE_SIZE) {
|
|
1010
|
+
client.destroy();
|
|
1011
|
+
clearTimeout(timeoutId);
|
|
1012
|
+
this.logger.warn('Remote ClamAV response size exceeded limit');
|
|
1013
|
+
resolve({
|
|
1014
|
+
scanned: false,
|
|
1015
|
+
infected: false,
|
|
1016
|
+
error: 'Response size exceeded limit',
|
|
1017
|
+
});
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
response += data.toString('utf8', 0, Math.min(data.length, MAX_RESPONSE_SIZE - response.length));
|
|
1021
|
+
});
|
|
1022
|
+
client.on('end', () => {
|
|
1023
|
+
clearTimeout(timeoutId);
|
|
1024
|
+
// 验证响应格式
|
|
1025
|
+
if (!this.isValidClamAVResponse(response)) {
|
|
1026
|
+
this.logger.warn('Invalid ClamAV response format');
|
|
1027
|
+
resolve({
|
|
1028
|
+
scanned: false,
|
|
1029
|
+
infected: false,
|
|
1030
|
+
error: 'Invalid response format',
|
|
1031
|
+
});
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
// 解析响应
|
|
1035
|
+
if (response.includes('FOUND')) {
|
|
1036
|
+
const match = response.match(/stream: ([^\s]+) FOUND/);
|
|
1037
|
+
const virusName = match ? match[1] : 'Unknown';
|
|
1038
|
+
// 验证病毒名称格式
|
|
1039
|
+
if (!this.isValidVirusName(virusName)) {
|
|
1040
|
+
this.logger.warn('Invalid virus name format');
|
|
1041
|
+
resolve({
|
|
1042
|
+
scanned: true,
|
|
1043
|
+
infected: true,
|
|
1044
|
+
virusName: 'Unknown',
|
|
1045
|
+
});
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
resolve({
|
|
1049
|
+
scanned: true,
|
|
1050
|
+
infected: true,
|
|
1051
|
+
virusName,
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
else if (response.includes('OK')) {
|
|
1055
|
+
resolve({
|
|
1056
|
+
scanned: true,
|
|
1057
|
+
infected: false,
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
resolve({
|
|
1062
|
+
scanned: false,
|
|
1063
|
+
infected: false,
|
|
1064
|
+
error: 'Unexpected response: ' + response.substring(0, 100),
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
client.on('error', (error) => {
|
|
1069
|
+
clearTimeout(timeoutId);
|
|
1070
|
+
this.logger.warn(`Remote ClamAV connection error: ${error.message}`);
|
|
1071
|
+
resolve({
|
|
1072
|
+
scanned: false,
|
|
1073
|
+
infected: false,
|
|
1074
|
+
error: error.message,
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
catch (error) {
|
|
1080
|
+
this.logger.warn(`Remote ClamAV scan failed: ${error.message}`);
|
|
1081
|
+
return {
|
|
1082
|
+
scanned: false,
|
|
1083
|
+
infected: false,
|
|
1084
|
+
error: error.message,
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* 验证 ClamAV 响应格式
|
|
1091
|
+
*/
|
|
1092
|
+
isValidClamAVResponse(response) {
|
|
1093
|
+
if (!response || typeof response !== 'string') {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
// ClamAV 响应应该包含 'stream:' 关键字
|
|
1097
|
+
// 和 'FOUND' 或 'OK' 等状态
|
|
1098
|
+
const validPatterns = [
|
|
1099
|
+
/^stream: .+ FOUND$/m,
|
|
1100
|
+
/^stream: OK$/m,
|
|
1101
|
+
/^stream: .+ ERROR$/m,
|
|
1102
|
+
];
|
|
1103
|
+
return validPatterns.some((pattern) => pattern.test(response));
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* 验证病毒名称格式
|
|
1107
|
+
*/
|
|
1108
|
+
isValidVirusName(name) {
|
|
1109
|
+
if (!name || typeof name !== 'string') {
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
// 病毒名称应该只包含字母、数字、连字符、下划线和点
|
|
1113
|
+
// 长度不超过 100 字符
|
|
1114
|
+
return /^[a-zA-Z0-9._-]{1,100}$/.test(name);
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* 检测嵌套压缩文件
|
|
1118
|
+
*/
|
|
1119
|
+
detectNestedArchive(buffer) {
|
|
1120
|
+
const archiveSignatures = [
|
|
1121
|
+
{ pattern: [0x50, 0x4b, 0x03, 0x04], name: 'ZIP' },
|
|
1122
|
+
{ pattern: [0x52, 0x61, 0x72, 0x21], name: 'RAR' },
|
|
1123
|
+
{ pattern: [0x1f, 0x8b], name: 'GZIP' },
|
|
1124
|
+
{ pattern: [0x42, 0x5a, 0x68], name: 'BZIP2' },
|
|
1125
|
+
{ pattern: [0x37, 0x7a, 0xbc, 0xaf], name: '7Z' },
|
|
1126
|
+
];
|
|
1127
|
+
// 检测每个签名出现的次数
|
|
1128
|
+
for (const sig of archiveSignatures) {
|
|
1129
|
+
let occurrences = 0;
|
|
1130
|
+
let offset = 0;
|
|
1131
|
+
// 查找所有出现位置
|
|
1132
|
+
while (offset < buffer.length) {
|
|
1133
|
+
const index = this.bufferIndexOf(buffer, sig.pattern, offset);
|
|
1134
|
+
if (index === -1)
|
|
1135
|
+
break;
|
|
1136
|
+
occurrences++;
|
|
1137
|
+
offset = index + sig.pattern.length;
|
|
1138
|
+
// 如果同一个签名出现多次,可能是嵌套
|
|
1139
|
+
if (occurrences > 1) {
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* 检测可执行内容
|
|
1148
|
+
*/
|
|
1149
|
+
detectExecutableContent(buffer) {
|
|
1150
|
+
const executableSignatures = [
|
|
1151
|
+
[0x4d, 0x5a], // PE (Windows)
|
|
1152
|
+
[0x7f, 0x45, 0x4c, 0x46], // ELF (Linux)
|
|
1153
|
+
[0xfe, 0xed, 0xfa, 0xce], // Mach-O (macOS)
|
|
1154
|
+
[0xfe, 0xed, 0xfa, 0xcf], // Mach-O 64-bit
|
|
1155
|
+
[0xca, 0xfe, 0xba, 0xbe], // Java class
|
|
1156
|
+
];
|
|
1157
|
+
return executableSignatures.some((sig) => this.bufferContains(buffer, sig));
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* 检测Office宏
|
|
1161
|
+
*/
|
|
1162
|
+
detectOfficeMacros(buffer) {
|
|
1163
|
+
const macroIndicators = [
|
|
1164
|
+
'vbaProject.bin',
|
|
1165
|
+
'VBA',
|
|
1166
|
+
'AutoOpen',
|
|
1167
|
+
'Auto_Open',
|
|
1168
|
+
'Workbook_Open',
|
|
1169
|
+
'Document_Open',
|
|
1170
|
+
'AutoExec',
|
|
1171
|
+
];
|
|
1172
|
+
const content = buffer.toString('ascii').toLowerCase();
|
|
1173
|
+
return macroIndicators.some((indicator) => content.includes(indicator.toLowerCase()));
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* 计算Shannon熵值
|
|
1177
|
+
* 用于检测加密或高度压缩的内容
|
|
1178
|
+
*/
|
|
1179
|
+
calculateEntropy(buffer) {
|
|
1180
|
+
const freq = new Map();
|
|
1181
|
+
// 统计字节频率
|
|
1182
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1183
|
+
const byte = buffer[i];
|
|
1184
|
+
freq.set(byte, (freq.get(byte) || 0) + 1);
|
|
1185
|
+
}
|
|
1186
|
+
// 计算熵值
|
|
1187
|
+
let entropy = 0;
|
|
1188
|
+
const len = buffer.length;
|
|
1189
|
+
for (const count of freq.values()) {
|
|
1190
|
+
const p = count / len;
|
|
1191
|
+
entropy -= p * Math.log2(p);
|
|
1192
|
+
}
|
|
1193
|
+
return entropy;
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* 检测多态性(文件类型伪装)
|
|
1197
|
+
*/
|
|
1198
|
+
detectPolymorphism(buffer) {
|
|
1199
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1200
|
+
// 简化实现:检查文件头是否与内容一致
|
|
1201
|
+
// 例如:声称是图片但包含可执行代码
|
|
1202
|
+
const isImage = this.bufferContains(buffer, [0xff, 0xd8, 0xff]) || // JPEG
|
|
1203
|
+
this.bufferContains(buffer, [0x89, 0x50, 0x4e, 0x47]); // PNG
|
|
1204
|
+
if (isImage && this.detectExecutableContent(buffer)) {
|
|
1205
|
+
return true; // 图片中嵌入了可执行代码
|
|
1206
|
+
}
|
|
1207
|
+
return false;
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* 在buffer中查找pattern的位置
|
|
1212
|
+
*/
|
|
1213
|
+
bufferIndexOf(buffer, pattern, startOffset = 0) {
|
|
1214
|
+
for (let i = startOffset; i <= buffer.length - pattern.length; i++) {
|
|
1215
|
+
let match = true;
|
|
1216
|
+
for (let j = 0; j < pattern.length; j++) {
|
|
1217
|
+
if (buffer[i + j] !== pattern[j]) {
|
|
1218
|
+
match = false;
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (match)
|
|
1223
|
+
return i;
|
|
1224
|
+
}
|
|
1225
|
+
return -1;
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
exports.MaliciousFileDetector = MaliciousFileDetector;
|
|
1229
|
+
exports.MaliciousFileDetector = MaliciousFileDetector = MaliciousFileDetector_1 = __decorate([
|
|
1230
|
+
(0, common_1.Injectable)(),
|
|
1231
|
+
__param(0, (0, common_1.Optional)()),
|
|
1232
|
+
__param(0, (0, common_1.Inject)('MALICIOUS_DETECTOR_OPTIONS')),
|
|
1233
|
+
__metadata("design:paramtypes", [Object])
|
|
1234
|
+
], MaliciousFileDetector);
|