codeforge-dev 1.5.7 → 1.7.0
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.
- package/.devcontainer/.env +2 -1
- package/.devcontainer/CHANGELOG.md +55 -9
- package/.devcontainer/CLAUDE.md +65 -15
- package/.devcontainer/README.md +67 -6
- package/.devcontainer/config/keybindings.json +5 -0
- package/.devcontainer/config/main-system-prompt.md +63 -2
- package/.devcontainer/config/settings.json +25 -6
- package/.devcontainer/devcontainer.json +23 -7
- package/.devcontainer/features/README.md +21 -7
- package/.devcontainer/features/ccburn/README.md +60 -0
- package/.devcontainer/features/ccburn/devcontainer-feature.json +38 -0
- package/.devcontainer/features/ccburn/install.sh +174 -0
- package/.devcontainer/features/ccstatusline/README.md +22 -21
- package/.devcontainer/features/ccstatusline/devcontainer-feature.json +1 -1
- package/.devcontainer/features/ccstatusline/install.sh +48 -16
- package/.devcontainer/features/claude-code/config/settings.json +60 -24
- package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +1 -1
- package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/__pycache__/format-on-stop.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-on-stop.py +21 -6
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/__pycache__/lint-file.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +7 -10
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/REVIEW-RUBRIC.md +440 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/architect.md +190 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/bash-exec.md +173 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/claude-guide.md +155 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/dependency-analyst.md +248 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/doc-writer.md +233 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/explorer.md +235 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/generalist.md +125 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/git-archaeologist.md +242 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/migrator.md +195 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/perf-profiler.md +265 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/refactorer.md +209 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/researcher.md +195 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/security-auditor.md +289 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/spec-writer.md +284 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/statusline-config.md +188 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/test-writer.md +245 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/hooks/hooks.json +12 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/guard-readonly-bash.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/redirect-builtin-agents.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/skill-suggester.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/syntax-validator.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/verify-no-regression.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/verify-tests-pass.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/guard-readonly-bash.py +611 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/redirect-builtin-agents.py +83 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/skill-suggester.py +85 -2
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/syntax-validator.py +9 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/verify-no-regression.py +221 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/verify-tests-pass.py +176 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/claude-agent-sdk/SKILL.md +599 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/claude-agent-sdk/references/sdk-typescript-reference.md +954 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/SKILL.md +276 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/references/advanced-commands.md +332 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/references/investigation-playbooks.md +319 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/SKILL.md +341 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/references/interpreting-results.md +235 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/references/tool-commands.md +395 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/SKILL.md +344 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/references/safe-transformations.md +247 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/references/smell-catalog.md +332 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/SKILL.md +277 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/references/owasp-patterns.md +269 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/references/secrets-patterns.md +253 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/SKILL.md +288 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/references/criteria-patterns.md +245 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/references/ears-templates.md +239 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/__pycache__/guard-protected.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +40 -39
- package/.devcontainer/scripts/setup-aliases.sh +10 -20
- package/.devcontainer/scripts/setup-config.sh +2 -0
- package/.devcontainer/scripts/setup-plugins.sh +38 -46
- package/.devcontainer/scripts/setup-projects.sh +175 -0
- package/.devcontainer/scripts/setup-symlink-claude.sh +36 -0
- package/.devcontainer/scripts/setup-update-claude.sh +11 -8
- package/.devcontainer/scripts/setup.sh +4 -2
- package/package.json +1 -1
- package/.devcontainer/scripts/setup-irie-claude.sh +0 -32
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# OWASP Top 10 Patterns with Code Examples
|
|
2
|
+
|
|
3
|
+
Detailed vulnerability patterns with vulnerable and fixed code for each OWASP category.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [A01: Broken Access Control](#a01-broken-access-control)
|
|
8
|
+
- [A02: Cryptographic Failures](#a02-cryptographic-failures)
|
|
9
|
+
- [A03: Injection](#a03-injection)
|
|
10
|
+
- [A05: Security Misconfiguration](#a05-security-misconfiguration)
|
|
11
|
+
- [A07: Authentication Failures](#a07-authentication-failures)
|
|
12
|
+
- [A10: Server-Side Request Forgery (SSRF)](#a10-server-side-request-forgery-ssrf)
|
|
13
|
+
- [Cross-Cutting Patterns](#cross-cutting-patterns)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## A01: Broken Access Control
|
|
18
|
+
|
|
19
|
+
### Insecure Direct Object Reference (IDOR)
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
# VULNERABLE: No authorization check — any user can access any order
|
|
23
|
+
@app.get("/api/orders/{order_id}")
|
|
24
|
+
async def get_order(order_id: int):
|
|
25
|
+
return db.get_order(order_id)
|
|
26
|
+
|
|
27
|
+
# FIXED: Verify the requesting user owns the resource
|
|
28
|
+
@app.get("/api/orders/{order_id}")
|
|
29
|
+
async def get_order(order_id: int, current_user: User = Depends(get_current_user)):
|
|
30
|
+
order = db.get_order(order_id)
|
|
31
|
+
if order.user_id != current_user.id:
|
|
32
|
+
raise HTTPException(status_code=403, detail="Forbidden")
|
|
33
|
+
return order
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Missing Function-Level Access Control
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# VULNERABLE: Admin endpoint with no role check
|
|
40
|
+
@app.delete("/api/users/{user_id}")
|
|
41
|
+
async def delete_user(user_id: int, current_user: User = Depends(get_current_user)):
|
|
42
|
+
db.delete_user(user_id)
|
|
43
|
+
|
|
44
|
+
# FIXED: Enforce role-based access
|
|
45
|
+
@app.delete("/api/users/{user_id}")
|
|
46
|
+
async def delete_user(user_id: int, current_user: User = Depends(require_admin)):
|
|
47
|
+
db.delete_user(user_id)
|
|
48
|
+
|
|
49
|
+
def require_admin(current_user: User = Depends(get_current_user)):
|
|
50
|
+
if current_user.role != "admin":
|
|
51
|
+
raise HTTPException(status_code=403, detail="Admin required")
|
|
52
|
+
return current_user
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## A02: Cryptographic Failures
|
|
58
|
+
|
|
59
|
+
### Weak Password Hashing
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# VULNERABLE: MD5 is not a password hash — it's fast and rainbow-table-vulnerable
|
|
63
|
+
import hashlib
|
|
64
|
+
hashed = hashlib.md5(password.encode()).hexdigest()
|
|
65
|
+
|
|
66
|
+
# FIXED: Use bcrypt with automatic salting
|
|
67
|
+
import bcrypt
|
|
68
|
+
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
|
|
69
|
+
|
|
70
|
+
# Verification
|
|
71
|
+
if bcrypt.checkpw(password.encode(), stored_hash):
|
|
72
|
+
grant_access()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Sensitive Data in Logs
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# VULNERABLE: Logging sensitive data
|
|
79
|
+
logger.info(f"User login: email={email}, password={password}")
|
|
80
|
+
logger.debug(f"API response: {response.json()}") # may contain tokens
|
|
81
|
+
|
|
82
|
+
# FIXED: Redact sensitive fields
|
|
83
|
+
logger.info(f"User login: email={email}")
|
|
84
|
+
logger.debug(f"API response: status={response.status_code}")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## A03: Injection
|
|
90
|
+
|
|
91
|
+
### SQL Injection
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# VULNERABLE: String interpolation in SQL
|
|
95
|
+
query = f"SELECT * FROM users WHERE email = '{email}' AND password = '{password}'"
|
|
96
|
+
cursor.execute(query)
|
|
97
|
+
# Attack: email = "' OR '1'='1' --"
|
|
98
|
+
|
|
99
|
+
# FIXED: Parameterized query
|
|
100
|
+
cursor.execute("SELECT * FROM users WHERE email = ? AND password = ?", (email, password_hash))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Command Injection
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// VULNERABLE: User input in shell command
|
|
107
|
+
const { exec } = require("child_process");
|
|
108
|
+
exec(`convert ${req.body.filename} output.png`);
|
|
109
|
+
// Attack: filename = "input.jpg; rm -rf /"
|
|
110
|
+
|
|
111
|
+
// FIXED: Use execFile with argument array (no shell)
|
|
112
|
+
const { execFile } = require("child_process");
|
|
113
|
+
execFile("convert", [req.body.filename, "output.png"]);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Template Injection (SSTI)
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# VULNERABLE: User input in template string
|
|
120
|
+
from jinja2 import Template
|
|
121
|
+
template = Template(f"Hello {user_input}")
|
|
122
|
+
# Attack: user_input = "{{ config.items() }}"
|
|
123
|
+
|
|
124
|
+
# FIXED: Pass user input as a variable, not template content
|
|
125
|
+
from jinja2 import Environment, select_autoescape
|
|
126
|
+
env = Environment(autoescape=select_autoescape())
|
|
127
|
+
template = env.from_string("Hello {{ name }}")
|
|
128
|
+
result = template.render(name=user_input)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## A05: Security Misconfiguration
|
|
134
|
+
|
|
135
|
+
### CORS Misconfiguration
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
# VULNERABLE: Allow all origins
|
|
139
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
140
|
+
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True)
|
|
141
|
+
|
|
142
|
+
# FIXED: Explicit origin allowlist
|
|
143
|
+
app.add_middleware(
|
|
144
|
+
CORSMiddleware,
|
|
145
|
+
allow_origins=["https://myapp.com", "https://staging.myapp.com"],
|
|
146
|
+
allow_credentials=True,
|
|
147
|
+
allow_methods=["GET", "POST", "PUT", "DELETE"],
|
|
148
|
+
allow_headers=["Authorization", "Content-Type"],
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Debug Mode in Production
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# VULNERABLE: Debug mode exposes stack traces and internal state
|
|
156
|
+
app = Flask(__name__)
|
|
157
|
+
app.run(debug=True) # NEVER in production
|
|
158
|
+
|
|
159
|
+
# FIXED: Environment-driven configuration
|
|
160
|
+
import os
|
|
161
|
+
app.run(debug=os.getenv("FLASK_ENV") == "development")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## A07: Authentication Failures
|
|
167
|
+
|
|
168
|
+
### No Rate Limiting on Login
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# VULNERABLE: No limit on login attempts
|
|
172
|
+
@app.post("/login")
|
|
173
|
+
async def login(email: str, password: str):
|
|
174
|
+
user = db.get_user_by_email(email)
|
|
175
|
+
if user and verify_password(password, user.password_hash):
|
|
176
|
+
return create_token(user)
|
|
177
|
+
raise HTTPException(status_code=401)
|
|
178
|
+
|
|
179
|
+
# FIXED: Rate limiting with exponential backoff
|
|
180
|
+
from slowapi import Limiter
|
|
181
|
+
limiter = Limiter(key_func=get_remote_address)
|
|
182
|
+
|
|
183
|
+
@app.post("/login")
|
|
184
|
+
@limiter.limit("5/minute")
|
|
185
|
+
async def login(request: Request, email: str, password: str):
|
|
186
|
+
user = db.get_user_by_email(email)
|
|
187
|
+
if user and verify_password(password, user.password_hash):
|
|
188
|
+
return create_token(user)
|
|
189
|
+
await asyncio.sleep(random.uniform(0.1, 0.5)) # timing attack mitigation
|
|
190
|
+
raise HTTPException(status_code=401)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## A10: Server-Side Request Forgery (SSRF)
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
# VULNERABLE: User-supplied URL fetched without validation
|
|
199
|
+
@app.post("/fetch-url")
|
|
200
|
+
async def fetch_url(url: str):
|
|
201
|
+
response = httpx.get(url) # can access internal services
|
|
202
|
+
return response.text
|
|
203
|
+
# Attack: url = "http://169.254.169.254/latest/meta-data/" (AWS metadata)
|
|
204
|
+
|
|
205
|
+
# FIXED: Validate URL against allowlist and block internal ranges
|
|
206
|
+
import ipaddress
|
|
207
|
+
from urllib.parse import urlparse
|
|
208
|
+
|
|
209
|
+
BLOCKED_NETWORKS = [
|
|
210
|
+
ipaddress.ip_network("10.0.0.0/8"),
|
|
211
|
+
ipaddress.ip_network("172.16.0.0/12"),
|
|
212
|
+
ipaddress.ip_network("192.168.0.0/16"),
|
|
213
|
+
ipaddress.ip_network("169.254.0.0/16"),
|
|
214
|
+
ipaddress.ip_network("127.0.0.0/8"),
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
def is_safe_url(url: str) -> bool:
|
|
218
|
+
parsed = urlparse(url)
|
|
219
|
+
if parsed.scheme not in ("http", "https"):
|
|
220
|
+
return False
|
|
221
|
+
try:
|
|
222
|
+
ip = ipaddress.ip_address(parsed.hostname)
|
|
223
|
+
return not any(ip in net for net in BLOCKED_NETWORKS)
|
|
224
|
+
except ValueError:
|
|
225
|
+
# Hostname, not IP — resolve and check
|
|
226
|
+
import socket
|
|
227
|
+
ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
|
|
228
|
+
return not any(ip in net for net in BLOCKED_NETWORKS)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Cross-Cutting Patterns
|
|
234
|
+
|
|
235
|
+
### Security Headers (All Frameworks)
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
# FastAPI middleware for security headers
|
|
239
|
+
@app.middleware("http")
|
|
240
|
+
async def add_security_headers(request, call_next):
|
|
241
|
+
response = await call_next(request)
|
|
242
|
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
243
|
+
response.headers["X-Frame-Options"] = "DENY"
|
|
244
|
+
response.headers["X-XSS-Protection"] = "0" # disabled in favor of CSP
|
|
245
|
+
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
|
246
|
+
response.headers["Content-Security-Policy"] = "default-src 'self'"
|
|
247
|
+
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
|
|
248
|
+
return response
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Input Validation Pattern
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from pydantic import BaseModel, Field, field_validator
|
|
255
|
+
|
|
256
|
+
class CreateUserRequest(BaseModel):
|
|
257
|
+
email: str = Field(..., max_length=254)
|
|
258
|
+
name: str = Field(..., min_length=1, max_length=100)
|
|
259
|
+
age: int = Field(..., ge=0, le=150)
|
|
260
|
+
|
|
261
|
+
@field_validator("email")
|
|
262
|
+
@classmethod
|
|
263
|
+
def validate_email(cls, v: str) -> str:
|
|
264
|
+
if "@" not in v or ".." in v:
|
|
265
|
+
raise ValueError("Invalid email format")
|
|
266
|
+
return v.lower().strip()
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Always validate at the boundary — even if the frontend validates, the backend must validate independently.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Secrets Detection Patterns
|
|
2
|
+
|
|
3
|
+
Comprehensive regex patterns and strategies for detecting accidentally committed secrets.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Cloud Provider Keys](#cloud-provider-keys)
|
|
8
|
+
- [Version Control Platform Tokens](#version-control-platform-tokens)
|
|
9
|
+
- [API Keys and Tokens](#api-keys-and-tokens)
|
|
10
|
+
- [Database Connection Strings](#database-connection-strings)
|
|
11
|
+
- [Certificates and Private Keys](#certificates-and-private-keys)
|
|
12
|
+
- [High-Entropy String Detection](#high-entropy-string-detection)
|
|
13
|
+
- [Prevention Strategies](#prevention-strategies)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Cloud Provider Keys
|
|
18
|
+
|
|
19
|
+
### AWS
|
|
20
|
+
|
|
21
|
+
```regex
|
|
22
|
+
# AWS Access Key ID (starts with AKIA)
|
|
23
|
+
AKIA[0-9A-Z]{16}
|
|
24
|
+
|
|
25
|
+
# AWS Secret Access Key (40-char base64 near "aws" keyword)
|
|
26
|
+
(?i)aws(.{0,20})?(?-i)['\"][0-9a-zA-Z/+]{40}['\"]
|
|
27
|
+
|
|
28
|
+
# AWS Session Token
|
|
29
|
+
(?i)aws(.{0,10})?session(.{0,10})?token(.{0,5})?['\"][A-Za-z0-9/+=]{100,}['\"]
|
|
30
|
+
|
|
31
|
+
# AWS Account ID (12 digits, context-dependent)
|
|
32
|
+
(?i)(aws.?account.?id|account.?id)[\s:=]*['\"]?\d{12}['\"]?
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Google Cloud
|
|
36
|
+
|
|
37
|
+
```regex
|
|
38
|
+
# GCP Service Account Key (JSON key file identifier)
|
|
39
|
+
\"type\":\s*\"service_account\"
|
|
40
|
+
|
|
41
|
+
# GCP API Key
|
|
42
|
+
AIza[0-9A-Za-z\\-_]{35}
|
|
43
|
+
|
|
44
|
+
# GCP OAuth Client Secret
|
|
45
|
+
(?i)(client_secret|client.secret)[\s:=]*['\"][a-zA-Z0-9_-]{24,}['\"]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Azure
|
|
49
|
+
|
|
50
|
+
```regex
|
|
51
|
+
# Azure Storage Account Key
|
|
52
|
+
(?i)(AccountKey|account_key|azure.storage.key)[\s:=]*['\"][A-Za-z0-9+/=]{86,88}['\"]
|
|
53
|
+
|
|
54
|
+
# Azure Connection String
|
|
55
|
+
(?i)(DefaultEndpointsProtocol=https?;AccountName=)[^\s;]+;AccountKey=[A-Za-z0-9+/=]+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Version Control Platform Tokens
|
|
61
|
+
|
|
62
|
+
```regex
|
|
63
|
+
# GitHub Personal Access Token (classic)
|
|
64
|
+
ghp_[A-Za-z0-9_]{36}
|
|
65
|
+
|
|
66
|
+
# GitHub OAuth Access Token
|
|
67
|
+
gho_[A-Za-z0-9_]{36}
|
|
68
|
+
|
|
69
|
+
# GitHub User-to-Server Token
|
|
70
|
+
ghu_[A-Za-z0-9_]{36}
|
|
71
|
+
|
|
72
|
+
# GitHub Server-to-Server Token
|
|
73
|
+
ghs_[A-Za-z0-9_]{36}
|
|
74
|
+
|
|
75
|
+
# GitHub Refresh Token
|
|
76
|
+
ghr_[A-Za-z0-9_]{36}
|
|
77
|
+
|
|
78
|
+
# GitLab Personal/Project/Group Access Token
|
|
79
|
+
glpat-[A-Za-z0-9_\-]{20,}
|
|
80
|
+
|
|
81
|
+
# Bitbucket App Password
|
|
82
|
+
(?i)(bitbucket.{0,20})(password|secret|token)[\s:=]*['\"][A-Za-z0-9]{20,}['\"]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## API Keys and Tokens
|
|
88
|
+
|
|
89
|
+
```regex
|
|
90
|
+
# Generic API Key pattern (key=value or key:value)
|
|
91
|
+
(?i)(api[_-]?key|apikey|api[_-]?secret|api[_-]?token)[\s]*[=:]\s*['\"][a-zA-Z0-9_\-]{16,}['\"]
|
|
92
|
+
|
|
93
|
+
# Bearer Token in code
|
|
94
|
+
(?i)(bearer|authorization)[\s:=]*['\"]Bearer\s+[A-Za-z0-9\-._~+/]+=*['\"]
|
|
95
|
+
|
|
96
|
+
# Slack Token
|
|
97
|
+
xox[bpors]-[0-9a-zA-Z]{10,250}
|
|
98
|
+
|
|
99
|
+
# Slack Webhook URL
|
|
100
|
+
https://hooks\.slack\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+
|
|
101
|
+
|
|
102
|
+
# Stripe API Key
|
|
103
|
+
(?:sk|pk)_(test|live)_[0-9a-zA-Z]{24,}
|
|
104
|
+
|
|
105
|
+
# Twilio Account SID and Auth Token
|
|
106
|
+
AC[a-f0-9]{32}
|
|
107
|
+
(?i)(twilio.{0,20})(auth.?token|secret)[\s:=]*['\"][a-f0-9]{32}['\"]
|
|
108
|
+
|
|
109
|
+
# SendGrid API Key
|
|
110
|
+
SG\.[A-Za-z0-9_\-]{22}\.[A-Za-z0-9_\-]{43}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Database Connection Strings
|
|
116
|
+
|
|
117
|
+
```regex
|
|
118
|
+
# PostgreSQL
|
|
119
|
+
(?i)postgres(ql)?://[^:]+:[^@]+@[^\s]+
|
|
120
|
+
|
|
121
|
+
# MySQL
|
|
122
|
+
(?i)mysql://[^:]+:[^@]+@[^\s]+
|
|
123
|
+
|
|
124
|
+
# MongoDB
|
|
125
|
+
(?i)mongodb(\+srv)?://[^:]+:[^@]+@[^\s]+
|
|
126
|
+
|
|
127
|
+
# Redis with password
|
|
128
|
+
(?i)redis://:[^@]+@[^\s]+
|
|
129
|
+
|
|
130
|
+
# JDBC with password
|
|
131
|
+
(?i)jdbc:[a-z]+://[^?]+\?.*password=[^\s&]+
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Certificates and Private Keys
|
|
137
|
+
|
|
138
|
+
```regex
|
|
139
|
+
# PEM-encoded private keys
|
|
140
|
+
-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----
|
|
141
|
+
|
|
142
|
+
# PEM-encoded certificates (less sensitive but may indicate bundled keys)
|
|
143
|
+
-----BEGIN CERTIFICATE-----
|
|
144
|
+
|
|
145
|
+
# PKCS#12 / PFX files (binary, detect by extension)
|
|
146
|
+
\.(p12|pfx)$
|
|
147
|
+
|
|
148
|
+
# SSH private key (not PEM format)
|
|
149
|
+
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## High-Entropy String Detection
|
|
155
|
+
|
|
156
|
+
For secrets that don't match known patterns, detect high-entropy strings:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
import math
|
|
160
|
+
import re
|
|
161
|
+
from collections import Counter
|
|
162
|
+
|
|
163
|
+
def shannon_entropy(s: str) -> float:
|
|
164
|
+
"""Calculate Shannon entropy of a string."""
|
|
165
|
+
if not s:
|
|
166
|
+
return 0.0
|
|
167
|
+
length = len(s)
|
|
168
|
+
freq = Counter(s)
|
|
169
|
+
return -sum((count / length) * math.log2(count / length) for count in freq.values())
|
|
170
|
+
|
|
171
|
+
def is_potential_secret(value: str) -> bool:
|
|
172
|
+
"""Heuristic: long, high-entropy strings in assignment contexts."""
|
|
173
|
+
if len(value) < 16:
|
|
174
|
+
return False
|
|
175
|
+
entropy = shannon_entropy(value)
|
|
176
|
+
# Base64 strings: entropy > 4.5, hex strings: entropy > 3.5
|
|
177
|
+
if entropy > 4.5 and len(value) >= 20:
|
|
178
|
+
return True
|
|
179
|
+
if entropy > 3.5 and len(value) >= 32 and re.match(r'^[0-9a-fA-F]+$', value):
|
|
180
|
+
return True
|
|
181
|
+
return False
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Entropy thresholds:**
|
|
185
|
+
- Random hex (32+ chars): entropy > 3.5
|
|
186
|
+
- Random base64 (20+ chars): entropy > 4.5
|
|
187
|
+
- English text: entropy ~ 3.5-4.0
|
|
188
|
+
- Code identifiers: entropy ~ 2.5-3.5
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Prevention Strategies
|
|
193
|
+
|
|
194
|
+
### Pre-commit Hook with gitleaks
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Install gitleaks
|
|
198
|
+
brew install gitleaks # macOS
|
|
199
|
+
# or download from https://github.com/gitleaks/gitleaks/releases
|
|
200
|
+
|
|
201
|
+
# Run manually
|
|
202
|
+
gitleaks detect --source . --verbose
|
|
203
|
+
|
|
204
|
+
# Pre-commit hook (.pre-commit-config.yaml)
|
|
205
|
+
repos:
|
|
206
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
207
|
+
rev: v8.18.0
|
|
208
|
+
hooks:
|
|
209
|
+
- id: gitleaks
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### .gitignore Patterns
|
|
213
|
+
|
|
214
|
+
```gitignore
|
|
215
|
+
# Environment files
|
|
216
|
+
.env
|
|
217
|
+
.env.*
|
|
218
|
+
!.env.example
|
|
219
|
+
|
|
220
|
+
# Private keys and certificates
|
|
221
|
+
*.pem
|
|
222
|
+
*.key
|
|
223
|
+
*.p12
|
|
224
|
+
*.pfx
|
|
225
|
+
*.jks
|
|
226
|
+
|
|
227
|
+
# Credential files
|
|
228
|
+
credentials.json
|
|
229
|
+
service-account*.json
|
|
230
|
+
*-credentials.*
|
|
231
|
+
*.keystore
|
|
232
|
+
|
|
233
|
+
# IDE and tool secrets
|
|
234
|
+
.idea/dataSources.xml
|
|
235
|
+
.vscode/settings.json # may contain tokens
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Emergency Response
|
|
239
|
+
|
|
240
|
+
If a secret is committed:
|
|
241
|
+
|
|
242
|
+
1. **Rotate the secret immediately** -- this is the priority, not git history cleanup
|
|
243
|
+
2. **Revoke the old secret** in the provider's console
|
|
244
|
+
3. **Remove from git history** (optional, for hygiene):
|
|
245
|
+
```bash
|
|
246
|
+
# Using git-filter-repo (preferred over filter-branch)
|
|
247
|
+
pip install git-filter-repo
|
|
248
|
+
git filter-repo --invert-paths --path secrets.json
|
|
249
|
+
```
|
|
250
|
+
4. **Force push** the cleaned history (coordinate with team)
|
|
251
|
+
5. **Audit access logs** for the compromised secret's usage during exposure window
|
|
252
|
+
|
|
253
|
+
**Critical:** Removing a secret from git history does NOT make it safe. If the repo was ever pushed to a remote (GitHub, GitLab), the secret was cached by the platform and potentially scraped by bots within minutes. Always rotate first.
|