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.
- ciphergate/__init__.py +95 -0
- ciphergate/__main__.py +6 -0
- ciphergate/cli.py +832 -0
- ciphergate/config.py +50 -0
- ciphergate/constants.py +274 -0
- ciphergate/detectors/__init__.py +5 -0
- ciphergate/detectors/base.py +53 -0
- ciphergate/detectors/custom_regex.py +37 -0
- ciphergate/detectors/dates.py +92 -0
- ciphergate/detectors/email.py +56 -0
- ciphergate/detectors/field_names.py +220 -0
- ciphergate/detectors/ip_address.py +85 -0
- ciphergate/detectors/network.py +42 -0
- ciphergate/detectors/payment_card.py +77 -0
- ciphergate/detectors/phone.py +67 -0
- ciphergate/detectors/presidio_ner.py +112 -0
- ciphergate/detectors/regex_safety.py +40 -0
- ciphergate/detectors/registry.py +194 -0
- ciphergate/detectors/secrets.py +130 -0
- ciphergate/errors.py +119 -0
- ciphergate/models/__init__.py +1 -0
- ciphergate/models/context.py +69 -0
- ciphergate/models/finding.py +108 -0
- ciphergate/models/policy.py +402 -0
- ciphergate/models/report.py +115 -0
- ciphergate/pipeline/__init__.py +1 -0
- ciphergate/pipeline/atomic_output.py +143 -0
- ciphergate/pipeline/batch.py +533 -0
- ciphergate/pipeline/inspector.py +282 -0
- ciphergate/pipeline/paths.py +98 -0
- ciphergate/pipeline/planner.py +192 -0
- ciphergate/pipeline/rescan.py +120 -0
- ciphergate/pipeline/sanitizer.py +392 -0
- ciphergate/pipeline/stdio.py +173 -0
- ciphergate/pipeline/streams.py +152 -0
- ciphergate/policy/__init__.py +26 -0
- ciphergate/policy/context.py +203 -0
- ciphergate/policy/engine.py +194 -0
- ciphergate/policy/loader.py +116 -0
- ciphergate/policy/precedence.py +149 -0
- ciphergate/policy/validator.py +632 -0
- ciphergate/py.typed +0 -0
- ciphergate/readers/__init__.py +113 -0
- ciphergate/readers/accesslog_reader.py +244 -0
- ciphergate/readers/base.py +131 -0
- ciphergate/readers/csv_reader.py +241 -0
- ciphergate/readers/json_reader.py +184 -0
- ciphergate/readers/jsonl_reader.py +97 -0
- ciphergate/readers/jsonlog_reader.py +178 -0
- ciphergate/readers/line_structure.py +125 -0
- ciphergate/readers/logfmt_reader.py +100 -0
- ciphergate/readers/stacktrace_reader.py +103 -0
- ciphergate/readers/syslog_reader.py +309 -0
- ciphergate/readers/text_reader.py +59 -0
- ciphergate/reporting/__init__.py +1 -0
- ciphergate/reporting/audit.py +85 -0
- ciphergate/reporting/safe_text.py +71 -0
- ciphergate/reporting/serializer.py +44 -0
- ciphergate/reporting/terminal.py +147 -0
- ciphergate/templates/starter_policy.yml +77 -0
- ciphergate/transforms/__init__.py +6 -0
- ciphergate/transforms/date_shift.py +79 -0
- ciphergate/transforms/generalize.py +107 -0
- ciphergate/transforms/keys.py +177 -0
- ciphergate/transforms/pseudonymize.py +113 -0
- ciphergate/transforms/redact.py +9 -0
- ciphergate/transforms/registry.py +201 -0
- ciphergate/transforms/replace.py +16 -0
- ciphergate_cli-0.7.0.dist-info/METADATA +531 -0
- ciphergate_cli-0.7.0.dist-info/RECORD +74 -0
- ciphergate_cli-0.7.0.dist-info/WHEEL +4 -0
- ciphergate_cli-0.7.0.dist-info/entry_points.txt +2 -0
- ciphergate_cli-0.7.0.dist-info/licenses/LICENSE +202 -0
- 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)
|