ciphergate-cli 0.14.0__tar.gz → 0.15.0__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.
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/CHANGELOG.md +18 -1
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/PKG-INFO +4 -2
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/README.md +3 -1
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/docs/security-and-privacy.md +2 -1
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/__init__.py +1 -1
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/models/report.py +6 -2
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/batch.py +6 -2
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/reporting/next_steps.py +28 -9
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/reporting/terminal.py +4 -10
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_batch.py +13 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_cli_e2e.py +14 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_next_steps.py +33 -1
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/.gitignore +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/LICENSE +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/NOTICE +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/SECURITY.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/docs/architecture.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/docs/claude-code-tooling.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/docs/detector-coverage.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/docs/examples.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/docs/limitations.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/docs/policy-schema.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/docs/threat-model.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/expected/sample.dev_local_dev.csv +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/expected/sample.dev_local_dev.json +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/expected/sample.dev_local_dev.jsonl +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/expected/sample.dev_local_dev.log +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/sample.csv +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/sample.json +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/sample.jsonl +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/sample.log +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/dev_local.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/developer_logs.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/external_ai.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/financial_template.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/healthcare_template.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/local_debug.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/multi_environment.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/prd_external_ai.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/stg_test.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/policies/test_dataset.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/pyproject.toml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/scripts/benchmark/README.md +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/__main__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/cli.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/config.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/constants.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/base.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/custom_regex.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/dates.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/email.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/field_names.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/iban.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/ip_address.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/mac_address.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/network.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/payment_card.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/phone.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/presidio_ner.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/regex_safety.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/registry.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/secrets.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/detectors/ssn.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/errors.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/models/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/models/context.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/models/finding.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/models/policy.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/atomic_output.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/inspector.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/paths.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/planner.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/rescan.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/sanitizer.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/stdio.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/pipeline/streams.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/policy/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/policy/context.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/policy/engine.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/policy/loader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/policy/precedence.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/policy/templates.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/policy/validator.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/py.typed +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/accesslog_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/base.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/csv_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/json_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/jsonl_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/jsonlog_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/line_structure.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/logfmt_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/stacktrace_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/syslog_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/readers/text_reader.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/reporting/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/reporting/audit.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/reporting/safe_text.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/reporting/serializer.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/templates/starter_policy.yml +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/transforms/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/transforms/date_shift.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/transforms/generalize.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/transforms/keys.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/transforms/pseudonymize.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/transforms/redact.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/transforms/registry.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/src/ciphergate/transforms/replace.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/conftest.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_api_batch_streams.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_atomic_output.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_domain_templates.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_environment_matrix.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_pipelines.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_presidio_real.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_stdio.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/regression/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/regression/test_known_cases.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/security/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/security/test_key_file.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/security/test_no_network.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/security/test_no_raw_value_leaks.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/security/test_path_safety.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/security/test_policy_fail_closed.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/security/test_review_findings.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/tooling/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/tooling/test_benchmark.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/tooling/test_ci_supply_chain.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/tooling/test_claude_tooling.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/tooling/test_git_tooling.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/tooling/test_publish_workflow.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/__init__.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_accesslog.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_detectors.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_jsonlog.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_logfmt.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_policy_engine.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_policy_precedence.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_policy_templates.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_policy_validation.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_presidio_ner.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_properties.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_readers.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_stacktrace.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_syslog.py +0 -0
- {ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/unit/test_transforms.py +0 -0
|
@@ -6,6 +6,22 @@ versioning: [PEP 440](https://peps.python.org/pep-0440/) with SemVer meaning (pr
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.15.0] - 2026-11-02
|
|
10
|
+
|
|
11
|
+
JSON reports now include the next-step hints, so CI pipelines can act on them. The field is new;
|
|
12
|
+
nothing existing changes.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- JSON reports (single-file and directory) include `next_steps`: the hints from the terminal
|
|
17
|
+
summary as `{"code", "message"}` pairs, empty for a successful run. The field is new and
|
|
18
|
+
optional to read; the schema names are unchanged.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- The `batch_empty` error for a directory with no recognised files now mentions
|
|
23
|
+
`--extension-format`.
|
|
24
|
+
|
|
9
25
|
## [0.14.0] - 2026-11-02
|
|
10
26
|
|
|
11
27
|
Adds `--extension-format` for mixed directories and tests the optional NER detector against a
|
|
@@ -379,7 +395,8 @@ Pre-release (alpha). Licensed under Apache-2.0. Not a final release: the securit
|
|
|
379
395
|
See `docs/limitations.md`. Deferred: optional NER/Presidio detectors, stdin/stdout streaming,
|
|
380
396
|
directory processing, structured log parsers, key files, reversible token vault, lossy decoding.
|
|
381
397
|
|
|
382
|
-
[Unreleased]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.
|
|
398
|
+
[Unreleased]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.15.0...HEAD
|
|
399
|
+
[0.15.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.14.0...v0.15.0
|
|
383
400
|
[0.14.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.13.0...v0.14.0
|
|
384
401
|
[0.13.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.12.0...v0.13.0
|
|
385
402
|
[0.12.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.11.0...v0.12.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ciphergate-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: Local-first, environment- and destination-aware inspection and sanitization of logs and test datasets.
|
|
5
5
|
Project-URL: Homepage, https://github.com/aashishkumar-tech/ciphergate
|
|
6
6
|
Project-URL: Source, https://github.com/aashishkumar-tech/ciphergate
|
|
@@ -50,7 +50,7 @@ vendor ticket. CipherGate replaces that with a local, explainable, scriptable ga
|
|
|
50
50
|
inspect -> plan -> sanitize --apply -> rescan
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
> **Status: 0.
|
|
53
|
+
> **Status: 0.15.0 (alpha).** No detector finds every sensitive value. A passing run means the
|
|
54
54
|
> configured processing completed under the selected policy; it is **not** proof that the output
|
|
55
55
|
> contains no sensitive information, and it is not a compliance (GDPR, HIPAA, PCI DSS, ...) or
|
|
56
56
|
> anonymization guarantee. Policy templates must be reviewed before use.
|
|
@@ -89,6 +89,8 @@ inspect -> plan -> sanitize --apply -> rescan
|
|
|
89
89
|
`--force`, input/output alias protection, input-change detection, cleanup on failure/Ctrl-C.
|
|
90
90
|
- **Reports:** counts by category/detector/action/reason/rule, safe locations, effective context
|
|
91
91
|
and its source, gates, key *status* (never key material), no raw values or fingerprints.
|
|
92
|
+
A failed run's report lists `next_steps` as `{"code", "message"}` pairs, the same hints the
|
|
93
|
+
terminal prints as `next:` lines, so CI can act on them.
|
|
92
94
|
|
|
93
95
|
## Install
|
|
94
96
|
|
|
@@ -11,7 +11,7 @@ vendor ticket. CipherGate replaces that with a local, explainable, scriptable ga
|
|
|
11
11
|
inspect -> plan -> sanitize --apply -> rescan
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
> **Status: 0.
|
|
14
|
+
> **Status: 0.15.0 (alpha).** No detector finds every sensitive value. A passing run means the
|
|
15
15
|
> configured processing completed under the selected policy; it is **not** proof that the output
|
|
16
16
|
> contains no sensitive information, and it is not a compliance (GDPR, HIPAA, PCI DSS, ...) or
|
|
17
17
|
> anonymization guarantee. Policy templates must be reviewed before use.
|
|
@@ -50,6 +50,8 @@ inspect -> plan -> sanitize --apply -> rescan
|
|
|
50
50
|
`--force`, input/output alias protection, input-change detection, cleanup on failure/Ctrl-C.
|
|
51
51
|
- **Reports:** counts by category/detector/action/reason/rule, safe locations, effective context
|
|
52
52
|
and its source, gates, key *status* (never key material), no raw values or fingerprints.
|
|
53
|
+
A failed run's report lists `next_steps` as `{"code", "message"}` pairs, the same hints the
|
|
54
|
+
terminal prints as `next:` lines, so CI can act on them.
|
|
53
55
|
|
|
54
56
|
## Install
|
|
55
57
|
|
|
@@ -67,7 +67,8 @@ Reports contain: run ID, UTC time, version, input basename (or `<redacted>`), fo
|
|
|
67
67
|
record counts, policy name/version/SHA-256 digest of the policy file, effective context with
|
|
68
68
|
sources, gates, counts by category/detector/action/band/reason/rule, safe locations (record,
|
|
69
69
|
line, field path, character span), rule IDs and reason codes, key status, changes, parse error
|
|
70
|
-
line numbers and codes, rescan summary, warnings
|
|
70
|
+
line numbers and codes, rescan summary, warnings, `next_steps` (fixed hint text with a reason
|
|
71
|
+
code, for failed runs only) and a disclaimer. They never contain values,
|
|
71
72
|
snippets, keys, mappings, or hashes of values. Field paths whose segments look like sensitive
|
|
72
73
|
values are replaced with `<redacted-key>`; control and bidi characters are escaped.
|
|
73
74
|
|
|
@@ -24,7 +24,7 @@ Supported public API (everything else is internal and may change without notice)
|
|
|
24
24
|
rescan_directory(root, policy, context=None, *, recursive=False, strict=None) -> BatchReport
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
-
__version__ = "0.
|
|
27
|
+
__version__ = "0.15.0"
|
|
28
28
|
|
|
29
29
|
from ciphergate.config import RuntimeSettings
|
|
30
30
|
from ciphergate.constants import (
|
|
@@ -11,6 +11,7 @@ from enum import StrEnum
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from ciphergate.constants import DISCLAIMER, REPORT_SCHEMA, ExitCode
|
|
14
|
+
from ciphergate.reporting.next_steps import next_step_items
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class RunStatus(StrEnum):
|
|
@@ -89,7 +90,7 @@ class RunReport:
|
|
|
89
90
|
return self.exit_code == 0
|
|
90
91
|
|
|
91
92
|
def to_dict(self) -> dict[str, Any]:
|
|
92
|
-
|
|
93
|
+
data: dict[str, Any] = {
|
|
93
94
|
"schema": REPORT_SCHEMA,
|
|
94
95
|
"command": self.command,
|
|
95
96
|
"status": str(self.status),
|
|
@@ -111,5 +112,8 @@ class RunReport:
|
|
|
111
112
|
"output": self.output,
|
|
112
113
|
"rescan": self.rescan.to_dict() if self.rescan else None,
|
|
113
114
|
"warnings": self.warnings,
|
|
114
|
-
"disclaimer": DISCLAIMER,
|
|
115
115
|
}
|
|
116
|
+
# Fixed hint text chosen by reason code: never contains input values.
|
|
117
|
+
data["next_steps"] = next_step_items(data)
|
|
118
|
+
data["disclaimer"] = DISCLAIMER
|
|
119
|
+
return data
|
|
@@ -52,6 +52,7 @@ from ciphergate.pipeline.sanitizer import (
|
|
|
52
52
|
from ciphergate.policy.validator import Policy
|
|
53
53
|
from ciphergate.readers import EXTENSION_FORMATS
|
|
54
54
|
from ciphergate.reporting.audit import new_run_id, utc_now
|
|
55
|
+
from ciphergate.reporting.next_steps import merge_next_steps
|
|
55
56
|
|
|
56
57
|
logger = logging.getLogger("ciphergate")
|
|
57
58
|
|
|
@@ -136,6 +137,7 @@ class BatchReport:
|
|
|
136
137
|
|
|
137
138
|
def to_dict(self) -> dict[str, Any]:
|
|
138
139
|
first = next((r.report for r in self.files if r.report is not None), None)
|
|
140
|
+
files = [r.to_dict() for r in self.files]
|
|
139
141
|
return {
|
|
140
142
|
"schema": BATCH_REPORT_SCHEMA,
|
|
141
143
|
"command": self.command,
|
|
@@ -148,10 +150,11 @@ class BatchReport:
|
|
|
148
150
|
"policy": first.policy if first is not None else None,
|
|
149
151
|
"context": first.context if first is not None else None,
|
|
150
152
|
"summary": self.summary(),
|
|
151
|
-
"files":
|
|
153
|
+
"files": files,
|
|
152
154
|
"skipped": self.skipped,
|
|
153
155
|
"output": self.output,
|
|
154
156
|
"warnings": self.warnings,
|
|
157
|
+
"next_steps": merge_next_steps([f["report"] for f in files if "report" in f]),
|
|
155
158
|
"disclaimer": DISCLAIMER,
|
|
156
159
|
}
|
|
157
160
|
|
|
@@ -324,7 +327,8 @@ def coverage_warnings(
|
|
|
324
327
|
def _require_files(discovery: Discovery) -> None:
|
|
325
328
|
if not discovery.files:
|
|
326
329
|
raise InputFormatError(
|
|
327
|
-
"no supported input files found in the directory (see --format, --
|
|
330
|
+
"no supported input files found in the directory (see --extension-format, --format, "
|
|
331
|
+
"--recursive)",
|
|
328
332
|
code="batch_empty",
|
|
329
333
|
)
|
|
330
334
|
|
|
@@ -83,7 +83,12 @@ _KEY_HINT = (
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
def next_steps(report: dict[str, Any]) -> list[str]:
|
|
86
|
-
"""
|
|
86
|
+
"""Hint messages for a run that ended with a non-zero exit code; empty otherwise."""
|
|
87
|
+
return [step["message"] for step in next_step_items(report)]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def next_step_items(report: dict[str, Any]) -> list[dict[str, str]]:
|
|
91
|
+
"""``{"code", "message"}`` pairs for a failed run; ``code`` is a stable reason code."""
|
|
87
92
|
if not report.get("exit_code"):
|
|
88
93
|
return []
|
|
89
94
|
codes = [gate["reason_code"] for gate in report.get("gates", [])]
|
|
@@ -93,14 +98,28 @@ def next_steps(report: dict[str, Any]) -> list[str]:
|
|
|
93
98
|
rescan = report.get("rescan")
|
|
94
99
|
if rescan and rescan.get("failed"):
|
|
95
100
|
codes.append(ReasonCode.RESIDUAL_FINDING)
|
|
96
|
-
|
|
101
|
+
items: list[dict[str, str]] = []
|
|
97
102
|
for code in codes:
|
|
98
103
|
hint = _HINTS.get(code)
|
|
99
|
-
if hint
|
|
100
|
-
|
|
104
|
+
if hint:
|
|
105
|
+
_add(items, str(code), hint)
|
|
101
106
|
for key in report.get("keys", []):
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if
|
|
105
|
-
|
|
106
|
-
return
|
|
107
|
+
status = key.get("status")
|
|
108
|
+
if status != "available":
|
|
109
|
+
code = ReasonCode.KEY_MISSING if status == "missing" else ReasonCode.KEY_INVALID
|
|
110
|
+
_add(items, str(code), _KEY_HINT.format(env=key["key_env"]))
|
|
111
|
+
return items
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def merge_next_steps(reports: list[dict[str, Any]]) -> list[dict[str, str]]:
|
|
115
|
+
"""Combine the hints of several reports (a directory run), first occurrence first."""
|
|
116
|
+
items: list[dict[str, str]] = []
|
|
117
|
+
for report in reports:
|
|
118
|
+
for step in report.get("next_steps") or next_step_items(report):
|
|
119
|
+
_add(items, step["code"], step["message"])
|
|
120
|
+
return items
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _add(items: list[dict[str, str]], code: str, message: str) -> None:
|
|
124
|
+
if all(step["message"] != message for step in items):
|
|
125
|
+
items.append({"code": code, "message": message})
|
|
@@ -7,7 +7,6 @@ from typing import Any
|
|
|
7
7
|
from ciphergate.constants import DISCLAIMER, RESCAN_LIMIT
|
|
8
8
|
from ciphergate.models.report import RunReport
|
|
9
9
|
from ciphergate.pipeline.batch import BatchReport
|
|
10
|
-
from ciphergate.reporting.next_steps import next_steps
|
|
11
10
|
from ciphergate.reporting.safe_text import escape_control
|
|
12
11
|
|
|
13
12
|
SUMMARY_ITEMS = 15
|
|
@@ -120,8 +119,8 @@ def render_summary(report: RunReport, *, verbose: bool = False) -> str:
|
|
|
120
119
|
lines.append(f" output: not written ({out['withheld_reason']})")
|
|
121
120
|
for warning in d["warnings"]:
|
|
122
121
|
lines.append(f" warning: {escape_control(warning)}")
|
|
123
|
-
for
|
|
124
|
-
lines.append(f" next: {
|
|
122
|
+
for step in d["next_steps"]:
|
|
123
|
+
lines.append(f" next: {step['message']}")
|
|
125
124
|
lines.append(f" note: {DISCLAIMER}")
|
|
126
125
|
return "\n".join(lines) + "\n"
|
|
127
126
|
|
|
@@ -161,13 +160,8 @@ def render_batch_summary(report: BatchReport, *, verbose: bool = False) -> str:
|
|
|
161
160
|
lines.append(f" output: not written ({out.get('withheld_reason')})")
|
|
162
161
|
for warning in d["warnings"]:
|
|
163
162
|
lines.append(f" warning: {escape_control(warning)}")
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
for hint in next_steps(entry.get("report") or {}):
|
|
167
|
-
if hint not in hints:
|
|
168
|
-
hints.append(hint)
|
|
169
|
-
for hint in hints:
|
|
170
|
-
lines.append(f" next: {hint}")
|
|
163
|
+
for step in d["next_steps"]:
|
|
164
|
+
lines.append(f" next: {step['message']}")
|
|
171
165
|
lines.append(" note: per-file details are in the JSON report (--report).")
|
|
172
166
|
lines.append(f" note: {DISCLAIMER}")
|
|
173
167
|
return "\n".join(lines) + "\n"
|
|
@@ -405,6 +405,11 @@ def test_directory_summary_carries_next_steps(tmp_path: Path) -> None:
|
|
|
405
405
|
(root / "app.log").write_text(SECRET_LINE, encoding="utf-8")
|
|
406
406
|
r = invoke("inspect", root, "--policy", POLICIES / "developer_logs.yml")
|
|
407
407
|
assert r.exit_code == 1 and "next: pass --environment" in r.stdout
|
|
408
|
+
report = tmp_path / "report.json"
|
|
409
|
+
invoke("inspect", root, "--policy", POLICIES / "developer_logs.yml", "--report", report)
|
|
410
|
+
data = _report(report)
|
|
411
|
+
assert "environment_required_but_missing" in [s["code"] for s in data["next_steps"]]
|
|
412
|
+
assert data["next_steps"] == data["files"][0]["report"]["next_steps"]
|
|
408
413
|
|
|
409
414
|
|
|
410
415
|
# --------------------------------------------------------------------------- --extension-format
|
|
@@ -496,3 +501,11 @@ def test_sanitize_directory_with_extension_format(tmp_path: Path, env) -> None:
|
|
|
496
501
|
assert r.exit_code == 0, r.stdout
|
|
497
502
|
assert sorted(p.name for p in out.iterdir()) == ["app.log", "users.dat"]
|
|
498
503
|
assert "alice@example.com" not in (out / "users.dat").read_text(encoding="utf-8")
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def test_empty_directory_error_mentions_extension_format(tmp_path: Path, env) -> None:
|
|
507
|
+
root = tmp_path / "in"
|
|
508
|
+
root.mkdir()
|
|
509
|
+
(root / "users.dat").write_text("email\nx\n", encoding="utf-8")
|
|
510
|
+
r = invoke("inspect", root, *PRD_EXTERNAL, env=env)
|
|
511
|
+
assert r.exit_code == 4 and "--extension-format" in r.stdout + r.stderr
|
|
@@ -206,6 +206,20 @@ def test_blocked_run_says_what_to_do_next(tmp_path: Path) -> None:
|
|
|
206
206
|
assert "next: pass --environment" in r.stdout and "next: pass --destination" in r.stdout
|
|
207
207
|
|
|
208
208
|
|
|
209
|
+
def test_json_report_lists_next_steps(tmp_path: Path) -> None:
|
|
210
|
+
src = tmp_path / "raw.csv"
|
|
211
|
+
shutil.copy(EXAMPLES / "sample.csv", src)
|
|
212
|
+
report = tmp_path / "report.json"
|
|
213
|
+
r = invoke("inspect", src, "--policy", POLICIES / "developer_logs.yml", "--report", report)
|
|
214
|
+
assert r.exit_code == 1
|
|
215
|
+
steps = json.loads(report.read_text(encoding="utf-8"))["next_steps"]
|
|
216
|
+
codes = [s["code"] for s in steps]
|
|
217
|
+
assert codes[:2] == ["environment_required_but_missing", "destination_required_but_missing"]
|
|
218
|
+
assert len(codes) == len(set(codes))
|
|
219
|
+
for step in steps:
|
|
220
|
+
assert f"next: {step['message']}" in r.stdout
|
|
221
|
+
|
|
222
|
+
|
|
209
223
|
def test_successful_run_has_no_next_steps(tmp_path: Path) -> None:
|
|
210
224
|
clean = tmp_path / "clean.log"
|
|
211
225
|
clean.write_text("service started\n", encoding="utf-8")
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
from ciphergate.constants import ReasonCode
|
|
6
|
-
from ciphergate.reporting.next_steps import _HINTS, next_steps
|
|
6
|
+
from ciphergate.reporting.next_steps import _HINTS, merge_next_steps, next_step_items, next_steps
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def report(**extra: Any) -> dict[str, Any]:
|
|
@@ -60,3 +60,35 @@ def test_hints_are_fixed_text() -> None:
|
|
|
60
60
|
# Hints come from a fixed table, so they can never carry input values.
|
|
61
61
|
unknown = {"by_reason_code": {"explicit_policy_match": 3, "not_a_code": 1}}
|
|
62
62
|
assert next_steps(report(summary=unknown)) == []
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_items_carry_stable_codes() -> None:
|
|
66
|
+
items = next_step_items(
|
|
67
|
+
report(
|
|
68
|
+
gates=[{"reason_code": ReasonCode.ENVIRONMENT_REQUIRED_BUT_MISSING}],
|
|
69
|
+
keys=[
|
|
70
|
+
{"key_env": "CIPHERGATE_HMAC_KEY", "status": "missing"},
|
|
71
|
+
{"key_env": "OTHER_KEY", "status": "too_short"},
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
assert [i["code"] for i in items] == [
|
|
76
|
+
"environment_required_but_missing",
|
|
77
|
+
"key_missing",
|
|
78
|
+
"key_invalid",
|
|
79
|
+
]
|
|
80
|
+
assert all(set(i) == {"code", "message"} for i in items)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_merge_keeps_first_occurrence_order() -> None:
|
|
84
|
+
a = report(gates=[{"reason_code": ReasonCode.DESTINATION_REQUIRED_BUT_MISSING}])
|
|
85
|
+
b = report(
|
|
86
|
+
gates=[
|
|
87
|
+
{"reason_code": ReasonCode.ENVIRONMENT_REQUIRED_BUT_MISSING},
|
|
88
|
+
{"reason_code": ReasonCode.DESTINATION_REQUIRED_BUT_MISSING},
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
assert [i["code"] for i in merge_next_steps([a, b])] == [
|
|
92
|
+
"destination_required_but_missing",
|
|
93
|
+
"environment_required_but_missing",
|
|
94
|
+
]
|
|
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
|
{ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/examples/expected/sample.dev_local_dev.jsonl
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
|
|
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
|
|
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
|
|
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
|
|
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
|
{ciphergate_cli-0.14.0 → ciphergate_cli-0.15.0}/tests/integration/test_environment_matrix.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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|