Major updates: - Added 35+ new skills from awesome-opencode-skills and antigravity repos - Merged SEO skills into seo-master - Merged architecture skills into architecture - Merged security skills into security-auditor and security-coder - Merged testing skills into testing-master and testing-patterns - Merged pentesting skills into pentesting - Renamed website-creator to thai-frontend-dev - Replaced skill-creator with github version - Removed Chutes references (use MiniMax API instead) - Added install-openclaw-skills.sh for cross-platform installation - Updated .env.example with MiniMax API credentials
325 lines
7.4 KiB
Markdown
325 lines
7.4 KiB
Markdown
---
|
|
name: security-coder
|
|
description: |
|
|
Master secure coding skill combining frontend, backend, and mobile security coding.
|
|
XSS prevention, injection prevention, authentication, API security.
|
|
Use when writing secure code or fixing security vulnerabilities.
|
|
---
|
|
|
|
# Security Coder
|
|
|
|
Comprehensive secure coding skill combining: frontend security, backend security, and mobile security coding practices.
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
| Task | Use Section |
|
|
|------|-------------|
|
|
| Prevent XSS | **Frontend Security** |
|
|
| Secure APIs | **Backend Security** |
|
|
| Mobile app security | **Mobile Security** |
|
|
| Fix vulnerabilities | **Common Fixes** |
|
|
| Input validation | **Input Security** |
|
|
| Auth implementation | **Authentication** |
|
|
|
|
---
|
|
|
|
## Frontend Security (XSS Prevention)
|
|
|
|
### Output Handling
|
|
| Unsafe | Safe |
|
|
|--------|------|
|
|
| `element.innerHTML = userInput` | `element.textContent = userInput` |
|
|
| `document.write(userInput)` | `element.appendChild(safeEl)` |
|
|
| `eval(userInput)` | Never eval user input |
|
|
| `href=userInput` (javascript:) | Validate protocol, use `#` for unknown |
|
|
|
|
### Sanitization
|
|
```javascript
|
|
// Use DOMPurify for HTML content
|
|
import DOMPurify from 'dompurify';
|
|
const clean = DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['b', 'i'] });
|
|
|
|
// Always use textContent for user data
|
|
element.textContent = userData;
|
|
|
|
// Encode for context
|
|
// HTML: < > & "
|
|
// JS: \x3C \x3E \u2028 \u2029
|
|
// URL: encodeURIComponent()
|
|
// CSS: escape or use allowlist
|
|
```
|
|
|
|
### Content Security Policy (CSP)
|
|
```http
|
|
Content-Security-Policy:
|
|
default-src 'self';
|
|
script-src 'self' 'nonce-{random}' https://trusted-cdn.com;
|
|
style-src 'self' 'nonce-{random}';
|
|
img-src 'self' https: data:;
|
|
font-src 'self';
|
|
connect-src 'self' https://api.example.com;
|
|
frame-ancestors 'none';
|
|
base-uri 'self';
|
|
form-action 'self';
|
|
```
|
|
|
|
### Safe DOM Manipulation
|
|
- Always prefer `textContent` over `innerHTML`
|
|
- Use `document.createElement()` over `document.write()`
|
|
- Validate URLs before setting `href`, `src`, `action`
|
|
- Use `srcdoc` only with sanitized content
|
|
|
|
---
|
|
|
|
## Backend Security (Injection Prevention)
|
|
|
|
### SQL Injection Prevention
|
|
```python
|
|
# BAD - vulnerable
|
|
query = f"SELECT * FROM users WHERE id = {user_id}"
|
|
|
|
# GOOD - parameterized
|
|
query = "SELECT * FROM users WHERE id = %s"
|
|
cursor.execute(query, (user_id,))
|
|
|
|
# GOOD - ORM
|
|
user = User.query.filter_by(id=user_id).first()
|
|
```
|
|
|
|
### NoSQL Injection Prevention
|
|
```python
|
|
# Validate input types
|
|
if not isinstance(user_id, int):
|
|
raise ValueError("Invalid ID type")
|
|
|
|
# Use parameterized queries
|
|
result = db.users.find_one({"_id": ObjectId(user_id)})
|
|
```
|
|
|
|
### Command Injection Prevention
|
|
```python
|
|
# NEVER use user input in shell commands
|
|
# BAD
|
|
os.system(f"grep {user_input} file")
|
|
|
|
# GOOD - subprocess with list
|
|
subprocess.run(["grep", user_input, "file"], shell=False)
|
|
|
|
# GOOD - shlex.quote
|
|
os.system(f"grep {shlex.quote(user_input)} file")
|
|
```
|
|
|
|
### Input Validation
|
|
```python
|
|
# Allowlist validation
|
|
import re
|
|
def validate_email(email):
|
|
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
return re.match(pattern, email) is not None
|
|
|
|
# Type validation
|
|
def get_user_id(user_id):
|
|
if isinstance(user_id, str) and user_id.isdigit():
|
|
return int(user_id)
|
|
raise ValueError("Invalid user ID")
|
|
|
|
# Range validation
|
|
def get_page(page, max_page=100):
|
|
page = int(page)
|
|
if page < 1:
|
|
page = 1
|
|
return min(page, max_page)
|
|
```
|
|
|
|
---
|
|
|
|
## API Security
|
|
|
|
### Authentication Headers
|
|
```http
|
|
Authorization: Bearer <jwt_token>
|
|
X-API-Key: <api_key>
|
|
```
|
|
|
|
### Rate Limiting
|
|
```python
|
|
# Implement rate limiting
|
|
from functools import wraps
|
|
import time
|
|
|
|
def rate_limit(max_calls, period):
|
|
def decorator(func):
|
|
calls = {}
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
now = time.time()
|
|
key = func.__name__
|
|
if key not in calls:
|
|
calls[key] = []
|
|
calls[key] = [t for t in calls[key] if now - t < period]
|
|
if len(calls[key]) >= max_calls:
|
|
raise TooManyRequestsError()
|
|
calls[key].append(now)
|
|
return func(*args, **kwargs)
|
|
return wrapper
|
|
return decorator
|
|
```
|
|
|
|
### Security Headers
|
|
```python
|
|
response.headers.update({
|
|
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
'X-Content-Type-Options': 'nosniff',
|
|
'X-Frame-Options': 'DENY',
|
|
'X-XSS-Protection': '1; mode=block',
|
|
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
|
|
'Content-Security-Policy': "default-src 'self'"
|
|
})
|
|
```
|
|
|
|
### CORS Configuration
|
|
```python
|
|
# Strict CORS
|
|
ALLOWED_ORIGINS = ['https://app.example.com']
|
|
|
|
@app.after_request
|
|
def add_cors(response):
|
|
origin = request.headers.get('Origin')
|
|
if origin in ALLOWED_ORIGINS:
|
|
response.headers['Access-Control-Allow-Origin'] = origin
|
|
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
|
|
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
|
|
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
return response
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication Security
|
|
|
|
### Password Hashing
|
|
```python
|
|
# Use bcrypt or Argon2
|
|
import bcrypt
|
|
|
|
def hash_password(password):
|
|
return bcrypt.hashpw(password.encode(), bcrypt.gensalt())
|
|
|
|
def verify_password(password, hashed):
|
|
return bcrypt.checkpw(password.encode(), hashed)
|
|
|
|
# Or Argon2
|
|
from argon2 import PasswordHasher
|
|
ph = PasswordHasher()
|
|
hash = ph.hash("secret")
|
|
ph.verify(hash, "secret")
|
|
```
|
|
|
|
### JWT Security
|
|
```python
|
|
# Sign with RS256 (asymmetric)
|
|
# Verify with public key
|
|
import jwt
|
|
|
|
# Token structure
|
|
payload = {
|
|
'sub': user_id,
|
|
'exp': datetime.utcnow() + timedelta(hours=1),
|
|
'iat': datetime.utcnow(),
|
|
'scope': ['read', 'write'] # Minimal scopes
|
|
}
|
|
|
|
# Short expiration, no sensitive data
|
|
token = jwt.encode(payload, private_key, algorithm='RS256')
|
|
```
|
|
|
|
### Session Management
|
|
```python
|
|
# Secure session config
|
|
SESSION_COOKIE = {
|
|
'httponly': True, # No JS access
|
|
'secure': True, # HTTPS only
|
|
'samesite': 'Lax', # CSRF protection
|
|
'max-age': 3600, # 1 hour
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Mobile Security
|
|
|
|
### iOS Security
|
|
- Keychain for sensitive data
|
|
- Certificate pinning
|
|
- jailbreak detection
|
|
- Biometric auth (Face ID, Touch ID)
|
|
- Obfuscate API keys
|
|
|
|
### Android Security
|
|
- Keystore for keys
|
|
- Network security config
|
|
- ProGuard/R8 obfuscation
|
|
- Safe browsing API
|
|
- Biometric auth
|
|
|
|
### React Native
|
|
```javascript
|
|
// Secure storage
|
|
import * as Keychain from 'react-native-keychain';
|
|
|
|
// Certificate pinning
|
|
const sslPinning = {
|
|
certs: ['cert1', 'cert2']
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Common Security Fixes
|
|
|
|
### IDOR Prevention
|
|
```python
|
|
# Always verify ownership
|
|
def get_document(doc_id, user_id):
|
|
doc = Document.query.get(doc_id)
|
|
if doc.owner_id != user_id:
|
|
abort(403) # Forbidden
|
|
return doc
|
|
```
|
|
|
|
### CSRF Prevention
|
|
```python
|
|
# Use CSRF tokens
|
|
from flask_wtf import FlaskForm
|
|
from wtforms import SubmitField
|
|
|
|
class MyForm(FlaskForm):
|
|
csrf_token = HiddenField()
|
|
submit = SubmitField()
|
|
```
|
|
|
|
### XXE Prevention
|
|
```python
|
|
# Disable XML external entities
|
|
import defusedxml
|
|
ET = defusedxml.ElementSafeTypes
|
|
```
|
|
|
|
---
|
|
|
|
## Security Checklist
|
|
|
|
- [ ] Input validation on all user data
|
|
- [ ] Parameterized queries (no string interpolation)
|
|
- [ ] Output encoding for context
|
|
- [ ] CSRF tokens on forms
|
|
- [ ] Secure session configuration
|
|
- [ ] HTTPS only
|
|
- [ ] Security headers
|
|
- [ ] Rate limiting
|
|
- [ ] Logging without sensitive data
|
|
- [ ] Error messages without stack traces
|