classifyre-cli 0.5.6__tar.gz → 0.5.8__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.
Files changed (357) hide show
  1. classifyre_cli-0.5.8/.turbo/turbo-build.log +3 -0
  2. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/PKG-INFO +1 -1
  3. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/package.json +1 -1
  4. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/pyproject.toml +1 -1
  5. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/main.py +149 -35
  6. classifyre_cli-0.5.8/src/notebook/redact.py +37 -0
  7. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/rest.py +16 -6
  8. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/detector_pipeline.py +21 -3
  9. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/parsed_content_provider.py +11 -3
  10. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/custom/source.py +29 -10
  11. classifyre_cli-0.5.8/src/utils/redaction.py +300 -0
  12. classifyre_cli-0.5.8/src/utils/validation.py +111 -0
  13. classifyre_cli-0.5.8/tests/test_secret_redaction.py +223 -0
  14. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/uv.lock +104 -104
  15. classifyre_cli-0.5.6/.turbo/turbo-build.log +0 -3
  16. classifyre_cli-0.5.6/src/notebook/redact.py +0 -143
  17. classifyre_cli-0.5.6/src/utils/validation.py +0 -56
  18. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/.gitignore +0 -0
  19. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/.python-version +0 -0
  20. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/README.md +0 -0
  21. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/main.py +0 -0
  22. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/scripts/generate_models.py +0 -0
  23. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/scripts/generate_runtime_packages.py +0 -0
  24. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/scripts/generate_sdk_completions.py +0 -0
  25. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/__init__.py +0 -0
  26. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/__init__.py +0 -0
  27. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/contract.py +0 -0
  28. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/preview.py +0 -0
  29. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/runner.py +0 -0
  30. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/sdk.py +0 -0
  31. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/session.py +0 -0
  32. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/config.py +0 -0
  33. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/__init__.py +0 -0
  34. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/base.py +0 -0
  35. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/broken_links/__init__.py +0 -0
  36. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/broken_links/detector.py +0 -0
  37. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/config.py +0 -0
  38. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/content/__init__.py +0 -0
  39. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/__init__.py +0 -0
  40. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/detector.py +0 -0
  41. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/extractor.py +0 -0
  42. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/__init__.py +0 -0
  43. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_base.py +0 -0
  44. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_factory.py +0 -0
  45. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_gliner2.py +0 -0
  46. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_image_classification.py +0 -0
  47. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_llm.py +0 -0
  48. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_object_detection.py +0 -0
  49. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_regex.py +0 -0
  50. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_tag.py +0 -0
  51. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_text_classification.py +0 -0
  52. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/trainer.py +0 -0
  53. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/dependencies.py +0 -0
  54. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/engine_version.py +0 -0
  55. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/errors.py +0 -0
  56. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/pii/__init__.py +0 -0
  57. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/pii/detector.py +0 -0
  58. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/secrets/__init__.py +0 -0
  59. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/secrets/detector.py +0 -0
  60. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/threat/__init__.py +0 -0
  61. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/threat/code_security_detector.py +0 -0
  62. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/threat/yara_detector.py +0 -0
  63. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/file_evaluation/__init__.py +0 -0
  64. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/file_evaluation/runner.py +0 -0
  65. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/graph/__init__.py +0 -0
  66. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/graph/edges.py +0 -0
  67. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/models/generated_detectors.py +0 -0
  68. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/models/generated_input.py +0 -0
  69. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/models/generated_single_asset_scan_results.py +0 -0
  70. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/__init__.py +0 -0
  71. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/child.py +0 -0
  72. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/cli.py +0 -0
  73. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/cohort.py +0 -0
  74. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/contract.py +0 -0
  75. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/execute.py +0 -0
  76. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/files.py +0 -0
  77. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/groups.py +0 -0
  78. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/outputs.py +0 -0
  79. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/packages.py +0 -0
  80. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/protocol.py +0 -0
  81. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/scaffold.py +0 -0
  82. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/sdk.py +0 -0
  83. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/serialize.py +0 -0
  84. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/__init__.py +0 -0
  85. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/base.py +0 -0
  86. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/console.py +0 -0
  87. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/factory.py +0 -0
  88. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/file.py +0 -0
  89. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/__init__.py +0 -0
  90. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/content_provider.py +0 -0
  91. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/detector_breaker.py +0 -0
  92. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/payload_window.py +0 -0
  93. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/scan_cache.py +0 -0
  94. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/text_artifact.py +0 -0
  95. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/worker_pool.py +0 -0
  96. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/__init__.py +0 -0
  97. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/asset_metadata.py +0 -0
  98. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/atlassian_common.py +0 -0
  99. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/azure_blob_storage/__init__.py +0 -0
  100. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/azure_blob_storage/source.py +0 -0
  101. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/base.py +0 -0
  102. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/confluence/__init__.py +0 -0
  103. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/confluence/source.py +0 -0
  104. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/custom/__init__.py +0 -0
  105. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/custom/env.py +0 -0
  106. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/custom/runner.py +0 -0
  107. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/databricks/__init__.py +0 -0
  108. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/databricks/source.py +0 -0
  109. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/delta_lake/__init__.py +0 -0
  110. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/delta_lake/source.py +0 -0
  111. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/dependencies.py +0 -0
  112. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/dropbox/__init__.py +0 -0
  113. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/dropbox/auth.py +0 -0
  114. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/dropbox/source.py +0 -0
  115. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/elasticsearch/__init__.py +0 -0
  116. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/elasticsearch/source.py +0 -0
  117. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/email/__init__.py +0 -0
  118. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/email/source.py +0 -0
  119. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/git/__init__.py +0 -0
  120. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/git/source.py +0 -0
  121. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/google_cloud_storage/__init__.py +0 -0
  122. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/google_cloud_storage/source.py +0 -0
  123. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/google_workspace/__init__.py +0 -0
  124. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/google_workspace/source.py +0 -0
  125. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/hive/__init__.py +0 -0
  126. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/hive/source.py +0 -0
  127. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/hugging_face/__init__.py +0 -0
  128. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/hugging_face/source.py +0 -0
  129. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/iceberg/__init__.py +0 -0
  130. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/iceberg/source.py +0 -0
  131. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/jira/__init__.py +0 -0
  132. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/jira/source.py +0 -0
  133. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/kafka/__init__.py +0 -0
  134. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/kafka/rest.py +0 -0
  135. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/kafka/source.py +0 -0
  136. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/lakehouse_base.py +0 -0
  137. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/local_folder/__init__.py +0 -0
  138. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/local_folder/source.py +0 -0
  139. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/meilisearch/__init__.py +0 -0
  140. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/meilisearch/source.py +0 -0
  141. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/microsoft_365/__init__.py +0 -0
  142. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/microsoft_365/source.py +0 -0
  143. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mongodb/__init__.py +0 -0
  144. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mongodb/source.py +0 -0
  145. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mssql/__init__.py +0 -0
  146. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mssql/source.py +0 -0
  147. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mysql/__init__.py +0 -0
  148. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mysql/source.py +0 -0
  149. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/neo4j/__init__.py +0 -0
  150. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/neo4j/source.py +0 -0
  151. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/notion/__init__.py +0 -0
  152. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/notion/client.py +0 -0
  153. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/notion/source.py +0 -0
  154. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/object_storage/base.py +0 -0
  155. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/opensearch/__init__.py +0 -0
  156. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/opensearch/source.py +0 -0
  157. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/oracle/__init__.py +0 -0
  158. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/oracle/source.py +0 -0
  159. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/postgresql/__init__.py +0 -0
  160. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/postgresql/source.py +0 -0
  161. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/powerbi/__init__.py +0 -0
  162. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/powerbi/source.py +0 -0
  163. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/recipe_normalizer.py +0 -0
  164. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/reddit/__init__.py +0 -0
  165. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/reddit/source.py +0 -0
  166. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/s3_client.py +0 -0
  167. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/s3_compatible_storage/README.md +0 -0
  168. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/s3_compatible_storage/__init__.py +0 -0
  169. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/s3_compatible_storage/source.py +0 -0
  170. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/sandbox/__init__.py +0 -0
  171. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/sandbox/source.py +0 -0
  172. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/search_engine_base.py +0 -0
  173. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/servicedesk/__init__.py +0 -0
  174. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/servicedesk/source.py +0 -0
  175. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/slack/__init__.py +0 -0
  176. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/slack/source.py +0 -0
  177. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/snowflake/__init__.py +0 -0
  178. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/snowflake/source.py +0 -0
  179. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/sqlite/__init__.py +0 -0
  180. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/sqlite/source.py +0 -0
  181. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/tableau/__init__.py +0 -0
  182. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/tableau/source.py +0 -0
  183. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/tabular_base.py +0 -0
  184. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/tabular_utils.py +0 -0
  185. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/wordpress/__init__.py +0 -0
  186. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/wordpress/source.py +0 -0
  187. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/youtube/__init__.py +0 -0
  188. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/youtube/source.py +0 -0
  189. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/telemetry.py +0 -0
  190. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/__init__.py +0 -0
  191. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/archive_extraction.py +0 -0
  192. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/content_extraction.py +0 -0
  193. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/dependency_groups.py +0 -0
  194. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/embedded_files.py +0 -0
  195. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/external_video.py +0 -0
  196. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/file_metadata.py +0 -0
  197. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/file_parser.py +0 -0
  198. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/file_to_images.py +0 -0
  199. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/hashing.py +0 -0
  200. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/legacy_office.py +0 -0
  201. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/payload.py +0 -0
  202. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/range_reader.py +0 -0
  203. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/resources.py +0 -0
  204. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/source_files.py +0 -0
  205. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/sql_lineage.py +0 -0
  206. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/torch_runtime.py +0 -0
  207. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/transcription.py +0 -0
  208. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/urn.py +0 -0
  209. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/uv_sync.py +0 -0
  210. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/video_processing.py +0 -0
  211. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/__init__.py +0 -0
  212. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/_lakehouse_fakes.py +0 -0
  213. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/conftest.py +0 -0
  214. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/__init__.py +0 -0
  215. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
  216. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/conftest.py +0 -0
  217. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/content/__init__.py +0 -0
  218. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/__init__.py +0 -0
  219. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/conftest.py +0 -0
  220. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_gliner2_classification.py +0 -0
  221. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_invoice_extraction.py +0 -0
  222. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_llm_runner.py +0 -0
  223. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_pipeline_integration.py +0 -0
  224. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_regex_runner.py +0 -0
  225. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_runner_hardening.py +0 -0
  226. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_transformer_runners.py +0 -0
  227. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/__init__.py +0 -0
  228. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/conftest.py +0 -0
  229. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/sample_invoice.pdf +0 -0
  230. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_chunking.py +0 -0
  231. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_detector.py +0 -0
  232. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
  233. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
  234. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_severity.py +0 -0
  235. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/secrets/__init__.py +0 -0
  236. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/secrets/test_secrets_detector.py +0 -0
  237. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
  238. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_base_detector.py +0 -0
  239. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
  240. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_detector_catalog_commercial.py +0 -0
  241. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_detector_pipeline_types.py +0 -0
  242. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_detector_schema_examples.py +0 -0
  243. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_detector_types.py +0 -0
  244. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_phase2_detectors.py +0 -0
  245. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_registry.py +0 -0
  246. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_tag_runner.py +0 -0
  247. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/threat/__init__.py +0 -0
  248. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/threat/test_code_security_detector.py +0 -0
  249. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/threat/test_yara_detector.py +0 -0
  250. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
  251. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/integration/test_wordpress_links_assets.py +0 -0
  252. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/pipeline/test_detector_breaker.py +0 -0
  253. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/pipeline/test_detector_outcomes.py +0 -0
  254. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/pipeline/test_detector_pipeline.py +0 -0
  255. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/pipeline/test_worker_pool.py +0 -0
  256. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_arrow_ipc_payloads.py +0 -0
  257. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_assets_metadata_catalog.py +0 -0
  258. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_contract.py +0 -0
  259. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_edges.py +0 -0
  260. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_patch.py +0 -0
  261. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_scan_cache.py +0 -0
  262. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_sdk.py +0 -0
  263. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_session.py +0 -0
  264. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_tags_end_to_end.py +0 -0
  265. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_azure_blob_storage_source.py +0 -0
  266. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_base_source_attachment.py +0 -0
  267. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_base_source_sampling.py +0 -0
  268. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_byte_valued_fields.py +0 -0
  269. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_config.py +0 -0
  270. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_confluence_source.py +0 -0
  271. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_custom_extractor.py +0 -0
  272. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_custom_source.py +0 -0
  273. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_custom_source_relationships.py +0 -0
  274. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_custom_source_tags_end_to_end.py +0 -0
  275. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_databricks_source.py +0 -0
  276. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_delta_lake_source.py +0 -0
  277. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_dependency_groups.py +0 -0
  278. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_detector_scope.py +0 -0
  279. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_dropbox_auth_command.py +0 -0
  280. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_dropbox_source.py +0 -0
  281. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_elasticsearch_source.py +0 -0
  282. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_email_source.py +0 -0
  283. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_file_evaluation_runner.py +0 -0
  284. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_git_source.py +0 -0
  285. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_google_cloud_storage_source.py +0 -0
  286. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_google_workspace_source.py +0 -0
  287. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_graph_edges.py +0 -0
  288. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_hashing.py +0 -0
  289. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_hive_source.py +0 -0
  290. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_hugging_face_source.py +0 -0
  291. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_iceberg_source.py +0 -0
  292. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_jira_source.py +0 -0
  293. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_kafka_source.py +0 -0
  294. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_lineage_loss_reporting.py +0 -0
  295. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_local_folder_source.py +0 -0
  296. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_main.py +0 -0
  297. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_meilisearch_source.py +0 -0
  298. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_microsoft_365_source.py +0 -0
  299. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_mongodb_source.py +0 -0
  300. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_mssql_source.py +0 -0
  301. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_mysql_source.py +0 -0
  302. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_neo4j_source.py +0 -0
  303. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_asset_extract.py +0 -0
  304. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_asset_tags.py +0 -0
  305. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_cohort.py +0 -0
  306. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_execute.py +0 -0
  307. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_files.py +0 -0
  308. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_groups.py +0 -0
  309. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_packages.py +0 -0
  310. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_query_assets.py +0 -0
  311. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_serialize.py +0 -0
  312. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_templates.py +0 -0
  313. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notion_source.py +0 -0
  314. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_object_storage_download_efficiency.py +0 -0
  315. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_opensearch_source.py +0 -0
  316. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_oracle_source.py +0 -0
  317. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_outputs.py +0 -0
  318. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_payload_window.py +0 -0
  319. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_payload_window_parquet.py +0 -0
  320. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_postgresql_source.py +0 -0
  321. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_powerbi_source.py +0 -0
  322. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_range_reading.py +0 -0
  323. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_recipe_normalizer.py +0 -0
  324. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_reddit_source.py +0 -0
  325. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_rest_backpressure.py +0 -0
  326. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_runtime_packages.py +0 -0
  327. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_s3_compatible_storage_source.py +0 -0
  328. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_sampling_automatic.py +0 -0
  329. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_sandbox_source.py +0 -0
  330. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_scan_cache.py +0 -0
  331. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_sdk_completions.py +0 -0
  332. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_servicedesk_source.py +0 -0
  333. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_slack_source.py +0 -0
  334. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_snowflake_source.py +0 -0
  335. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_source_dependency_groups.py +0 -0
  336. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_source_strategy_conformance.py +0 -0
  337. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_sqlite_source.py +0 -0
  338. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_tableau_source.py +0 -0
  339. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_tabular_automatic_sampling.py +0 -0
  340. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_tabular_lineage.py +0 -0
  341. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_tabular_utils.py +0 -0
  342. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_torch_runtime.py +0 -0
  343. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_urn.py +0 -0
  344. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_uv_sync.py +0 -0
  345. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_wordpress_source.py +0 -0
  346. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_youtube_source.py +0 -0
  347. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_youtube_source_integration.py +0 -0
  348. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_archive_extraction.py +0 -0
  349. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_content_extraction.py +0 -0
  350. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_embedded_files.py +0 -0
  351. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_file_metadata.py +0 -0
  352. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_file_parser.py +0 -0
  353. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_file_to_images.py +0 -0
  354. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_legacy_office.py +0 -0
  355. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_payload.py +0 -0
  356. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_transcription.py +0 -0
  357. {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_video_processing.py +0 -0
@@ -0,0 +1,3 @@
1
+ $ uv sync
2
+ Resolved 323 packages in 194ms
3
+ Checked 51 packages in 1ms
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: classifyre-cli
3
- Version: 0.5.6
3
+ Version: 0.5.8
4
4
  Summary: Classifyre CLI — scan and classify unstructured data sources
5
5
  License: MIT
6
6
  Keywords: data,ingestion,metadata,pii,secrets,unstructured
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classifyre/cli",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "build": "uv sync",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "classifyre-cli"
3
- version = "0.5.6"
3
+ version = "0.5.8"
4
4
  description = "Classifyre CLI — scan and classify unstructured data sources"
5
5
  readme = "README.md"
6
6
  # Upper bound is load-bearing: onnxruntime publishes no cp315 wheels (notably
@@ -21,7 +21,12 @@ from .sources.dropbox.auth import ( # noqa: E402
21
21
  add_dropbox_auth_arguments,
22
22
  run_dropbox_auth_command,
23
23
  )
24
- from .utils.validation import validate_input, validate_test_connection # noqa: E402
24
+ from .utils.redaction import Redactor, redact_generic, sanitize_for_logging # noqa: E402
25
+ from .utils.validation import ( # noqa: E402
26
+ RecipeValidationError,
27
+ validate_input,
28
+ validate_test_connection,
29
+ )
25
30
 
26
31
  logger = logging.getLogger(__name__)
27
32
  _SURROGATE_RE = re.compile(r"[\ud800-\udfff]")
@@ -56,6 +61,53 @@ def _sanitize_for_json(value: Any) -> Any:
56
61
  return value
57
62
 
58
63
 
64
+ def _safe_text(text: Any, redactor: Redactor | None) -> str:
65
+ """Render ``text`` for logs or stored run errors without secrets.
66
+
67
+ Value-based redaction (exact ``masked`` leaves from the recipe) runs
68
+ first, then credential-shaped patterns, so short secrets and
69
+ driver-echoed connection strings are hidden even when the exact value
70
+ is unknown.
71
+ """
72
+ raw = text if isinstance(text, str) else str(text)
73
+ if redactor is not None:
74
+ raw = redactor.redact(raw)
75
+ return redact_generic(raw)
76
+
77
+
78
+ def _safe_error_copy(exc: Exception, redactor: Redactor | None) -> Exception:
79
+ """Copy ``exc`` with redacted args so ``str()`` of it is log-safe.
80
+
81
+ Preserves the exception type name used by ``sink.fail()``. Falls back
82
+ to a plain RuntimeError when the copy cannot be made.
83
+ """
84
+ import copy as _copy
85
+
86
+ try:
87
+ clone = _copy.copy(exc)
88
+ clone.args = tuple(
89
+ _safe_text(arg, redactor) if isinstance(arg, str) else arg for arg in exc.args
90
+ )
91
+ return clone
92
+ except Exception:
93
+ return RuntimeError(_safe_text(exc, redactor))
94
+
95
+
96
+ def _log_debug_traceback(
97
+ log: Any, message: str, exc: BaseException, redactor: Redactor | None
98
+ ) -> None:
99
+ """Log a traceback at debug without leaking secrets through exc_info.
100
+
101
+ ``logger.debug(..., exc_info=True)`` formats the original exception,
102
+ bypassing any redaction. Formatting first and redacting the text keeps
103
+ the traceback useful while hiding credential values.
104
+ """
105
+ import traceback as _traceback
106
+
107
+ formatted = "".join(_traceback.format_exception(type(exc), exc, exc.__traceback__))
108
+ log.debug("%s\n%s", message, _safe_text(formatted, redactor))
109
+
110
+
59
111
  async def _emit_text_chunks_with_retry(
60
112
  sink: Any,
61
113
  asset_hash: str,
@@ -178,7 +230,7 @@ def _asset_to_payload(asset: Any) -> dict[str, Any]:
178
230
  raise TypeError(f"Unsupported asset payload type: {type(asset)}")
179
231
 
180
232
 
181
- async def _flush_buffered_edges(source: Any, sink: Any) -> None:
233
+ async def _flush_buffered_edges(source: Any, sink: Any, redactor: Redactor | None = None) -> None:
182
234
  """Emit edges buffered so far, without asking for end-of-run relationships.
183
235
 
184
236
  Augmentation can produce an edge per asset; holding a whole scan's lineage
@@ -192,7 +244,10 @@ async def _flush_buffered_edges(source: Any, sink: Any) -> None:
192
244
  try:
193
245
  edges = source.drain_edges()
194
246
  except Exception as drain_error:
195
- logger.warning("Could not drain buffered edges (non-fatal): %s", drain_error)
247
+ logger.warning(
248
+ "Could not drain buffered edges (non-fatal): %s",
249
+ _safe_text(drain_error, redactor),
250
+ )
196
251
  return
197
252
  if not edges:
198
253
  return
@@ -203,12 +258,12 @@ async def _flush_buffered_edges(source: Any, sink: Any) -> None:
203
258
  logger.warning(
204
259
  "Buffered relationship emission failed, %d edge(s) lost (non-fatal): %s",
205
260
  len(edges),
206
- emit_error,
261
+ _safe_text(emit_error, redactor),
207
262
  )
208
263
  if report is not None:
209
264
  report.record_lost(
210
265
  len(edges),
211
- f"{type(emit_error).__name__}: {emit_error}",
266
+ _safe_text(f"{type(emit_error).__name__}: {emit_error}", redactor),
212
267
  )
213
268
  return
214
269
  tally = result if isinstance(result, dict) else {}
@@ -219,7 +274,13 @@ async def _flush_buffered_edges(source: Any, sink: Any) -> None:
219
274
  )
220
275
 
221
276
 
222
- async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False) -> None:
277
+ async def _emit_relationships(
278
+ source: Any,
279
+ sink: Any,
280
+ *,
281
+ partial: bool = False,
282
+ redactor: Redactor | None = None,
283
+ ) -> None:
223
284
  """Send everything the connector learned about how its assets relate.
224
285
 
225
286
  Two sources, one flush: edges buffered mid-scan via ``add_edge`` and edges a
@@ -252,10 +313,13 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
252
313
  # The connector's relationship code raised. How many edges that cost is
253
314
  # unknowable — it never got to say — so this is counted as one failed
254
315
  # relationship pass rather than as N lost edges.
255
- logger.warning("Could not collect relationships (non-fatal): %s", collect_error)
316
+ logger.warning(
317
+ "Could not collect relationships (non-fatal): %s",
318
+ _safe_text(collect_error, redactor),
319
+ )
256
320
  if report is not None:
257
321
  report.record_failure(
258
- f"{type(collect_error).__name__}: {collect_error}",
322
+ _safe_text(f"{type(collect_error).__name__}: {collect_error}", redactor),
259
323
  )
260
324
 
261
325
  # A connector that declares relationships() but produced nothing is not the
@@ -274,12 +338,12 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
274
338
  logger.warning(
275
339
  "Relationship emission failed, %d edge(s) lost (non-fatal): %s",
276
340
  len(edges),
277
- emit_error,
341
+ _safe_text(emit_error, redactor),
278
342
  )
279
343
  if report is not None:
280
344
  report.record_lost(
281
345
  len(edges),
282
- f"{type(emit_error).__name__}: {emit_error}",
346
+ _safe_text(f"{type(emit_error).__name__}: {emit_error}", redactor),
283
347
  )
284
348
  return
285
349
 
@@ -333,11 +397,16 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
333
397
  # before phase 2; every later use is None-guarded.
334
398
  augmentation: Any = None
335
399
 
400
+ # Built before any logging or error formatting: every message below that
401
+ # touches the recipe, a driver error, or a notebook verdict goes through
402
+ # it, so secrets never reach runner logs or stored run errors.
403
+ redactor = Redactor.from_recipe(recipe)
404
+
336
405
  try:
337
406
  try:
338
407
  source = get_source(recipe, source_id=source_id, runner_id=runner_id)
339
408
  except ValueError as e:
340
- logger.error("Failed to initialize source: %s", e)
409
+ logger.error("Failed to initialize source: %s", _safe_text(e, redactor))
341
410
  sys.exit(1)
342
411
 
343
412
  try:
@@ -348,27 +417,46 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
348
417
  validate_test_connection(result)
349
418
  logger.info("Test connection output is valid")
350
419
  except Exception as validation_error:
351
- logger.warning("Test connection output validation failed: %s", validation_error)
420
+ logger.warning(
421
+ "Test connection output validation failed: %s",
422
+ _safe_text(validation_error, redactor),
423
+ )
352
424
 
353
- print(json.dumps(result, indent=2))
425
+ print(
426
+ json.dumps(
427
+ sanitize_for_logging(result, redactor),
428
+ indent=2,
429
+ )
430
+ )
354
431
  if result.get("status") == "FAILURE":
355
432
  sys.exit(1)
356
433
 
357
434
  elif args.command == "discover":
358
435
  result = source.test_connection()
359
436
  if result.get("status") == "FAILURE":
360
- logger.error("Aborting: Connection test failed: %s", result.get("message"))
437
+ logger.error(
438
+ "Aborting: Connection test failed: %s",
439
+ _safe_text(result.get("message"), redactor),
440
+ )
361
441
  sys.exit(1)
362
442
 
363
443
  logger.info("Discovering resources...")
364
444
  data = source.discover()
365
- print(json.dumps(data, indent=2))
445
+ print(
446
+ json.dumps(
447
+ sanitize_for_logging(data, redactor),
448
+ indent=2,
449
+ )
450
+ )
366
451
 
367
452
  elif args.command == "extract":
368
453
  result = source.test_connection()
369
454
  if result.get("status") == "FAILURE":
370
455
  msg = result.get("message", "")
371
- logger.error("Aborting: Connection test failed: %s", msg)
456
+ logger.error(
457
+ "Aborting: Connection test failed: %s",
458
+ _safe_text(msg, redactor),
459
+ )
372
460
  sys.exit(1)
373
461
 
374
462
  logger.info("Starting extraction...")
@@ -420,7 +508,7 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
420
508
  if not warm_ok:
421
509
  logger.warning(
422
510
  "Dependency warm-up incomplete (workers will retry): %s",
423
- warm_detail,
511
+ _safe_text(warm_detail, redactor),
424
512
  )
425
513
 
426
514
  worker_pool = DetectorWorkerPool(max_workers=pool_workers)
@@ -598,7 +686,7 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
598
686
  await augmentation.augment(asset)
599
687
  if augmentation.flush_requested:
600
688
  augmentation.clear_flush_request()
601
- await _flush_buffered_edges(source, sink)
689
+ await _flush_buffered_edges(source, sink, redactor)
602
690
 
603
691
  if hasattr(sink, "update_asset_status"):
604
692
  await sink.update_asset_status(asset_hash, "PROCESSING")
@@ -653,13 +741,15 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
653
741
  # cannot mistake missing embeddings for a
654
742
  # healthy completed scan.
655
743
  chunks_ok = False
656
- detail = f"{asset_hash}: {chunk_exc}"
744
+ detail = _safe_text(
745
+ f"{asset_hash}: {chunk_exc}", redactor
746
+ )
657
747
  chunk_errors.append(detail)
658
748
  logger.error(
659
749
  "Text-chunk emission exhausted retries"
660
750
  " for asset %s: %s",
661
751
  asset_hash,
662
- chunk_exc,
752
+ _safe_text(chunk_exc, redactor),
663
753
  )
664
754
 
665
755
  f_total, f_by_sev, f_by_det = _compute_findings_counts(
@@ -714,10 +804,16 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
714
804
  processed_count += 1
715
805
  except Exception as exc:
716
806
  error_count += 1
717
- logger.error("Asset %s failed: %s", asset_hash, exc)
807
+ logger.error(
808
+ "Asset %s failed: %s",
809
+ asset_hash,
810
+ _safe_text(exc, redactor),
811
+ )
718
812
  if hasattr(sink, "update_asset_status"):
719
813
  try:
720
- error_msg = str(exc) or type(exc).__name__
814
+ error_msg = _safe_text(
815
+ str(exc) or type(exc).__name__, redactor
816
+ )
721
817
  await sink.update_asset_status(
722
818
  asset_hash, "ERROR", error_message=error_msg
723
819
  )
@@ -779,7 +875,7 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
779
875
  "findings and are retried on the next run.",
780
876
  degraded["label"],
781
877
  degraded["cause"],
782
- degraded["reason"],
878
+ _safe_text(degraded["reason"], redactor),
783
879
  degraded["attempted_payloads"],
784
880
  degraded["skipped_payloads"],
785
881
  )
@@ -810,7 +906,7 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
810
906
  # Relationships go *before* finish(): finish() finalizes the
811
907
  # run and marks it COMPLETED, and edges that land after that
812
908
  # belong to a run that already claimed to be done.
813
- await _emit_relationships(source, sink)
909
+ await _emit_relationships(source, sink, redactor=redactor)
814
910
 
815
911
  await sink.finish()
816
912
  logger.info(
@@ -822,12 +918,12 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
822
918
  if _is_timeout_error(extraction_error):
823
919
  logger.warning(
824
920
  "Source timed out during extraction, partial results flushed: %s",
825
- extraction_error,
921
+ _safe_text(extraction_error, redactor),
826
922
  )
827
923
  # The assets this run did ingest keep their
828
924
  # relationships. Dropping them here is what made a slow
829
925
  # warehouse scan produce a graph with no lineage at all.
830
- await _emit_relationships(source, sink, partial=True)
926
+ await _emit_relationships(source, sink, partial=True, redactor=redactor)
831
927
  # A run that timed out mid-extraction did not visit the
832
928
  # rest of the source, so absence from it proves nothing.
833
929
  # Without this the timeout retires every asset the run
@@ -842,10 +938,17 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
842
938
  return
843
939
  if sink_started:
844
940
  try:
845
- await sink.fail(extraction_error)
941
+ # Redacted copy: sink.fail stores the message on
942
+ # the run record, which is readable beyond the
943
+ # operator who holds the credential.
944
+ await sink.fail(_safe_error_copy(extraction_error, redactor))
846
945
  except Exception as sink_error:
847
946
  logger.error(
848
- "Failed to mark sink failure: %s", sink_error, exc_info=True
947
+ "Failed to mark sink failure: %s",
948
+ _safe_text(sink_error, redactor),
949
+ )
950
+ _log_debug_traceback(
951
+ logger, "Sink failure traceback:", sink_error, redactor
849
952
  )
850
953
  raise
851
954
  finally:
@@ -855,17 +958,19 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
855
958
  worker_pool.shutdown(wait=True)
856
959
 
857
960
  except Exception as e:
858
- logger.debug("Traceback for %s failure:", args.command, exc_info=True)
961
+ _log_debug_traceback(logger, f"Traceback for {args.command} failure:", e, redactor)
859
962
  if _is_timeout_error(e):
860
- logger.warning("SCAN TIMED OUT (source unreachable): %s", e)
963
+ logger.warning("SCAN TIMED OUT (source unreachable): %s", _safe_text(e, redactor))
861
964
  return
862
- logger.error("SCAN FAILED: %s", e)
965
+ logger.error("SCAN FAILED: %s", _safe_text(e, redactor))
863
966
  sys.exit(1)
864
967
  finally:
865
968
  source.cleanup()
866
969
  except Exception as e:
867
- logger.debug("Traceback for fatal error:", exc_info=True)
868
- logger.error("FATAL: %s", e)
970
+ # No recipe redactor here (failure happened before the recipe was
971
+ # usable): generic patterns still hide credential-shaped values.
972
+ _log_debug_traceback(logger, "Traceback for fatal error:", e, None)
973
+ logger.error("FATAL: %s", _safe_text(e, None))
869
974
  sys.exit(1)
870
975
 
871
976
 
@@ -971,7 +1076,8 @@ def run_evaluate_file_command(args: argparse.Namespace) -> None:
971
1076
  output["detector_errors"] = runner.detector_errors
972
1077
  print(json.dumps(_sanitize_for_json(output), ensure_ascii=False))
973
1078
  except Exception as e:
974
- logger.error("File evaluation failed: %s", e, exc_info=True)
1079
+ logger.error("File evaluation failed: %s", _safe_text(e, None))
1080
+ _log_debug_traceback(logger, "File evaluation traceback:", e, None)
975
1081
  sys.exit(1)
976
1082
 
977
1083
 
@@ -1153,8 +1259,16 @@ def main() -> None:
1153
1259
  try:
1154
1260
  validate_input(recipe, source_type)
1155
1261
  logger.info("Recipe is valid")
1262
+ except RecipeValidationError as e:
1263
+ # Already log-safe by construction (masked values hidden); the extra
1264
+ # pass only guards credential-shaped patterns in driver text.
1265
+ logger.error("%s", _safe_text(e, Redactor.from_recipe(recipe)))
1266
+ sys.exit(1)
1156
1267
  except Exception as e:
1157
- logger.error("Recipe validation failed: %s", e)
1268
+ logger.error(
1269
+ "Recipe validation failed: %s",
1270
+ _safe_text(e, Redactor.from_recipe(recipe)),
1271
+ )
1158
1272
  sys.exit(1)
1159
1273
 
1160
1274
  run_command(args, recipe)
@@ -0,0 +1,37 @@
1
+ """Keep secret values out of everything a human or a log file will see.
2
+
3
+ Re-exported from :mod:`src.utils.redaction` (the canonical home, kept in
4
+ ``utils`` so recipe validation can use it without importing the notebook
5
+ package, which would be a circular import). Import from either location;
6
+ new code outside the notebook runtime should prefer ``utils.redaction``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from ..utils.redaction import (
12
+ ENCODED_SECRET_MIN_LENGTH,
13
+ ENCRYPTED_CONFIG_PATHS,
14
+ MAX_LOG_ID_CHARS,
15
+ MIN_REDACTABLE_LENGTH,
16
+ PLACEHOLDER,
17
+ RedactingStream,
18
+ Redactor,
19
+ redact_generic,
20
+ redact_with_recipe,
21
+ sanitize_for_logging,
22
+ short_id_for_log,
23
+ )
24
+
25
+ __all__ = [
26
+ "ENCODED_SECRET_MIN_LENGTH",
27
+ "ENCRYPTED_CONFIG_PATHS",
28
+ "MAX_LOG_ID_CHARS",
29
+ "MIN_REDACTABLE_LENGTH",
30
+ "PLACEHOLDER",
31
+ "RedactingStream",
32
+ "Redactor",
33
+ "redact_generic",
34
+ "redact_with_recipe",
35
+ "sanitize_for_logging",
36
+ "short_id_for_log",
37
+ ]
@@ -394,7 +394,12 @@ class RestOutputSink:
394
394
  if not self._runner_id:
395
395
  return
396
396
 
397
- error_message = f"{type(error).__name__}: {error}"
397
+ # Defense in depth: the caller already redacts known recipe values,
398
+ # but the sink is the last point before the message is stored on the
399
+ # run record, so credential-shaped patterns are hidden here as well.
400
+ from ..utils.redaction import redact_generic
401
+
402
+ error_message = redact_generic(f"{type(error).__name__}: {error}")
398
403
  try:
399
404
  payload = UpdateRunnerStatusRequest(status="ERROR", error_message=error_message)
400
405
  self._request_json(
@@ -405,8 +410,8 @@ class RestOutputSink:
405
410
  except Exception as update_error:
406
411
  logger.warning(
407
412
  "Failed to update runner status to ERROR after failure %s: %s",
408
- error,
409
- update_error,
413
+ redact_generic(str(error)),
414
+ redact_generic(str(update_error)),
410
415
  )
411
416
 
412
417
  async def emit_edges(self, edges: list[IngestEdge]) -> dict[str, Any]:
@@ -451,11 +456,14 @@ class RestOutputSink:
451
456
  # Edge emission is best-effort: log and continue — but a chunk
452
457
  # the API rejected is lost, not dropped, so the run is
453
458
  # downgraded rather than reporting an unqualified success.
459
+ # Redacted: these messages land on the run record.
460
+ from ..utils.redaction import redact_generic as _redact_generic
461
+
454
462
  totals["lost"] += len(chunk)
455
- message = f"{type(exc).__name__}: {exc}"
463
+ message = _redact_generic(f"{type(exc).__name__}: {exc}")
456
464
  if message not in errors:
457
465
  errors.append(message)
458
- logger.warning("Failed to emit edges to graph: %s", exc)
466
+ logger.warning("Failed to emit edges to graph: %s", _redact_generic(str(exc)))
459
467
  if errors:
460
468
  totals["errors"] = errors
461
469
  return totals
@@ -562,7 +570,9 @@ class RestOutputSink:
562
570
  )
563
571
 
564
572
  if response.status_code >= 400:
565
- body_preview = response.text.strip()[:400]
573
+ from ..utils.redaction import redact_generic as _redact_generic2
574
+
575
+ body_preview = _redact_generic2(response.text.strip()[:400])
566
576
  raise RuntimeError(
567
577
  f"REST output request failed ({method} {url}): "
568
578
  f"{response.status_code} {response.reason} {body_preview}"
@@ -25,6 +25,7 @@ from ..models.generated_single_asset_scan_results import (
25
25
  )
26
26
  from ..sources.base import BaseSource
27
27
  from ..utils.file_parser import TextExtractionCoverageError, resolve_mime_type
28
+ from ..utils.redaction import Redactor, redact_generic, short_id_for_log
28
29
  from .content_provider import ContentProvider
29
30
  from .detector_breaker import DetectorBreaker
30
31
  from .text_artifact import TextArtifact
@@ -132,6 +133,10 @@ class DetectorPipeline:
132
133
  self.detectors = detectors
133
134
  self.source = source
134
135
  self.runner_id = runner_id
136
+ # Built once: candidate ids logged per asset below can embed
137
+ # credential material (a reversible base64 asset hash over a secret
138
+ # bearing notebook id), so every id rendered to logs goes through it.
139
+ self._redactor = Redactor.from_recipe(getattr(source, "recipe", None))
135
140
  self._worker_pool = worker_pool
136
141
  self._detector_info: dict[int, _DetectorInfo] = {}
137
142
  if content_provider is not None:
@@ -696,10 +701,16 @@ class DetectorPipeline:
696
701
  continue
697
702
  candidate_ids.append(value)
698
703
 
704
+ # Candidate ids are logged redacted and shortened: the second
705
+ # candidate is the asset hash, which is reversible base64 over the
706
+ # connector's raw id -- and a notebook that builds ids from secrets
707
+ # turns this line into a credential leak. The head still identifies
708
+ # the document, which is all debugging needs.
699
709
  logger.info(
700
- "_iter_text_content_pages(%s): trying candidates %s",
710
+ "_iter_text_content_pages(%s): trying %d candidate(s) %s",
701
711
  asset.name,
702
- candidate_ids,
712
+ len(candidate_ids),
713
+ [short_id_for_log(item, self._redactor) for item in candidate_ids],
703
714
  )
704
715
 
705
716
  for candidate_id in candidate_ids:
@@ -714,7 +725,14 @@ class DetectorPipeline:
714
725
  # Text extraction failure must not discard independent link or
715
726
  # binary detector results. The structured empty-text coverage
716
727
  # signal still records that this asset was not embedded.
717
- message = f"Text extraction failed for {candidate_id}: {exc}"
728
+ # Redacted like the candidates above: this message is stored
729
+ # on the run's scan stats.
730
+ safe_cause = redact_generic(self._redactor.redact(str(exc)))
731
+ message = (
732
+ f"Text extraction failed for "
733
+ f"{short_id_for_log(candidate_id, self._redactor)}: "
734
+ f"{safe_cause}"
735
+ )
718
736
  logger.warning(message)
719
737
  self._text_extraction_errors.setdefault(str(asset.hash), []).append(message)
720
738
  self._text_extraction_statuses[str(asset.hash)] = TextExtractionStatus(
@@ -8,6 +8,7 @@ from collections.abc import AsyncGenerator, Mapping
8
8
 
9
9
  from ..models.generated_single_asset_scan_results import DetectionResult, SingleAssetScanResults
10
10
  from ..sources.base import BaseSource
11
+ from ..utils.redaction import Redactor, short_id_for_log
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
@@ -24,6 +25,10 @@ class ParsedContentProvider:
24
25
 
25
26
  def __init__(self, source: BaseSource) -> None:
26
27
  self._source = source
28
+ # asset_id here is often the reversible asset hash (see
29
+ # detector_pipeline._iter_text_content_pages), so it is never
30
+ # logged raw.
31
+ self._redactor = Redactor.from_recipe(getattr(source, "recipe", None))
27
32
 
28
33
  async def fetch_text_pages(self, asset_id: str) -> AsyncGenerator[str, None]:
29
34
  saw_text = False
@@ -43,19 +48,22 @@ class ParsedContentProvider:
43
48
  if isinstance(pages_processed, set) and asset_id in pages_processed:
44
49
  logger.info(
45
50
  "fetch_text_pages(%s): source already processed, skipping fallback",
46
- asset_id,
51
+ short_id_for_log(asset_id, self._redactor),
47
52
  )
48
53
  return
49
54
 
50
55
  result = await self._source.fetch_content_bytes(asset_id)
51
56
  if result is None:
52
- logger.info("fetch_text_pages(%s): fetch_content_bytes returned None", asset_id)
57
+ logger.info(
58
+ "fetch_text_pages(%s): fetch_content_bytes returned None",
59
+ short_id_for_log(asset_id, self._redactor),
60
+ )
53
61
  return
54
62
 
55
63
  raw_bytes, mime = result
56
64
  logger.info(
57
65
  "fetch_text_pages(%s): fallback iter_asset_pages path (%s, %d bytes)",
58
- asset_id,
66
+ short_id_for_log(asset_id, self._redactor),
59
67
  mime,
60
68
  len(raw_bytes),
61
69
  )