headerhawk 1.0.0__py3-none-any.whl
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.
- cli/__init__.py +0 -0
- cli/main.py +114 -0
- core/__init__.py +0 -0
- core/analyzer.py +74 -0
- core/scanner.py +30 -0
- headerhawk-1.0.0.dist-info/METADATA +132 -0
- headerhawk-1.0.0.dist-info/RECORD +13 -0
- headerhawk-1.0.0.dist-info/WHEEL +5 -0
- headerhawk-1.0.0.dist-info/entry_points.txt +2 -0
- headerhawk-1.0.0.dist-info/licenses/LICENSE +21 -0
- headerhawk-1.0.0.dist-info/top_level.txt +3 -0
- reports/__init__.py +0 -0
- reports/html_report.py +81 -0
cli/__init__.py
ADDED
|
File without changes
|
cli/main.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
from core.scanner import get_headers
|
|
6
|
+
from core.analyzer import analyze
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
BANNER = r"""
|
|
11
|
+
_ _ _ _ _ _
|
|
12
|
+
| | | | ___ __ _ __| | ___ _ __ | | | | __ ___ | | __
|
|
13
|
+
| |_| |/ _ \/ _` |/ _` |/ _ \ '__| | |_| |/ _` \ \ /\ / / |/ /
|
|
14
|
+
| _ | __/ (_| | (_| | __/ | | _ | (_| |\ V V /| <
|
|
15
|
+
|_| |_|\___|\__,_|\__,_|\___|_| |_| |_|\__,_| \_/\_/ |_|\_\
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
RISK_COLORS = {
|
|
19
|
+
"Critical": "bold red",
|
|
20
|
+
"High": "bold yellow",
|
|
21
|
+
"Medium": "bold blue"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def print_banner():
|
|
25
|
+
console.print(f"[cyan]{BANNER}[/cyan]")
|
|
26
|
+
console.print("[bold white] Security Header Analyzer — by Sachin Singh[/bold white]")
|
|
27
|
+
console.print("[dim] github.com/sachinsinsinwar/headerhawk[/dim]\n")
|
|
28
|
+
|
|
29
|
+
def print_results(result, findings, score, info_disclosure, fail_on=None):
|
|
30
|
+
console.print(f"\n[bold]Target:[/bold] {result['url']}")
|
|
31
|
+
console.print(f"[bold]Status Code:[/bold] {result['status_code']}")
|
|
32
|
+
|
|
33
|
+
score_color = "green" if score >= 70 else "yellow" if score >= 40 else "red"
|
|
34
|
+
console.print(f"[bold]Security Score:[/bold] [{score_color}]{score}/100[/{score_color}]\n")
|
|
35
|
+
|
|
36
|
+
table = Table(show_header=True, header_style="bold cyan", expand=True)
|
|
37
|
+
table.add_column("Header", style="dim", width=35)
|
|
38
|
+
table.add_column("Status", width=10)
|
|
39
|
+
table.add_column("Risk", width=10)
|
|
40
|
+
table.add_column("Attack Scenario")
|
|
41
|
+
|
|
42
|
+
fail = False
|
|
43
|
+
for f in findings:
|
|
44
|
+
if f["status"] == "MISSING":
|
|
45
|
+
status = "[red]MISSING[/red]"
|
|
46
|
+
attack = f["attack"]
|
|
47
|
+
risk_style = RISK_COLORS.get(f["risk"], "white")
|
|
48
|
+
risk = f"[{risk_style}]{f['risk']}[/{risk_style}]"
|
|
49
|
+
if fail_on and f["risk"].lower() == fail_on.lower():
|
|
50
|
+
fail = True
|
|
51
|
+
else:
|
|
52
|
+
status = "[green]PRESENT[/green]"
|
|
53
|
+
attack = "[dim]—[/dim]"
|
|
54
|
+
risk_style = RISK_COLORS.get(f["risk"], "white")
|
|
55
|
+
risk = f"[{risk_style}]{f['risk']}[/{risk_style}]"
|
|
56
|
+
|
|
57
|
+
table.add_row(f["header"], status, risk, attack)
|
|
58
|
+
|
|
59
|
+
console.print(table)
|
|
60
|
+
|
|
61
|
+
if info_disclosure:
|
|
62
|
+
console.print("\n[bold red]Info Disclosure Detected:[/bold red]")
|
|
63
|
+
for item in info_disclosure:
|
|
64
|
+
console.print(f" [yellow]{item}[/yellow]")
|
|
65
|
+
|
|
66
|
+
if fail:
|
|
67
|
+
console.print(f"\n[bold red]FAIL: Critical headers missing. Exiting with code 1.[/bold red]")
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
def main():
|
|
71
|
+
print_banner()
|
|
72
|
+
|
|
73
|
+
parser = argparse.ArgumentParser(
|
|
74
|
+
prog="headerhawk",
|
|
75
|
+
description="Security Header Analyzer — HeaderHawk"
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument("--url", required=True, help="Target URL to scan")
|
|
78
|
+
parser.add_argument("--report", choices=["html"], help="Generate report (html)")
|
|
79
|
+
parser.add_argument("--fail-on", choices=["critical", "high", "medium"],
|
|
80
|
+
help="Exit with code 1 if this risk level is found missing (for CI/CD)")
|
|
81
|
+
|
|
82
|
+
args = parser.parse_args()
|
|
83
|
+
|
|
84
|
+
console.print(f"[dim]Scanning {args.url} ...[/dim]\n")
|
|
85
|
+
|
|
86
|
+
result = get_headers(args.url)
|
|
87
|
+
|
|
88
|
+
if "error" in result:
|
|
89
|
+
console.print(f"[bold red]Error:[/bold red] {result['error']}")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
analysis = analyze(result["headers"])
|
|
93
|
+
|
|
94
|
+
print_results(
|
|
95
|
+
result,
|
|
96
|
+
analysis["findings"],
|
|
97
|
+
analysis["score"],
|
|
98
|
+
analysis["info_disclosure"],
|
|
99
|
+
fail_on=args.fail_on
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if args.report == "html":
|
|
103
|
+
from reports.html_report import generate
|
|
104
|
+
filename = generate(
|
|
105
|
+
result["url"],
|
|
106
|
+
result["status_code"],
|
|
107
|
+
analysis["score"],
|
|
108
|
+
analysis["findings"],
|
|
109
|
+
analysis["info_disclosure"]
|
|
110
|
+
)
|
|
111
|
+
console.print(f"\n[bold green]HTML report saved:[/bold green] {filename}")
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
main()
|
core/__init__.py
ADDED
|
File without changes
|
core/analyzer.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
HEADERS_TO_CHECK = {
|
|
2
|
+
"strict-transport-security": {
|
|
3
|
+
"risk": "Critical",
|
|
4
|
+
"attack": "SSL stripping / MITM — attacker intercepts and downgrades HTTPS to HTTP"
|
|
5
|
+
},
|
|
6
|
+
"content-security-policy": {
|
|
7
|
+
"risk": "Critical",
|
|
8
|
+
"attack": "XSS — attacker injects malicious scripts, steals session cookies"
|
|
9
|
+
},
|
|
10
|
+
"access-control-allow-origin": {
|
|
11
|
+
"risk": "Critical",
|
|
12
|
+
"attack": "CORS misconfiguration — cross-origin requests steal authenticated data"
|
|
13
|
+
},
|
|
14
|
+
"x-frame-options": {
|
|
15
|
+
"risk": "High",
|
|
16
|
+
"attack": "Clickjacking — attacker embeds site in iframe, tricks user into clicking"
|
|
17
|
+
},
|
|
18
|
+
"x-content-type-options": {
|
|
19
|
+
"risk": "High",
|
|
20
|
+
"attack": "MIME sniffing — browser executes uploaded files as scripts"
|
|
21
|
+
},
|
|
22
|
+
"referrer-policy": {
|
|
23
|
+
"risk": "Medium",
|
|
24
|
+
"attack": "URL data leakage — sensitive tokens in URL leak to third parties"
|
|
25
|
+
},
|
|
26
|
+
"permissions-policy": {
|
|
27
|
+
"risk": "Medium",
|
|
28
|
+
"attack": "Feature abuse — unauthorized access to camera, mic, location"
|
|
29
|
+
},
|
|
30
|
+
"cache-control": {
|
|
31
|
+
"risk": "Medium",
|
|
32
|
+
"attack": "Sensitive data cached — auth pages found in shared/public browsers"
|
|
33
|
+
},
|
|
34
|
+
"x-xss-protection": {
|
|
35
|
+
"risk": "Medium",
|
|
36
|
+
"attack": "Reflected XSS — exploits legacy browsers without XSS filter"
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
RISK_SCORE = {"Critical": 30, "High": 15, "Medium": 5}
|
|
41
|
+
|
|
42
|
+
def analyze(headers):
|
|
43
|
+
headers_lower = {k.lower(): v for k, v in headers.items()}
|
|
44
|
+
findings = []
|
|
45
|
+
score = 100
|
|
46
|
+
|
|
47
|
+
for header, info in HEADERS_TO_CHECK.items():
|
|
48
|
+
if header not in headers_lower:
|
|
49
|
+
findings.append({
|
|
50
|
+
"header": header,
|
|
51
|
+
"status": "MISSING",
|
|
52
|
+
"risk": info["risk"],
|
|
53
|
+
"attack": info["attack"]
|
|
54
|
+
})
|
|
55
|
+
score -= RISK_SCORE[info["risk"]]
|
|
56
|
+
else:
|
|
57
|
+
findings.append({
|
|
58
|
+
"header": header,
|
|
59
|
+
"status": "PRESENT",
|
|
60
|
+
"risk": info["risk"],
|
|
61
|
+
"value": headers_lower[header],
|
|
62
|
+
"attack": ""
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
info_disclosure = []
|
|
66
|
+
for h in ["server", "x-powered-by", "x-aspnet-version"]:
|
|
67
|
+
if h in headers_lower:
|
|
68
|
+
info_disclosure.append(f"{h}: {headers_lower[h]}")
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"findings": findings,
|
|
72
|
+
"score": max(score, 0),
|
|
73
|
+
"info_disclosure": info_disclosure
|
|
74
|
+
}
|
core/scanner.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
def get_headers(url):
|
|
4
|
+
if not url.startswith("http"):
|
|
5
|
+
url = "https://" + url
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
response = requests.get(
|
|
9
|
+
url,
|
|
10
|
+
timeout=10,
|
|
11
|
+
allow_redirects=True,
|
|
12
|
+
headers={"User-Agent": "HeaderHawk/1.0 Security Scanner"}
|
|
13
|
+
)
|
|
14
|
+
return {
|
|
15
|
+
"url": url,
|
|
16
|
+
"status_code": response.status_code,
|
|
17
|
+
"headers": dict(response.headers)
|
|
18
|
+
}
|
|
19
|
+
except requests.exceptions.SSLError:
|
|
20
|
+
try:
|
|
21
|
+
response = requests.get(url, timeout=10, verify=False)
|
|
22
|
+
return {
|
|
23
|
+
"url": url,
|
|
24
|
+
"status_code": response.status_code,
|
|
25
|
+
"headers": dict(response.headers)
|
|
26
|
+
}
|
|
27
|
+
except Exception as e:
|
|
28
|
+
return {"url": url, "error": str(e)}
|
|
29
|
+
except Exception as e:
|
|
30
|
+
return {"url": url, "error": str(e)}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: headerhawk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Security Header Analyzer — CLI tool for pentesters, DevOps and developers
|
|
5
|
+
Home-page: https://github.com/sachinsinsinwar/headerhawk
|
|
6
|
+
Author: Sachin Singh
|
|
7
|
+
Author-email: sachinsinsinwar8@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Security
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: requests
|
|
16
|
+
Requires-Dist: rich
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# 🦅 HeaderHawk
|
|
29
|
+
|
|
30
|
+
> Security Header Analyzer — Built for Pentesters, DevOps, and Developers
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
HeaderHawk scans any website for missing HTTP security headers, assigns risk scores, explains real attack scenarios, and generates shareable HTML reports.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- Checks 9 critical security headers
|
|
43
|
+
- Risk scoring — Critical / High / Medium
|
|
44
|
+
- Shows real attack scenario for each missing header
|
|
45
|
+
- Detects server info disclosure (version leakage)
|
|
46
|
+
- Generates HTML report
|
|
47
|
+
- CI/CD ready — fail builds on missing critical headers
|
|
48
|
+
- Works on Kali Linux, Ubuntu, macOS
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
\`\`\`bash
|
|
55
|
+
git clone https://github.com/sachinsinsinwar/headerhawk.git
|
|
56
|
+
cd headerhawk
|
|
57
|
+
pip install -r requirements.txt
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
\`\`\`bash
|
|
65
|
+
# Basic scan
|
|
66
|
+
python -m cli.main --url https://target.com
|
|
67
|
+
|
|
68
|
+
# Generate HTML report
|
|
69
|
+
python -m cli.main --url https://target.com --report html
|
|
70
|
+
|
|
71
|
+
# CI/CD — exit code 1 if critical headers missing
|
|
72
|
+
python -m cli.main --url https://target.com --fail-on critical
|
|
73
|
+
|
|
74
|
+
# Help
|
|
75
|
+
python -m cli.main -h
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Example Output
|
|
81
|
+
|
|
82
|
+
\`\`\`
|
|
83
|
+
Target: https://example.com
|
|
84
|
+
Status Code: 200
|
|
85
|
+
Security Score: 30/100
|
|
86
|
+
|
|
87
|
+
Header Status Risk Attack Scenario
|
|
88
|
+
strict-transport-security PRESENT Critical —
|
|
89
|
+
content-security-policy MISSING Critical XSS — attacker injects malicious scripts
|
|
90
|
+
x-frame-options MISSING High Clickjacking — attacker embeds site in iframe
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Headers Checked
|
|
96
|
+
|
|
97
|
+
| Header | Risk | Attack if Missing |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Strict-Transport-Security | Critical | SSL stripping / MITM |
|
|
100
|
+
| Content-Security-Policy | Critical | XSS attacks |
|
|
101
|
+
| Access-Control-Allow-Origin | Critical | CORS misconfiguration |
|
|
102
|
+
| X-Frame-Options | High | Clickjacking |
|
|
103
|
+
| X-Content-Type-Options | High | MIME sniffing |
|
|
104
|
+
| Referrer-Policy | Medium | URL data leakage |
|
|
105
|
+
| Permissions-Policy | Medium | Feature abuse |
|
|
106
|
+
| Cache-Control | Medium | Sensitive data cached |
|
|
107
|
+
| X-XSS-Protection | Medium | Reflected XSS |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Who Is This For
|
|
112
|
+
|
|
113
|
+
**Pentester / Attacker** — Recon phase. Find missing headers on target, understand what attacks are possible.
|
|
114
|
+
|
|
115
|
+
**Security Engineer** — Scan UAT before formal VAPT. Fix issues before the auditor finds them.
|
|
116
|
+
|
|
117
|
+
**DevOps** — Add to GitHub Actions pipeline. Block deployments if critical headers are missing.
|
|
118
|
+
|
|
119
|
+
**Developer** — Compare staging vs production. Catch header mismatches before release.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Created By
|
|
124
|
+
|
|
125
|
+
**Sachin Singh** — DevSecOps Engineer
|
|
126
|
+
[LinkedIn](https://linkedin.com/in/sachin-sinsinwar) | [GitHub](https://github.com/sachinsinsinwar)
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
cli/main.py,sha256=Pgii1sFSjwCQwerdjuISCBZYdGb32wbLlL1U4lDkxPs,3861
|
|
3
|
+
core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
core/analyzer.py,sha256=UQ3hdvhoRT14fKFJ38Z4TR1hFcvhKFa8J-zlT9jP_e4,2436
|
|
5
|
+
core/scanner.py,sha256=tm55spCtT3_nPB7p44_f6O5bBKsCCH65KzeTELJFiz8,888
|
|
6
|
+
headerhawk-1.0.0.dist-info/licenses/LICENSE,sha256=RTjaMY6agKhubZhwFNAGCTpHi0URXcZ-BY2JdxeFXL4,1069
|
|
7
|
+
reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
reports/html_report.py,sha256=g7mxYvHID4_AbHylLR3yPMj8n5lro-O0af4tlirruks,3185
|
|
9
|
+
headerhawk-1.0.0.dist-info/METADATA,sha256=AFuUZR5gwi_KILm6kesdeuScm-qJRRHUcS7By70UzFU,3584
|
|
10
|
+
headerhawk-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
11
|
+
headerhawk-1.0.0.dist-info/entry_points.txt,sha256=8846y2EhFi6FknOucVYJTiOsprOmlTCmm6RRcfFlHEI,45
|
|
12
|
+
headerhawk-1.0.0.dist-info/top_level.txt,sha256=gWIaEdhcLuYg-bcox8iKbPZ-fw1zwGXyRWJxWk2wP_w,17
|
|
13
|
+
headerhawk-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sachin Singh
|
|
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.
|
reports/__init__.py
ADDED
|
File without changes
|
reports/html_report.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
RISK_COLORS = {
|
|
4
|
+
"Critical": "#ff4444",
|
|
5
|
+
"High": "#ff9900",
|
|
6
|
+
"Medium": "#3399ff"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def generate(url, status_code, score, findings, info_disclosure):
|
|
10
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
11
|
+
score_color = "#44ff88" if score >= 70 else "#ff9900" if score >= 40 else "#ff4444"
|
|
12
|
+
|
|
13
|
+
rows = ""
|
|
14
|
+
for f in findings:
|
|
15
|
+
status_color = "#44ff88" if f["status"] == "PRESENT" else "#ff4444"
|
|
16
|
+
risk_color = RISK_COLORS.get(f["risk"], "#ffffff")
|
|
17
|
+
attack = f["attack"] if f["attack"] else "—"
|
|
18
|
+
rows += f"""
|
|
19
|
+
<tr>
|
|
20
|
+
<td>{f['header']}</td>
|
|
21
|
+
<td style="color:{status_color};font-weight:bold">{f['status']}</td>
|
|
22
|
+
<td style="color:{risk_color};font-weight:bold">{f['risk']}</td>
|
|
23
|
+
<td>{attack}</td>
|
|
24
|
+
</tr>"""
|
|
25
|
+
|
|
26
|
+
disclosure_html = ""
|
|
27
|
+
if info_disclosure:
|
|
28
|
+
items = "".join(f"<li>{i}</li>" for i in info_disclosure)
|
|
29
|
+
disclosure_html = f"""
|
|
30
|
+
<div class="disclosure">
|
|
31
|
+
<h3>⚠ Info Disclosure Detected</h3>
|
|
32
|
+
<ul>{items}</ul>
|
|
33
|
+
</div>"""
|
|
34
|
+
|
|
35
|
+
html = f"""<!DOCTYPE html>
|
|
36
|
+
<html lang="en">
|
|
37
|
+
<head>
|
|
38
|
+
<meta charset="UTF-8">
|
|
39
|
+
<title>HeaderHawk Report — {url}</title>
|
|
40
|
+
<style>
|
|
41
|
+
body {{ font-family: 'Segoe UI', sans-serif; background: #0d1117; color: #e6edf3; margin: 0; padding: 2rem; }}
|
|
42
|
+
h1 {{ color: #58a6ff; font-size: 1.8rem; margin-bottom: 0.2rem; }}
|
|
43
|
+
.meta {{ color: #8b949e; font-size: 13px; margin-bottom: 2rem; }}
|
|
44
|
+
.score {{ font-size: 3rem; font-weight: bold; color: {score_color}; }}
|
|
45
|
+
.score-label {{ color: #8b949e; font-size: 13px; margin-bottom: 2rem; }}
|
|
46
|
+
table {{ width: 100%; border-collapse: collapse; margin-top: 1rem; }}
|
|
47
|
+
th {{ background: #161b22; color: #58a6ff; padding: 10px 14px; text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; }}
|
|
48
|
+
td {{ padding: 10px 14px; border-bottom: 1px solid #21262d; font-size: 13px; vertical-align: top; }}
|
|
49
|
+
tr:hover {{ background: #161b22; }}
|
|
50
|
+
.disclosure {{ background: #3d1a1a; border: 1px solid #5a2020; border-radius: 8px; padding: 1rem 1.5rem; margin-top: 2rem; }}
|
|
51
|
+
.disclosure h3 {{ color: #ff7b72; margin-bottom: 8px; }}
|
|
52
|
+
.disclosure ul {{ color: #e3b341; padding-left: 1.2rem; font-size: 13px; line-height: 1.8; }}
|
|
53
|
+
.footer {{ margin-top: 3rem; color: #6e7681; font-size: 12px; border-top: 1px solid #21262d; padding-top: 1rem; }}
|
|
54
|
+
</style>
|
|
55
|
+
</head>
|
|
56
|
+
<body>
|
|
57
|
+
<h1>🦅 HeaderHawk — Security Report</h1>
|
|
58
|
+
<div class="meta">Target: <strong>{url}</strong> | Status: {status_code} | Scanned: {now}</div>
|
|
59
|
+
<div class="score">{score}/100</div>
|
|
60
|
+
<div class="score-label">Security Score</div>
|
|
61
|
+
<table>
|
|
62
|
+
<thead>
|
|
63
|
+
<tr>
|
|
64
|
+
<th>Header</th>
|
|
65
|
+
<th>Status</th>
|
|
66
|
+
<th>Risk</th>
|
|
67
|
+
<th>Attack Scenario</th>
|
|
68
|
+
</tr>
|
|
69
|
+
</thead>
|
|
70
|
+
<tbody>{rows}</tbody>
|
|
71
|
+
</table>
|
|
72
|
+
{disclosure_html}
|
|
73
|
+
<div class="footer">Generated by HeaderHawk — Created by Sachin Singh | github.com/sachinsinsinwar/headerhawk</div>
|
|
74
|
+
</body>
|
|
75
|
+
</html>"""
|
|
76
|
+
|
|
77
|
+
filename = f"headerhawk_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
|
|
78
|
+
with open(filename, "w") as f:
|
|
79
|
+
f.write(html)
|
|
80
|
+
|
|
81
|
+
return filename
|