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, #pretty_path

Constructor Details

#initialize(config, nonce: nil) ⇒ CSP

Returns a new instance of CSP.



20
21
22
23
# File 'lib/rhales/security/csp.rb', line 20

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

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



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

def config
  @config
end

#nonceObject (readonly)

Returns the value of attribute nonce.



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

def nonce
  @nonce
end

Class Method Details

.generate_nonceObject

Generate a new nonce value



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

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



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
58
59
# File 'lib/rhales/security/csp.rb', line 26

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, :debug, "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



112
113
114
115
116
# File 'lib/rhales/security/csp.rb', line 112

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)


103
104
105
106
107
# File 'lib/rhales/security/csp.rb', line 103

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



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
99
100
# File 'lib/rhales/security/csp.rb', line 72

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