first commit
This commit is contained in:
284
SECURITY.md
Normal file
284
SECURITY.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# Security Implementation Guide
|
||||
|
||||
This document outlines the security measures implemented in Notely.
|
||||
|
||||
## 🔐 Authentication Security
|
||||
|
||||
### Password Hashing
|
||||
|
||||
- **Algorithm**: Argon2id (memory-hard, resistant to GPU attacks)
|
||||
- **Configuration**:
|
||||
- Memory: 64 MB
|
||||
- Time: 1 iteration
|
||||
- Parallelism: 4 threads
|
||||
- Salt: 16 random bytes (cryptographically secure)
|
||||
|
||||
```go
|
||||
// Generated hash format:
|
||||
$argon2id$v=19$m=65536,t=1,p=4$salt_hex$hash_hex
|
||||
```
|
||||
|
||||
### JWT Tokens
|
||||
|
||||
- **Algorithm**: HS256 (HMAC-SHA256)
|
||||
- **Access Token TTL**: 1 hour
|
||||
- **Refresh Token TTL**: 7 days (HTTP-only secure cookie)
|
||||
- **Claims**:
|
||||
- `user_id`: User's MongoDB ObjectID
|
||||
- `email`: User's email address
|
||||
- `username`: User's username
|
||||
- `iat`: Issued at timestamp
|
||||
- `exp`: Expiration timestamp
|
||||
- `iss`: Issuer (verified against hardcoded value)
|
||||
|
||||
### Brute-Force Protection
|
||||
|
||||
- Track failed login attempts in `login_attempts` collection
|
||||
- Rate limit: Max 5 failed attempts per IP per 15 minutes
|
||||
- Account lockout: 15 minutes after 5 consecutive failures
|
||||
- Cleanup: Expired records auto-deleted via TTL index
|
||||
|
||||
## 🛡️ Authorization Security
|
||||
|
||||
### Role-Based Access Control (RBAC)
|
||||
|
||||
```
|
||||
Space Roles:
|
||||
├── Owner (all permissions)
|
||||
├── Editor (create/edit/delete notes)
|
||||
└── Viewer (read-only)
|
||||
```
|
||||
|
||||
### Space-Level Data Isolation
|
||||
|
||||
**ALL queries include mandatory `space_id` filter**
|
||||
|
||||
```go
|
||||
// Correct query pattern:
|
||||
db.notes.find({ space_id: spaceID, ... })
|
||||
|
||||
// Never allow:
|
||||
db.notes.find({ user_id: userID }) // ❌ Cross-space leak possible
|
||||
```
|
||||
|
||||
### Middleware Authorization Flow
|
||||
|
||||
```
|
||||
1. Extract JWT token → Verify signature & expiration
|
||||
2. Load user credentials → Verify user is active
|
||||
3. Check space membership → Verify user_id + space_id + role
|
||||
4. Execute request → With space_id context
|
||||
```
|
||||
|
||||
## 🔑 Data Encryption
|
||||
|
||||
### At Rest
|
||||
|
||||
- OAuth client secrets encrypted with AES-256-GCM
|
||||
- Stored in MongoDB with encryption key in environment variables
|
||||
- Decryption happens only when reading from database
|
||||
|
||||
```go
|
||||
plaintext, err := encryptor.Encrypt(clientSecret) // Stores encrypted blob
|
||||
recovered, err := encryptor.Decrypt(plaintext) // Decrypts on retrieval
|
||||
```
|
||||
|
||||
### In Transit
|
||||
|
||||
- HTTPS/TLS required in production (enforced via Nginx)
|
||||
- Secure cookies: `Secure`, `HttpOnly`, `SameSite=Lax` flags
|
||||
- All sensitive data transmitted over encrypted channels
|
||||
|
||||
## 🚨 Input Validation
|
||||
|
||||
### Backend Validation (MANDATORY)
|
||||
|
||||
Every endpoint validates:
|
||||
|
||||
1. **Type validation** - JSON schema validation
|
||||
2. **Length limits** - min/max string lengths
|
||||
3. **Format validation** - email, ObjectID, URL formats
|
||||
4. **Range validation** - pagination limits
|
||||
|
||||
```go
|
||||
type CreateNoteRequest struct {
|
||||
Title string `validate:"required,min=1,max=255"`
|
||||
Content string `validate:"max=50000"`
|
||||
Tags []string `validate:"max=100,dive,max=50"`
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend Validation
|
||||
|
||||
- **Input sanitization** - trim whitespace
|
||||
- **Format validation** - regex patterns
|
||||
- **Debounced searches** - prevent query spam
|
||||
- **Client-side feedback** - improve UX
|
||||
|
||||
### Output Sanitization
|
||||
|
||||
Markdown → HTML conversion sanitized with DOMPurify:
|
||||
|
||||
```javascript
|
||||
// XSS prevention
|
||||
const dirty = marked.parse(userMarkdown);
|
||||
const clean = DOMPurify.sanitize(dirty);
|
||||
|
||||
// Blocks: scripts, event handlers, dangerous attributes
|
||||
```
|
||||
|
||||
## 🌐 Web Security Headers
|
||||
|
||||
Implemented via Nginx and Go middleware:
|
||||
|
||||
| Header | Value | Purpose |
|
||||
| --------------------------- | --------------------------------- | ------------------------------- |
|
||||
| `Strict-Transport-Security` | `max-age=31536000` | Force HTTPS |
|
||||
| `X-Content-Type-Options` | `nosniff` | Prevent MIME sniffing |
|
||||
| `X-Frame-Options` | `DENY` | Prevent clickjacking |
|
||||
| `X-XSS-Protection` | `1; mode=block` | XSS protection (older browsers) |
|
||||
| `Content-Security-Policy` | Restrictive policy | Prevent XSS attacks |
|
||||
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Referrer control |
|
||||
|
||||
**CSP Policy:**
|
||||
|
||||
```
|
||||
default-src 'self'
|
||||
script-src 'self' 'unsafe-inline' (for development only)
|
||||
style-src 'self' 'unsafe-inline'
|
||||
img-src 'self' data: https:
|
||||
font-src 'self'
|
||||
connect-src 'self'
|
||||
frame-ancestors 'none'
|
||||
```
|
||||
|
||||
## 🍪 Cookie Security
|
||||
|
||||
### Access Token (via Authorization header)
|
||||
|
||||
- Stored in **memory** (not localStorage)
|
||||
- Passed via `Authorization: Bearer {token}`
|
||||
|
||||
### Refresh Token (HTTP-only cookie)
|
||||
|
||||
```go
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: token,
|
||||
Path: "/",
|
||||
MaxAge: 7 * 24 * 60 * 60, // 7 days
|
||||
HttpOnly: true, // ✅ Cannot access from JavaScript
|
||||
Secure: true, // ✅ HTTPS only
|
||||
SameSite: http.SameSiteLaxMode, // ✅ CSRF protection
|
||||
})
|
||||
```
|
||||
|
||||
## 🔄 Rate Limiting
|
||||
|
||||
### API Rate Limiting
|
||||
|
||||
- **General**: 50 requests / second per IP
|
||||
- **Login**: 10 requests / second per IP
|
||||
- **Burst allowance**: 20 additional requests
|
||||
|
||||
```nginx
|
||||
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||||
limit_req zone=api_limit burst=20 nodelay;
|
||||
```
|
||||
|
||||
### Login Attempt Tracking
|
||||
|
||||
- Track per email + IP combination
|
||||
- Maximum 5 attempts per 15 minutes
|
||||
- Exponential backoff on repeated failures
|
||||
|
||||
## 🔒 Database Security
|
||||
|
||||
### MongoDB
|
||||
|
||||
- **Authentication**: Username/password with role-based access
|
||||
- **Network**: Runs in secure Docker network (not exposed)
|
||||
- **Admin credentials**: Stored in Kubernetes Secrets (not in code)
|
||||
- **Backups**: TBD - use MongoDB Atlas or encrypted backups
|
||||
|
||||
### Connection String
|
||||
|
||||
```
|
||||
mongodb://admin:password@mongodb:27017/dbname?authSource=admin
|
||||
```
|
||||
|
||||
## 🚨 Logging & Monitoring
|
||||
|
||||
### Security Events Logged
|
||||
|
||||
- ✅ User registration attempts
|
||||
- ✅ Login attempts (success/failure)
|
||||
- ✅ Authorization failures
|
||||
- ✅ Permission denied events
|
||||
- ✅ Sensitive data access
|
||||
|
||||
### Data NOT logged
|
||||
|
||||
- ❌ Passwords/hashes
|
||||
- ❌ JWT tokens
|
||||
- ❌ Encryption keys
|
||||
- ❌ OAuth secrets
|
||||
|
||||
## 🧪 Security Testing
|
||||
|
||||
### What to Test
|
||||
|
||||
1. **Authentication**: Register, login, token refresh, logout
|
||||
2. **Authorization**: RBAC enforcement, space isolation
|
||||
3. **Input validation**: Invalid data rejection
|
||||
4. **XSS prevention**: Markdown sanitization
|
||||
5. **CSRF protection**: Token validation
|
||||
6. **Rate limiting**: Too many requests blocked
|
||||
7. **SQL Injection**: MongoDB-specific (parameterized queries safe)
|
||||
|
||||
### Manual Testing Commands
|
||||
|
||||
```bash
|
||||
# Test invalid input
|
||||
curl -X POST http://localhost:8080/api/v1/auth/login \
|
||||
-d '{"email":"not-an-email","password":""}'
|
||||
|
||||
# Test expired token
|
||||
curl -H "Authorization: Bearer expired.token.here" \
|
||||
http://localhost:8080/api/v1/spaces
|
||||
|
||||
# Test rate limiting
|
||||
for i in {1..100}; do
|
||||
curl http://localhost:8080/api/v1/auth/login &
|
||||
done
|
||||
```
|
||||
|
||||
## 🛠️ Production Checklist
|
||||
|
||||
- [ ] Change default JWT_SECRET (min 32 characters)
|
||||
- [ ] Change default ENCRYPTION_KEY (32 bytes)
|
||||
- [ ] Generate TLS certificates (Let's Encrypt recommended)
|
||||
- [ ] Configure Nginx SSL/TLS
|
||||
- [ ] Enable HTTPS redirect
|
||||
- [ ] Set up database backups
|
||||
- [ ] Configure logging & monitoring
|
||||
- [ ] Implement CORS whitelist (specific domains)
|
||||
- [ ] Set up rate limiting (tuned to your traffic)
|
||||
- [ ] Enable database authentication
|
||||
- [ ] Use Kubernetes Network Policies
|
||||
- [ ] Set up Pod Security Policies
|
||||
- [ ] Enable audit logging
|
||||
- [ ] Configure Secrets encryption at rest
|
||||
|
||||
## 📚 References
|
||||
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [MongoDB Security](https://docs.mongodb.com/manual/security/)
|
||||
- [JWT Best Practices](https://tools.ietf.org/html/rfc8949)
|
||||
- [Argon2 Specification](https://github.com/P-H-C/phc-winner-argon2)
|
||||
- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: March 2026
|
||||
**Security Level**: Production-Grade
|
||||
Reference in New Issue
Block a user