This document outlines security considerations and best practices for deploying and operating MarkGo.
IMPORTANT: The .env file is automatically excluded from version control via .gitignore. However, you must ensure:
-
Never commit
.envto Git# Verify .env is ignored git check-ignore .env # Should output: .env
-
Use
.env.exampleas a template- Copy
.env.exampleto.envfor local development .env.examplecontains no secrets, only placeholders- Safe to commit
.env.exampleto show required configuration
- Copy
-
Rotate secrets if accidentally committed
- If you accidentally commit secrets to Git:
- Immediately rotate all exposed credentials (SMTP passwords, API keys, etc.)
- Remove the commit from Git history (use
git filter-branchor BFG Repo-Cleaner) - Never assume deletion removes the secret - anyone who pulled the repo has it
- If you accidentally commit secrets to Git:
# SMTP Credentials (for contact form)
EMAIL_SMTP_PASSWORD="your-smtp-password-here" # Keep secret!
# Admin Credentials (for /admin and /debug endpoints)
ADMIN_USERNAME="admin" # Change from default
ADMIN_PASSWORD="secure-password" # Use strong password (16+ chars, mixed case, numbers, symbols)-
.envfile is NOT in version control - SMTP password is stored securely (use secrets manager in production)
- Admin password is strong and unique
- Admin credentials are different from default values
- Environment-specific
.envfiles are used (dev, staging, prod)
MarkGo uses HTTP Basic Authentication for admin and debug endpoints. This provides minimal security suitable for:
- Internal-only deployments
- Development/staging environments
- Low-risk scenarios
Limitations of Basic Auth:
- Credentials transmitted with every request (even over HTTPS, base64-encoded)
- No session management or token expiration
- No account lockout after failed attempts
- No multi-factor authentication (MFA)
Recommendations:
-
Always use HTTPS in production
# Example nginx config server { listen 443 ssl; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://localhost:3000; } }
-
Restrict admin access by IP (recommended for production)
location /admin { allow 192.168.1.0/24; # Your office network allow 10.0.0.1; # Your home IP deny all; proxy_pass http://localhost:3000; } location /debug { allow 127.0.0.1; # Localhost only deny all; proxy_pass http://localhost:3000; }
-
Disable debug endpoints in production
- Debug endpoints (
/debug/*) are only enabled whenENVIRONMENT=development - Production deployments automatically disable these endpoints
- If you need debugging in production, use production-grade APM tools instead
- Debug endpoints (
| Endpoint Pattern | Auth Required | Environment | Purpose |
|---|---|---|---|
/admin/* |
β Basic Auth | All | Admin dashboard and management |
/debug/* |
β Basic Auth | Development only | Runtime profiling and debugging |
/api/* |
β None | All | Public API endpoints |
/articles/* |
β None | All | Public content |
What we fixed: Prevented localhost bypass vulnerability where localhost.evil.com could bypass CORS restrictions.
Implementation: Exact origin matching using map lookup:
// Secure: Only explicitly allowed origins
allowedMap := make(map[string]bool)
for _, origin := range allowedOrigins {
allowedMap[origin] = true
}
// Exact match required
if origin != "" && allowedMap[origin] {
c.Header("Access-Control-Allow-Origin", origin)
}Configuration:
# In .env - specify exact allowed origins
CORS_ALLOWED_ORIGINS="https://yourdomain.com,https://api.yourdomain.com"Protection against:
- Brute force attacks on contact form
- API abuse and spam
- Memory exhaustion attacks
Implementation:
- IP-based rate limiting with 10,000 IP cap (prevents unbounded memory growth)
- Uses
RemoteAddr(notX-Forwarded-For) to prevent header spoofing - Background cleanup every 1 minute removes stale entries
- Separate limits for general requests and contact form
Configuration:
# General rate limit
RATE_LIMIT_GENERAL_REQUESTS=100 # requests per window
RATE_LIMIT_GENERAL_WINDOW=1m # 1 minute window
# Contact form rate limit (stricter)
RATE_LIMIT_CONTACT_REQUESTS=3 # requests per window
RATE_LIMIT_CONTACT_WINDOW=15m # 15 minute windowNote: Math-based CAPTCHA is implemented on contact form for additional protection.
Automatically applied to all responses:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
- Email addresses validated using RFC-compliant regex
- Markdown content sanitized with bluemonday
- File paths validated to prevent directory traversal
- All user input validated before processing
Risk Level: Medium
Why: User explicitly declined CSRF implementation in favor of simplicity. Math CAPTCHA provides basic protection.
Mitigation:
- Math-based CAPTCHA reduces automated attacks
- Rate limiting prevents spam/abuse
- Contact form is the only write endpoint
Future consideration: Implement CSRF tokens if spam becomes an issue.
Risk Level: Medium (if not behind reverse proxy)
Why: Simple authentication sufficient for target audience (tech-savvy developers managing personal blogs).
Mitigation:
- Use strong passwords (16+ characters)
- Deploy behind reverse proxy with IP whitelisting
- Always use HTTPS in production
- Consider oauth2-proxy for enhanced security if needed
Risk Level: Low
Why: Stateless architecture for simplicity. Basic Auth credentials sent with each request.
Mitigation:
- HTTPS encrypts credentials in transit
- Short-lived connections minimize exposure
- No session cookies to steal/hijack
- Change default admin credentials
- Use strong passwords (16+ characters, mixed case, numbers, symbols)
- Review and set
CORS_ALLOWED_ORIGINSto exact allowed domains - Configure rate limits appropriate for your traffic
- Ensure
.envis in.gitignoreand not committed - Set
ENVIRONMENT=productionto disable debug endpoints
- Deploy behind reverse proxy (nginx, Caddy, etc.)
- Enable HTTPS/TLS with valid certificates
- Configure IP whitelisting for
/adminendpoints - Set up firewall rules to restrict admin access
- Use secrets manager for sensitive config (AWS Secrets Manager, HashiCorp Vault, etc.)
- Test that
/debugendpoints are disabled (should return 404) - Verify CORS headers are correctly applied
- Test rate limiting with multiple requests
- Confirm admin login works with strong password
- Monitor logs for suspicious activity
- Set up automated security scanning (Dependabot, Snyk, etc.)
- Enable structured logging in production
- Monitor failed admin login attempts
- Track rate limit violations
- Alert on unusual traffic patterns
- Regular dependency updates for security patches
# Check for known vulnerabilities
go list -json -deps ./... | nancy sleuth
# Update dependencies
go get -u ./...
go mod tidy# Run gosec security scanner
gosec ./...
# Run staticcheck
staticcheck ./...# Run all tests including security-related tests
go test ./... -v
# Run specific security tests
go test ./internal/middleware -v # CORS and rate limiting
go test ./internal/handlers -v # Contact form validationIf you discover a security vulnerability in MarkGo:
- DO NOT open a public GitHub issue
- Email security concerns to the maintainer privately
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact assessment
- Suggested fix (if available)
We take security seriously and will respond to legitimate reports promptly.
Last Updated: 2025-10-22 Version: 1.6.0