classifyre-cli 0.4.86__tar.gz → 0.4.87__tar.gz
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.
- classifyre_cli-0.4.87/.turbo/turbo-build.log +3 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/PKG-INFO +1 -1
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/package.json +1 -1
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/pyproject.toml +1 -1
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/pii/detector.py +30 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/outputs/base.py +8 -1
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/outputs/factory.py +18 -4
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/outputs/rest.py +17 -7
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/sandbox/source.py +8 -3
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/pii/test_pii_chunking.py +128 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_outputs.py +50 -12
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/uv.lock +2 -2
- classifyre_cli-0.4.86/.turbo/turbo-build.log +0 -3
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/.gitignore +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/.python-version +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/README.md +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/main.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/config.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/base.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/config.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/main.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/models/generated_input.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/outputs/console.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/outputs/file.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/base.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/databricks/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/hive/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/mysql/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/tabular_base.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/telemetry.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/file_parser.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/payload.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/resources.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/validation.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/conftest.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_config.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_main.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/tests/utils/test_video_processing.py +0 -0
|
@@ -672,10 +672,40 @@ class PIIDetector(BaseDetector):
|
|
|
672
672
|
# ------------------------------------------------------------------
|
|
673
673
|
|
|
674
674
|
def _analyze_content(self, content: str, *, entities: list[str] | None = None) -> list[Any]:
|
|
675
|
+
"""Analyze *content*, splitting it first if it would overflow spaCy.
|
|
676
|
+
|
|
677
|
+
This is the single chokepoint every caller goes through -- full-text
|
|
678
|
+
pages, individual tabular cells, and pre-chunked windows alike -- so the
|
|
679
|
+
oversize guard lives here rather than at any one call site. A page that
|
|
680
|
+
arrives already chunked passes straight through.
|
|
681
|
+
"""
|
|
675
682
|
if self.analyzer is None:
|
|
676
683
|
if self._init_error is not None:
|
|
677
684
|
raise self._init_error
|
|
678
685
|
return []
|
|
686
|
+
if len(content) > self._spacy_char_limit():
|
|
687
|
+
return self._analyze_oversized(content, entities=entities)
|
|
688
|
+
return self._analyze_once(content, entities=entities)
|
|
689
|
+
|
|
690
|
+
def _analyze_oversized(self, content: str, *, entities: list[str] | None) -> list[Any]:
|
|
691
|
+
"""Analyze text past the spaCy ceiling window by window, merged into one result set.
|
|
692
|
+
|
|
693
|
+
Results are re-based onto absolute offsets in *content* and deduped
|
|
694
|
+
across the overlap, keeping the highest-scoring hit for each span.
|
|
695
|
+
"""
|
|
696
|
+
merged: dict[tuple[str, int, int], Any] = {}
|
|
697
|
+
for chunk, offset in self._auto_chunks(content):
|
|
698
|
+
for result in self._analyze_once(chunk, entities=entities):
|
|
699
|
+
result.start += offset
|
|
700
|
+
result.end += offset
|
|
701
|
+
key = (result.entity_type, result.start, result.end)
|
|
702
|
+
existing = merged.get(key)
|
|
703
|
+
if existing is None or result.score > existing.score:
|
|
704
|
+
merged[key] = result
|
|
705
|
+
return sorted(merged.values(), key=lambda r: (r.start, r.end))
|
|
706
|
+
|
|
707
|
+
def _analyze_once(self, content: str, *, entities: list[str] | None = None) -> list[Any]:
|
|
708
|
+
"""One Presidio pass over text already known to fit the spaCy window."""
|
|
679
709
|
try:
|
|
680
710
|
return self.analyzer.analyze(text=content, language="en", entities=entities)
|
|
681
711
|
except ModuleNotFoundError as exc:
|
|
@@ -7,6 +7,12 @@ from pydantic import BaseModel
|
|
|
7
7
|
|
|
8
8
|
OutputType = Literal["rest", "file", "console"]
|
|
9
9
|
|
|
10
|
+
# How long to wait for the API to answer a write-back. Generous enough that a
|
|
11
|
+
# bulk ingest of a large batch finishes well inside it — the old 30 s cut those
|
|
12
|
+
# off mid-flight and made urllib3 re-upload the same body — but finite, so a
|
|
13
|
+
# wedged API fails the run instead of hanging it forever.
|
|
14
|
+
DEFAULT_REST_TIMEOUT_SEC = 300
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
@dataclass(frozen=True)
|
|
12
18
|
class OutputRuntimeContext:
|
|
@@ -24,7 +30,8 @@ class OutputSettings:
|
|
|
24
30
|
runner_id: str | None
|
|
25
31
|
managed_runner: bool
|
|
26
32
|
rest_url: str | None = None
|
|
27
|
-
|
|
33
|
+
# Read timeout in seconds; None disables it. See resolve_output_settings.
|
|
34
|
+
rest_timeout_sec: int | None = DEFAULT_REST_TIMEOUT_SEC
|
|
28
35
|
file_path: str | None = None
|
|
29
36
|
|
|
30
37
|
|
|
@@ -4,7 +4,13 @@ import argparse
|
|
|
4
4
|
import os
|
|
5
5
|
from typing import Any, cast
|
|
6
6
|
|
|
7
|
-
from .base import
|
|
7
|
+
from .base import (
|
|
8
|
+
DEFAULT_REST_TIMEOUT_SEC,
|
|
9
|
+
OutputRuntimeContext,
|
|
10
|
+
OutputSettings,
|
|
11
|
+
OutputSink,
|
|
12
|
+
OutputType,
|
|
13
|
+
)
|
|
8
14
|
from .console import ConsoleOutputSink
|
|
9
15
|
from .file import FileOutputSink
|
|
10
16
|
from .rest import RestOutputSink
|
|
@@ -96,9 +102,17 @@ def resolve_output_settings(
|
|
|
96
102
|
)
|
|
97
103
|
rest_url = str(rest_url_value) if rest_url_value is not None else None
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
# 5 minutes by default (see DEFAULT_REST_TIMEOUT_SEC): long enough that a
|
|
106
|
+
# bulk ingest of a large batch is never cut off mid-flight, short enough
|
|
107
|
+
# that a wedged API surfaces as a failure. Override with
|
|
108
|
+
# CLASSIFYRE_OUTPUT_REST_TIMEOUT_SEC; 0 or negative disables the read
|
|
109
|
+
# timeout entirely.
|
|
110
|
+
rest_timeout_sec: int | None = _parse_int(
|
|
111
|
+
_coalesce(env_rest_timeout, DEFAULT_REST_TIMEOUT_SEC),
|
|
112
|
+
DEFAULT_REST_TIMEOUT_SEC,
|
|
113
|
+
)
|
|
114
|
+
if rest_timeout_sec is not None and rest_timeout_sec < 1:
|
|
115
|
+
rest_timeout_sec = None
|
|
102
116
|
|
|
103
117
|
file_path_value = _coalesce(
|
|
104
118
|
getattr(args, "output_file_path", None),
|
|
@@ -55,9 +55,12 @@ class _JitteredRetry(Retry):
|
|
|
55
55
|
# What we retry and why:
|
|
56
56
|
# connect=8 — pod restarted / not yet ready (RemoteDisconnected, ConnectionReset,
|
|
57
57
|
# ConnectTimeout). Request never reached the application.
|
|
58
|
-
# read=8 — API is under load and slow to respond (ReadTimeout).
|
|
59
|
-
#
|
|
60
|
-
#
|
|
58
|
+
# read=8 — API is under load and slow to respond (ReadTimeout), i.e. it did
|
|
59
|
+
# not answer within DEFAULT_REST_TIMEOUT_SEC. Safe to retry because
|
|
60
|
+
# all endpoints are idempotent (bulk ingest is upsert-based,
|
|
61
|
+
# status/findings updates are set-operations) — but note that a
|
|
62
|
+
# retry re-uploads the whole body, which is why that budget is
|
|
63
|
+
# generous rather than tight.
|
|
61
64
|
# status=8 — transient HTTP errors from an overloaded or restarting API:
|
|
62
65
|
# 408 Request Timeout - API-level timeout
|
|
63
66
|
# 429 Too Many Requests - rate-limited / backpressure
|
|
@@ -76,8 +79,7 @@ class _JitteredRetry(Retry):
|
|
|
76
79
|
# attempt 7 → ~60 s (capped)
|
|
77
80
|
# attempt 8 → ~60 s (capped)
|
|
78
81
|
# Total extra wait: ~182 s (~3 min) — covers extended load spikes on a
|
|
79
|
-
# single-node VPS before event-loop pressure drops.
|
|
80
|
-
# call costs 8 * 120 s + 182 s = ~18 min, acceptable for long-running scans.
|
|
82
|
+
# single-node VPS before event-loop pressure drops.
|
|
81
83
|
#
|
|
82
84
|
# POST and PATCH are explicitly allowed: without this urllib3 only retries
|
|
83
85
|
# idempotent methods (GET/HEAD) by default.
|
|
@@ -161,6 +163,12 @@ class ExternalRunnerResponse(BaseModel):
|
|
|
161
163
|
source_id: str = Field(validation_alias="sourceId")
|
|
162
164
|
|
|
163
165
|
|
|
166
|
+
# Applied to connection establishment only, kept short so a refused or
|
|
167
|
+
# unreachable API fails fast and is retried by _RETRY_POLICY. How long the API
|
|
168
|
+
# may then take to answer is a separate budget (timeout_sec, 5 min by default).
|
|
169
|
+
_CONNECT_TIMEOUT_SEC = 30
|
|
170
|
+
|
|
171
|
+
|
|
164
172
|
class RestOutputSink:
|
|
165
173
|
output_type: OutputType = "rest"
|
|
166
174
|
|
|
@@ -169,7 +177,7 @@ class RestOutputSink:
|
|
|
169
177
|
context: OutputRuntimeContext,
|
|
170
178
|
*,
|
|
171
179
|
base_url: str,
|
|
172
|
-
timeout_sec: int,
|
|
180
|
+
timeout_sec: int | None,
|
|
173
181
|
):
|
|
174
182
|
self.context = context
|
|
175
183
|
self.batch_size = context.batch_size
|
|
@@ -443,7 +451,9 @@ class RestOutputSink:
|
|
|
443
451
|
method=method,
|
|
444
452
|
url=url,
|
|
445
453
|
json=payload,
|
|
446
|
-
|
|
454
|
+
# (connect, read): the read budget is the API's whole processing
|
|
455
|
+
# time, not just the first byte. None waits indefinitely.
|
|
456
|
+
timeout=(_CONNECT_TIMEOUT_SEC, self.timeout_sec),
|
|
447
457
|
)
|
|
448
458
|
|
|
449
459
|
if response.status_code >= 400:
|
|
@@ -10,6 +10,7 @@ from urllib.parse import quote
|
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
12
|
from ...models.generated_input import SandboxInput
|
|
13
|
+
from ...outputs.base import DEFAULT_REST_TIMEOUT_SEC
|
|
13
14
|
from ..object_storage.base import ObjectRef, ObjectStorageSourceBase
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -48,15 +49,19 @@ class SandboxSource(ObjectStorageSourceBase):
|
|
|
48
49
|
return default
|
|
49
50
|
|
|
50
51
|
def _max_object_bytes(self) -> int:
|
|
51
|
-
#
|
|
52
|
-
#
|
|
52
|
+
# Spool threshold (RAM before spilling to disk), not a refusal ceiling —
|
|
53
|
+
# uploads are no longer size-capped. Raised above the object-storage
|
|
54
|
+
# family's conservative 5 MiB default because these files are local.
|
|
53
55
|
return 50 * 1024 * 1024
|
|
54
56
|
|
|
55
57
|
def _request(self, method: str, path: str, *, stream: bool = False) -> requests.Response:
|
|
56
58
|
response = self._session.request(
|
|
57
59
|
method,
|
|
58
60
|
f"{self._api_url}{path}",
|
|
59
|
-
|
|
61
|
+
# (connect, read). The read budget applies per socket read, so 5
|
|
62
|
+
# minutes is generous for a streamed download of any size; the old
|
|
63
|
+
# flat 120 s applied to connect and read alike.
|
|
64
|
+
timeout=(30, DEFAULT_REST_TIMEOUT_SEC),
|
|
60
65
|
stream=stream,
|
|
61
66
|
headers={"Connection": "close"},
|
|
62
67
|
)
|
|
@@ -32,6 +32,51 @@ class _Cfg:
|
|
|
32
32
|
self.max_length = max_length
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class _FakeResult:
|
|
36
|
+
"""Shape of a Presidio RecognizerResult, as far as the detector uses it."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, entity_type, start, end, score):
|
|
39
|
+
self.entity_type = entity_type
|
|
40
|
+
self.start = start
|
|
41
|
+
self.end = end
|
|
42
|
+
self.score = score
|
|
43
|
+
self.recognition_metadata = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _FakeAnalyzer:
|
|
47
|
+
"""Finds every occurrence of a needle and reports chunk-relative offsets.
|
|
48
|
+
|
|
49
|
+
Real Presidio raises E088 above nlp.max_length; this records what it was
|
|
50
|
+
handed so the tests can assert nothing was skipped and offsets came back
|
|
51
|
+
absolute.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
NEEDLE = "alice@example.com"
|
|
55
|
+
|
|
56
|
+
def __init__(self, score=0.9):
|
|
57
|
+
self.calls: list[str] = []
|
|
58
|
+
self._score = score
|
|
59
|
+
|
|
60
|
+
def analyze(self, *, text, language, entities=None):
|
|
61
|
+
self.calls.append(text)
|
|
62
|
+
results = []
|
|
63
|
+
start = text.find(self.NEEDLE)
|
|
64
|
+
while start != -1:
|
|
65
|
+
results.append(
|
|
66
|
+
_FakeResult("EMAIL_ADDRESS", start, start + len(self.NEEDLE), self._score)
|
|
67
|
+
)
|
|
68
|
+
start = text.find(self.NEEDLE, start + 1)
|
|
69
|
+
return results
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _detector_with_analyzer(analyzer, cfg=None) -> PIIDetector:
|
|
73
|
+
detector = _detector_with(cfg or _Cfg())
|
|
74
|
+
detector.analyzer = analyzer
|
|
75
|
+
detector._init_error = None
|
|
76
|
+
detector._supported_entities_cache = frozenset({"EMAIL_ADDRESS"})
|
|
77
|
+
return detector
|
|
78
|
+
|
|
79
|
+
|
|
35
80
|
class TestUnwrapInt:
|
|
36
81
|
def test_unwraps_root_model(self):
|
|
37
82
|
assert _unwrap_int(ChunkSize(root=500)) == 500
|
|
@@ -197,3 +242,86 @@ class TestAutoChunking:
|
|
|
197
242
|
|
|
198
243
|
assert chunks[-1][1] + len(chunks[-1][0]) == len(text)
|
|
199
244
|
assert all(len(chunk) <= 1_000 for chunk, _ in chunks)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class TestAnalyzeContentGuard:
|
|
248
|
+
"""_analyze_content is the chokepoint every caller shares. Guarding only the
|
|
249
|
+
full-text loop left the tabular per-cell path calling Presidio with the raw
|
|
250
|
+
cell, so one multi-megabyte text column still raised E088 -- swallowed, so
|
|
251
|
+
the page reported zero findings."""
|
|
252
|
+
|
|
253
|
+
def _text_with_needles(self, length: int, positions: list[int]) -> str:
|
|
254
|
+
buf = list("x" * length)
|
|
255
|
+
for pos in positions:
|
|
256
|
+
buf[pos : pos + len(_FakeAnalyzer.NEEDLE)] = _FakeAnalyzer.NEEDLE
|
|
257
|
+
return "".join(buf)
|
|
258
|
+
|
|
259
|
+
def test_short_text_is_a_single_pass(self):
|
|
260
|
+
analyzer = _FakeAnalyzer()
|
|
261
|
+
detector = _detector_with_analyzer(analyzer)
|
|
262
|
+
|
|
263
|
+
detector._analyze_content("contact alice@example.com today")
|
|
264
|
+
|
|
265
|
+
assert len(analyzer.calls) == 1
|
|
266
|
+
|
|
267
|
+
def test_oversized_text_is_split_before_reaching_presidio(self):
|
|
268
|
+
analyzer = _FakeAnalyzer()
|
|
269
|
+
detector = _detector_with_analyzer(analyzer)
|
|
270
|
+
text = self._text_with_needles(2_023_954, [1_500_000])
|
|
271
|
+
|
|
272
|
+
detector._analyze_content(text)
|
|
273
|
+
|
|
274
|
+
assert len(analyzer.calls) > 1
|
|
275
|
+
limit = PIIDetector._SPACY_DEFAULT_MAX_LENGTH
|
|
276
|
+
assert all(len(call) <= limit for call in analyzer.calls)
|
|
277
|
+
|
|
278
|
+
def test_offsets_come_back_absolute(self):
|
|
279
|
+
analyzer = _FakeAnalyzer()
|
|
280
|
+
detector = _detector_with_analyzer(analyzer)
|
|
281
|
+
text = self._text_with_needles(2_023_954, [10, 640_000, 1_500_000, 2_000_000])
|
|
282
|
+
|
|
283
|
+
results = detector._analyze_content(text)
|
|
284
|
+
|
|
285
|
+
# A finding past the first window is exactly what the old code lost.
|
|
286
|
+
assert [r.start for r in results] == [10, 640_000, 1_500_000, 2_000_000]
|
|
287
|
+
assert all(text[r.start : r.end] == _FakeAnalyzer.NEEDLE for r in results)
|
|
288
|
+
|
|
289
|
+
def test_overlap_does_not_duplicate_findings(self):
|
|
290
|
+
analyzer = _FakeAnalyzer()
|
|
291
|
+
detector = _detector_with_analyzer(analyzer)
|
|
292
|
+
window = PIIDetector._AUTO_CHUNK_CHARS
|
|
293
|
+
# Sitting inside the overlap zone, this one is seen by two windows.
|
|
294
|
+
seam = window - (PIIDetector._AUTO_CHUNK_OVERLAP // 2)
|
|
295
|
+
text = self._text_with_needles(1_200_000, [seam])
|
|
296
|
+
|
|
297
|
+
results = detector._analyze_content(text)
|
|
298
|
+
|
|
299
|
+
assert len(results) == 1
|
|
300
|
+
assert results[0].start == seam
|
|
301
|
+
|
|
302
|
+
def test_oversized_tabular_cell_is_analyzed_not_dropped(self):
|
|
303
|
+
analyzer = _FakeAnalyzer()
|
|
304
|
+
detector = _detector_with_analyzer(analyzer)
|
|
305
|
+
cell_value = self._text_with_needles(2_023_954, [1_800_000])
|
|
306
|
+
|
|
307
|
+
results = detector._analyze_structured_cell(
|
|
308
|
+
cell_value, allowed_entity_types={"EMAIL_ADDRESS"}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
assert [r.start for r in results] == [1_800_000]
|
|
312
|
+
assert cell_value[results[0].start : results[0].end] == _FakeAnalyzer.NEEDLE
|
|
313
|
+
|
|
314
|
+
def test_chunk_size_larger_than_the_spacy_limit_is_still_safe(self):
|
|
315
|
+
# An explicit chunk_size above the ceiling used to hand Presidio an
|
|
316
|
+
# oversized window just the same.
|
|
317
|
+
analyzer = _FakeAnalyzer()
|
|
318
|
+
detector = _detector_with_analyzer(
|
|
319
|
+
analyzer, _Cfg(chunk_size=ChunkSize(root=1_500_000), chunk_overlap=ChunkOverlap(root=0))
|
|
320
|
+
)
|
|
321
|
+
text = self._text_with_needles(2_023_954, [1_400_000])
|
|
322
|
+
|
|
323
|
+
for chunk, _offset in detector._iter_chunks(text):
|
|
324
|
+
detector._analyze_content(chunk)
|
|
325
|
+
|
|
326
|
+
limit = PIIDetector._SPACY_DEFAULT_MAX_LENGTH
|
|
327
|
+
assert all(len(call) <= limit for call in analyzer.calls)
|
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
|
-
from src.outputs.base import OutputRuntimeContext
|
|
10
|
+
from src.outputs.base import DEFAULT_REST_TIMEOUT_SEC, OutputRuntimeContext
|
|
11
11
|
from src.outputs.console import ConsoleOutputSink
|
|
12
12
|
from src.outputs.factory import resolve_output_settings
|
|
13
13
|
from src.outputs.file import FileOutputSink
|
|
@@ -87,6 +87,32 @@ def test_output_resolution_rest_uses_api_url_env_fallback(
|
|
|
87
87
|
assert settings.rest_url == "http://api-env.example:8000"
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def test_output_resolution_uses_the_five_minute_read_timeout_by_default() -> None:
|
|
91
|
+
settings = resolve_output_settings(_args(source_id="source-1"))
|
|
92
|
+
|
|
93
|
+
assert settings.rest_timeout_sec == DEFAULT_REST_TIMEOUT_SEC == 300
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_output_resolution_env_overrides_the_read_timeout(
|
|
97
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
98
|
+
) -> None:
|
|
99
|
+
monkeypatch.setenv("CLASSIFYRE_OUTPUT_REST_TIMEOUT_SEC", "45")
|
|
100
|
+
|
|
101
|
+
settings = resolve_output_settings(_args(source_id="source-1"))
|
|
102
|
+
|
|
103
|
+
assert settings.rest_timeout_sec == 45
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_output_resolution_treats_non_positive_read_timeout_as_unlimited(
|
|
107
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
108
|
+
) -> None:
|
|
109
|
+
monkeypatch.setenv("CLASSIFYRE_OUTPUT_REST_TIMEOUT_SEC", "0")
|
|
110
|
+
|
|
111
|
+
settings = resolve_output_settings(_args(source_id="source-1"))
|
|
112
|
+
|
|
113
|
+
assert settings.rest_timeout_sec is None
|
|
114
|
+
|
|
115
|
+
|
|
90
116
|
def test_output_resolution_defaults_to_console_without_source_id() -> None:
|
|
91
117
|
settings = resolve_output_settings(_args())
|
|
92
118
|
|
|
@@ -167,7 +193,7 @@ class _FakeSession:
|
|
|
167
193
|
method: str,
|
|
168
194
|
url: str,
|
|
169
195
|
json: dict[str, Any] | None,
|
|
170
|
-
timeout:
|
|
196
|
+
timeout: tuple[float, float | None],
|
|
171
197
|
) -> _FakeResponse:
|
|
172
198
|
self.calls.append(
|
|
173
199
|
{
|
|
@@ -199,7 +225,9 @@ async def test_rest_output_managed_runner_flow(monkeypatch: pytest.MonkeyPatch)
|
|
|
199
225
|
managed_runner=True,
|
|
200
226
|
batch_size=20,
|
|
201
227
|
)
|
|
202
|
-
sink = RestOutputSink(
|
|
228
|
+
sink = RestOutputSink(
|
|
229
|
+
context, base_url="http://localhost:8000", timeout_sec=DEFAULT_REST_TIMEOUT_SEC
|
|
230
|
+
)
|
|
203
231
|
|
|
204
232
|
await sink.start()
|
|
205
233
|
await sink.emit_batch([{"hash": "h1"}])
|
|
@@ -231,7 +259,7 @@ async def test_rest_output_keeps_namespace_base_for_discovery_and_finding_batche
|
|
|
231
259
|
sink = RestOutputSink(
|
|
232
260
|
context,
|
|
233
261
|
base_url="http://api.internal/prefix/namespace-id/",
|
|
234
|
-
timeout_sec=
|
|
262
|
+
timeout_sec=DEFAULT_REST_TIMEOUT_SEC,
|
|
235
263
|
)
|
|
236
264
|
|
|
237
265
|
await sink.register_discovered_assets(["hash-1", "hash-2"])
|
|
@@ -267,7 +295,7 @@ async def test_register_discovered_assets_returns_a_bare_list_without_payload_cu
|
|
|
267
295
|
sink = RestOutputSink(
|
|
268
296
|
OutputRuntimeContext(source_id="s", runner_id="r", managed_runner=True, batch_size=20),
|
|
269
297
|
base_url="http://localhost:8000",
|
|
270
|
-
timeout_sec=
|
|
298
|
+
timeout_sec=DEFAULT_REST_TIMEOUT_SEC,
|
|
271
299
|
)
|
|
272
300
|
|
|
273
301
|
result = await sink.register_discovered_assets(["a", "b"], include_scan_cache=True)
|
|
@@ -297,7 +325,7 @@ async def test_register_discovered_assets_collects_payload_cursors(
|
|
|
297
325
|
sink = RestOutputSink(
|
|
298
326
|
OutputRuntimeContext(source_id="s", runner_id="r", managed_runner=True, batch_size=20),
|
|
299
327
|
base_url="http://localhost:8000",
|
|
300
|
-
timeout_sec=
|
|
328
|
+
timeout_sec=DEFAULT_REST_TIMEOUT_SEC,
|
|
301
329
|
)
|
|
302
330
|
|
|
303
331
|
cache, cursors = await sink.register_discovered_assets(
|
|
@@ -320,7 +348,7 @@ async def test_asset_payload_carries_the_payload_cursor(
|
|
|
320
348
|
sink = RestOutputSink(
|
|
321
349
|
OutputRuntimeContext(source_id="s", runner_id="r", managed_runner=True, batch_size=20),
|
|
322
350
|
base_url="http://localhost:8000",
|
|
323
|
-
timeout_sec=
|
|
351
|
+
timeout_sec=DEFAULT_REST_TIMEOUT_SEC,
|
|
324
352
|
)
|
|
325
353
|
|
|
326
354
|
cursor = {"v": 1, "offset": 300, "exhausted": False, "checksum": "c1"}
|
|
@@ -342,7 +370,9 @@ async def test_rest_output_finalize_includes_sampling_cursor(
|
|
|
342
370
|
managed_runner=True,
|
|
343
371
|
batch_size=20,
|
|
344
372
|
)
|
|
345
|
-
sink = RestOutputSink(
|
|
373
|
+
sink = RestOutputSink(
|
|
374
|
+
context, base_url="http://localhost:8000", timeout_sec=DEFAULT_REST_TIMEOUT_SEC
|
|
375
|
+
)
|
|
346
376
|
|
|
347
377
|
cursor = {"tables": {"db_#_users": {"pk": [42]}}}
|
|
348
378
|
sink.set_sampling_cursor(cursor)
|
|
@@ -373,7 +403,9 @@ async def test_rest_output_omits_null_fields_in_nested_payload(
|
|
|
373
403
|
managed_runner=True,
|
|
374
404
|
batch_size=20,
|
|
375
405
|
)
|
|
376
|
-
sink = RestOutputSink(
|
|
406
|
+
sink = RestOutputSink(
|
|
407
|
+
context, base_url="http://localhost:8000", timeout_sec=DEFAULT_REST_TIMEOUT_SEC
|
|
408
|
+
)
|
|
377
409
|
|
|
378
410
|
await sink.start()
|
|
379
411
|
await sink.emit_batch(
|
|
@@ -425,7 +457,9 @@ async def test_rest_output_auto_creates_external_runner(monkeypatch: pytest.Monk
|
|
|
425
457
|
managed_runner=False,
|
|
426
458
|
batch_size=20,
|
|
427
459
|
)
|
|
428
|
-
sink = RestOutputSink(
|
|
460
|
+
sink = RestOutputSink(
|
|
461
|
+
context, base_url="http://localhost:8000", timeout_sec=DEFAULT_REST_TIMEOUT_SEC
|
|
462
|
+
)
|
|
429
463
|
|
|
430
464
|
await sink.start()
|
|
431
465
|
await sink.emit_batch([{"hash": "h1"}])
|
|
@@ -447,7 +481,9 @@ async def test_rest_output_fail_marks_runner_error(monkeypatch: pytest.MonkeyPat
|
|
|
447
481
|
managed_runner=False,
|
|
448
482
|
batch_size=20,
|
|
449
483
|
)
|
|
450
|
-
sink = RestOutputSink(
|
|
484
|
+
sink = RestOutputSink(
|
|
485
|
+
context, base_url="http://localhost:8000", timeout_sec=DEFAULT_REST_TIMEOUT_SEC
|
|
486
|
+
)
|
|
451
487
|
|
|
452
488
|
await sink.start()
|
|
453
489
|
await sink.fail(RuntimeError("boom"))
|
|
@@ -470,7 +506,9 @@ async def test_rest_output_fail_marks_managed_runner_error(
|
|
|
470
506
|
managed_runner=True,
|
|
471
507
|
batch_size=20,
|
|
472
508
|
)
|
|
473
|
-
sink = RestOutputSink(
|
|
509
|
+
sink = RestOutputSink(
|
|
510
|
+
context, base_url="http://localhost:8000", timeout_sec=DEFAULT_REST_TIMEOUT_SEC
|
|
511
|
+
)
|
|
474
512
|
|
|
475
513
|
await sink.start()
|
|
476
514
|
await sink.fail(RuntimeError("boom"))
|
|
@@ -830,7 +830,7 @@ wheels = [
|
|
|
830
830
|
|
|
831
831
|
[[package]]
|
|
832
832
|
name = "classifyre-cli"
|
|
833
|
-
version = "0.4.
|
|
833
|
+
version = "0.4.87"
|
|
834
834
|
source = { editable = "." }
|
|
835
835
|
dependencies = [
|
|
836
836
|
{ name = "beautifulsoup4" },
|
|
@@ -1369,7 +1369,7 @@ youtube = [
|
|
|
1369
1369
|
|
|
1370
1370
|
[[package]]
|
|
1371
1371
|
name = "classifyre-schemas"
|
|
1372
|
-
version = "0.4.
|
|
1372
|
+
version = "0.4.87"
|
|
1373
1373
|
source = { editable = "../../packages/schemas" }
|
|
1374
1374
|
dependencies = [
|
|
1375
1375
|
{ name = "fastjsonschema" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_object_detection.py
RENAMED
|
File without changes
|
|
File without changes
|
{classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/custom/runners/_text_classification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/detectors/threat/code_security_detector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{classifyre_cli-0.4.86 → classifyre_cli-0.4.87}/src/models/generated_single_asset_scan_results.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|