Class: Hedra::Analyzer

Inherits:
Object
  • Object
show all
Defined in:
lib/hedra/analyzer.rb

Constant Summary collapse

SECURITY_HEADERS =
{
  'content-security-policy' => {
    required: true,
    severity: :critical,
    message: 'Content-Security-Policy header is missing',
    fix: "Add CSP header: Content-Security-Policy: default-src 'self'"
  },
  'strict-transport-security' => {
    required: true,
    severity: :critical,
    message: 'Strict-Transport-Security (HSTS) header is missing',
    fix: 'Add HSTS header: Strict-Transport-Security: max-age=31536000; includeSubDomains'
  },
  'x-frame-options' => {
    required: true,
    severity: :warning,
    message: 'X-Frame-Options header is missing',
    fix: 'Add X-Frame-Options: DENY or SAMEORIGIN'
  },
  'x-content-type-options' => {
    required: true,
    severity: :warning,
    message: 'X-Content-Type-Options header is missing',
    fix: 'Add X-Content-Type-Options: nosniff'
  },
  'referrer-policy' => {
    required: true,
    severity: :info,
    message: 'Referrer-Policy header is missing',
    fix: 'Add Referrer-Policy: strict-origin-when-cross-origin'
  },
  'permissions-policy' => {
    required: false,
    severity: :info,
    message: 'Permissions-Policy header is missing',
    fix: 'Consider adding Permissions-Policy to control browser features'
  },
  'cross-origin-opener-policy' => {
    required: false,
    severity: :info,
    message: 'Cross-Origin-Opener-Policy header is missing',
    fix: 'Add Cross-Origin-Opener-Policy: same-origin'
  },
  'cross-origin-embedder-policy' => {
    required: false,
    severity: :info,
    message: 'Cross-Origin-Embedder-Policy header is missing',
    fix: 'Add Cross-Origin-Embedder-Policy: require-corp'
  },
  'cross-origin-resource-policy' => {
    required: false,
    severity: :info,
    message: 'Cross-Origin-Resource-Policy header is missing',
    fix: 'Add Cross-Origin-Resource-Policy: same-origin'
  }
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(check_certificates: true, check_security_txt: false) ⇒ Analyzer

Returns a new instance of Analyzer.



64
65
66
67
68
69
70
# File 'lib/hedra/analyzer.rb', line 64

def initialize(check_certificates: true, check_security_txt: false)
  @plugin_manager = PluginManager.new
  @scorer = Scorer.new
  @certificate_checker = check_certificates ? CertificateChecker.new : nil
  @security_txt_checker = check_security_txt ? SecurityTxtChecker.new : nil
  load_custom_rules
end

Instance Method Details

#analyze(url, headers, http_client: nil) ⇒ Object



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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/hedra/analyzer.rb', line 72

def analyze(url, headers, http_client: nil)
  normalized_headers = normalize_headers(headers)
  findings = []

  # Check for missing required headers
  SECURITY_HEADERS.each do |header_name, config|
    next unless config[:required]

    next if normalized_headers.key?(header_name)

    findings << {
      header: header_name,
      issue: config[:message],
      severity: config[:severity],
      recommended_fix: config[:fix]
    }
  end

  # Validate header values
  findings.concat(validate_header_values(normalized_headers))

  # Apply custom rules
  findings.concat(apply_custom_rules(normalized_headers))

  # Run plugin checks
  findings.concat(@plugin_manager.run_checks(normalized_headers))

  # Check SSL certificate
  findings.concat(@certificate_checker.check(url)) if @certificate_checker

  # Check security.txt
  findings.concat(@security_txt_checker.check(url, http_client)) if @security_txt_checker && http_client

  # Calculate security score
  score = @scorer.calculate(normalized_headers, findings)

  {
    url: url,
    timestamp: Time.now.iso8601,
    headers: headers,
    findings: findings,
    score: score
  }
end