ciphergate-cli 0.16.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.
Files changed (162) hide show
  1. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/CHANGELOG.md +39 -1
  2. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/PKG-INFO +28 -3
  3. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/README.md +27 -2
  4. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/docs/architecture.md +1 -0
  5. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/docs/limitations.md +17 -0
  6. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/docs/policy-schema.md +72 -0
  7. ciphergate_cli-0.17.0/policies/hospital_us_override.yml +46 -0
  8. ciphergate_cli-0.17.0/policies/packs/eu_gdpr.yml +78 -0
  9. ciphergate_cli-0.17.0/policies/packs/healthcare_baseline.yml +109 -0
  10. ciphergate_cli-0.17.0/policies/packs/india_dpdp.yml +97 -0
  11. ciphergate_cli-0.17.0/policies/packs/us_hipaa.yml +80 -0
  12. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/__init__.py +1 -1
  13. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/cli.py +4 -0
  14. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/policy.py +5 -0
  15. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/review.py +9 -2
  16. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/engine.py +5 -0
  17. ciphergate_cli-0.17.0/src/ciphergate/policy/layers.py +508 -0
  18. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/loader.py +11 -0
  19. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/templates.py +1 -12
  20. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/validator.py +33 -2
  21. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/audit.py +14 -0
  22. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/terminal.py +2 -0
  23. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/registry.py +5 -3
  24. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_cli_e2e.py +18 -0
  25. ciphergate_cli-0.17.0/tests/unit/test_policy_layers.py +438 -0
  26. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/.gitignore +0 -0
  27. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/LICENSE +0 -0
  28. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/NOTICE +0 -0
  29. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/SECURITY.md +0 -0
  30. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/docs/claude-code-tooling.md +0 -0
  31. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/docs/detector-coverage.md +0 -0
  32. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/docs/examples.md +0 -0
  33. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/docs/security-and-privacy.md +0 -0
  34. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/docs/threat-model.md +0 -0
  35. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/examples/expected/sample.dev_local_dev.csv +0 -0
  36. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/examples/expected/sample.dev_local_dev.json +0 -0
  37. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/examples/expected/sample.dev_local_dev.jsonl +0 -0
  38. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/examples/expected/sample.dev_local_dev.log +0 -0
  39. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/examples/sample.csv +0 -0
  40. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/examples/sample.json +0 -0
  41. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/examples/sample.jsonl +0 -0
  42. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/examples/sample.log +0 -0
  43. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/dev_local.yml +0 -0
  44. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/developer_logs.yml +0 -0
  45. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/external_ai.yml +0 -0
  46. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/financial_template.yml +0 -0
  47. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/healthcare_template.yml +0 -0
  48. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/local_debug.yml +0 -0
  49. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/multi_environment.yml +0 -0
  50. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/prd_external_ai.yml +0 -0
  51. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/stg_test.yml +0 -0
  52. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/policies/test_dataset.yml +0 -0
  53. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/pyproject.toml +0 -0
  54. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/scripts/benchmark/README.md +0 -0
  55. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/__main__.py +0 -0
  56. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/config.py +0 -0
  57. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/constants.py +0 -0
  58. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/__init__.py +0 -0
  59. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/base.py +0 -0
  60. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/custom_regex.py +0 -0
  61. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/dates.py +0 -0
  62. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/email.py +0 -0
  63. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/field_names.py +0 -0
  64. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/iban.py +0 -0
  65. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/ip_address.py +0 -0
  66. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/mac_address.py +0 -0
  67. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/network.py +0 -0
  68. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/payment_card.py +0 -0
  69. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/phone.py +0 -0
  70. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/presidio_ner.py +0 -0
  71. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/regex_safety.py +0 -0
  72. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/registry.py +0 -0
  73. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/secrets.py +0 -0
  74. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/detectors/ssn.py +0 -0
  75. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/errors.py +0 -0
  76. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/__init__.py +0 -0
  77. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/context.py +0 -0
  78. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/finding.py +0 -0
  79. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/models/report.py +0 -0
  80. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/__init__.py +0 -0
  81. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/atomic_output.py +0 -0
  82. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/batch.py +0 -0
  83. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/inspector.py +0 -0
  84. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/paths.py +0 -0
  85. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/planner.py +0 -0
  86. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/rescan.py +0 -0
  87. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/sanitizer.py +0 -0
  88. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/stdio.py +0 -0
  89. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/pipeline/streams.py +0 -0
  90. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/__init__.py +0 -0
  91. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/context.py +0 -0
  92. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/policy/precedence.py +0 -0
  93. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/py.typed +0 -0
  94. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/__init__.py +0 -0
  95. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/accesslog_reader.py +0 -0
  96. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/base.py +0 -0
  97. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/csv_reader.py +0 -0
  98. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/json_reader.py +0 -0
  99. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/jsonl_reader.py +0 -0
  100. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/jsonlog_reader.py +0 -0
  101. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/line_structure.py +0 -0
  102. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/logfmt_reader.py +0 -0
  103. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/stacktrace_reader.py +0 -0
  104. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/syslog_reader.py +0 -0
  105. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/readers/text_reader.py +0 -0
  106. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/__init__.py +0 -0
  107. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/next_steps.py +0 -0
  108. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/review_page.py +0 -0
  109. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/safe_text.py +0 -0
  110. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/reporting/serializer.py +0 -0
  111. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/templates/starter_policy.yml +0 -0
  112. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/__init__.py +0 -0
  113. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/date_shift.py +0 -0
  114. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/generalize.py +0 -0
  115. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/keys.py +0 -0
  116. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/pseudonymize.py +0 -0
  117. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/redact.py +0 -0
  118. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/src/ciphergate/transforms/replace.py +0 -0
  119. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/__init__.py +0 -0
  120. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/conftest.py +0 -0
  121. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/__init__.py +0 -0
  122. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_api_batch_streams.py +0 -0
  123. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_atomic_output.py +0 -0
  124. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_batch.py +0 -0
  125. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_domain_templates.py +0 -0
  126. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_environment_matrix.py +0 -0
  127. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_pipelines.py +0 -0
  128. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_presidio_real.py +0 -0
  129. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_review.py +0 -0
  130. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/integration/test_stdio.py +0 -0
  131. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/regression/__init__.py +0 -0
  132. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/regression/test_known_cases.py +0 -0
  133. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/security/__init__.py +0 -0
  134. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/security/test_key_file.py +0 -0
  135. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/security/test_no_network.py +0 -0
  136. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/security/test_no_raw_value_leaks.py +0 -0
  137. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/security/test_path_safety.py +0 -0
  138. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/security/test_policy_fail_closed.py +0 -0
  139. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/security/test_review_findings.py +0 -0
  140. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/security/test_review_page.py +0 -0
  141. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/tooling/__init__.py +0 -0
  142. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/tooling/test_benchmark.py +0 -0
  143. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/tooling/test_ci_supply_chain.py +0 -0
  144. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/tooling/test_claude_tooling.py +0 -0
  145. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/tooling/test_git_tooling.py +0 -0
  146. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/tooling/test_publish_workflow.py +0 -0
  147. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/__init__.py +0 -0
  148. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_accesslog.py +0 -0
  149. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_detectors.py +0 -0
  150. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_jsonlog.py +0 -0
  151. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_logfmt.py +0 -0
  152. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_next_steps.py +0 -0
  153. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_policy_engine.py +0 -0
  154. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_policy_precedence.py +0 -0
  155. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_policy_templates.py +0 -0
  156. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_policy_validation.py +0 -0
  157. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_presidio_ner.py +0 -0
  158. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_properties.py +0 -0
  159. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_readers.py +0 -0
  160. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_stacktrace.py +0 -0
  161. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_syslog.py +0 -0
  162. {ciphergate_cli-0.16.0 → ciphergate_cli-0.17.0}/tests/unit/test_transforms.py +0 -0
@@ -6,6 +6,43 @@ 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
+
9
46
  ## [0.16.0] - 2026-11-02
10
47
 
11
48
  `ciphergate review` writes a local HTML page that shows, as a trace, what sanitize would change
@@ -416,7 +453,8 @@ Pre-release (alpha). Licensed under Apache-2.0. Not a final release: the securit
416
453
  See `docs/limitations.md`. Deferred: optional NER/Presidio detectors, stdin/stdout streaming,
417
454
  directory processing, structured log parsers, key files, reversible token vault, lossy decoding.
418
455
 
419
- [Unreleased]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.16.0...HEAD
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
420
458
  [0.16.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.15.0...v0.16.0
421
459
  [0.15.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.14.0...v0.15.0
422
460
  [0.14.0]: https://github.com/aashishkumar-tech/ciphergate/compare/v0.13.0...v0.14.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ciphergate-cli
3
- Version: 0.16.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.16.0 (alpha).** No detector finds every sensitive value. A passing run means the
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.
@@ -232,10 +232,35 @@ controlled pipeline configuration.
232
232
  ## Shipped policy templates (review before use)
233
233
 
234
234
  `dev_local`, `stg_test`, `prd_external_ai`, `developer_logs`, `test_dataset`, `external_ai`,
235
- `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
236
236
  whose outcomes vary by environment *and* destination). All validate in CI. Templates named for
237
237
  healthcare or finance are starting points, **not** HIPAA/PCI controls.
238
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
+
239
264
  ## Commands and exit codes
240
265
 
241
266
  `init`, `validate-policy`, `inspect`, `plan`, `sanitize` (needs `--apply`), `rescan`, `review`
@@ -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.16.0 (alpha).** No detector finds every sensitive value. A passing run means the
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.
@@ -193,10 +193,35 @@ controlled pipeline configuration.
193
193
  ## Shipped policy templates (review before use)
194
194
 
195
195
  `dev_local`, `stg_test`, `prd_external_ai`, `developer_logs`, `test_dataset`, `external_ai`,
196
- `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
197
197
  whose outcomes vary by environment *and* destination). All validate in CI. Templates named for
198
198
  healthcare or finance are starting points, **not** HIPAA/PCI controls.
199
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
+
200
225
  ## Commands and exit codes
201
226
 
202
227
  `init`, `validate-policy`, `inspect`, `plan`, `sanitize` (needs `--apply`), `rescan`, `review`
@@ -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 |
@@ -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
@@ -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
@@ -0,0 +1,109 @@
1
+ # TEMPLATE REQUIRING REVIEW. Layering pack: a strict starting point for healthcare-style data
2
+ # in any country. Name it in `extends:` (or extend a jurisdiction pack such as us_hipaa) and add
3
+ # your own file on top. It is NOT legal or compliance advice and NOT a de-identification method:
4
+ # clinical free text, rare conditions, small geographies and combinations of quasi-identifiers
5
+ # can remain identifying, and detection can miss values. Expert review is still needed.
6
+ #
7
+ # What it does, for every destination:
8
+ # * credential findings are hard-blocked (no later layer can weaken that);
9
+ # * clinical values (diagnosis, medication, ...): blocked toward external/unknown destinations,
10
+ # human review everywhere else;
11
+ # * direct identifiers (pii.*, patient ids, health/financial/employment fields): redacted;
12
+ # * dates of birth and clinical dates: reduced to the year;
13
+ # * internal infrastructure (hostnames, internal URLs/IPs, trace ids): redacted;
14
+ # * anything else that looks sensitive: review.
15
+ # No key is needed. Later layers may swap a transform for another (for example keyed
16
+ # pseudonyms), but weakening a block/review or allowing a value needs an explicit `relaxes`.
17
+ policy_version: 1
18
+ name: healthcare-baseline
19
+ metadata:
20
+ description: "Strict global healthcare baseline for policy layering. Template requiring review; not a compliance claim."
21
+ pack_version: 1
22
+ context:
23
+ domain_profile: healthcare
24
+ requirements:
25
+ environment: true
26
+ destination: true
27
+ settings:
28
+ strict: true
29
+ scan_json_keys: true
30
+ fail_on_parse_error: true
31
+ unknown_sensitive_behavior: review
32
+ custom_categories:
33
+ - health.clinical_date
34
+ classifications:
35
+ - id: baseline-clinical-dates
36
+ field_names: [admission_date, discharge_date, visit_date, procedure_date, encounter_date, death_date, date_of_death]
37
+ category: health.clinical_date
38
+ confidence: 0.9
39
+ - id: baseline-clinical-values
40
+ field_names: [diagnoses, problem_list, lab_result, test_result, symptoms, treatment, chief_complaint]
41
+ category: health.phi
42
+ confidence: 0.8
43
+ rules:
44
+ - id: baseline-block-credentials
45
+ hard: true
46
+ priority: 1000
47
+ match:
48
+ categories: [credential.secret]
49
+ action: block
50
+ reason: credentials_never_in_health_data
51
+
52
+ - id: baseline-block-clinical-values-external
53
+ priority: 800
54
+ match:
55
+ destinations: [external_vendor, external_ai, public_repository, unknown]
56
+ categories: [health.phi]
57
+ field_names: [diagnosis, diagnoses, medication, medications, condition, allergies, prescription, clinical_notes, icd_code, procedure_code, problem_list, lab_result, test_result, symptoms, treatment, chief_complaint]
58
+ action: block
59
+ reason: clinical_values_need_expert_review_before_external_release
60
+
61
+ - id: baseline-review-clinical-values
62
+ priority: 400
63
+ match:
64
+ categories: [health.phi]
65
+ field_names: [diagnosis, diagnoses, medication, medications, condition, allergies, prescription, clinical_notes, icd_code, procedure_code, problem_list, lab_result, test_result, symptoms, treatment, chief_complaint]
66
+ action: review
67
+ reason: clinical_values_require_human_decision
68
+
69
+ - id: baseline-dob-year
70
+ priority: 300
71
+ match:
72
+ categories: [pii.date_of_birth]
73
+ action: generalize
74
+ params:
75
+ strategy: year
76
+ reason: keep_birth_year_only
77
+
78
+ - id: baseline-clinical-dates-year
79
+ priority: 300
80
+ match:
81
+ categories: [health.clinical_date]
82
+ action: generalize
83
+ params:
84
+ strategy: year
85
+ reason: keep_clinical_year_only
86
+
87
+ - id: baseline-redact-identifiers
88
+ priority: 200
89
+ match:
90
+ categories: [pii.*, health.phi, financial.sensitive, employment.sensitive]
91
+ action: redact
92
+ reason: remove_identifier
93
+
94
+ - id: baseline-redact-infrastructure
95
+ priority: 200
96
+ match:
97
+ categories: [network.telemetry, security.internal]
98
+ action: redact
99
+ reason: remove_internal_infrastructure_detail
100
+
101
+ defaults:
102
+ location.*:
103
+ action: redact
104
+ identifier.*:
105
+ action: redact
106
+ proprietary.*:
107
+ action: review
108
+ "*":
109
+ action: review
@@ -0,0 +1,97 @@
1
+ # TEMPLATE REQUIRING REVIEW. Layering pack for Indian healthcare-style data, built on
2
+ # healthcare_baseline. Its field list draws on the Digital Personal Data Protection Act, 2023,
3
+ # the sensitive personal data list in the IT (SPDI) Rules, 2011 and identifiers used by the
4
+ # Ayushman Bharat Digital Mission (ABDM). Using it does NOT establish that processing meets the
5
+ # DPDP Act, its rules, the Aadhaar Act or ABDM policies, and it is not legal or compliance
6
+ # advice. Detection is incomplete, especially in free text and in Indian languages.
7
+ #
8
+ # What this pack adds on top of the baseline:
9
+ # * Aadhaar numbers: by field name, and by value when written in groups ("1234 5678 9012" or
10
+ # with hyphens). An unseparated 12-digit Aadhaar is found by field name only (too many log
11
+ # values are 12-digit numbers), and the Verhoeff checksum is not checked. Aadhaar is fully
12
+ # redacted, not masked to the last 4 digits;
13
+ # * ABHA (health account) numbers ("12-3456-7890-1234") and ABHA addresses ("name@abdm",
14
+ # "name@sbx") by value and field name;
15
+ # * PAN by value (the 4th letter must be a valid holder type) and field name;
16
+ # * voter ID (EPIC), passport, driving licence, ration card, UHID/IP/OP numbers by field name;
17
+ # * PIN codes treated as address; SPDI-style sensitive fields (sexual orientation, biometric
18
+ # data) blocked toward external/unknown destinations and reviewed elsewhere;
19
+ # * ages banded by 10 (90 and over as "90+"), like the other jurisdiction packs.
20
+ #
21
+ # Things this pack cannot decide for you: notice and consent (or another legitimate use),
22
+ # purpose limitation, retention, rules on processing children's data, and cross-border
23
+ # transfer restrictions for the destination you name.
24
+ policy_version: 1
25
+ name: india-dpdp
26
+ extends: [healthcare_baseline]
27
+ metadata:
28
+ description: "India healthcare pack (Aadhaar, ABHA, PAN and SPDI-style sensitive fields). Template requiring review; not legal advice."
29
+ pack_version: 1
30
+ custom_categories:
31
+ - pii.age
32
+ - pii.special_category
33
+ - pii.biometric_identifier
34
+ detectors:
35
+ custom:
36
+ - id: in-aadhaar-grouped
37
+ category: pii.government_id
38
+ pattern: '\b[2-9][0-9]{3}[ -][0-9]{4}[ -][0-9]{4}\b'
39
+ confidence: 0.75
40
+ - id: in-abha-number
41
+ category: health.phi
42
+ pattern: '\b[0-9]{2}-[0-9]{4}-[0-9]{4}-[0-9]{4}\b'
43
+ confidence: 0.85
44
+ - id: in-abha-address
45
+ category: health.phi
46
+ pattern: '\b[a-z0-9][a-z0-9._]{2,31}@(abdm|sbx)\b'
47
+ confidence: 0.85
48
+ ignore_case: true
49
+ - id: in-pan
50
+ category: pii.government_id
51
+ pattern: '\b[A-Z]{3}[ABCFGHJLPT][A-Z][0-9]{4}[A-Z]\b'
52
+ confidence: 0.8
53
+ classifications:
54
+ - id: in-government-ids
55
+ field_names: [aadhaar, aadhaar_number, aadhaar_no, uid, uidai_number, pan, pan_number, pan_no, epic_number, voter_id, passport_no, driving_licence, driving_licence_number, dl_number, ration_card_number]
56
+ category: pii.government_id
57
+ - id: in-health-ids
58
+ field_names: [abha, abha_number, abha_id, abha_address, health_id, uhid, ip_number, op_number, opd_number, ipd_number, ayushman_card_number, pmjay_id]
59
+ category: health.phi
60
+ - id: in-pin-code
61
+ field_names: [pincode, pin_code, postal_pin]
62
+ category: pii.address
63
+ - id: in-sensitive
64
+ field_names: [sexual_orientation, caste, religion, gender_identity]
65
+ category: pii.special_category
66
+ - id: in-biometrics
67
+ field_names: [fingerprint, iris_scan, face_template, biometric_template]
68
+ category: pii.biometric_identifier
69
+ - id: in-age
70
+ field_names: [age, patient_age, age_years, umar]
71
+ category: pii.age
72
+ rules:
73
+ - id: in-block-sensitive-external
74
+ priority: 800
75
+ match:
76
+ destinations: [external_vendor, external_ai, public_repository, unknown]
77
+ categories: [pii.special_category, pii.biometric_identifier]
78
+ action: block
79
+ reason: sensitive_personal_data_needs_review_before_external_release
80
+
81
+ - id: in-review-sensitive
82
+ priority: 400
83
+ match:
84
+ categories: [pii.special_category, pii.biometric_identifier]
85
+ action: review
86
+ reason: sensitive_personal_data_requires_human_decision
87
+
88
+ - id: in-age-band
89
+ priority: 300
90
+ match:
91
+ categories: [pii.age]
92
+ action: generalize
93
+ params:
94
+ strategy: age_band
95
+ band_size: 10
96
+ max_age: 90
97
+ reason: ages_banded