ciphergate-cli 0.15.0__tar.gz → 0.17.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.15.0 → ciphergate_cli-0.17.0}/CHANGELOG.md +61 -1
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/PKG-INFO +77 -5
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/README.md +76 -4
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/docs/architecture.md +2 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/docs/detector-coverage.md +25 -2
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/docs/limitations.md +17 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/docs/policy-schema.md +72 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/docs/security-and-privacy.md +15 -0
- ciphergate_cli-0.17.0/policies/hospital_us_override.yml +46 -0
- ciphergate_cli-0.17.0/policies/packs/eu_gdpr.yml +78 -0
- ciphergate_cli-0.17.0/policies/packs/healthcare_baseline.yml +109 -0
- ciphergate_cli-0.17.0/policies/packs/india_dpdp.yml +97 -0
- ciphergate_cli-0.17.0/policies/packs/us_hipaa.yml +80 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/scripts/benchmark/README.md +8 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/__init__.py +1 -1
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/cli.py +85 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/policy.py +5 -0
- ciphergate_cli-0.17.0/src/ciphergate/pipeline/review.py +528 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/sanitizer.py +14 -1
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/engine.py +5 -0
- ciphergate_cli-0.17.0/src/ciphergate/policy/layers.py +508 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/loader.py +11 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/templates.py +1 -12
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/validator.py +33 -2
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/audit.py +14 -0
- ciphergate_cli-0.17.0/src/ciphergate/reporting/review_page.py +871 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/safe_text.py +10 -3
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/terminal.py +2 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/registry.py +5 -3
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_cli_e2e.py +28 -1
- ciphergate_cli-0.17.0/tests/integration/test_review.py +255 -0
- ciphergate_cli-0.17.0/tests/security/test_review_page.py +48 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/tooling/test_benchmark.py +55 -0
- ciphergate_cli-0.17.0/tests/unit/test_policy_layers.py +438 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/.gitignore +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/LICENSE +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/NOTICE +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/SECURITY.md +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/docs/claude-code-tooling.md +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/docs/examples.md +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/docs/threat-model.md +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/examples/expected/sample.dev_local_dev.csv +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/examples/expected/sample.dev_local_dev.json +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/examples/expected/sample.dev_local_dev.jsonl +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/examples/expected/sample.dev_local_dev.log +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/examples/sample.csv +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/examples/sample.json +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/examples/sample.jsonl +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/examples/sample.log +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/dev_local.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/developer_logs.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/external_ai.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/financial_template.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/healthcare_template.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/local_debug.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/multi_environment.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/prd_external_ai.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/stg_test.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/policies/test_dataset.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/pyproject.toml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/__main__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/config.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/constants.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/base.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/custom_regex.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/dates.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/email.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/field_names.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/iban.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/ip_address.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/mac_address.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/network.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/payment_card.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/phone.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/presidio_ner.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/regex_safety.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/registry.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/secrets.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/ssn.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/errors.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/context.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/finding.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/report.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/atomic_output.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/batch.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/inspector.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/paths.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/planner.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/rescan.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/stdio.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/streams.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/context.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/precedence.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/py.typed +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/accesslog_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/base.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/csv_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/json_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/jsonl_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/jsonlog_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/line_structure.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/logfmt_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/stacktrace_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/syslog_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/text_reader.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/next_steps.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/serializer.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/templates/starter_policy.yml +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/date_shift.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/generalize.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/keys.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/pseudonymize.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/redact.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/replace.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/conftest.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_api_batch_streams.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_atomic_output.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_batch.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_domain_templates.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_environment_matrix.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_pipelines.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_presidio_real.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/integration/test_stdio.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/regression/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/regression/test_known_cases.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/security/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/security/test_key_file.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/security/test_no_network.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/security/test_no_raw_value_leaks.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/security/test_path_safety.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/security/test_policy_fail_closed.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/security/test_review_findings.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/tooling/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/tooling/test_ci_supply_chain.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/tooling/test_claude_tooling.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/tooling/test_git_tooling.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/tooling/test_publish_workflow.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/__init__.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_accesslog.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_detectors.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_jsonlog.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_logfmt.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_next_steps.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_policy_engine.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_policy_precedence.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_policy_templates.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_policy_validation.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_presidio_ner.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_properties.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_readers.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_stacktrace.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_syslog.py +0 -0
- {ciphergate_cli-0.15.0 → ciphergate_cli-0.17.0}/tests/unit/test_transforms.py +0 -0
|
@@ -6,6 +6,64 @@ versioning: [PEP 440](https://peps.python.org/pep-0440/) with SemVer meaning (pr
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.17.0] - 2026-11-02
|
|
10
|
+
|
|
11
|
+
Policies can now be built in layers. A shipped baseline pack, a jurisdiction pack (US, EU or
|
|
12
|
+
India) and your own small file merge into one policy, and a later layer cannot quietly weaken
|
|
13
|
+
an earlier one. The packs are templates requiring review, not legal or compliance advice.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Policy layering: `extends: [pack, ...]` builds one effective policy from shipped packs plus
|
|
18
|
+
your own file. Layers merge so that a later layer can never quietly weaken an earlier one.
|
|
19
|
+
Conflicting context pins, weaker settings or requirements, disabled detectors, and rules or
|
|
20
|
+
defaults that would override an earlier block, review or transform with something weaker
|
|
21
|
+
fail validation. A rule may weaken an earlier rule or default only by naming it in the new
|
|
22
|
+
`relaxes` field; this is reported as a warning. Earlier `hard` and `guardrail` rules can
|
|
23
|
+
never be relaxed.
|
|
24
|
+
- Shipped packs (templates requiring review): `healthcare_baseline`, a strict global healthcare
|
|
25
|
+
default, and `us_hipaa`, field classifications modelled on the HIPAA Safe Harbor identifier
|
|
26
|
+
list, with ages over 89 shown as `90+`. `us_hipaa` is not Safe Harbor or Expert Determination
|
|
27
|
+
by itself.
|
|
28
|
+
- New template `hospital_us_override`, an example hospital layer on top of `us_hipaa`.
|
|
29
|
+
- Packs `eu_gdpr` and `india_dpdp` (templates requiring review, not legal advice). `eu_gdpr`
|
|
30
|
+
blocks GDPR Art. 9 special-category fields toward external destinations and reviews them
|
|
31
|
+
elsewhere, and finds member-state identifier fields plus Italian codice fiscale values.
|
|
32
|
+
`india_dpdp` finds grouped Aadhaar numbers, ABHA numbers and addresses, PAN and Indian
|
|
33
|
+
identifier fields, and blocks SPDI-style sensitive fields toward external destinations. One
|
|
34
|
+
policy can extend several jurisdiction packs.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- When equally protective findings overlap in one value, the more confident one now names the
|
|
39
|
+
redaction marker (for example a grouped Aadhaar number is marked `pii.government_id`, not
|
|
40
|
+
`pii.phone`). What gets removed is unchanged.
|
|
41
|
+
- Reports list the policy's layers (name, kind, source, file digest), and each decision names
|
|
42
|
+
the layer that made it. `policy.digest` now covers every layer. `validate-policy` prints the
|
|
43
|
+
layer chain and adds `layers` to its JSON output. The terminal summary and the review page
|
|
44
|
+
show the layer next to the rule id.
|
|
45
|
+
|
|
46
|
+
## [0.16.0] - 2026-11-02
|
|
47
|
+
|
|
48
|
+
`ciphergate review` writes a local HTML page that shows, as a trace, what sanitize would change
|
|
49
|
+
and how the run went. The CLI's other commands and the report format are unchanged.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- `ciphergate review INPUT --policy P -o page.html` writes one local HTML page that shows
|
|
54
|
+
what sanitize would change. It covers findings, how many are transformed, kept, blocked
|
|
55
|
+
or waiting for review, a per-category table, rescan results, possible partial finds,
|
|
56
|
+
and each record with its changes highlighted and labelled with category, action, rule
|
|
57
|
+
and reason code. No sanitized file is written. The page holds the would-be output; the
|
|
58
|
+
raw input is added only with `--include-input`. The page has no scripts, loads nothing,
|
|
59
|
+
is written atomically, and is not overwritten without `--force`. The exit code matches
|
|
60
|
+
`plan`. The page is laid out as a trace: a tree of the run, its steps with timings, and the
|
|
61
|
+
records; each record opens as Input and Output panels.
|
|
62
|
+
- `scripts/benchmark/ner_names.py` measures the optional NER detector on generated log lines.
|
|
63
|
+
With `en_core_web_sm` it found any part of 57-59% of names and the whole name in 41%, missed
|
|
64
|
+
most lowercase and upper-case names, and flagged 3 of 16 name-free lines. Details and limits
|
|
65
|
+
are in `docs/detector-coverage.md`. The detector itself is unchanged.
|
|
66
|
+
|
|
9
67
|
## [0.15.0] - 2026-11-02
|
|
10
68
|
|
|
11
69
|
JSON reports now include the next-step hints, so CI pipelines can act on them. The field is new;
|
|
@@ -395,7 +453,9 @@ Pre-release (alpha). Licensed under Apache-2.0. Not a final release: the securit
|
|
|
395
453
|
See `docs/limitations.md`. Deferred: optional NER/Presidio detectors, stdin/stdout streaming,
|
|
396
454
|
directory processing, structured log parsers, key files, reversible token vault, lossy decoding.
|
|
397
455
|
|
|
398
|
-
[Unreleased]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.
|
|
456
|
+
[Unreleased]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.17.0...HEAD
|
|
457
|
+
[0.17.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.16.0...v0.17.0
|
|
458
|
+
[0.16.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.15.0...v0.16.0
|
|
399
459
|
[0.15.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.14.0...v0.15.0
|
|
400
460
|
[0.14.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.13.0...v0.14.0
|
|
401
461
|
[0.13.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.12.0...v0.13.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ciphergate-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.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.17.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.
|
|
@@ -91,6 +91,8 @@ inspect -> plan -> sanitize --apply -> rescan
|
|
|
91
91
|
and its source, gates, key *status* (never key material), no raw values or fingerprints.
|
|
92
92
|
A failed run's report lists `next_steps` as `{"code", "message"}` pairs, the same hints the
|
|
93
93
|
terminal prints as `next:` lines, so CI can act on them.
|
|
94
|
+
- **Review page:** `ciphergate review` writes one local HTML file that shows what sanitize
|
|
95
|
+
would change, record by record, with run metrics. See [Review page](#review-page).
|
|
94
96
|
|
|
95
97
|
## Install
|
|
96
98
|
|
|
@@ -230,14 +232,40 @@ controlled pipeline configuration.
|
|
|
230
232
|
## Shipped policy templates (review before use)
|
|
231
233
|
|
|
232
234
|
`dev_local`, `stg_test`, `prd_external_ai`, `developer_logs`, `test_dataset`, `external_ai`,
|
|
233
|
-
`healthcare_template`, `financial_template`, `local_debug`, and `multi_environment` (one policy
|
|
235
|
+
`healthcare_template`, `financial_template`, `local_debug`, `hospital_us_override` (a layered example, see below), and `multi_environment` (one policy
|
|
234
236
|
whose outcomes vary by environment *and* destination). All validate in CI. Templates named for
|
|
235
237
|
healthcare or finance are starting points, **not** HIPAA/PCI controls.
|
|
236
238
|
|
|
239
|
+
### Policy layering
|
|
240
|
+
|
|
241
|
+
A policy can `extends:` shipped packs, so a team keeps only its own differences in a small file:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
healthcare_baseline (global strict default) -> us_hipaa (US identifier list) -> your file
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
ciphergate init --template hospital_us_override --output my-hospital.yml
|
|
249
|
+
ciphergate validate-policy my-hospital.yml
|
|
250
|
+
# layers: healthcare_baseline -> us_hipaa -> hospital-us-override
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
A later layer can add context, detectors, classifications and stricter rules. It cannot quietly
|
|
254
|
+
weaken an earlier one: a weaker setting, a disabled detector or a rule that would override a
|
|
255
|
+
block, review or redaction with something weaker fails validation unless the rule names what it
|
|
256
|
+
relaxes (`relaxes: [rule-id]`), which is then reported as a warning. Hard and guardrail rules
|
|
257
|
+
can never be relaxed. Reports list every layer with its digest and show which layer decided
|
|
258
|
+
each value. Jurisdiction packs: `us_hipaa` (modelled on the HIPAA Safe Harbor identifier list
|
|
259
|
+
but **not** Safe Harbor or Expert Determination by itself), `eu_gdpr` (GDPR special categories
|
|
260
|
+
and member-state identifiers) and `india_dpdp` (Aadhaar, ABHA, PAN and sensitive fields). None
|
|
261
|
+
of them is legal advice or makes processing lawful. A policy can extend several. See
|
|
262
|
+
[docs/policy-schema.md](docs/policy-schema.md#layering-extends).
|
|
263
|
+
|
|
237
264
|
## Commands and exit codes
|
|
238
265
|
|
|
239
|
-
`init`, `validate-policy`, `inspect`, `plan`, `sanitize` (needs `--apply`), `rescan`, `
|
|
240
|
-
(decision for a hypothetical finding — no data
|
|
266
|
+
`init`, `validate-policy`, `inspect`, `plan`, `sanitize` (needs `--apply`), `rescan`, `review`
|
|
267
|
+
(local HTML page, see below), `explain` (decision for a hypothetical finding — no data
|
|
268
|
+
needed). All support `--help`.
|
|
241
269
|
|
|
242
270
|
Common options: `--policy`, `--format csv|json|jsonl|text|logfmt|jsonlog|accesslog|syslog|stacktrace`, `--report PATH`,
|
|
243
271
|
`--report-format json|text` (JSON without `--report` prints only JSON on stdout),
|
|
@@ -485,6 +513,50 @@ kubectl logs my-pod | ciphergate sanitize - --format text --policy policies/prd_
|
|
|
485
513
|
| 8 | Unexpected internal error (safe message) |
|
|
486
514
|
| 130 | Interrupted (no output published) |
|
|
487
515
|
|
|
516
|
+
### Review page
|
|
517
|
+
|
|
518
|
+
`ciphergate review` runs the same steps as `sanitize --apply`, including the rescan, but writes
|
|
519
|
+
no sanitized file. It writes one HTML page instead:
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
ciphergate review app.log --policy policies/developer_logs.yml --environment dev --destination local_dev -o build/review.html
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Open the file in a browser. It is laid out like a trace: a tree on the left, details of the
|
|
526
|
+
selected item on the right.
|
|
527
|
+
|
|
528
|
+
- **review run:** a verdict, then metrics. These include findings and how many are
|
|
529
|
+
transformed, kept by an `allow` rule, need review or are blocked (with percentages), records
|
|
530
|
+
with findings, values changed, records dropped, rescan results and possible partial finds.
|
|
531
|
+
- **Steps**, with measured timings: the plan pass (read input, detect, decide), transform,
|
|
532
|
+
rescan and output.
|
|
533
|
+
- *detect* shows counts by category, detector and confidence.
|
|
534
|
+
- *decide* shows the policy and context, what happened to each category, counts by action,
|
|
535
|
+
rule and reason code, plus gates and key status.
|
|
536
|
+
- **Records with findings:** each one opens as Input and Output panels with a decisions table.
|
|
537
|
+
Changed parts are highlighted, and hovering over one shows its category, action, rule and
|
|
538
|
+
reason code. Values kept by an `allow` rule are underlined. A dashed outline marks a
|
|
539
|
+
*possible partial find*: a replaced name or address next to a capitalised word that stayed.
|
|
540
|
+
This is a hint, not a check.
|
|
541
|
+
|
|
542
|
+
What the page contains, and how to handle it:
|
|
543
|
+
|
|
544
|
+
- By default it contains the would-be **output**, not the input. Treat it like the sanitized
|
|
545
|
+
file: detectors can miss values.
|
|
546
|
+
- `--include-input` also adds the **raw input** next to the output, with a warning banner. A
|
|
547
|
+
page made with it must be handled like the original file. Do not share, upload or commit it.
|
|
548
|
+
- If the run is blocked, needs review or a key is missing, there is no output preview. The page
|
|
549
|
+
shows the plan and the decisions for each record. Planned transforms are labelled as not run.
|
|
550
|
+
- There are no default locations: `-o` is required. `build/` is in `.gitignore`. The page is not
|
|
551
|
+
overwritten without `--force`, can't be the input or the policy file, and is written
|
|
552
|
+
atomically (0600 on POSIX). Directories and stdin/stdout are not supported.
|
|
553
|
+
- The page has no scripts and a Content-Security-Policy that blocks all loads, so opening it
|
|
554
|
+
makes no network request. All text is escaped.
|
|
555
|
+
- The exit code is the plan's: 0 ready, 1 blocked/review, 6 key/config, 7 rescan found gating
|
|
556
|
+
findings. Use `sanitize` and `rescan` as the CI gate. Up to 500 records are shown, each cut
|
|
557
|
+
to 4,000 characters; the counts include all records. It takes a little longer than
|
|
558
|
+
`sanitize` (about 15% on a 40,000-line synthetic log).
|
|
559
|
+
|
|
488
560
|
### Troubleshooting
|
|
489
561
|
|
|
490
562
|
- `destination_required_but_missing` — pass `--destination` (and `--environment`).
|
|
@@ -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.17.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.
|
|
@@ -52,6 +52,8 @@ inspect -> plan -> sanitize --apply -> rescan
|
|
|
52
52
|
and its source, gates, key *status* (never key material), no raw values or fingerprints.
|
|
53
53
|
A failed run's report lists `next_steps` as `{"code", "message"}` pairs, the same hints the
|
|
54
54
|
terminal prints as `next:` lines, so CI can act on them.
|
|
55
|
+
- **Review page:** `ciphergate review` writes one local HTML file that shows what sanitize
|
|
56
|
+
would change, record by record, with run metrics. See [Review page](#review-page).
|
|
55
57
|
|
|
56
58
|
## Install
|
|
57
59
|
|
|
@@ -191,14 +193,40 @@ controlled pipeline configuration.
|
|
|
191
193
|
## Shipped policy templates (review before use)
|
|
192
194
|
|
|
193
195
|
`dev_local`, `stg_test`, `prd_external_ai`, `developer_logs`, `test_dataset`, `external_ai`,
|
|
194
|
-
`healthcare_template`, `financial_template`, `local_debug`, and `multi_environment` (one policy
|
|
196
|
+
`healthcare_template`, `financial_template`, `local_debug`, `hospital_us_override` (a layered example, see below), and `multi_environment` (one policy
|
|
195
197
|
whose outcomes vary by environment *and* destination). All validate in CI. Templates named for
|
|
196
198
|
healthcare or finance are starting points, **not** HIPAA/PCI controls.
|
|
197
199
|
|
|
200
|
+
### Policy layering
|
|
201
|
+
|
|
202
|
+
A policy can `extends:` shipped packs, so a team keeps only its own differences in a small file:
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
healthcare_baseline (global strict default) -> us_hipaa (US identifier list) -> your file
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
ciphergate init --template hospital_us_override --output my-hospital.yml
|
|
210
|
+
ciphergate validate-policy my-hospital.yml
|
|
211
|
+
# layers: healthcare_baseline -> us_hipaa -> hospital-us-override
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
A later layer can add context, detectors, classifications and stricter rules. It cannot quietly
|
|
215
|
+
weaken an earlier one: a weaker setting, a disabled detector or a rule that would override a
|
|
216
|
+
block, review or redaction with something weaker fails validation unless the rule names what it
|
|
217
|
+
relaxes (`relaxes: [rule-id]`), which is then reported as a warning. Hard and guardrail rules
|
|
218
|
+
can never be relaxed. Reports list every layer with its digest and show which layer decided
|
|
219
|
+
each value. Jurisdiction packs: `us_hipaa` (modelled on the HIPAA Safe Harbor identifier list
|
|
220
|
+
but **not** Safe Harbor or Expert Determination by itself), `eu_gdpr` (GDPR special categories
|
|
221
|
+
and member-state identifiers) and `india_dpdp` (Aadhaar, ABHA, PAN and sensitive fields). None
|
|
222
|
+
of them is legal advice or makes processing lawful. A policy can extend several. See
|
|
223
|
+
[docs/policy-schema.md](docs/policy-schema.md#layering-extends).
|
|
224
|
+
|
|
198
225
|
## Commands and exit codes
|
|
199
226
|
|
|
200
|
-
`init`, `validate-policy`, `inspect`, `plan`, `sanitize` (needs `--apply`), `rescan`, `
|
|
201
|
-
(decision for a hypothetical finding — no data
|
|
227
|
+
`init`, `validate-policy`, `inspect`, `plan`, `sanitize` (needs `--apply`), `rescan`, `review`
|
|
228
|
+
(local HTML page, see below), `explain` (decision for a hypothetical finding — no data
|
|
229
|
+
needed). All support `--help`.
|
|
202
230
|
|
|
203
231
|
Common options: `--policy`, `--format csv|json|jsonl|text|logfmt|jsonlog|accesslog|syslog|stacktrace`, `--report PATH`,
|
|
204
232
|
`--report-format json|text` (JSON without `--report` prints only JSON on stdout),
|
|
@@ -446,6 +474,50 @@ kubectl logs my-pod | ciphergate sanitize - --format text --policy policies/prd_
|
|
|
446
474
|
| 8 | Unexpected internal error (safe message) |
|
|
447
475
|
| 130 | Interrupted (no output published) |
|
|
448
476
|
|
|
477
|
+
### Review page
|
|
478
|
+
|
|
479
|
+
`ciphergate review` runs the same steps as `sanitize --apply`, including the rescan, but writes
|
|
480
|
+
no sanitized file. It writes one HTML page instead:
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
ciphergate review app.log --policy policies/developer_logs.yml --environment dev --destination local_dev -o build/review.html
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Open the file in a browser. It is laid out like a trace: a tree on the left, details of the
|
|
487
|
+
selected item on the right.
|
|
488
|
+
|
|
489
|
+
- **review run:** a verdict, then metrics. These include findings and how many are
|
|
490
|
+
transformed, kept by an `allow` rule, need review or are blocked (with percentages), records
|
|
491
|
+
with findings, values changed, records dropped, rescan results and possible partial finds.
|
|
492
|
+
- **Steps**, with measured timings: the plan pass (read input, detect, decide), transform,
|
|
493
|
+
rescan and output.
|
|
494
|
+
- *detect* shows counts by category, detector and confidence.
|
|
495
|
+
- *decide* shows the policy and context, what happened to each category, counts by action,
|
|
496
|
+
rule and reason code, plus gates and key status.
|
|
497
|
+
- **Records with findings:** each one opens as Input and Output panels with a decisions table.
|
|
498
|
+
Changed parts are highlighted, and hovering over one shows its category, action, rule and
|
|
499
|
+
reason code. Values kept by an `allow` rule are underlined. A dashed outline marks a
|
|
500
|
+
*possible partial find*: a replaced name or address next to a capitalised word that stayed.
|
|
501
|
+
This is a hint, not a check.
|
|
502
|
+
|
|
503
|
+
What the page contains, and how to handle it:
|
|
504
|
+
|
|
505
|
+
- By default it contains the would-be **output**, not the input. Treat it like the sanitized
|
|
506
|
+
file: detectors can miss values.
|
|
507
|
+
- `--include-input` also adds the **raw input** next to the output, with a warning banner. A
|
|
508
|
+
page made with it must be handled like the original file. Do not share, upload or commit it.
|
|
509
|
+
- If the run is blocked, needs review or a key is missing, there is no output preview. The page
|
|
510
|
+
shows the plan and the decisions for each record. Planned transforms are labelled as not run.
|
|
511
|
+
- There are no default locations: `-o` is required. `build/` is in `.gitignore`. The page is not
|
|
512
|
+
overwritten without `--force`, can't be the input or the policy file, and is written
|
|
513
|
+
atomically (0600 on POSIX). Directories and stdin/stdout are not supported.
|
|
514
|
+
- The page has no scripts and a Content-Security-Policy that blocks all loads, so opening it
|
|
515
|
+
makes no network request. All text is escaped.
|
|
516
|
+
- The exit code is the plan's: 0 ready, 1 blocked/review, 6 key/config, 7 rescan found gating
|
|
517
|
+
findings. Use `sanitize` and `rescan` as the CI gate. Up to 500 records are shown, each cut
|
|
518
|
+
to 4,000 characters; the counts include all records. It takes a little longer than
|
|
519
|
+
`sanitize` (about 15% on a 40,000-line synthetic log).
|
|
520
|
+
|
|
449
521
|
### Troubleshooting
|
|
450
522
|
|
|
451
523
|
- `destination_required_but_missing` — pass `--destination` (and `--environment`).
|
|
@@ -33,6 +33,7 @@ Report (reporting/) privacy-safe JSON / text summary
|
|
|
33
33
|
| `readers/` | Parse and write each format; limits; encodings; JSON Pointer paths | decide actions |
|
|
34
34
|
| `detectors/` | Produce candidate findings; per-run `DetectorSet` (no global registry) | decide actions |
|
|
35
35
|
| `policy/loader.py` | Safe YAML (no tags, no aliases, no duplicate keys, size limit) | execute code |
|
|
36
|
+
| `policy/layers.py` | Resolve `extends` (shipped packs by name), merge layers, reject weakening | read local files by name |
|
|
36
37
|
| `policy/validator.py` | Schema + semantic checks, compile into immutable `Policy` | — |
|
|
37
38
|
| `policy/precedence.py` | Computed precedence levels and static overlap analysis | — |
|
|
38
39
|
| `policy/context.py` | Pinned vs caller context, run-level gates | infer one context dimension from another |
|
|
@@ -40,6 +41,7 @@ Report (reporting/) privacy-safe JSON / text summary
|
|
|
40
41
|
| `transforms/` | Execute approved actions; HMAC pseudonyms; key lookup | log values or keys |
|
|
41
42
|
| `pipeline/` | Orchestration, two passes, rescan, atomic output, path safety | publish on any failure |
|
|
42
43
|
| `reporting/` | Report building, control-char escaping, field-path masking, rendering | include values/fingerprints |
|
|
44
|
+
| `pipeline/review.py`, `reporting/review_page.py` | `review`: sanitize steps without publishing, per-record changes, local HTML page | publish output, add input without `--include-input`, load anything |
|
|
43
45
|
| `cli.py` | Typer CLI, exit codes, safe error rendering, SIGTERM -> interrupt | enable rich tracebacks (they print locals) |
|
|
44
46
|
|
|
45
47
|
## Why two passes
|
|
@@ -32,8 +32,31 @@ in `tests/unit/test_presidio_ner.py` use a fake engine. `tests/integration/test_
|
|
|
32
32
|
runs the real Presidio with `en_core_web_sm`: it finds a common synthetic name and place, ignores
|
|
33
33
|
text with no entities, removes them in a `sanitize` run with network access disabled, and fails
|
|
34
34
|
closed when the model is missing. CI runs it in the `ner` job (not a required check). These are
|
|
35
|
-
smoke tests on a few sentences
|
|
36
|
-
|
|
35
|
+
smoke tests on a few sentences.
|
|
36
|
+
|
|
37
|
+
`scripts/benchmark/ner_names.py` measures name detection on 320 generated log lines (8 regions x
|
|
38
|
+
5 ways of writing a name x 8 lines) plus 16 name-free lines. With `en_core_web_sm` (spaCy 3.8,
|
|
39
|
+
Presidio 2.2.366), seeds 7 and 11:
|
|
40
|
+
|
|
41
|
+
| | seed 7 | seed 11 |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| names with any part found | 184/320 (57%) | 190/320 (59%) |
|
|
44
|
+
| names found in full | 130/320 (41%) | 132/320 (41%) |
|
|
45
|
+
| name-free lines flagged | 3/16 | 3/16 |
|
|
46
|
+
|
|
47
|
+
- When only part of a name is found, the rest stays in the output (for example the family name).
|
|
48
|
+
- `First Last` and `Last, First` are found most often (50-61 of 64). Lowercase (27/64) and
|
|
49
|
+
upper-case (15/64) names are mostly missed.
|
|
50
|
+
- English, Chinese and German names were found more often (27-35 of 40) than Indian and West
|
|
51
|
+
African names (12-17 of 40).
|
|
52
|
+
- The false alarms were product names (`Kafka`, `Jenkins`, `Grafana`).
|
|
53
|
+
- `min_score` from 0.4 to 0.85 changed nothing: Presidio gives these spaCy entities one fixed
|
|
54
|
+
score, so the threshold cannot trade misses against false alarms with this model.
|
|
55
|
+
- The default `en_core_web_lg` was not measured. Run the script with `--model en_core_web_lg`
|
|
56
|
+
after installing it.
|
|
57
|
+
|
|
58
|
+
These are synthetic lines written by the project, not a public accuracy claim. Treat the NER
|
|
59
|
+
detector as an extra layer: do not rely on it alone to remove names, especially from free text.
|
|
37
60
|
|
|
38
61
|
## Text/log specifics
|
|
39
62
|
|
|
@@ -17,6 +17,23 @@
|
|
|
17
17
|
- Environment, target environment and destination are declared by the caller or policy and are
|
|
18
18
|
not verified. CipherGate does not inspect cloud identity or control deployments.
|
|
19
19
|
|
|
20
|
+
## Policy layering
|
|
21
|
+
|
|
22
|
+
- `extends` names shipped packs only; a policy cannot extend a local file in this release.
|
|
23
|
+
- The weakening check compares rule selectors statically and conservatively: it can reject a
|
|
24
|
+
rule that would never actually override the earlier one (narrow the selectors or use
|
|
25
|
+
`relaxes`), and it cannot see differences that depend on detector behaviour.
|
|
26
|
+
- The `us_hipaa` pack does not implement the 3-digit ZIP allowance (whole ZIP codes are
|
|
27
|
+
redacted), cannot check the Safe Harbor "no actual knowledge" condition, does not parse
|
|
28
|
+
photos or biometric data, and cannot know every "other unique identifying number". It is
|
|
29
|
+
not Safe Harbor or Expert Determination by itself.
|
|
30
|
+
- The `eu_gdpr` pack finds most national identifiers by field name only (the Italian codice
|
|
31
|
+
fiscale also by value). It cannot decide a lawful basis, an Art. 9(2) condition or whether a
|
|
32
|
+
destination is outside the EEA.
|
|
33
|
+
- The `india_dpdp` pack finds Aadhaar numbers by value only when written in groups and does not
|
|
34
|
+
verify the Verhoeff checksum; voter ID, passport and licence numbers are found by field name
|
|
35
|
+
only. It cannot check consent, purpose limitation, retention or cross-border rules.
|
|
36
|
+
|
|
20
37
|
## Formats and processing
|
|
21
38
|
|
|
22
39
|
- JSON is parsed as a whole document (default limit 32 MiB). CSV/JSONL/text are streamed with
|
|
@@ -10,6 +10,7 @@ Validate with `ciphergate validate-policy FILE` (never reads data).
|
|
|
10
10
|
|---|---|---|---|
|
|
11
11
|
| `policy_version` | `1` | required | other versions rejected |
|
|
12
12
|
| `name` | slug `[a-z0-9][a-z0-9_.-]{0,63}` | required | also the default pseudonym namespace |
|
|
13
|
+
| `extends` | list of shipped pack names (max 4) | `[]` | build on [policy packs](#layering-extends) |
|
|
13
14
|
| `metadata` | mapping | `{}` | free-form extension namespace; not interpreted |
|
|
14
15
|
| `context` | [Context](#context) | all unset | pinned context |
|
|
15
16
|
| `requirements` | `{environment, target_environment, destination}` booleans | `true, false, true` | which context must be present (a run without a destination is still treated as untrusted; turning the requirement off warns) |
|
|
@@ -181,6 +182,77 @@ future. Requires `strict: true` and the rescan gate; validation always warns, an
|
|
|
181
182
|
expire automatically — review them. No shipped policy contains an
|
|
182
183
|
approval. Rules still apply — an approval does not allow raw data through.
|
|
183
184
|
|
|
185
|
+
## Layering (`extends`)
|
|
186
|
+
|
|
187
|
+
A policy can build on shipped **packs** instead of repeating them:
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
policy_version: 1
|
|
191
|
+
name: my-hospital
|
|
192
|
+
extends: [us_hipaa] # us_hipaa itself extends healthcare_baseline
|
|
193
|
+
context: {environment: prd, target_environment: external, destination: external_ai}
|
|
194
|
+
classifications:
|
|
195
|
+
- {id: local-ids, field_names: [visit_number], category: pii.customer_identifier}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Shipped packs (`policies/packs/`, all **templates requiring review**):
|
|
199
|
+
|
|
200
|
+
| Pack | Extends | What it adds |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| `healthcare_baseline` | — | Strict default for healthcare-style data anywhere: credentials hard-blocked; clinical values blocked toward external/unknown destinations and sent to review elsewhere; identifiers redacted; DOB and clinical dates cut to the year; internal infrastructure redacted; everything else review. Needs no key. |
|
|
203
|
+
| `us_hipaa` | `healthcare_baseline` | Field classifications modelled on the 18 HIPAA Safe Harbor identifier types (geography, dates, ages, record/plan/account numbers, licenses, vehicles, devices, URLs, biometrics, photos); ages banded with 90 and over as `90+`. It is **not** Safe Harbor or Expert Determination by itself: the 3-digit ZIP allowance and the "no actual knowledge" condition are not implemented, binary photos/biometrics are not parsed, and free-text detection is incomplete. |
|
|
204
|
+
| `eu_gdpr` | `healthcare_baseline` | GDPR Art. 9 special-category fields (genetic, biometric, ethnic origin, religion, political opinion, trade union, sex life/orientation) blocked toward external/unknown destinations and reviewed elsewhere; national and health-insurance id fields from several member states plus an Italian codice fiscale value pattern; ages banded. It cannot decide a lawful basis or Chapter V transfers, and pseudonymised data is still personal data, so it redacts by default. |
|
|
205
|
+
| `india_dpdp` | `healthcare_baseline` | Aadhaar (field name, and value when grouped as `1234 5678 9012`; no Verhoeff check; unseparated 12 digits by field name only), ABHA numbers and `@abdm`/`@sbx` addresses, PAN (value and field), EPIC/passport/licence/ration card/UHID fields, PIN codes as address; SPDI-style sensitive fields blocked toward external/unknown destinations and reviewed elsewhere; ages banded. It cannot decide consent, purpose, retention or cross-border rules. |
|
|
206
|
+
|
|
207
|
+
`policies/hospital_us_override.yml` is an example third layer (`ciphergate init --template
|
|
208
|
+
hospital_us_override`). A policy may extend several jurisdiction packs at once (for example
|
|
209
|
+
`extends: [us_hipaa, eu_gdpr, india_dpdp]` for a hospital group); the strictest combination
|
|
210
|
+
applies.
|
|
211
|
+
|
|
212
|
+
**Resolution.** Names are compared to the shipped packs and never used as paths; local files
|
|
213
|
+
cannot be extended in this release. Packs may extend packs (at most 4 levels, 8 layers; cycles
|
|
214
|
+
are rejected). Layers are ordered base first, your policy last; a pack reached twice is used once.
|
|
215
|
+
Every check in this document then runs on the merged policy.
|
|
216
|
+
|
|
217
|
+
**Merge rules — a later layer can never quietly weaken an earlier one:**
|
|
218
|
+
|
|
219
|
+
| Part | Merge | Rejected (validation error) |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `context` | later layers may pin unset fields | pinning a different value (`layer_context_conflict`); `allow_cli_override` for a field an earlier layer pinned without allowing overrides (`layer_weakens_context`) |
|
|
222
|
+
| `requirements` | any layer requiring a field requires it | setting `false` where an earlier layer has `true` (`layer_weakens_setting`) |
|
|
223
|
+
| `settings` | stricter value kept: `strict`, `fail_on_parse_error`, `scan_json_keys`, `rescan.enabled` (true), `unknown_sensitive_behavior` (`block` > `review`), `csv_duplicate_headers` (`reject`), `malformed_record_action` (unset); `rescan.fail_on_categories` lists are combined (an unset value contributes every high-risk category) | a weaker value (`layer_weakens_setting`) |
|
|
224
|
+
| `detectors` | custom detectors and internal host suffixes are added; enabling Presidio is allowed | disabling a detector or Presidio that an earlier layer runs, or turning off `reject_risky_regex` (`layer_weakens_detectors` / `layer_weakens_setting`) |
|
|
225
|
+
| `custom_categories`, `transfers` | combined | — |
|
|
226
|
+
| `classifications`, `rules` | combined | reusing an id from an earlier layer (`duplicate_id`) |
|
|
227
|
+
| `defaults` | a later default replaces an earlier one for the same selector | a later default that would take over a category from an earlier default with a weaker action (`layer_weakens_default`) |
|
|
228
|
+
| `name`, `metadata` | the last layer's | — |
|
|
229
|
+
|
|
230
|
+
**Rules.** Actions are ranked `block` > `review` > any transform > `allow`. A later rule that
|
|
231
|
+
[outranks](#precedence-deterministic) an earlier rule it may overlap with and picks a lower-ranked
|
|
232
|
+
action fails with `layer_weakens_rule`. To do it on purpose, name the earlier rule in
|
|
233
|
+
`relaxes`:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
rules:
|
|
237
|
+
- id: hospital-keep-state
|
|
238
|
+
priority: 300
|
|
239
|
+
match: {categories: [location.coarse], field_names: [state]}
|
|
240
|
+
action: allow
|
|
241
|
+
relaxes: ["defaults:location.*"] # or an earlier rule id
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Each relaxation is printed as a validation warning and appears in every run report's warnings.
|
|
245
|
+
`hard` and `guardrail` rules from an earlier layer can **never** be relaxed. A later `allow` rule
|
|
246
|
+
over categories an earlier non-allow default handles needs `relaxes: ["defaults:<selector>"]`.
|
|
247
|
+
Swapping one transform for another (for example `pseudonymize` instead of `redact`) needs no
|
|
248
|
+
`relaxes`. `relaxes` is rejected in a policy without `extends`, and must name a rule or default
|
|
249
|
+
from an earlier layer.
|
|
250
|
+
|
|
251
|
+
**Provenance.** `validate-policy` prints the layer chain. Reports add `policy.layers` (each
|
|
252
|
+
layer's name, kind, source and file digest) and each decision's `layer`; `policy.digest` covers
|
|
253
|
+
every layer in order, so editing any pack changes it. With `--path-display redacted` your own
|
|
254
|
+
policy file name is hidden; shipped pack names are kept.
|
|
255
|
+
|
|
184
256
|
## Invalid examples
|
|
185
257
|
|
|
186
258
|
```yaml
|
|
@@ -75,6 +75,21 @@ values are replaced with `<redacted-key>`; control and bidi characters are escap
|
|
|
75
75
|
Logging uses the standard library and never receives values. `--verbose` lists more items;
|
|
76
76
|
`--debug` adds traceback frame locations for internal errors without exception messages.
|
|
77
77
|
|
|
78
|
+
## Review page
|
|
79
|
+
|
|
80
|
+
`ciphergate review` is the only command that puts data into a file other than the output. By
|
|
81
|
+
default the page holds the would-be sanitized output, so it is as sensitive as that output.
|
|
82
|
+
With `--include-input` it also holds the raw input and must be handled like the input; the
|
|
83
|
+
page and the command's stderr both say so. The JSON report never contains either.
|
|
84
|
+
|
|
85
|
+
The would-be output is written to a `mkstemp` file in the page's directory, rescanned, and
|
|
86
|
+
always removed; it is never published. The page itself uses the same atomic,
|
|
87
|
+
no-overwrite-by-default writer as outputs and refuses the input and policy paths. It contains
|
|
88
|
+
no scripts. A Content-Security-Policy (`default-src 'none'`) and `no-referrer` stop any load
|
|
89
|
+
or request, and every text is HTML-escaped with control and bidi characters shown as escapes.
|
|
90
|
+
Field paths in the decision tables use the same masking as reports. The possible-partial-find
|
|
91
|
+
mark is a heuristic and can miss partial finds or flag whole ones.
|
|
92
|
+
|
|
78
93
|
## Files
|
|
79
94
|
|
|
80
95
|
Inputs must be regular files (FIFOs/devices refused). A directory INPUT starts a batch run:
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# TEMPLATE REQUIRING REVIEW. Example of the third policy layer: one hospital's own small file on
|
|
2
|
+
# top of the shipped packs (healthcare_baseline -> us_hipaa -> this file). Replace the synthetic
|
|
3
|
+
# formats and field names below with your own. Using it does NOT perform HIPAA Safe Harbor or
|
|
4
|
+
# Expert Determination and is not legal or compliance advice.
|
|
5
|
+
#
|
|
6
|
+
# A layer like this can add context, detectors, classifications and stricter rules. It cannot
|
|
7
|
+
# quietly weaken the packs: a weaker setting or a rule that would override a pack's block,
|
|
8
|
+
# review or redaction with something weaker fails validation unless it names that rule (or
|
|
9
|
+
# default) in `relaxes`, which is then printed as a warning and recorded in every report.
|
|
10
|
+
policy_version: 1
|
|
11
|
+
name: hospital-us-override
|
|
12
|
+
extends: [us_hipaa]
|
|
13
|
+
metadata:
|
|
14
|
+
description: "Example hospital layer for PRD logs going to an external AI assistant. Template requiring review."
|
|
15
|
+
context:
|
|
16
|
+
environment: prd
|
|
17
|
+
target_environment: external
|
|
18
|
+
destination: external_ai
|
|
19
|
+
use_case: debugging
|
|
20
|
+
requirements:
|
|
21
|
+
target_environment: true
|
|
22
|
+
detectors:
|
|
23
|
+
custom:
|
|
24
|
+
# Synthetic local record-number format; replace with the hospital's real format.
|
|
25
|
+
- id: hospital-record-number
|
|
26
|
+
category: pii.customer_identifier
|
|
27
|
+
pattern: '\bHRN-[0-9]{8}\b'
|
|
28
|
+
confidence: 0.9
|
|
29
|
+
classifications:
|
|
30
|
+
- id: hospital-local-ids
|
|
31
|
+
field_names: [visit_number, encounter_id, bed_assignment, wristband_id]
|
|
32
|
+
category: pii.customer_identifier
|
|
33
|
+
- id: hospital-state
|
|
34
|
+
field_names: [state, state_code]
|
|
35
|
+
category: location.coarse
|
|
36
|
+
rules:
|
|
37
|
+
# Safe Harbor keeps the state; the baseline redacts all locations by default, so keeping it
|
|
38
|
+
# is a deliberate, recorded relaxation.
|
|
39
|
+
- id: hospital-keep-state
|
|
40
|
+
priority: 300
|
|
41
|
+
match:
|
|
42
|
+
categories: [location.coarse]
|
|
43
|
+
field_names: [state, state_code]
|
|
44
|
+
action: allow
|
|
45
|
+
relaxes: ["defaults:location.*"]
|
|
46
|
+
reason: state_is_kept_for_regional_debugging
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# TEMPLATE REQUIRING REVIEW. Layering pack for EU healthcare-style data, built on
|
|
2
|
+
# healthcare_baseline. It is modelled on GDPR concepts but using it does NOT make processing
|
|
3
|
+
# lawful and is not legal or compliance advice. GDPR has no fixed identifier list like HIPAA
|
|
4
|
+
# Safe Harbor: whether data is personal depends on whether a person is identifiable by any
|
|
5
|
+
# means reasonably likely to be used (Recital 26). Detection is incomplete, especially in free
|
|
6
|
+
# text and in languages other than English.
|
|
7
|
+
#
|
|
8
|
+
# What this pack adds on top of the baseline:
|
|
9
|
+
# * Art. 9 special categories (health is already covered by the baseline; this adds genetic,
|
|
10
|
+
# biometric, racial or ethnic origin, political opinions, religious or philosophical
|
|
11
|
+
# beliefs, trade union membership, sex life and sexual orientation fields): blocked toward
|
|
12
|
+
# external/unknown destinations, human review everywhere else;
|
|
13
|
+
# * national and health-insurance identifier fields from several member states, plus a value
|
|
14
|
+
# pattern for the Italian codice fiscale; other national numbers are found by field name only;
|
|
15
|
+
# * ages banded by 10 (90 and over as "90+"), like the other jurisdiction packs.
|
|
16
|
+
#
|
|
17
|
+
# Things this pack cannot decide for you:
|
|
18
|
+
# * a lawful basis and an Art. 9(2) condition for the processing;
|
|
19
|
+
# * Chapter V international transfers: an `external_ai` or `external_vendor` destination may
|
|
20
|
+
# be outside the EEA, and CipherGate does not know where a destination is;
|
|
21
|
+
# * whether a keyed pseudonym is acceptable: pseudonymised data is still personal data
|
|
22
|
+
# (Art. 4(5), Recital 26), so this pack redacts rather than pseudonymizes by default.
|
|
23
|
+
policy_version: 1
|
|
24
|
+
name: eu-gdpr
|
|
25
|
+
extends: [healthcare_baseline]
|
|
26
|
+
metadata:
|
|
27
|
+
description: "EU healthcare pack modelled on GDPR special categories and member-state identifiers. Template requiring review; not legal advice."
|
|
28
|
+
pack_version: 1
|
|
29
|
+
custom_categories:
|
|
30
|
+
- pii.age
|
|
31
|
+
- pii.special_category
|
|
32
|
+
- pii.biometric_identifier
|
|
33
|
+
detectors:
|
|
34
|
+
custom:
|
|
35
|
+
# Italian codice fiscale: 6 letters, 2 digits, month letter, 2 digits, letter, 3 digits, letter.
|
|
36
|
+
- id: eu-it-codice-fiscale
|
|
37
|
+
category: pii.government_id
|
|
38
|
+
pattern: '\b[A-Z]{6}[0-9]{2}[ABCDEHLMPRST][0-9]{2}[A-Z][0-9]{3}[A-Z]\b'
|
|
39
|
+
confidence: 0.85
|
|
40
|
+
classifications:
|
|
41
|
+
- id: eu-special-categories
|
|
42
|
+
field_names: [ethnicity, ethnic_origin, race, racial_origin, religion, religious_belief, philosophical_belief, political_opinion, political_party, party_affiliation, trade_union, union_membership, sexual_orientation, sex_life, gender_identity, genetic_data, genotype, dna_sequence, genetic_test_result]
|
|
43
|
+
category: pii.special_category
|
|
44
|
+
- id: eu-biometrics
|
|
45
|
+
field_names: [fingerprint, face_template, facial_image, iris_scan, retina_scan, voiceprint, biometric_template]
|
|
46
|
+
category: pii.biometric_identifier
|
|
47
|
+
- id: eu-national-ids
|
|
48
|
+
field_names: [codice_fiscale, steuer_id, steueridentifikationsnummer, bsn, burgerservicenummer, nir, numero_securite_sociale, dni, nie, nif, pesel, personnummer, henkilotunnus, cpr_number, rodne_cislo, ppsn, ehic_number, health_insurance_number, krankenversicherungsnummer, carte_vitale_number]
|
|
49
|
+
category: pii.government_id
|
|
50
|
+
- id: eu-age
|
|
51
|
+
field_names: [age, patient_age, age_years, alter, edad, eta]
|
|
52
|
+
category: pii.age
|
|
53
|
+
rules:
|
|
54
|
+
- id: eu-block-special-categories-external
|
|
55
|
+
priority: 800
|
|
56
|
+
match:
|
|
57
|
+
destinations: [external_vendor, external_ai, public_repository, unknown]
|
|
58
|
+
categories: [pii.special_category, pii.biometric_identifier]
|
|
59
|
+
action: block
|
|
60
|
+
reason: special_category_data_needs_review_before_external_release
|
|
61
|
+
|
|
62
|
+
- id: eu-review-special-categories
|
|
63
|
+
priority: 400
|
|
64
|
+
match:
|
|
65
|
+
categories: [pii.special_category, pii.biometric_identifier]
|
|
66
|
+
action: review
|
|
67
|
+
reason: special_category_data_requires_human_decision
|
|
68
|
+
|
|
69
|
+
- id: eu-age-band
|
|
70
|
+
priority: 300
|
|
71
|
+
match:
|
|
72
|
+
categories: [pii.age]
|
|
73
|
+
action: generalize
|
|
74
|
+
params:
|
|
75
|
+
strategy: age_band
|
|
76
|
+
band_size: 10
|
|
77
|
+
max_age: 90
|
|
78
|
+
reason: ages_banded
|