This document provides detailed specifications for each component in the Security Header Analyzer architecture. Each component has specific responsibilities, key functions, and well-defined interfaces.
Module: sha/main.py
- Parse command-line arguments using
argparse - Validate user input (timeout, max_redirects)
- Orchestrate the analysis workflow
- Handle exceptions and set appropriate exit codes
- Display output to user
Main entry point that coordinates the entire analysis workflow.
Workflow:
- Parse arguments
- Fetch headers
- Analyze headers
- Generate report
- Display and exit
Exit Codes:
0- Success1- Network error2- Invalid input3- HTTP error130- User interruption (Ctrl+C)
Parse and validate command-line arguments.
Arguments:
url(required) - Target URL to analyze--json- Output in JSON format--timeout N- Request timeout in seconds (default: 10)--no-redirects- Don't follow redirects--max-redirects N- Maximum redirects to follow (default: 5)--user-agent STR- Custom User-Agent string--debug- Enable debug logging--version- Show version and exit
Validation:
- Timeout must be positive
- max_redirects must be non-negative
User Input (argv)
↓
parse_args()
↓
fetch_headers()
↓
analyze_headers()
↓
generate_report()
↓
print() + exit()
Module: sha/fetcher.py
- Make HTTP HEAD requests to target URLs
- Implement SSRF (Server-Side Request Forgery) protection
- Validate redirect destinations against DNS rebinding
- Handle network errors gracefully
- Normalize header names to lowercase
- Handle multiple Set-Cookie headers specially
Fetch headers with full error handling and security checks.
Process:
- Normalize URL (add https:// if missing)
- Validate URL safety (SSRF protection)
- Make HTTP HEAD request
- Handle redirects if enabled
- Validate redirect destination (DNS rebinding protection)
- Normalize header names to lowercase
- Handle multiple Set-Cookie headers
Returns: Dictionary of normalized headers
Raises:
NetworkError- Connection, timeout, or SSL errorsInvalidURLError- Malformed URL or SSRF attempt blockedHTTPError- HTTP 4xx/5xx responses
Add HTTPS protocol if missing.
Examples:
normalize_url("example.com") # → "https://example.com"
normalize_url("http://example.com") # → "http://example.com"SSRF protection via DNS validation.
Checks:
- URL is properly formatted
- Hostname doesn't resolve to private IP addresses
- Hostname is not localhost or similar
Raises: InvalidURLError if unsafe
Known Limitation: TOCTOU vulnerability (see Security section)
DNS rebinding protection after redirects.
Re-validates the final URL after following redirects to prevent DNS rebinding attacks.
Blocked IP Ranges:
- IPv4:
127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 - IPv6:
::1,fc00::/7,fe80::/10
Blocked Hostnames:
localhost,local,localdomain
Configuration:
- Configurable timeout (default: 10 seconds)
- Configurable redirect limit (default: 5 redirects)
- SSL/TLS certificate verification enabled
Multiple Set-Cookie Headers:
# HTTP response may have multiple Set-Cookie headers
# Combined into single value with '; ' separator
_normalize_headers_with_cookies(response) -> Dict[str, str]Module: sha/analyzer.py
- Coordinate analysis across all registered analyzers
- Maintain backward compatibility with legacy code
- Aggregate findings from individual analyzers
- Ensure all registered headers are checked
Main entry point that coordinates analysis across all registered analyzers.
Algorithm:
findings = []
for header_key, analyze_func in ANALYZER_REGISTRY.items():
value = headers.get(header_key) # None if missing
finding = analyze_func(value) # Call analyzer
findings.append(finding) # Aggregate
return findingsReturns: List of Finding dictionaries
Uses ANALYZER_REGISTRY from sha/analyzers/__init__.py to dispatch to the appropriate analyzer for each header.
Benefits:
- No modification needed to add new analyzers
- Analyzers tested in isolation
- Clear separation of concerns
Exports commonly used analyzer functions for legacy code:
from .analyzers import hsts, csp, xframeModules: sha/analyzers/*.py (15 total)
Every analyzer follows this pattern:
"""Analyzer docstring."""
from typing import Any, Dict, Optional
# 1. Header key (lowercase)
HEADER_KEY = "header-name"
# 2. Configuration dictionary
CONFIG = {
"display_name": "Header-Name",
"severity_missing": "high",
"description": "Brief description",
"validation": {...},
"messages": {...},
"recommendations": {...}
}
# 3. Analysis function
def analyze(value: Optional[str]) -> Dict[str, Any]:
"""Analyze header value and return finding."""
if value is None:
return {
"header_name": CONFIG["display_name"],
"status": "missing",
"severity": CONFIG["severity_missing"],
"message": CONFIG["messages"]["missing"],
"actual_value": None,
"recommendation": CONFIG["recommendations"]["missing"]
}
# Validation logic here
# ...
return finding- Parse header values into components
- Validate against best practices
- Assess severity levels
- Generate clear recommendations
- Return structured findings
| Module | Header | Severity Missing |
|---|---|---|
hsts.py |
Strict-Transport-Security | Critical |
csp.py |
Content-Security-Policy | Critical |
xframe.py |
X-Frame-Options | High |
referrer_policy.py |
Referrer-Policy | High |
content_type.py |
X-Content-Type-Options | Medium |
set_cookie.py |
Set-Cookie | Medium |
cache_control.py |
Cache-Control | Medium |
expect_ct.py |
Expect-CT | Medium |
x_permitted_cross_domain_policies.py |
X-Permitted-Cross-Domain-Policies | Medium |
x_xss_protection.py |
X-XSS-Protection | Low (deprecated) |
x_download_options.py |
X-Download-Options | Low |
permissions_policy.py |
Permissions-Policy | Low |
coep.py |
Cross-Origin-Embedder-Policy | Low |
coop.py |
Cross-Origin-Opener-Policy | Low |
corp.py |
Cross-Origin-Resource-Policy | Low |
Validation Logic:
- Check if header is missing → Critical
- Parse max-age value
- Check max-age >= 126 days (10,886,400 seconds)
- Check for includeSubDomains directive
- Check for preload directive
- Assign status: good / acceptable / bad
Status Assignment:
- Good: max-age >= 1 year + includeSubDomains + preload
- Acceptable: max-age >= 126 days + includeSubDomains
- Bad: max-age < 126 days or malformed
Module: sha/analyzers/__init__.py
- Import all analyzer modules
- Maintain ANALYZER_REGISTRY dictionary
- Maintain CONFIG_REGISTRY dictionary
- Provide utility functions for registry access
Maps header keys to analyzer functions:
ANALYZER_REGISTRY: Dict[str, Callable] = {
"strict-transport-security": hsts.analyze,
"x-frame-options": xframe.analyze,
# ... 15 total entries
}Maps header keys to configuration dictionaries:
CONFIG_REGISTRY: Dict[str, Dict[str, Any]] = {
"strict-transport-security": hsts.CONFIG,
"x-frame-options": xframe.CONFIG,
# ... 15 total entries
}def get_all_header_keys() -> List[str]
def get_analyzer(header_key: str) -> Callable
def get_config(header_key: str) -> Dict[str, Any]Module: sha/reporter.py
- Format findings for human readability (text mode)
- Serialize findings to JSON (automation mode)
- Calculate summary statistics
- Sort findings by severity
Main entry point supporting text and JSON formats.
Parameters:
findings: List[Finding]- Analysis resultsurl: str- Target URLformat: str- "text" or "json"
Returns: Formatted report string
Create human-readable terminal output.
Format:
======================================================================
SECURITY HEADER ANALYSIS REPORT
======================================================================
URL: https://example.com
Timestamp: 2025-12-12T10:00:00.000Z
SUMMARY
----------------------------------------------------------------------
Critical Issues: 0
High Issues: 2
Medium Issues: 1
Low Issues: 0
DETAILED FINDINGS
----------------------------------------------------------------------
[High] Strict-Transport-Security
Status: missing
Message: HSTS header is not set
Recommendation: Add 'Strict-Transport-Security: max-age=31536000; ...'
Serialize to JSON for automation.
Structure:
{
"url": "https://example.com",
"timestamp": "2025-12-12T10:00:00.000Z",
"summary": {
"critical": 0,
"high": 2,
"medium": 1,
"low": 0,
"info": 12
},
"findings": [...]
}Count issues by severity level.
Returns:
{
"critical": 0,
"high": 2,
"medium": 1,
"low": 0,
"info": 12
}Order findings from critical → high → medium → low → info.
Uses SEVERITY_RANK from config.py for ordering.
get_severity_label(severity) -> str- Format severity for displayget_timestamp() -> str- ISO 8601 timestampget_total_issues(summary) -> int- Sum non-info issuesformat_summary_oneline(summary) -> str- Compact summary
Module: sha/config.py
- Define default constants
- Define exception classes
- Define private IP ranges for SSRF protection
- Define status and severity constants
DEFAULT_TIMEOUT = 10 # seconds
DEFAULT_MAX_REDIRECTS = 5
DEFAULT_USER_AGENT = "SecurityHeaderAnalyzer/1.0.0"
# Status constants
STATUS_GOOD = "good"
STATUS_ACCEPTABLE = "acceptable"
STATUS_BAD = "bad"
STATUS_MISSING = "missing"
# Severity constants
SEVERITY_CRITICAL = "critical"
SEVERITY_HIGH = "high"
SEVERITY_MEDIUM = "medium"
SEVERITY_LOW = "low"
SEVERITY_INFO = "info"class SecurityHeaderAnalyzerError(Exception):
"""Base exception."""
class NetworkError(SecurityHeaderAnalyzerError):
"""Network-related errors."""
class InvalidURLError(SecurityHeaderAnalyzerError):
"""Invalid or unsafe URLs."""
class HTTPError(SecurityHeaderAnalyzerError):
"""HTTP errors (4xx, 5xx)."""Defines SSRF protection lists:
PRIVATE_IP_RANGES: List[str]LOCALHOST_NAMES: Set[str]
- System Design - Architecture overview
- Data Flow - Request processing pipeline
- Registry Pattern - Analyzer registration
- Extensibility - Adding new analyzers
- Security - SSRF protection details