ciphergate-cli 0.7.0__py3-none-any.whl

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 (74) hide show
  1. ciphergate/__init__.py +95 -0
  2. ciphergate/__main__.py +6 -0
  3. ciphergate/cli.py +832 -0
  4. ciphergate/config.py +50 -0
  5. ciphergate/constants.py +274 -0
  6. ciphergate/detectors/__init__.py +5 -0
  7. ciphergate/detectors/base.py +53 -0
  8. ciphergate/detectors/custom_regex.py +37 -0
  9. ciphergate/detectors/dates.py +92 -0
  10. ciphergate/detectors/email.py +56 -0
  11. ciphergate/detectors/field_names.py +220 -0
  12. ciphergate/detectors/ip_address.py +85 -0
  13. ciphergate/detectors/network.py +42 -0
  14. ciphergate/detectors/payment_card.py +77 -0
  15. ciphergate/detectors/phone.py +67 -0
  16. ciphergate/detectors/presidio_ner.py +112 -0
  17. ciphergate/detectors/regex_safety.py +40 -0
  18. ciphergate/detectors/registry.py +194 -0
  19. ciphergate/detectors/secrets.py +130 -0
  20. ciphergate/errors.py +119 -0
  21. ciphergate/models/__init__.py +1 -0
  22. ciphergate/models/context.py +69 -0
  23. ciphergate/models/finding.py +108 -0
  24. ciphergate/models/policy.py +402 -0
  25. ciphergate/models/report.py +115 -0
  26. ciphergate/pipeline/__init__.py +1 -0
  27. ciphergate/pipeline/atomic_output.py +143 -0
  28. ciphergate/pipeline/batch.py +533 -0
  29. ciphergate/pipeline/inspector.py +282 -0
  30. ciphergate/pipeline/paths.py +98 -0
  31. ciphergate/pipeline/planner.py +192 -0
  32. ciphergate/pipeline/rescan.py +120 -0
  33. ciphergate/pipeline/sanitizer.py +392 -0
  34. ciphergate/pipeline/stdio.py +173 -0
  35. ciphergate/pipeline/streams.py +152 -0
  36. ciphergate/policy/__init__.py +26 -0
  37. ciphergate/policy/context.py +203 -0
  38. ciphergate/policy/engine.py +194 -0
  39. ciphergate/policy/loader.py +116 -0
  40. ciphergate/policy/precedence.py +149 -0
  41. ciphergate/policy/validator.py +632 -0
  42. ciphergate/py.typed +0 -0
  43. ciphergate/readers/__init__.py +113 -0
  44. ciphergate/readers/accesslog_reader.py +244 -0
  45. ciphergate/readers/base.py +131 -0
  46. ciphergate/readers/csv_reader.py +241 -0
  47. ciphergate/readers/json_reader.py +184 -0
  48. ciphergate/readers/jsonl_reader.py +97 -0
  49. ciphergate/readers/jsonlog_reader.py +178 -0
  50. ciphergate/readers/line_structure.py +125 -0
  51. ciphergate/readers/logfmt_reader.py +100 -0
  52. ciphergate/readers/stacktrace_reader.py +103 -0
  53. ciphergate/readers/syslog_reader.py +309 -0
  54. ciphergate/readers/text_reader.py +59 -0
  55. ciphergate/reporting/__init__.py +1 -0
  56. ciphergate/reporting/audit.py +85 -0
  57. ciphergate/reporting/safe_text.py +71 -0
  58. ciphergate/reporting/serializer.py +44 -0
  59. ciphergate/reporting/terminal.py +147 -0
  60. ciphergate/templates/starter_policy.yml +77 -0
  61. ciphergate/transforms/__init__.py +6 -0
  62. ciphergate/transforms/date_shift.py +79 -0
  63. ciphergate/transforms/generalize.py +107 -0
  64. ciphergate/transforms/keys.py +177 -0
  65. ciphergate/transforms/pseudonymize.py +113 -0
  66. ciphergate/transforms/redact.py +9 -0
  67. ciphergate/transforms/registry.py +201 -0
  68. ciphergate/transforms/replace.py +16 -0
  69. ciphergate_cli-0.7.0.dist-info/METADATA +531 -0
  70. ciphergate_cli-0.7.0.dist-info/RECORD +74 -0
  71. ciphergate_cli-0.7.0.dist-info/WHEEL +4 -0
  72. ciphergate_cli-0.7.0.dist-info/entry_points.txt +2 -0
  73. ciphergate_cli-0.7.0.dist-info/licenses/LICENSE +202 -0
  74. ciphergate_cli-0.7.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,149 @@
1
+ """Deterministic precedence levels and static rule-overlap analysis.
2
+
3
+ Levels (lower number wins; the level is computed from the rule, not chosen freely):
4
+
5
+ 1. Engine hard constraints (missing/invalid context, credential never allowed) — not rules.
6
+ 2. ``hard: true`` rules (must not be ``allow``).
7
+ 3. ``guardrail: true`` rules — environment-boundary rules (must name environments and a target
8
+ environment or destination; must not be ``allow``).
9
+ 4. Field rules — any of ``field_names`` / ``field_paths`` / ``field_regex``.
10
+ 5. Context rules — any of ``environments`` / ``target_environments`` / ``destinations`` /
11
+ ``domain_profiles`` / ``use_cases`` (and no field selector).
12
+ 6. Category rules — ``categories`` / ``detectors`` / ``formats`` only.
13
+ 7. Confidence-only rules — only ``min_confidence`` / ``max_confidence``.
14
+ 8. Profile defaults (``defaults:`` mapping by category).
15
+ 9. Engine fallback — ``settings.unknown_sensitive_behavior`` (``review`` or ``block``), never allow.
16
+
17
+ Within a level the highest ``priority`` wins. Two matching rules at the same level and priority are
18
+ only allowed when they choose the same action and parameters; otherwise validation rejects the
19
+ policy as a conflict (declaration order is never used to break a real disagreement).
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import fnmatch
25
+
26
+ from ciphergate.detectors.field_names import normalize_field_name
27
+ from ciphergate.models.policy import Rule, RuleMatch
28
+
29
+ LEVEL_HARD_CONSTRAINT = 1
30
+ LEVEL_HARD_RULE = 2
31
+ LEVEL_GUARDRAIL = 3
32
+ LEVEL_FIELD = 4
33
+ LEVEL_CONTEXT = 5
34
+ LEVEL_CATEGORY = 6
35
+ LEVEL_CONFIDENCE = 7
36
+ LEVEL_DEFAULT = 8
37
+ LEVEL_FALLBACK = 9
38
+
39
+ LEVEL_NAMES = {
40
+ LEVEL_HARD_CONSTRAINT: "hard_constraint",
41
+ LEVEL_HARD_RULE: "hard_rule",
42
+ LEVEL_GUARDRAIL: "guardrail",
43
+ LEVEL_FIELD: "field",
44
+ LEVEL_CONTEXT: "context",
45
+ LEVEL_CATEGORY: "category",
46
+ LEVEL_CONFIDENCE: "confidence",
47
+ LEVEL_DEFAULT: "profile_default",
48
+ LEVEL_FALLBACK: "engine_fallback",
49
+ }
50
+
51
+ _FIELD_SELECTORS = ("field_names", "field_paths", "field_regex")
52
+ _CONTEXT_SELECTORS = (
53
+ "environments",
54
+ "target_environments",
55
+ "destinations",
56
+ "domain_profiles",
57
+ "use_cases",
58
+ )
59
+ _CATEGORY_SELECTORS = ("categories", "detectors", "formats")
60
+
61
+
62
+ def rule_level(rule: Rule) -> int:
63
+ m = rule.match
64
+ if rule.hard:
65
+ return LEVEL_HARD_RULE
66
+ if rule.guardrail:
67
+ return LEVEL_GUARDRAIL
68
+ if any(getattr(m, s) is not None for s in _FIELD_SELECTORS):
69
+ return LEVEL_FIELD
70
+ if any(getattr(m, s) is not None for s in _CONTEXT_SELECTORS):
71
+ return LEVEL_CONTEXT
72
+ if any(getattr(m, s) is not None for s in _CATEGORY_SELECTORS):
73
+ return LEVEL_CATEGORY
74
+ return LEVEL_CONFIDENCE
75
+
76
+
77
+ def category_selector_matches(selector: str, category: str) -> bool:
78
+ if selector == "*":
79
+ return True
80
+ if selector.endswith(".*"):
81
+ return category.startswith(selector[:-1])
82
+ return selector == category
83
+
84
+
85
+ def category_selectors_overlap(a: str, b: str) -> bool:
86
+ if a == "*" or b == "*":
87
+ return True
88
+ if a.endswith(".*") and b.endswith(".*"):
89
+ return a == b
90
+ if a.endswith(".*"):
91
+ return category_selector_matches(a, b)
92
+ if b.endswith(".*"):
93
+ return category_selector_matches(b, a)
94
+ return a == b
95
+
96
+
97
+ def _sets_overlap(a: list[str] | None, b: list[str] | None) -> bool:
98
+ if a is None or b is None:
99
+ return True
100
+ return bool({str(x) for x in a} & {str(x) for x in b})
101
+
102
+
103
+ def _globs_overlap(a: list[str] | None, b: list[str] | None) -> bool:
104
+ if a is None or b is None:
105
+ return True
106
+ for x in a:
107
+ for y in b:
108
+ if x == y or fnmatch.fnmatchcase(x, y) or fnmatch.fnmatchcase(y, x):
109
+ return True
110
+ if any(c in x + y for c in "*?["):
111
+ return True # cannot prove disjoint; treat as overlapping
112
+ return False
113
+
114
+
115
+ def matches_may_overlap(a: RuleMatch, b: RuleMatch) -> bool:
116
+ """Conservative: returns False only when the two selectors provably cannot match together."""
117
+ if (
118
+ a.categories is not None
119
+ and b.categories is not None
120
+ and not any(category_selectors_overlap(x, y) for x in a.categories for y in b.categories)
121
+ ):
122
+ return False
123
+ for name in (
124
+ "detectors",
125
+ "environments",
126
+ "target_environments",
127
+ "destinations",
128
+ "domain_profiles",
129
+ "use_cases",
130
+ "formats",
131
+ ):
132
+ if not _sets_overlap(getattr(a, name), getattr(b, name)):
133
+ return False
134
+ if a.field_names is not None and b.field_names is not None:
135
+ an = {normalize_field_name(n) for n in a.field_names}
136
+ bn = {normalize_field_name(n) for n in b.field_names}
137
+ no_other_field_selectors = (
138
+ a.field_paths is None
139
+ and b.field_paths is None
140
+ and a.field_regex is None
141
+ and b.field_regex is None
142
+ )
143
+ if not an & bn and no_other_field_selectors:
144
+ return False
145
+ if not _globs_overlap(a.field_paths, b.field_paths):
146
+ return False
147
+ lo_a, hi_a = a.min_confidence or 0.0, 1.0 if a.max_confidence is None else a.max_confidence
148
+ lo_b, hi_b = b.min_confidence or 0.0, 1.0 if b.max_confidence is None else b.max_confidence
149
+ return not (hi_a < lo_b or hi_b < lo_a)