Class: Rhales::CSP

Inherits:
Object
  • Object
show all
Includes:
Utils::LoggingHelpers
Defined in:
lib/rhales/security/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

Methods included from Utils::LoggingHelpers

#format_value, #log_timed_operation, #log_with_metadata

Methods included from Utils

#now, #now_in_μs

Constructor Details

#initialize(config, nonce: nil) ⇒ CSP

Returns a new instance of CSP.



18
19
20
21
# File 'lib/rhales/security/csp.rb', line 18

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

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



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

def config
  @config
end

#nonceObject (readonly)

Returns the value of attribute nonce.



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

def nonce
  @nonce
end

Class Method Details

.generate_nonceObject

Generate a new nonce value



60
61
62
63
64
65
66
67
# File 'lib/rhales/security/csp.rb', line 60

def self.generate_nonce
  nonce = SecureRandom.hex(16)

  # Log nonce generation for security audit trail
  Rhales.logger.debug("CSP nonce generated: nonce=#{nonce} length=#{nonce.length} entropy_bits=#{nonce.length * 4}")

  nonce
end

Instance Method Details

#build_headerObject

Build CSP header string from configuration



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/rhales/security/csp.rb', line 24

def build_header
  return nil unless @config.csp_enabled

  policy_directives = []
  nonce_used = false

  @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 do |source|
        interpolated = interpolate_nonce(source)
        nonce_used = true if interpolated != source
        interpolated
      end
      directive_string = "#{directive} #{processed_sources.join(' ')}"
      policy_directives << directive_string
    end
  end

  header = policy_directives.join('; ')

  # Log CSP header generation for security audit
  (Rhales.logger, :info, "CSP header generated",
    nonce_used: nonce_used,
    nonce: @nonce,
    directive_count: policy_directives.size,
    header_length: header.length
  )

  header
end

#interpolate_nonce(source) ⇒ Object (private)

Interpolate nonce placeholder in source values



110
111
112
113
114
# File 'lib/rhales/security/csp.rb', line 110

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)


101
102
103
104
105
# File 'lib/rhales/security/csp.rb', line 101

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



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rhales/security/csp.rb', line 70

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