hedra 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ab829bc24159c4c23c53be1f82efbc8f61374401c2ae7f882140c8bb8de8e37f
4
+ data.tar.gz: 9a83e90dfb32a981e978c57c37973dee68d55adfdebe071517127ce9a98f1d92
5
+ SHA512:
6
+ metadata.gz: 2f499c341d3ed325fe3be5eb4e9ab6db27a0e3c54441c9dc19d48a6631d79f8c0dcfea050f6d4cc7322c7bcba4d9d3a7149a71044db42bae3c697edc4e360956
7
+ data.tar.gz: 8b242f020e24deb65b6c218938532d43ddcdc15963015e644c8cd7edc4c5244d04547e38133fae18f139841dd37244a46387186660a2fec4b236257a0f9e5fea
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Hedra Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,342 @@
1
+ # Hedra 🛡️
2
+
3
+ A comprehensive security header analyzer for modern web applications. Scan, audit, and monitor HTTP security headers with ease.
4
+
5
+ ```
6
+ _ _ _
7
+ | | | | ___ __| |_ __ __ _
8
+ | |_| |/ _ \/ _` | '__/ _` |
9
+ | _ | __/ (_| | | | (_| |
10
+ |_| |_|\___|\__,_|_| \__,_|
11
+
12
+ Security Header Analyzer
13
+ ```
14
+
15
+ ## Features
16
+
17
+ - 🔍 **Comprehensive Scanning** - Analyze security headers for single or multiple URLs
18
+ - 🎯 **Deep Auditing** - Detailed security header analysis with recommendations
19
+ - 👁️ **Continuous Monitoring** - Watch URLs for header changes over time
20
+ - 📊 **Multiple Output Formats** - Table, JSON, and CSV export options
21
+ - 🔌 **Plugin Architecture** - Extend with custom header checks
22
+ - ⚡ **Concurrent Scanning** - Fast parallel URL scanning with configurable concurrency
23
+ - 🌐 **Proxy Support** - HTTP and SOCKS proxy compatibility
24
+ - 🎨 **Beautiful CLI** - Color-coded output with severity badges
25
+ - 📈 **Security Scoring** - 0-100 score based on header coverage
26
+
27
+ ## Installation
28
+
29
+ ### From Source
30
+
31
+ ```bash
32
+ # Clone the repository
33
+ git clone https://github.com/hedra/hedra.git
34
+ cd hedra
35
+
36
+ # Install dependencies
37
+ bundle install
38
+
39
+ # Build the gem
40
+ rake build
41
+
42
+ # Install the gem
43
+ gem install pkg/hedra-1.0.0.gem
44
+ ```
45
+
46
+ ### Quick Start
47
+
48
+ ```bash
49
+ bundle install
50
+ chmod +x bin/hedra
51
+ bin/hedra --help
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ ### Basic Scanning
57
+
58
+ Scan a single URL:
59
+ ```bash
60
+ hedra scan https://example.com
61
+ ```
62
+
63
+ Scan multiple URLs from a file:
64
+ ```bash
65
+ hedra scan -f urls.txt
66
+ ```
67
+
68
+ ### Deep Audit
69
+
70
+ Perform detailed security analysis:
71
+ ```bash
72
+ hedra audit https://example.com
73
+ ```
74
+
75
+ Export audit results as JSON:
76
+ ```bash
77
+ hedra audit https://example.com --json --output result.json
78
+ ```
79
+
80
+ ### Advanced Scanning
81
+
82
+ Concurrent scanning with custom settings:
83
+ ```bash
84
+ hedra scan -f urls.txt --concurrency 20 --timeout 15
85
+ ```
86
+
87
+ Scan through a proxy:
88
+ ```bash
89
+ hedra scan https://example.com --proxy http://127.0.0.1:8080
90
+ ```
91
+
92
+ Custom User-Agent and follow redirects:
93
+ ```bash
94
+ hedra scan https://example.com --user-agent "MyBot/1.0" --follow-redirects
95
+ ```
96
+
97
+ ### Continuous Monitoring
98
+
99
+ Watch a URL and check every hour:
100
+ ```bash
101
+ hedra watch https://example.com --interval 3600
102
+ ```
103
+
104
+ ### Compare Headers
105
+
106
+ Compare security headers between two URLs:
107
+ ```bash
108
+ hedra compare https://staging.example.com https://prod.example.com
109
+ ```
110
+
111
+ ### Export Results
112
+
113
+ Export scan results:
114
+ ```bash
115
+ hedra scan -f urls.txt --output results.csv --format csv
116
+ ```
117
+
118
+ ### Plugin Management
119
+
120
+ List installed plugins:
121
+ ```bash
122
+ hedra plugin list
123
+ ```
124
+
125
+ Install a custom plugin:
126
+ ```bash
127
+ hedra plugin install path/to/plugin.rb
128
+ ```
129
+
130
+ Remove a plugin:
131
+ ```bash
132
+ hedra plugin remove my_plugin
133
+ ```
134
+
135
+ ## Security Headers Checked
136
+
137
+ Hedra analyzes the following security headers:
138
+
139
+ ### Critical Headers
140
+ - **Content-Security-Policy (CSP)** - Prevents XSS and injection attacks
141
+ - **Strict-Transport-Security (HSTS)** - Enforces HTTPS connections
142
+
143
+ ### Important Headers
144
+ - **X-Frame-Options** - Prevents clickjacking attacks
145
+ - **X-Content-Type-Options** - Prevents MIME-sniffing attacks
146
+
147
+ ### Recommended Headers
148
+ - **Referrer-Policy** - Controls referrer information
149
+ - **Permissions-Policy** - Controls browser features
150
+ - **Cross-Origin-Opener-Policy (COOP)** - Isolates browsing context
151
+ - **Cross-Origin-Embedder-Policy (COEP)** - Controls resource embedding
152
+ - **Cross-Origin-Resource-Policy (CORP)** - Controls resource sharing
153
+
154
+ ## Configuration
155
+
156
+ Create a config file at `~/.hedra/config.yml`:
157
+
158
+ ```yaml
159
+ timeout: 10
160
+ concurrency: 10
161
+ follow_redirects: false
162
+ user_agent: "Hedra/1.0.0"
163
+ output_format: table
164
+ ```
165
+
166
+ ### Custom Rules
167
+
168
+ Add custom header checks in `~/.hedra/rules.yml`:
169
+
170
+ ```yaml
171
+ rules:
172
+ - header: "X-Custom-Security"
173
+ type: missing
174
+ severity: warning
175
+ message: "Custom security header is missing"
176
+ fix: "Add X-Custom-Security header"
177
+
178
+ - header: "Server"
179
+ type: pattern
180
+ pattern: "(Apache|nginx|IIS)"
181
+ severity: info
182
+ message: "Server header exposes server software"
183
+ fix: "Remove or obfuscate Server header"
184
+ ```
185
+
186
+ ## Plugin Development
187
+
188
+ Create custom plugins to extend Hedra's functionality:
189
+
190
+ ```ruby
191
+ # ~/.hedra/plugins/my_plugin.rb
192
+ module Hedra
193
+ class MyPlugin < Plugin
194
+ def self.check(headers)
195
+ findings = []
196
+
197
+ unless headers.key?('x-my-header')
198
+ findings << {
199
+ header: 'x-my-header',
200
+ issue: 'My custom header is missing',
201
+ severity: :warning,
202
+ recommended_fix: 'Add X-My-Header'
203
+ }
204
+ end
205
+
206
+ findings
207
+ end
208
+ end
209
+ end
210
+ ```
211
+
212
+ ## Output Examples
213
+
214
+ ### Table Output
215
+ ```
216
+ https://example.com
217
+ Score: 75/100
218
+ Timestamp: 2025-11-12T10:30:00Z
219
+
220
+ ┌─────────────────────────────┬──────────────────────────────┬──────────┐
221
+ │ Header │ Issue │ Severity │
222
+ ├─────────────────────────────┼──────────────────────────────┼──────────┤
223
+ │ permissions-policy │ Header is missing │ ● INFO │
224
+ │ cross-origin-opener-policy │ Header is missing │ ● INFO │
225
+ └─────────────────────────────┴──────────────────────────────┴──────────┘
226
+ ```
227
+
228
+ ### JSON Output
229
+ ```json
230
+ {
231
+ "url": "https://example.com",
232
+ "timestamp": "2025-11-12T10:30:00Z",
233
+ "headers": {
234
+ "content-security-policy": "default-src 'self'",
235
+ "strict-transport-security": "max-age=31536000"
236
+ },
237
+ "findings": [
238
+ {
239
+ "header": "x-frame-options",
240
+ "issue": "X-Frame-Options header is missing",
241
+ "severity": "warning",
242
+ "recommended_fix": "Add X-Frame-Options: DENY or SAMEORIGIN"
243
+ }
244
+ ],
245
+ "score": 75
246
+ }
247
+ ```
248
+
249
+ ## Development
250
+
251
+ ### Running Tests
252
+
253
+ ```bash
254
+ # Run all tests
255
+ bundle exec rspec
256
+
257
+ # Run with coverage
258
+ bundle exec rspec --format documentation
259
+
260
+ # Run specific test file
261
+ bundle exec rspec spec/hedra/analyzer_spec.rb
262
+ ```
263
+
264
+ ### Linting
265
+
266
+ ```bash
267
+ # Run RuboCop
268
+ bundle exec rubocop
269
+
270
+ # Auto-fix issues
271
+ bundle exec rubocop -a
272
+ ```
273
+
274
+ ### Building
275
+
276
+ ```bash
277
+ # Build gem
278
+ rake build
279
+
280
+ # Install locally
281
+ gem install pkg/hedra-1.0.0.gem
282
+ ```
283
+
284
+ ## CI/CD
285
+
286
+ Hedra includes GitHub Actions CI configuration that:
287
+ - Runs tests on Ruby 3.0, 3.1, and 3.2
288
+ - Executes RuboCop linting
289
+ - Builds the gem package
290
+
291
+ ## Architecture
292
+
293
+ ### Core Components
294
+
295
+ - **CLI** - Thor-based command-line interface with subcommands
296
+ - **Analyzer** - Core logic for header analysis and validation
297
+ - **HttpClient** - HTTP wrapper with retry logic, proxy support, and TLS verification
298
+ - **Scorer** - Calculates security scores based on header coverage
299
+ - **PluginManager** - Discovers and executes custom plugins
300
+ - **Exporter** - Handles JSON and CSV output formats
301
+
302
+ ### Design Decisions
303
+
304
+ 1. **Modular Architecture** - Each header check is isolated, making it easy to add new checks
305
+ 2. **Secure Defaults** - TLS verification on, no redirect following, conservative timeouts
306
+ 3. **Thread-Safe Concurrency** - Uses Ruby's concurrent-ruby gem for safe parallel scanning
307
+ 4. **Extensible Plugin System** - Simple base class for custom header checks
308
+ 5. **Comprehensive Testing** - WebMock stubs prevent live network calls in tests
309
+
310
+ ## Contributing
311
+
312
+ 1. Fork the repository
313
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
314
+ 3. Write tests for your changes
315
+ 4. Ensure tests pass (`bundle exec rspec`)
316
+ 5. Ensure linting passes (`bundle exec rubocop`)
317
+ 6. Commit your changes (`git commit -am 'Add amazing feature'`)
318
+ 7. Push to the branch (`git push origin feature/amazing-feature`)
319
+ 8. Open a Pull Request
320
+
321
+ ## License
322
+
323
+ MIT License - see [LICENSE](LICENSE) file for details.
324
+
325
+ ## Support
326
+
327
+ - 📖 Documentation: [GitHub Wiki](https://github.com/hedra/hedra/wiki)
328
+ - 🐛 Issues: [GitHub Issues](https://github.com/hedra/hedra/issues)
329
+ - 💬 Discussions: [GitHub Discussions](https://github.com/hedra/hedra/discussions)
330
+
331
+ ## Acknowledgments
332
+
333
+ Built with:
334
+ - [Thor](https://github.com/rails/thor) - CLI framework
335
+ - [HTTP.rb](https://github.com/httprb/http) - HTTP client
336
+ - [TTY::Table](https://github.com/piotrmurach/tty-table) - Terminal tables
337
+ - [Pastel](https://github.com/piotrmurach/pastel) - Terminal colors
338
+ - [RSpec](https://rspec.info/) - Testing framework
339
+
340
+ ---
341
+
342
+ Made with ❤️ by the Hedra Team
data/bin/hedra ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/hedra'
5
+
6
+ begin
7
+ Hedra::CLI.start(ARGV)
8
+ rescue Hedra::Error => e
9
+ warn "ERROR: #{e.message}"
10
+ exit 1
11
+ rescue Interrupt
12
+ warn "\nInterrupted"
13
+ exit 130
14
+ end
@@ -0,0 +1,17 @@
1
+ # Hedra Configuration Example
2
+ # Copy to ~/.hedra/config.yml to customize
3
+
4
+ # HTTP client settings
5
+ timeout: 10
6
+ concurrency: 10
7
+ follow_redirects: false
8
+ user_agent: "Hedra/1.0.0"
9
+
10
+ # Proxy settings (optional)
11
+ # proxy: "http://127.0.0.1:8080"
12
+
13
+ # Output preferences
14
+ output_format: table # table, json, or csv
15
+
16
+ # Rate limiting (optional)
17
+ # rate_limit: "5/s"
@@ -0,0 +1,16 @@
1
+ # Custom security header rules
2
+ # Copy to ~/.hedra/rules.yml to enable
3
+
4
+ rules:
5
+ - header: "X-Custom-Security"
6
+ type: missing
7
+ severity: warning
8
+ message: "Custom security header is missing"
9
+ fix: "Add X-Custom-Security header"
10
+
11
+ - header: "Server"
12
+ type: pattern
13
+ pattern: "(Apache|nginx|IIS)"
14
+ severity: info
15
+ message: "Server header exposes server software"
16
+ fix: "Remove or obfuscate Server header"
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Hedra
6
+ class Analyzer
7
+ SECURITY_HEADERS = {
8
+ 'content-security-policy' => {
9
+ required: true,
10
+ severity: :critical,
11
+ message: 'Content-Security-Policy header is missing',
12
+ fix: "Add CSP header: Content-Security-Policy: default-src 'self'"
13
+ },
14
+ 'strict-transport-security' => {
15
+ required: true,
16
+ severity: :critical,
17
+ message: 'Strict-Transport-Security (HSTS) header is missing',
18
+ fix: 'Add HSTS header: Strict-Transport-Security: max-age=31536000; includeSubDomains'
19
+ },
20
+ 'x-frame-options' => {
21
+ required: true,
22
+ severity: :warning,
23
+ message: 'X-Frame-Options header is missing',
24
+ fix: 'Add X-Frame-Options: DENY or SAMEORIGIN'
25
+ },
26
+ 'x-content-type-options' => {
27
+ required: true,
28
+ severity: :warning,
29
+ message: 'X-Content-Type-Options header is missing',
30
+ fix: 'Add X-Content-Type-Options: nosniff'
31
+ },
32
+ 'referrer-policy' => {
33
+ required: true,
34
+ severity: :info,
35
+ message: 'Referrer-Policy header is missing',
36
+ fix: 'Add Referrer-Policy: strict-origin-when-cross-origin'
37
+ },
38
+ 'permissions-policy' => {
39
+ required: false,
40
+ severity: :info,
41
+ message: 'Permissions-Policy header is missing',
42
+ fix: 'Consider adding Permissions-Policy to control browser features'
43
+ },
44
+ 'cross-origin-opener-policy' => {
45
+ required: false,
46
+ severity: :info,
47
+ message: 'Cross-Origin-Opener-Policy header is missing',
48
+ fix: 'Add Cross-Origin-Opener-Policy: same-origin'
49
+ },
50
+ 'cross-origin-embedder-policy' => {
51
+ required: false,
52
+ severity: :info,
53
+ message: 'Cross-Origin-Embedder-Policy header is missing',
54
+ fix: 'Add Cross-Origin-Embedder-Policy: require-corp'
55
+ },
56
+ 'cross-origin-resource-policy' => {
57
+ required: false,
58
+ severity: :info,
59
+ message: 'Cross-Origin-Resource-Policy header is missing',
60
+ fix: 'Add Cross-Origin-Resource-Policy: same-origin'
61
+ }
62
+ }.freeze
63
+
64
+ def initialize
65
+ @plugin_manager = PluginManager.new
66
+ @scorer = Scorer.new
67
+ load_custom_rules
68
+ end
69
+
70
+ def analyze(url, headers)
71
+ normalized_headers = normalize_headers(headers)
72
+ findings = []
73
+
74
+ # Check for missing required headers
75
+ SECURITY_HEADERS.each do |header_name, config|
76
+ next unless config[:required]
77
+
78
+ next if normalized_headers.key?(header_name)
79
+
80
+ findings << {
81
+ header: header_name,
82
+ issue: config[:message],
83
+ severity: config[:severity],
84
+ recommended_fix: config[:fix]
85
+ }
86
+ end
87
+
88
+ # Validate header values
89
+ findings.concat(validate_header_values(normalized_headers))
90
+
91
+ # Apply custom rules
92
+ findings.concat(apply_custom_rules(normalized_headers))
93
+
94
+ # Run plugin checks
95
+ findings.concat(@plugin_manager.run_checks(normalized_headers))
96
+
97
+ # Calculate security score
98
+ score = @scorer.calculate(normalized_headers, findings)
99
+
100
+ {
101
+ url: url,
102
+ timestamp: Time.now.iso8601,
103
+ headers: headers,
104
+ findings: findings,
105
+ score: score
106
+ }
107
+ end
108
+
109
+ private
110
+
111
+ def normalize_headers(headers)
112
+ headers.transform_keys { |k| k.to_s.downcase }
113
+ end
114
+
115
+ def validate_header_values(headers)
116
+ findings = []
117
+
118
+ # Validate CSP
119
+ if headers['content-security-policy']
120
+ csp = headers['content-security-policy']
121
+ if csp.include?('unsafe-inline') || csp.include?('unsafe-eval')
122
+ findings << {
123
+ header: 'content-security-policy',
124
+ issue: 'CSP contains unsafe directives (unsafe-inline or unsafe-eval)',
125
+ severity: :warning,
126
+ recommended_fix: 'Remove unsafe-inline and unsafe-eval, use nonces or hashes'
127
+ }
128
+ end
129
+ end
130
+
131
+ # Validate HSTS
132
+ if headers['strict-transport-security']
133
+ hsts = headers['strict-transport-security']
134
+ if hsts =~ /max-age=(\d+)/
135
+ max_age = ::Regexp.last_match(1).to_i
136
+ if max_age < 31_536_000
137
+ findings << {
138
+ header: 'strict-transport-security',
139
+ issue: 'HSTS max-age is less than 1 year (31536000 seconds)',
140
+ severity: :warning,
141
+ recommended_fix: 'Set max-age to at least 31536000'
142
+ }
143
+ end
144
+ end
145
+ end
146
+
147
+ # Validate X-Frame-Options
148
+ if headers['x-frame-options']
149
+ xfo = headers['x-frame-options'].upcase
150
+ unless %w[DENY SAMEORIGIN].include?(xfo.split.first)
151
+ findings << {
152
+ header: 'x-frame-options',
153
+ issue: 'X-Frame-Options has invalid value',
154
+ severity: :warning,
155
+ recommended_fix: 'Use DENY or SAMEORIGIN'
156
+ }
157
+ end
158
+ end
159
+
160
+ # Validate X-Content-Type-Options
161
+ if headers['x-content-type-options']
162
+ xcto = headers['x-content-type-options'].downcase
163
+ unless xcto == 'nosniff'
164
+ findings << {
165
+ header: 'x-content-type-options',
166
+ issue: 'X-Content-Type-Options should be "nosniff"',
167
+ severity: :info,
168
+ recommended_fix: 'Set to nosniff'
169
+ }
170
+ end
171
+ end
172
+
173
+ findings
174
+ end
175
+
176
+ def load_custom_rules
177
+ @custom_rules = []
178
+ config_path = File.expand_path('~/.hedra/rules.yml')
179
+ return unless File.exist?(config_path)
180
+
181
+ rules = YAML.load_file(config_path)
182
+ @custom_rules = rules['rules'] || []
183
+ rescue StandardError => e
184
+ warn "Failed to load custom rules: #{e.message}"
185
+ end
186
+
187
+ def apply_custom_rules(headers)
188
+ findings = []
189
+
190
+ @custom_rules.each do |rule|
191
+ header_name = rule['header'].downcase
192
+ pattern = Regexp.new(rule['pattern']) if rule['pattern']
193
+
194
+ if rule['type'] == 'missing' && !headers.key?(header_name)
195
+ findings << {
196
+ header: header_name,
197
+ issue: rule['message'],
198
+ severity: rule['severity'].to_sym,
199
+ recommended_fix: rule['fix']
200
+ }
201
+ elsif rule['type'] == 'pattern' && headers[header_name]
202
+ if pattern && headers[header_name] =~ pattern
203
+ findings << {
204
+ header: header_name,
205
+ issue: rule['message'],
206
+ severity: rule['severity'].to_sym,
207
+ recommended_fix: rule['fix']
208
+ }
209
+ end
210
+ end
211
+ end
212
+
213
+ findings
214
+ end
215
+ end
216
+ end