commitguardian 0.1.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 (197) hide show
  1. commitguard/__init__.py +26 -0
  2. commitguard/__main__.py +6 -0
  3. commitguard/api/__init__.py +18 -0
  4. commitguard/api/app.py +1376 -0
  5. commitguard/api/governance.py +1085 -0
  6. commitguard/api/hosting.py +196 -0
  7. commitguard/api/http.py +252 -0
  8. commitguard/api/settings.py +169 -0
  9. commitguard/audit/__init__.py +13 -0
  10. commitguard/audit/logger.py +34 -0
  11. commitguard/audit/models.py +222 -0
  12. commitguard/audit/storage.py +59 -0
  13. commitguard/ci/__init__.py +7 -0
  14. commitguard/ci/context.py +60 -0
  15. commitguard/cli/__init__.py +6 -0
  16. commitguard/cli/app.py +74 -0
  17. commitguard/cli/commands/__init__.py +1 -0
  18. commitguard/cli/commands/benchmark.py +441 -0
  19. commitguard/cli/commands/check.py +100 -0
  20. commitguard/cli/commands/ci.py +165 -0
  21. commitguard/cli/commands/dashboard.py +141 -0
  22. commitguard/cli/commands/doctor.py +533 -0
  23. commitguard/cli/commands/github.py +449 -0
  24. commitguard/cli/commands/hook.py +156 -0
  25. commitguard/cli/commands/init.py +137 -0
  26. commitguard/cli/commands/install.py +152 -0
  27. commitguard/cli/commands/policy.py +36 -0
  28. commitguard/cli/commands/report.py +39 -0
  29. commitguard/cli/commands/reproduce.py +123 -0
  30. commitguard/cli/commands/scan.py +47 -0
  31. commitguard/cli/common.py +44 -0
  32. commitguard/cli/output.py +89 -0
  33. commitguard/cli/render.py +367 -0
  34. commitguard/config/__init__.py +6 -0
  35. commitguard/config/defaults.py +53 -0
  36. commitguard/config/enforcement.py +53 -0
  37. commitguard/config/loader.py +174 -0
  38. commitguard/config/schema.py +105 -0
  39. commitguard/config/sources.py +183 -0
  40. commitguard/controlplane/__init__.py +24 -0
  41. commitguard/controlplane/access.py +231 -0
  42. commitguard/controlplane/commands.py +393 -0
  43. commitguard/controlplane/errors.py +88 -0
  44. commitguard/controlplane/identity.py +478 -0
  45. commitguard/controlplane/members.py +219 -0
  46. commitguard/controlplane/notifications.py +787 -0
  47. commitguard/controlplane/pagination.py +146 -0
  48. commitguard/controlplane/policies.py +1204 -0
  49. commitguard/controlplane/queries.py +1814 -0
  50. commitguard/controlplane/results.py +909 -0
  51. commitguard/controlplane/rules.py +184 -0
  52. commitguard/controlplane/views.py +799 -0
  53. commitguard/core/__init__.py +6 -0
  54. commitguard/core/context.py +31 -0
  55. commitguard/core/decision.py +58 -0
  56. commitguard/core/engine.py +82 -0
  57. commitguard/core/result.py +177 -0
  58. commitguard/detectors/__init__.py +6 -0
  59. commitguard/detectors/base.py +58 -0
  60. commitguard/detectors/bot.py +87 -0
  61. commitguard/detectors/coauthor.py +86 -0
  62. commitguard/detectors/identity.py +76 -0
  63. commitguard/detectors/registry.py +72 -0
  64. commitguard/detectors/trailer.py +211 -0
  65. commitguard/exceptions/__init__.py +33 -0
  66. commitguard/exceptions/base.py +9 -0
  67. commitguard/exceptions/configuration.py +22 -0
  68. commitguard/exceptions/detection.py +11 -0
  69. commitguard/exceptions/git.py +41 -0
  70. commitguard/exceptions/service.py +25 -0
  71. commitguard/git/__init__.py +12 -0
  72. commitguard/git/commands.py +101 -0
  73. commitguard/git/commit.py +97 -0
  74. commitguard/git/diff.py +36 -0
  75. commitguard/git/hooks.py +527 -0
  76. commitguard/git/push.py +93 -0
  77. commitguard/git/ranges.py +71 -0
  78. commitguard/git/repository.py +447 -0
  79. commitguard/github/__init__.py +34 -0
  80. commitguard/github/actions.py +163 -0
  81. commitguard/github/app.py +935 -0
  82. commitguard/github/auth.py +217 -0
  83. commitguard/github/check_runs.py +172 -0
  84. commitguard/github/checks.py +210 -0
  85. commitguard/github/client.py +844 -0
  86. commitguard/github/enforcement_status.py +209 -0
  87. commitguard/github/errors.py +129 -0
  88. commitguard/github/events.py +563 -0
  89. commitguard/github/identifiers.py +90 -0
  90. commitguard/github/installations.py +566 -0
  91. commitguard/github/markdown.py +19 -0
  92. commitguard/github/permissions.py +70 -0
  93. commitguard/github/pull_requests.py +53 -0
  94. commitguard/github/queue.py +47 -0
  95. commitguard/github/recovery.py +124 -0
  96. commitguard/github/repositories.py +305 -0
  97. commitguard/github/server.py +52 -0
  98. commitguard/github/settings.py +174 -0
  99. commitguard/github/storage.py +2315 -0
  100. commitguard/github/webhooks.py +129 -0
  101. commitguard/github/worker.py +628 -0
  102. commitguard/github/workflow.py +286 -0
  103. commitguard/governance/__init__.py +26 -0
  104. commitguard/governance/bulk.py +765 -0
  105. commitguard/governance/cache.py +88 -0
  106. commitguard/governance/common.py +216 -0
  107. commitguard/governance/exceptions.py +861 -0
  108. commitguard/governance/groups.py +448 -0
  109. commitguard/governance/inventory.py +386 -0
  110. commitguard/governance/posture.py +1272 -0
  111. commitguard/governance/resolver.py +632 -0
  112. commitguard/governance/rollouts.py +760 -0
  113. commitguard/governance/rules.py +371 -0
  114. commitguard/governance/schedules.py +663 -0
  115. commitguard/governance/service.py +120 -0
  116. commitguard/governance/settings.py +365 -0
  117. commitguard/governance/simulation.py +618 -0
  118. commitguard/governance/workflow.py +734 -0
  119. commitguard/notifications/__init__.py +2 -0
  120. commitguard/notifications/channels/__init__.py +1 -0
  121. commitguard/notifications/channels/base.py +22 -0
  122. commitguard/notifications/channels/email.py +110 -0
  123. commitguard/notifications/channels/in_app.py +74 -0
  124. commitguard/notifications/channels/sink.py +58 -0
  125. commitguard/notifications/channels/webhook.py +233 -0
  126. commitguard/notifications/deduplication.py +57 -0
  127. commitguard/notifications/dispatcher.py +201 -0
  128. commitguard/notifications/models.py +439 -0
  129. commitguard/notifications/outbox.py +106 -0
  130. commitguard/notifications/preferences.py +224 -0
  131. commitguard/notifications/retry.py +282 -0
  132. commitguard/notifications/service.py +128 -0
  133. commitguard/notifications/settings.py +167 -0
  134. commitguard/notifications/templates.py +108 -0
  135. commitguard/observability/__init__.py +5 -0
  136. commitguard/observability/logging.py +161 -0
  137. commitguard/observability/metrics.py +105 -0
  138. commitguard/policies/__init__.py +6 -0
  139. commitguard/policies/defaults.py +48 -0
  140. commitguard/policies/evaluator.py +66 -0
  141. commitguard/policies/governance.py +498 -0
  142. commitguard/policies/loader.py +23 -0
  143. commitguard/policies/mandatory.py +52 -0
  144. commitguard/policies/model.py +46 -0
  145. commitguard/provenance/__init__.py +9 -0
  146. commitguard/provenance/author.py +146 -0
  147. commitguard/provenance/committer.py +16 -0
  148. commitguard/provenance/normalization.py +158 -0
  149. commitguard/provenance/signatures.py +34 -0
  150. commitguard/provenance/trailers.py +256 -0
  151. commitguard/research/__init__.py +26 -0
  152. commitguard/research/compare.py +231 -0
  153. commitguard/research/datasets.py +1484 -0
  154. commitguard/research/detection.py +183 -0
  155. commitguard/research/environment.py +185 -0
  156. commitguard/research/gitenv.py +108 -0
  157. commitguard/research/hooks.py +247 -0
  158. commitguard/research/metrics.py +85 -0
  159. commitguard/research/performance.py +194 -0
  160. commitguard/research/platform.py +288 -0
  161. commitguard/research/report.py +372 -0
  162. commitguard/research/repository.py +111 -0
  163. commitguard/research/reproduction.py +297 -0
  164. commitguard/research/results.py +94 -0
  165. commitguard/rules/__init__.py +11 -0
  166. commitguard/rules/data/ai-domains.yaml +51 -0
  167. commitguard/rules/data/ai-identities.yaml +131 -0
  168. commitguard/rules/data/bot-identities.yaml +53 -0
  169. commitguard/rules/data/patterns.yaml +52 -0
  170. commitguard/rules/loader.py +102 -0
  171. commitguard/rules/matcher.py +212 -0
  172. commitguard/rules/models.py +269 -0
  173. commitguard/security/__init__.py +5 -0
  174. commitguard/security/hashing.py +30 -0
  175. commitguard/security/rate_limit.py +33 -0
  176. commitguard/security/safe_yaml.py +69 -0
  177. commitguard/security/sanitization.py +85 -0
  178. commitguard/security/secrets.py +169 -0
  179. commitguard/security/validation.py +89 -0
  180. commitguard/services/__init__.py +15 -0
  181. commitguard/services/analysis.py +119 -0
  182. commitguard/services/audit.py +95 -0
  183. commitguard/services/ci.py +383 -0
  184. commitguard/services/enforcement.py +102 -0
  185. commitguard/services/hooks.py +254 -0
  186. commitguard/services/remediation.py +99 -0
  187. commitguard/services/reports.py +146 -0
  188. commitguard/services/scan.py +172 -0
  189. commitguard/utils/__init__.py +1 -0
  190. commitguard/utils/filesystem.py +72 -0
  191. commitguard/utils/platform.py +35 -0
  192. commitguard/utils/subprocess.py +84 -0
  193. commitguardian-0.1.0.dist-info/METADATA +694 -0
  194. commitguardian-0.1.0.dist-info/RECORD +197 -0
  195. commitguardian-0.1.0.dist-info/WHEEL +4 -0
  196. commitguardian-0.1.0.dist-info/entry_points.txt +2 -0
  197. commitguardian-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,129 @@
1
+ """Webhook boundary: authenticity, headers, size and JSON validation.
2
+
3
+ Processing order for every delivery (nothing untrusted is parsed before the
4
+ signature is verified)::
5
+
6
+ size limit -> X-Hub-Signature-256 (HMAC-SHA256, constant-time compare)
7
+ -> X-GitHub-Event / X-GitHub-Delivery format
8
+ -> strict JSON object (UTF-8, no duplicate keys, bounded depth)
9
+
10
+ A delivery that fails any step raises :class:`WebhookValidationError` carrying
11
+ the HTTP status to return (401 for authenticity, 400/413/415 otherwise); the
12
+ event is never processed. Replay protection (delivery ID deduplication) is
13
+ applied afterwards by :mod:`commitguard.github.storage`, because only a
14
+ verified delivery may consume a delivery ID.
15
+ """
16
+
17
+ import hashlib
18
+ import hmac
19
+ import json
20
+ import re
21
+ from collections.abc import Mapping
22
+ from dataclasses import dataclass
23
+ from typing import Any
24
+
25
+ from commitguard.github.errors import WebhookValidationError
26
+ from commitguard.security.secrets import Secret
27
+
28
+ SIGNATURE_HEADER = "x-hub-signature-256"
29
+ EVENT_HEADER = "x-github-event"
30
+ DELIVERY_HEADER = "x-github-delivery"
31
+ MAX_WEBHOOK_BYTES = 25 * 1024 * 1024 # GitHub caps webhook payloads at 25 MB
32
+ MAX_JSON_DEPTH = 64
33
+
34
+ _SIGNATURE_RE = re.compile(r"\Asha256=([0-9a-f]{64})\Z")
35
+ _EVENT_RE = re.compile(r"\A[a-z_]{1,64}\Z")
36
+ _DELIVERY_RE = re.compile(r"\A[0-9A-Za-z-]{8,72}\Z")
37
+
38
+
39
+ def compute_signature(secret: Secret, body: bytes) -> str:
40
+ """The ``X-Hub-Signature-256`` value GitHub sends for ``body``."""
41
+ digest = hmac.new(secret.reveal().encode("utf-8"), body, hashlib.sha256).hexdigest()
42
+ return f"sha256={digest}"
43
+
44
+
45
+ def verify_signature(secret: Secret, body: bytes, header: str | None) -> None:
46
+ """Raise ``WebhookValidationError(401)`` unless ``header`` authenticates ``body``."""
47
+ if not secret:
48
+ raise WebhookValidationError("webhook secret is not configured", status=500)
49
+ if header is None or header == "":
50
+ raise WebhookValidationError("missing webhook signature", status=401)
51
+ match = _SIGNATURE_RE.match(header.strip())
52
+ if match is None:
53
+ raise WebhookValidationError("malformed webhook signature", status=401)
54
+ expected = compute_signature(secret, body)[len("sha256=") :]
55
+ if not hmac.compare_digest(expected.encode("ascii"), match.group(1).encode("ascii")):
56
+ raise WebhookValidationError("invalid webhook signature", status=401)
57
+
58
+
59
+ @dataclass(frozen=True, slots=True)
60
+ class WebhookDelivery:
61
+ delivery_id: str
62
+ event: str
63
+ payload: dict[str, Any]
64
+ body_sha256: str
65
+
66
+
67
+ def _reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
68
+ result: dict[str, Any] = {}
69
+ for key, value in pairs:
70
+ if key in result:
71
+ raise ValueError("duplicate JSON object key")
72
+ result[key] = value
73
+ return result
74
+
75
+
76
+ def _depth_ok(value: Any, limit: int) -> bool:
77
+ stack: list[tuple[Any, int]] = [(value, 1)]
78
+ while stack:
79
+ item, depth = stack.pop()
80
+ if depth > limit:
81
+ return False
82
+ if isinstance(item, dict):
83
+ stack.extend((v, depth + 1) for v in item.values())
84
+ elif isinstance(item, list):
85
+ stack.extend((v, depth + 1) for v in item)
86
+ return True
87
+
88
+
89
+ def parse_json_object(body: bytes) -> dict[str, Any]:
90
+ if not body:
91
+ raise WebhookValidationError("empty webhook payload")
92
+ try:
93
+ document = json.loads(body.decode("utf-8"), object_pairs_hook=_reject_duplicate_keys)
94
+ except (UnicodeDecodeError, ValueError, RecursionError):
95
+ raise WebhookValidationError("webhook payload is not valid JSON") from None
96
+ if not isinstance(document, dict):
97
+ raise WebhookValidationError("webhook payload must be a JSON object")
98
+ if not _depth_ok(document, MAX_JSON_DEPTH):
99
+ raise WebhookValidationError("webhook payload is nested too deeply")
100
+ return document
101
+
102
+
103
+ def header(headers: Mapping[str, str], name: str) -> str | None:
104
+ """Case-insensitive header lookup."""
105
+ lowered = name.lower()
106
+ for key, value in headers.items():
107
+ if key.lower() == lowered:
108
+ return value
109
+ return None
110
+
111
+
112
+ def parse_delivery(headers: Mapping[str, str], body: bytes, secret: Secret) -> WebhookDelivery:
113
+ """Authenticate and parse one webhook delivery."""
114
+ if len(body) > MAX_WEBHOOK_BYTES:
115
+ raise WebhookValidationError("webhook payload too large", status=413)
116
+ verify_signature(secret, body, header(headers, SIGNATURE_HEADER))
117
+ event = header(headers, EVENT_HEADER)
118
+ if event is None or not _EVENT_RE.match(event):
119
+ raise WebhookValidationError("missing or invalid X-GitHub-Event header")
120
+ delivery_id = header(headers, DELIVERY_HEADER)
121
+ if delivery_id is None or not _DELIVERY_RE.match(delivery_id):
122
+ raise WebhookValidationError("missing or invalid X-GitHub-Delivery header")
123
+ payload = parse_json_object(body)
124
+ return WebhookDelivery(
125
+ delivery_id=delivery_id,
126
+ event=event,
127
+ payload=payload,
128
+ body_sha256=hashlib.sha256(body).hexdigest(),
129
+ )