specwright/templates/skills/dev-team/devops/security-hardening/SKILL.md
# Security Hardening Skill > Template for Security Specialists (HTTPS, Secrets, Scanning) > Version: 1.0.0 > Created: 2026-01-09 ## Skill Purpose Implement comprehensive security measures including HTTPS/TLS, secrets management, vulnerability scanning, authentication hardening, and compliance with security best practices. ## When to Activate This Skill **Activate when:** - Setting up SSL/TLS certificates - Configuring secrets management - Implementing security headers - Running security aud
npx skillsauth add michsindlinger/specwright specwright/templates/skills/dev-team/devops/security-hardeningInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Template for Security Specialists (HTTPS, Secrets, Scanning) Version: 1.0.0 Created: 2026-01-09
Implement comprehensive security measures including HTTPS/TLS, secrets management, vulnerability scanning, authentication hardening, and compliance with security best practices.
Activate when:
Delegation from main agent:
@agent:[AGENT_NAME] "Set up SSL certificates and HTTPS redirect"
@agent:[AGENT_NAME] "Implement secrets management with environment variables"
@agent:[AGENT_NAME] "Add security scanning to CI/CD pipeline"
@agent:[AGENT_NAME] "Configure security headers and CSP"
Force SSL:
# config/environments/production.rb
Rails.application.configure do
# Force all access to the app over SSL
config.force_ssl = true
# Use HSTS with subdomains and preloading
config.ssl_options = {
hsts: {
expires: 1.year,
subdomains: true,
preload: true
}
}
end
Security Headers:
# config/initializers/security_headers.rb
Rails.application.config.action_dispatch.default_headers.merge!(
'X-Frame-Options' => 'DENY',
'X-Content-Type-Options' => 'nosniff',
'X-XSS-Protection' => '1; mode=block',
'Referrer-Policy' => 'strict-origin-when-cross-origin',
'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()'
)
# Content Security Policy
Rails.application.configure do
config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https
# Report violations
policy.report_uri "/csp-violation-report"
end
# Generate nonce for inline scripts
config.content_security_policy_nonce_generator = ->(request) {
SecureRandom.base64(16)
}
# Report-only mode for testing
# config.content_security_policy_report_only = true
end
Secure Cookies:
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
key: '_myapp_session',
secure: Rails.env.production?, # Only send over HTTPS
httponly: true, # Not accessible via JavaScript
same_site: :lax, # CSRF protection
expire_after: 24.hours
# config/initializers/devise.rb (if using Devise)
Devise.setup do |config|
config.rememberable_options = {
secure: true,
httponly: true,
same_site: :lax
}
# Timeout inactive sessions
config.timeout_in = 30.minutes
# Require password confirmation for sensitive actions
config.paranoid = true
# Strong password requirements
config.password_length = 12..128
end
Parameter Filtering:
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [
:password,
:password_confirmation,
:current_password,
:secret,
:token,
:api_key,
:private_key,
:ssn,
:credit_card,
/api[_-]?key/i,
/secret/i,
/token/i
]
SQL Injection Prevention:
# GOOD - Parameterized queries
User.where('email = ?', params[:email])
User.where(email: params[:email])
# BAD - String interpolation (vulnerable)
# User.where("email = '#{params[:email]}'") # NEVER DO THIS
# GOOD - Safe dynamic queries
User.where(params[:filters].permit(:email, :status))
# ActiveRecord automatically escapes
User.find_by(email: user_input)
Mass Assignment Protection:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
@user = User.new(user_params)
# ...
end
private
def user_params
# Whitelist only allowed attributes
params.require(:user).permit(:name, :email, :password, :password_confirmation)
# Never permit :admin, :role, or sensitive fields without explicit checks
end
end
Environment Variables (12-Factor):
# .env (NEVER commit this file)
DATABASE_URL=postgresql://user:password@localhost/myapp
SECRET_KEY_BASE=your_secret_key_here
AWS_ACCESS_KEY_ID=your_aws_key
AWS_SECRET_ACCESS_KEY=your_aws_secret
STRIPE_SECRET_KEY=sk_test_xxxxx
# .env.example (commit this template)
DATABASE_URL=postgresql://user:password@localhost/myapp
SECRET_KEY_BASE=generate_with_rails_secret
AWS_ACCESS_KEY_ID=your_aws_key
AWS_SECRET_ACCESS_KEY=your_aws_secret
STRIPE_SECRET_KEY=sk_test_xxxxx
# .gitignore
.env
.env.local
.env.*.local
config/master.key
config/credentials/*.key
# Load with dotenv gem (development/test only)
# Gemfile
group :development, :test do
gem 'dotenv-rails'
end
Rails Encrypted Credentials:
# Generate master key (keep this secret!)
rails credentials:edit
# credentials.yml.enc (safe to commit)
aws:
access_key_id: YOUR_ACCESS_KEY
secret_access_key: YOUR_SECRET_KEY
stripe:
publishable_key: pk_live_xxxxx
secret_key: sk_live_xxxxx
database:
password: secure_password
# Access in code
Rails.application.credentials.aws[:access_key_id]
Rails.application.credentials.stripe[:secret_key]
# Environment-specific credentials
rails credentials:edit --environment production
Rails.application.credentials.database[:password]
GitHub Secrets (for CI/CD):
# .github/workflows/ci.yml
jobs:
deploy:
steps:
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
# Deployment commands
HashiCorp Vault (Enterprise):
# Gemfile
gem 'vault'
# config/initializers/vault.rb
Vault.configure do |config|
config.address = ENV['VAULT_ADDR']
config.token = ENV['VAULT_TOKEN']
config.ssl_verify = true
end
# Usage
class SecretService
def self.database_password
Vault.logical.read('secret/data/myapp/database')&.data&.dig(:data, :password)
end
def self.api_key(service)
Vault.logical.read("secret/data/myapp/#{service}")&.data&.dig(:data, :api_key)
end
end
# Use in database.yml
production:
url: <%= "postgresql://user:#{SecretService.database_password}@host/db" %>
Bundler Audit (Ruby Dependencies):
# Install
gem install bundler-audit
# Update vulnerability database
bundle audit update
# Scan for vulnerabilities
bundle audit check
# CI Integration
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
bundle-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Run Bundler Audit
run: |
gem install bundler-audit
bundle audit update
bundle audit check
npm Audit (JavaScript Dependencies):
# Scan for vulnerabilities
npm audit
# Fix automatically
npm audit fix
# CI Integration
# .github/workflows/security.yml
- name: Run npm audit
run: npm audit --audit-level=moderate
Brakeman (Rails Static Analysis):
# Install
gem install brakeman
# Scan application
brakeman
# Generate report
brakeman -o brakeman-report.html
# CI Integration
- name: Run Brakeman
run: |
gem install brakeman
brakeman --no-pager --quiet --exit-on-warn
Container Scanning (Trivy):
# .github/workflows/container-security.yml
name: Container Security
on: [push]
jobs:
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
OWASP Dependency Check:
# .github/workflows/owasp.yml
- name: OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'myapp'
path: '.'
format: 'HTML'
Let's Encrypt (Free SSL):
# DigitalOcean App Platform
# - Automatically provisions Let's Encrypt certificates
# - Auto-renewal every 90 days
# - No configuration needed
# Manual Certbot (for custom setups)
sudo certbot certonly --standalone -d example.com -d www.example.com
# Auto-renewal cron
0 0 * * * certbot renew --quiet
Nginx SSL Configuration:
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com www.example.com;
# Redirect all HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Application proxy
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Test SSL Configuration:
# SSL Labs test
# https://www.ssllabs.com/ssltest/analyze.html?d=example.com
# Command line test
openssl s_client -connect example.com:443 -servername example.com
# Check certificate expiration
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -dates
Rack Attack:
# Gemfile
gem 'rack-attack'
# config/initializers/rack_attack.rb
class Rack::Attack
# Throttle login attempts
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
if req.path == '/users/sign_in' && req.post?
req.ip
end
end
# Throttle API requests
throttle('api/ip', limit: 100, period: 1.hour) do |req|
req.ip if req.path.start_with?('/api')
end
# Block suspicious requests
blocklist('block bad actors') do |req|
# Block IPs from file
File.read('config/blocklist.txt').split("\n").include?(req.ip)
end
# Safelist trusted IPs
safelist('allow from localhost') do |req|
'127.0.0.1' == req.ip || '::1' == req.ip
end
# Track suspicious activity
track('suspicious activity', limit: 10, period: 1.hour) do |req|
req.ip if req.path.include?('admin') && !req.authorized?
end
end
# Handle throttled requests
Rack::Attack.throttled_responder = lambda do |env|
[429, {'Content-Type' => 'application/json'}, [{
error: 'Rate limit exceeded',
retry_after: env['rack.attack.match_data'][:period]
}.to_json]]
end
# config/application.rb
config.middleware.use Rack::Attack
CloudFlare (DDoS Protection):
1. Add domain to CloudFlare
2. Update nameservers
3. Enable:
- DDoS protection (automatic)
- WAF (Web Application Firewall)
- Rate limiting rules
- Bot fight mode
- SSL/TLS encryption (Full Strict)
Password Requirements:
# app/models/user.rb
class User < ApplicationRecord
validates :password,
length: { minimum: 12 },
format: {
with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
message: 'must include uppercase, lowercase, number, and special character'
}
# Check against common passwords
validate :password_not_common
private
def password_not_common
common = File.read('config/common_passwords.txt').split("\n")
if common.include?(password)
errors.add(:password, 'is too common')
end
end
end
Multi-Factor Authentication (Devise + OTP):
# Gemfile
gem 'devise-two-factor'
gem 'rqrcode'
# User model
class User < ApplicationRecord
devise :two_factor_authenticatable,
:otp_secret_encryption_key => ENV['OTP_SECRET_KEY']
has_one_time_password
end
# Controller
class Users::TwoFactorController < ApplicationController
def verify
if current_user.validate_and_consume_otp!(params[:otp_attempt])
sign_in(current_user)
redirect_to root_path
else
flash[:error] = 'Invalid code'
render :show
end
end
end
OAuth Integration (Google, GitHub):
# Gemfile
gem 'omniauth-google-oauth2'
gem 'omniauth-github'
gem 'omniauth-rails_csrf_protection'
# config/initializers/devise.rb
config.omniauth :google_oauth2,
ENV['GOOGLE_CLIENT_ID'],
ENV['GOOGLE_CLIENT_SECRET'],
scope: 'email,profile'
config.omniauth :github,
ENV['GITHUB_CLIENT_ID'],
ENV['GITHUB_CLIENT_SECRET'],
scope: 'user:email'
[MCP_TOOLS]
<!-- Populated during skill creation based on: 1. User's installed MCP servers 2. User's selection for this skill Recommended for this skill (examples): - Security scanning services (Snyk, Trivy) - Secret management (HashiCorp Vault, AWS Secrets Manager) - Certificate management (Let's Encrypt, cert-manager) - Vulnerability databases (CVE, NIST) Note: Skills work without MCP servers, but functionality may be limited --># Ruby/Rails
gem install bundler-audit
gem install brakeman
# JavaScript
npm install -g snyk
# Container scanning
brew install trivy
# SSL testing
brew install testssl
# HashiCorp Vault
brew install vault
# SOPS (encrypted files)
brew install sops
# Git-secret
brew install git-secret
# GitHub Security Advisories
gh api repos/owner/repo/vulnerability-alerts
# OWASP ZAP (penetration testing)
brew install --cask owasp-zap
1. Document security contact info
2. Define severity levels
3. Create runbooks for common incidents
4. Set up secure communication channel
5. Maintain updated dependency inventory
1. Monitor security alerts
2. Review logs regularly
3. Track failed authentication attempts
4. Monitor for anomalous behavior
5. Subscribe to security advisories
1. Assess severity and impact
2. Contain the incident
3. Eradicate the vulnerability
4. Recover services
5. Document lessons learned
6. Notify affected users (if required)
Security measure | Performance impact | Priority
--------------------------|-------------------|----------
Force SSL | Minimal | Critical
HSTS | None | Critical
Security headers | None | Critical
CSP | Minimal | High
Rate limiting | Minimal | High
Session encryption | Low | High
Database query encryption | Medium | Medium
Full disk encryption | Medium | Medium
Remember: Security is not a feature to add later - it must be built in from day one. Regular audits, updates, and education are essential to maintaining a secure application.
tools
Session Handoff: Erstellt eine vollständige Zusammenfassung der aktuellen Session für einen sauberen Kontextwechsel. NUR bei explizitem Aufruf (/session-handoff). NICHT automatisch auslösen. Geeignet wenn der User die Session resetten will, den Kontext aufräumen will, oder bei ~120k Tokens angelangt ist.
development
Pre-Mortem Risk Analysis: Strukturierte Prospective-Hindsight-Übung um launch-blocking Risiken vor Commitment aufzudecken. Team stellt sich vor, das Produkt sei 14 Tage nach Launch gefloppt, und arbeitet rückwärts. Klassifiziert Risiken in Tigers (echt), Paper Tigers (hypothetisch), Elephants (unausgesprochen). Nutze diesen Skill vor Build-Commitment, bei zu hoher Stakeholder-Confidence, vor Major-Releases, oder wenn das Team vage Sorgen nicht artikulieren kann. Trigger: /pre-mortem, 'pre-mortem', 'risk analysis', 'was könnte schiefgehen', 'risiken vor launch'.
testing
Six-Sigma Atomicity Validator for create-spec stories
tools
UX pattern definition guidance for navigation, user flows, interactions, and accessibility