gitrupt 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 (48) hide show
  1. gitrupt/__init__.py +13 -0
  2. gitrupt/cli.py +546 -0
  3. gitrupt/config.py +269 -0
  4. gitrupt/git.py +590 -0
  5. gitrupt/hooks/__init__.py +7 -0
  6. gitrupt/hooks/install.py +255 -0
  7. gitrupt/hooks/pre_commit.py +103 -0
  8. gitrupt/hooks/pre_push.py +178 -0
  9. gitrupt/models.py +190 -0
  10. gitrupt/policy.py +36 -0
  11. gitrupt/reporting.py +316 -0
  12. gitrupt/risk.py +197 -0
  13. gitrupt/scanner.py +117 -0
  14. gitrupt/scanners/__init__.py +17 -0
  15. gitrupt/scanners/adapters.py +166 -0
  16. gitrupt/scanners/base.py +113 -0
  17. gitrupt/scanners/binaries.py +185 -0
  18. gitrupt/scanners/code_rules/__init__.py +36 -0
  19. gitrupt/scanners/code_rules/base.py +27 -0
  20. gitrupt/scanners/code_rules/go.py +65 -0
  21. gitrupt/scanners/code_rules/javascript.py +106 -0
  22. gitrupt/scanners/code_rules/php.py +71 -0
  23. gitrupt/scanners/code_rules/powershell.py +85 -0
  24. gitrupt/scanners/code_rules/python.py +153 -0
  25. gitrupt/scanners/code_rules/ruby.py +76 -0
  26. gitrupt/scanners/code_rules/rust.py +41 -0
  27. gitrupt/scanners/code_rules/shell.py +112 -0
  28. gitrupt/scanners/dependencies.py +244 -0
  29. gitrupt/scanners/ecosystems/__init__.py +30 -0
  30. gitrupt/scanners/ecosystems/base.py +60 -0
  31. gitrupt/scanners/ecosystems/node.py +128 -0
  32. gitrupt/scanners/ecosystems/python.py +157 -0
  33. gitrupt/scanners/entropy.py +123 -0
  34. gitrupt/scanners/forbidden_files.py +201 -0
  35. gitrupt/scanners/malware.py +219 -0
  36. gitrupt/scanners/osv_client.py +221 -0
  37. gitrupt/scanners/registry.py +66 -0
  38. gitrupt/scanners/secret_rules.py +368 -0
  39. gitrupt/scanners/secrets.py +558 -0
  40. gitrupt/scanners/suspicious_code.py +208 -0
  41. gitrupt/scanners/yara_loader.py +65 -0
  42. gitrupt/scanners/yara_rules_builtin.py +141 -0
  43. gitrupt-0.1.0.dist-info/METADATA +342 -0
  44. gitrupt-0.1.0.dist-info/RECORD +48 -0
  45. gitrupt-0.1.0.dist-info/WHEEL +5 -0
  46. gitrupt-0.1.0.dist-info/entry_points.txt +2 -0
  47. gitrupt-0.1.0.dist-info/licenses/LICENSE +23 -0
  48. gitrupt-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,368 @@
1
+ """
2
+ Provider-specific secret detection rules.
3
+
4
+ Every rule is a compiled regex + metadata. Add a provider by appending a
5
+ SecretRule to PROVIDER_RULES. The scanner picks it up automatically.
6
+
7
+ Design notes:
8
+ - Patterns are anchored to the prefix the provider guarantees.
9
+ - Where the provider gives a known length, we require it, to avoid
10
+ matching fragments of longer strings.
11
+ - Where lengths vary, we use {N,M} ranges, never bare +.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ from dataclasses import dataclass
18
+
19
+ from gitrupt.models import Severity
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class SecretRule:
24
+ rule_id: str
25
+ pattern: re.Pattern
26
+ severity: Severity
27
+ confidence: float
28
+ message: str
29
+ description: str
30
+ recommendation: str
31
+ # When set, the rule only fires if one of these words appears
32
+ # within CONTEXT_WINDOW characters of the match. Used to suppress
33
+ # entropy-only false positives.
34
+ context_words: tuple[str, ...] = ()
35
+
36
+
37
+ CONTEXT_WINDOW = 120
38
+
39
+ # ═══════════════════════════════════════════════════════════════════════════
40
+ # AI providers
41
+ # ═══════════════════════════════════════════════════════════════════════════
42
+
43
+ AI_RULES: tuple[SecretRule, ...] = (
44
+ SecretRule(
45
+ rule_id="openai-project-key",
46
+ pattern=re.compile(r"\bsk-proj-[A-Za-z0-9_-]{80,200}\b"),
47
+ severity=Severity.CRITICAL,
48
+ confidence=0.95,
49
+ message="OpenAI project API key detected",
50
+ description="OpenAI project-scoped key (sk-proj-). Grants access to the project's resources.",
51
+ recommendation="Revoke in platform.openai.com → API keys, then rotate.",
52
+ ),
53
+ SecretRule(
54
+ rule_id="openai-legacy-key",
55
+ pattern=re.compile(r"\bsk-[A-Za-z0-9]{48}\b"),
56
+ severity=Severity.CRITICAL,
57
+ confidence=0.85,
58
+ message="OpenAI legacy API key detected",
59
+ description="Legacy OpenAI key format (sk- + 48 chars).",
60
+ recommendation="Migrate to project-scoped keys and revoke the legacy key.",
61
+ ),
62
+ SecretRule(
63
+ rule_id="anthropic-api-key",
64
+ pattern=re.compile(r"\bsk-ant-api\d{2}-[A-Za-z0-9_-]{80,120}\b"),
65
+ severity=Severity.CRITICAL,
66
+ confidence=0.95,
67
+ message="Anthropic (Claude) API key detected",
68
+ description="Anthropic API key (sk-ant-apiNN-).",
69
+ recommendation="Revoke at console.anthropic.com → API keys.",
70
+ ),
71
+ SecretRule(
72
+ rule_id="anthropic-admin-key",
73
+ pattern=re.compile(r"\bsk-ant-admin\d{2}-[A-Za-z0-9_-]{80,120}\b"),
74
+ severity=Severity.CRITICAL,
75
+ confidence=0.95,
76
+ message="Anthropic admin API key detected",
77
+ description="Anthropic admin key — broader privileges than a standard key.",
78
+ recommendation="Revoke immediately at console.anthropic.com.",
79
+ ),
80
+ SecretRule(
81
+ rule_id="groq-api-key",
82
+ pattern=re.compile(r"\bgsk_[A-Za-z0-9]{52}\b"),
83
+ severity=Severity.CRITICAL,
84
+ confidence=0.95,
85
+ message="Groq API key detected",
86
+ description="Groq key (gsk_ + 52 alphanumeric). Full API access, no scopes.",
87
+ recommendation="Revoke at console.groq.com/keys.",
88
+ ),
89
+ SecretRule(
90
+ rule_id="xai-api-key",
91
+ pattern=re.compile(r"\bxai-[A-Za-z0-9]{20,80}\b"),
92
+ severity=Severity.CRITICAL,
93
+ confidence=0.9,
94
+ message="xAI (Grok) API key detected",
95
+ description="xAI key (xai- prefix).",
96
+ recommendation="Revoke at console.x.ai → API keys.",
97
+ ),
98
+ SecretRule(
99
+ rule_id="google-ai-api-key",
100
+ pattern=re.compile(r"\bAIza[0-9A-Za-z_\-]{35}\b"),
101
+ severity=Severity.HIGH,
102
+ confidence=0.85,
103
+ message="Google API key detected (Gemini / Maps / GCP)",
104
+ description="Google API key (AIza + 35 chars).",
105
+ recommendation="Restrict the key by API + referrer in GCP console.",
106
+ ),
107
+ SecretRule(
108
+ rule_id="huggingface-token",
109
+ pattern=re.compile(r"\bhf_[A-Za-z0-9]{34,}\b"),
110
+ severity=Severity.HIGH,
111
+ confidence=0.9,
112
+ message="Hugging Face access token detected",
113
+ description="HF token (hf_ prefix).",
114
+ recommendation="Revoke at huggingface.co/settings/tokens.",
115
+ ),
116
+ SecretRule(
117
+ rule_id="replicate-token",
118
+ pattern=re.compile(r"\br8_[A-Za-z0-9]{36,40}\b"),
119
+ severity=Severity.HIGH,
120
+ confidence=0.9,
121
+ message="Replicate API token detected",
122
+ description="Replicate token (r8_ + 36–40 chars).",
123
+ recommendation="Revoke at replicate.com/account/api-tokens.",
124
+ ),
125
+ SecretRule(
126
+ rule_id="perplexity-api-key",
127
+ pattern=re.compile(r"\bpplx-[A-Za-z0-9]{48}\b"),
128
+ severity=Severity.HIGH,
129
+ confidence=0.9,
130
+ message="Perplexity API key detected",
131
+ description="Perplexity key (pplx- + 48 chars).",
132
+ recommendation="Revoke at perplexity.ai/settings/api.",
133
+ ),
134
+ SecretRule(
135
+ rule_id="cohere-api-key",
136
+ pattern=re.compile(r"\bco-[A-Za-z0-9]{38,42}\b"),
137
+ severity=Severity.MEDIUM,
138
+ confidence=0.6,
139
+ message="Possible Cohere API key",
140
+ description="Cohere key format is 40-char base62 with optional co- prefix.",
141
+ recommendation="Verify in Cohere dashboard and revoke if real.",
142
+ context_words=("cohere", "co_", "COHERE"),
143
+ ),
144
+ )
145
+
146
+ # ═══════════════════════════════════════════════════════════════════════════
147
+ # Payments, email, comms
148
+ # ═══════════════════════════════════════════════════════════════════════════
149
+
150
+ PAYMENT_RULES: tuple[SecretRule, ...] = (
151
+ SecretRule(
152
+ rule_id="stripe-live-secret",
153
+ pattern=re.compile(r"\bsk_live_[A-Za-z0-9]{24,99}\b"),
154
+ severity=Severity.CRITICAL,
155
+ confidence=0.95,
156
+ message="Stripe live secret key detected",
157
+ description="Stripe live-mode secret key — full account access.",
158
+ recommendation="Roll the key immediately in Stripe Dashboard → Developers → API keys.",
159
+ ),
160
+ SecretRule(
161
+ rule_id="stripe-live-restricted",
162
+ pattern=re.compile(r"\brk_live_[A-Za-z0-9]{24,99}\b"),
163
+ severity=Severity.HIGH,
164
+ confidence=0.9,
165
+ message="Stripe live restricted key detected",
166
+ description="Stripe live restricted key.",
167
+ recommendation="Revoke in Stripe Dashboard.",
168
+ ),
169
+ SecretRule(
170
+ rule_id="sendgrid-api-key",
171
+ pattern=re.compile(r"\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b"),
172
+ severity=Severity.HIGH,
173
+ confidence=0.9,
174
+ message="SendGrid API key detected",
175
+ description="SendGrid key (SG.ID.VALUE).",
176
+ recommendation="Revoke at app.sendgrid.com → Settings → API Keys.",
177
+ ),
178
+ )
179
+
180
+ COMMS_RULES: tuple[SecretRule, ...] = (
181
+ SecretRule(
182
+ rule_id="slack-token",
183
+ pattern=re.compile(r"\bxox[baprs]-[0-9A-Za-z-]{10,}\b"),
184
+ severity=Severity.HIGH,
185
+ confidence=0.9,
186
+ message="Slack token detected",
187
+ description="Slack bot/user/app token (xox[baprs]-).",
188
+ recommendation="Revoke at api.slack.com/apps.",
189
+ ),
190
+ SecretRule(
191
+ rule_id="slack-webhook",
192
+ pattern=re.compile(
193
+ r"https://hooks\.slack\.com/services/T[A-Z0-9]{8,}/B[A-Z0-9]{8,}/[A-Za-z0-9]{20,}"
194
+ ),
195
+ severity=Severity.MEDIUM,
196
+ confidence=0.95,
197
+ message="Slack incoming webhook URL",
198
+ description="Slack webhook — anyone with this URL can post to the channel.",
199
+ recommendation="Rotate the webhook in Slack app settings.",
200
+ ),
201
+ SecretRule(
202
+ rule_id="discord-bot-token",
203
+ pattern=re.compile(
204
+ r"\b(?:mfa\.[A-Za-z0-9_-]{20,}|[MN][A-Za-z0-9_-]{23}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,39})\b"
205
+ ),
206
+ severity=Severity.HIGH,
207
+ confidence=0.85,
208
+ message="Discord bot token detected",
209
+ description="Discord token (MFA or standard three-part format).",
210
+ recommendation="Regenerate in Discord Developer Portal.",
211
+ ),
212
+ SecretRule(
213
+ rule_id="twilio-api-key",
214
+ pattern=re.compile(r"\bSK[0-9a-fA-F]{32}\b"),
215
+ severity=Severity.MEDIUM,
216
+ confidence=0.7,
217
+ message="Possible Twilio API key SID",
218
+ description="Twilio API key SID (SK + 32 hex).",
219
+ recommendation="Verify and revoke in Twilio console if real.",
220
+ ),
221
+ )
222
+
223
+ # ═══════════════════════════════════════════════════════════════════════════
224
+ # Cloud / infrastructure
225
+ # ═══════════════════════════════════════════════════════════════════════════
226
+
227
+ CLOUD_RULES: tuple[SecretRule, ...] = (
228
+ SecretRule(
229
+ rule_id="aws-access-key-id",
230
+ pattern=re.compile(
231
+ r"\b(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\b"
232
+ ),
233
+ severity=Severity.CRITICAL,
234
+ confidence=0.95,
235
+ message="AWS access key ID detected",
236
+ description="AWS access key ID — pairs with a secret key.",
237
+ recommendation="Rotate via IAM → Users → Security credentials.",
238
+ ),
239
+ SecretRule(
240
+ rule_id="aws-secret-access-key",
241
+ pattern=re.compile(r"\b[A-Za-z0-9/+=]{40}\b"),
242
+ severity=Severity.CRITICAL,
243
+ confidence=0.55,
244
+ message="Possible AWS secret access key",
245
+ description="40-char base64 string near an AWS context word.",
246
+ recommendation="Rotate via IAM → Users → Security credentials.",
247
+ context_words=(
248
+ "aws_secret", "aws_secret_access_key", "AWS_SECRET_ACCESS_KEY",
249
+ "secret_access_key", "aws", "AWS",
250
+ ),
251
+ ),
252
+ SecretRule(
253
+ rule_id="azure-connection-string",
254
+ pattern=re.compile(
255
+ r"(?:AccountKey|SharedAccessKey)=[A-Za-z0-9+/=]{40,}",
256
+ ),
257
+ severity=Severity.CRITICAL,
258
+ confidence=0.85,
259
+ message="Azure connection string credential",
260
+ description="Azure SQL/Storage connection string with embedded key.",
261
+ recommendation="Rotate the account key in the Azure portal.",
262
+ ),
263
+ SecretRule(
264
+ rule_id="azure-client-secret",
265
+ pattern=re.compile(
266
+ r"\b[A-Za-z0-9_~.]{3}\dQ~[A-Za-z0-9_~.\-]{31,34}\b"
267
+ ),
268
+ severity=Severity.HIGH,
269
+ confidence=0.6,
270
+ message="Possible Azure AD client secret",
271
+ description="Azure AD client secrets have a distinctive ~ pattern.",
272
+ recommendation="Rotate the client secret in Azure AD → App registrations.",
273
+ context_words=("client_secret", "AZURE_CLIENT_SECRET", "azure"),
274
+ ),
275
+ )
276
+
277
+ # ═══════════════════════════════════════════════════════════════════════════
278
+ # Developer tooling
279
+ # ═══════════════════════════════════════════════════════════════════════════
280
+
281
+ DEVELOPS_RULES: tuple[SecretRule, ...] = (
282
+ SecretRule(
283
+ rule_id="github-pat-classic",
284
+ pattern=re.compile(r"\bghp_[A-Za-z0-9]{36}\b"),
285
+ severity=Severity.CRITICAL,
286
+ confidence=0.95,
287
+ message="GitHub classic PAT detected",
288
+ description="GitHub personal access token (classic).",
289
+ recommendation="Revoke at github.com/settings/tokens.",
290
+ ),
291
+ SecretRule(
292
+ rule_id="github-pat-fine-grained",
293
+ pattern=re.compile(r"\bgithub_pat_[A-Za-z0-9_]{82}\b"),
294
+ severity=Severity.CRITICAL,
295
+ confidence=0.95,
296
+ message="GitHub fine-grained PAT detected",
297
+ description="GitHub fine-grained personal access token.",
298
+ recommendation="Revoke at github.com/settings/tokens.",
299
+ ),
300
+ SecretRule(
301
+ rule_id="github-server-token",
302
+ pattern=re.compile(r"\bghs_[A-Za-z0-9]{36}\b"),
303
+ severity=Severity.HIGH,
304
+ confidence=0.9,
305
+ message="GitHub server-to-server token detected",
306
+ description="GitHub App server token (ghs_).",
307
+ recommendation="Rotate the GitHub App's private key.",
308
+ ),
309
+ SecretRule(
310
+ rule_id="npm-token",
311
+ pattern=re.compile(r"\bnpm_[A-Za-z0-9]{36}\b"),
312
+ severity=Severity.HIGH,
313
+ confidence=0.9,
314
+ message="npm access token detected",
315
+ description="npm token (npm_ + 36 chars).",
316
+ recommendation="Revoke at npmjs.com → Access Tokens.",
317
+ ),
318
+ SecretRule(
319
+ rule_id="pypi-token",
320
+ pattern=re.compile(r"\bpypi-[A-Za-z0-9_-]{50,}\b"),
321
+ severity=Severity.HIGH,
322
+ confidence=0.9,
323
+ message="PyPI API token detected",
324
+ description="PyPI upload token (pypi- prefix).",
325
+ recommendation="Revoke at pypi.org → Account → API tokens.",
326
+ ),
327
+ SecretRule(
328
+ rule_id="digitalocean-token",
329
+ pattern=re.compile(r"\bdop_v1_[a-f0-9]{64}\b"),
330
+ severity=Severity.HIGH,
331
+ confidence=0.95,
332
+ message="DigitalOcean personal access token detected",
333
+ description="DigitalOcean token (dop_v1_ + 64 hex).",
334
+ recommendation="Revoke at cloud.digitalocean.com → API → Tokens.",
335
+ ),
336
+ SecretRule(
337
+ rule_id="heroku-api-key",
338
+ pattern=re.compile(
339
+ r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b"
340
+ ),
341
+ severity=Severity.LOW,
342
+ confidence=0.4,
343
+ message="Possible Heroku API key (UUID format)",
344
+ description="Heroku keys are UUIDs — only flagged near Heroku context.",
345
+ recommendation="Verify in Heroku Account → API Key.",
346
+ context_words=("heroku", "HEROKU", "heroku_api"),
347
+ ),
348
+ )
349
+
350
+ PROVIDER_RULES: tuple[SecretRule, ...] = (
351
+ AI_RULES + PAYMENT_RULES + COMMS_RULES + CLOUD_RULES + DEVELOPS_RULES
352
+ )
353
+
354
+ # ═══════════════════════════════════════════════════════════════════════════
355
+ # Private key blocks
356
+ # ═══════════════════════════════════════════════════════════════════════════
357
+
358
+ PRIVATE_KEY_RULE = SecretRule(
359
+ rule_id="private-key-block",
360
+ pattern=re.compile(
361
+ r"-----BEGIN ((?:RSA|EC|DSA|OPENSSH|PGP|SSH2|ENCRYPTED) )?PRIVATE KEY(?: BLOCK)?-----"
362
+ ),
363
+ severity=Severity.CRITICAL,
364
+ confidence=1.0,
365
+ message="Private key block detected",
366
+ description="PEM-encoded private key material.",
367
+ recommendation="Remove from the commit; rotate the key.",
368
+ )