Your Jekyll site feels secure because it's static, but you're actually vulnerable to DDoS attacks, content scraping, credential stuffing, and various web attacks. Static doesn't mean invincible. Attackers can overwhelm your GitHub Pages hosting, scrape your content, or exploit misconfigurations. The false sense of security is dangerous. You need layered protection combining Cloudflare's network-level security with Ruby-based security tools for your development workflow.
Static sites have unique security considerations. While there's no database or server-side code to hack, attackers focus on: (1) Denial of Service through traffic overload, (2) Content theft and scraping, (3) Credential stuffing on forms or APIs, (4) Exploiting third-party JavaScript vulnerabilities, and (5) Abusing GitHub Pages infrastructure. Your security strategy must address these vectors.
Cloudflare provides the first line of defense at the network edge, while Ruby security gems help secure your development pipeline and content. This layered approach—network security, content security, and development security—creates a comprehensive defense. Remember, security is not a one-time setup but an ongoing process of monitoring, updating, and adapting to new threats.
| Security Layer | Threats Addressed | Cloudflare Features | Ruby Gems |
|---|---|---|---|
| Network Security | DDoS, bot attacks, malicious traffic | DDoS Protection, Rate Limiting, Firewall | rack-attack, secure_headers |
| Content Security | XSS, code injection, data theft | WAF Rules, SSL/TLS, Content Scanning | brakeman, bundler-audit |
| Access Security | Unauthorized access, admin breaches | Access Rules, IP Restrictions, 2FA | devise, pundit (adapted) |
| Pipeline Security | Malicious commits, dependency attacks | API Security, Token Management | gemsurance, license_finder |
Cloudflare offers numerous security features. Configure these specifically for Jekyll:
# Configure via API
cf.zones.settings.ssl.edit(
zone_id: zone.id,
value: 'full' # Full SSL encryption
)
# Enable always use HTTPS
cf.zones.settings.always_use_https.edit(
zone_id: zone.id,
value: 'on'
)
# Enable HSTS
cf.zones.settings.security_header.edit(
zone_id: zone.id,
value: {
strict_transport_security: {
enabled: true,
max_age: 31536000,
include_subdomains: true,
preload: true
}
}
)
# Enable under attack mode via API
def enable_under_attack_mode(enable = true)
cf.zones.settings.security_level.edit(
zone_id: zone.id,
value: enable ? 'under_attack' : 'high'
)
end
# Configure rate limiting
cf.zones.rate_limits.create(
zone_id: zone.id,
threshold: 100,
period: 60,
action: {
mode: 'ban',
timeout: 3600
},
match: {
request: {
methods: ['_ALL_'],
schemes: ['_ALL_'],
url: '*.yourdomain.com/*'
},
response: {
status: [200],
origin_traffic: false
}
}
)
# Enable bot fight mode
cf.zones.settings.bot_fight_mode.edit(
zone_id: zone.id,
value: 'on'
)
# Configure bot management for specific paths
cf.zones.settings.bot_management.edit(
zone_id: zone.id,
value: {
enable_js: true,
fight_mode: true,
whitelist: [
'googlebot',
'bingbot',
'slurp' # Yahoo
]
}
)
Secure your development and build process:
While designed for Rails, adapt Brakeman for Jekyll:
gem 'brakeman'
# Custom configuration for Jekyll
Brakeman.run(
app_path: '.',
output_files: ['security_report.html'],
check_arguments: {
# Check for unsafe Liquid usage
check_liquid: true,
# Check for inline JavaScript
check_xss: true
}
)
# Create Rake task
task :security_scan do
require 'brakeman'
tracker = Brakeman.run('.')
puts tracker.report.to_s
if tracker.warnings.any?
puts "⚠️ Found #{tracker.warnings.count} security warnings"
exit 1 if ENV['FAIL_ON_WARNINGS']
end
end
Check for vulnerable dependencies:
gem 'bundler-audit'
# Run in CI/CD pipeline
task :audit_dependencies do
require 'bundler/audit/cli'
puts "Auditing Gemfile dependencies..."
Bundler::Audit::CLI.start(['check', '--update'])
# Also check for insecure licenses
Bundler::Audit::CLI.start(['check', '--license'])
end
# Pre-commit hook
task :pre_commit_security do
Rake::Task['audit_dependencies'].invoke
Rake::Task['security_scan'].invoke
# Also run Ruby security scanner
system('gem scan')
end
Generate proper security headers:
gem 'secure_headers'
# Configure for Jekyll output
SecureHeaders::Configuration.default do |config|
config.csp = {
default_src: %w['self'],
script_src: %w['self' 'unsafe-inline' https://static.cloudflareinsights.com],
style_src: %w['self' 'unsafe-inline'],
img_src: %w['self' data: https:],
font_src: %w['self' https:],
connect_src: %w['self' https://cloudflareinsights.com],
report_uri: %w[/csp-violation-report]
}
config.hsts = "max-age=#{20.years.to_i}; includeSubdomains; preload"
config.x_frame_options = "DENY"
config.x_content_type_options = "nosniff"
config.x_xss_protection = "1; mode=block"
config.referrer_policy = "strict-origin-when-cross-origin"
end
# Generate headers for Jekyll
def security_headers
SecureHeaders.header_hash_for(:default).map do |name, value|
""
end.join("\n")
end
Protect your local development server:
gem 'rack-attack'
# config.ru
require 'rack/attack'
Rack::Attack.blocklist('bad bots') do |req|
# Block known bad user agents
req.user_agent =~ /(Scanner|Bot|Spider|Crawler)/i
end
Rack::Attack.throttle('requests by ip', limit: 100, period: 60) do |req|
req.ip
end
use Rack::Attack
run Jekyll::Commands::Serve
Configure Cloudflare WAF specifically for Jekyll:
# lib/security/waf_manager.rb
class WAFManager
RULES = {
'jekyll_xss_protection' => {
description: 'Block XSS attempts in Jekyll parameters',
expression: '(http.request.uri.query contains "