Class: Rhales::CSP

Inherits:
Object
  • Object
show all
Defined in:
lib/rhales/csp.rb

Overview

Content Security Policy (CSP) header generation and management

Provides secure defaults and nonce integration for CSP headers. Converts policy configuration into proper CSP header strings.

Usage: csp = Rhales::CSP.new(config, nonce: ‘abc123’) header = csp.build_header # => “default-src ‘self’; script-src ‘self’ ‘nonce-abc123’; …”

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, nonce: nil) ⇒ CSP

Returns a new instance of CSP.



16
17
18
19
# File 'lib/rhales/csp.rb', line 16

def initialize(config, nonce: nil)
  @config = config
  @nonce = nonce
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



14
15
16
# File 'lib/rhales/csp.rb', line 14

def config
  @config
end

#nonceObject (readonly)

Returns the value of attribute nonce.



14
15
16
# File 'lib/rhales/csp.rb', line 14

def nonce
  @nonce
end

Class Method Details

.generate_nonceObject

Generate a new nonce value



43
44
45
# File 'lib/rhales/csp.rb', line 43

def self.generate_nonce
  SecureRandom.hex(16)
end

Instance Method Details

#build_headerObject

Build CSP header string from configuration



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/rhales/csp.rb', line 22

def build_header
  return nil unless @config.csp_enabled

  policy_directives = []

  @config.csp_policy.each do |directive, sources|
    if sources.empty?
      # For directives with no sources (like upgrade-insecure-requests)
      policy_directives << directive
    else
      # Process sources and interpolate nonce if present
      processed_sources = sources.map { |source| interpolate_nonce(source) }
      directive_string = "#{directive} #{processed_sources.join(' ')}"
      policy_directives << directive_string
    end
  end

  policy_directives.join('; ')
end

#interpolate_nonce(source) ⇒ Object (private)

Interpolate nonce placeholder in source values



88
89
90
91
92
# File 'lib/rhales/csp.rb', line 88

def interpolate_nonce(source)
  return source unless @nonce && source.include?('{{nonce}}')

  source.gsub('{{nonce}}', @nonce)
end

#nonce_required?Boolean

Check if nonce is required for any directive

Returns:

  • (Boolean)


79
80
81
82
83
# File 'lib/rhales/csp.rb', line 79

def nonce_required?
  return false unless @config.csp_enabled

  @config.csp_policy.values.flatten.any? { |source| source.include?('{{nonce}}') }
end

#validate_policy!Object

Validate CSP policy configuration



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/rhales/csp.rb', line 48

def validate_policy!
  return unless @config.csp_enabled

  errors = []

  # Ensure policy is a hash
  unless @config.csp_policy.is_a?(Hash)
    errors << 'csp_policy must be a hash'
    raise Rhales::Configuration::ConfigurationError, "CSP policy errors: #{errors.join(', ')}"
  end

  # Validate each directive
  @config.csp_policy.each do |directive, sources|
    unless sources.is_a?(Array)
      errors << "#{directive} sources must be an array"
    end

    # Check for dangerous sources
    if sources.include?("'unsafe-eval'")
      errors << "#{directive} contains dangerous 'unsafe-eval' source"
    end

    if sources.include?("'unsafe-inline'") && !%w[style-src].include?(directive)
      errors << "#{directive} contains dangerous 'unsafe-inline' source"
    end
  end

  raise Rhales::Configuration::ConfigurationError, "CSP policy errors: #{errors.join(', ')}" unless errors.empty?
end