Some checks failed
CI / Security Scan (push) Successful in 20s
CI / Lint & Format (push) Failing after 22s
CI / Unit Tests (push) Has been skipped
CI / Security Tests (push) Has been skipped
CI / Advanced Security Tests (push) Has been skipped
CI / Memory Leak Check (push) Has been skipped
CI / SBOM Generation (push) Has been skipped
CI / Build & Push Image (push) Has been skipped
109 lines
3.0 KiB
Python
109 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Security headers audit for FlaskPaste.
|
|
|
|
Verifies all required security headers are present and correctly configured.
|
|
"""
|
|
|
|
import sys
|
|
|
|
sys.path.insert(0, ".")
|
|
|
|
from app import create_app
|
|
|
|
# Required headers and their expected values
|
|
REQUIRED_HEADERS = {
|
|
"X-Content-Type-Options": "nosniff",
|
|
"X-Frame-Options": "DENY",
|
|
"Content-Security-Policy": "default-src 'none'",
|
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
"Permissions-Policy": "geolocation=(), microphone=(), camera=()",
|
|
"Cache-Control": "no-store, no-cache, must-revalidate",
|
|
"Pragma": "no-cache",
|
|
}
|
|
|
|
# Headers that should NOT be present
|
|
FORBIDDEN_HEADERS = [
|
|
"X-Powered-By",
|
|
"Server",
|
|
]
|
|
|
|
# Endpoints to test
|
|
TEST_ENDPOINTS = [
|
|
("/", "GET", 200),
|
|
("/health", "GET", 200),
|
|
("/challenge", "GET", 200),
|
|
("/nonexistent", "GET", 400), # Error response
|
|
]
|
|
|
|
|
|
def run_audit():
|
|
"""Run security headers audit."""
|
|
print("=" * 60)
|
|
print("SECURITY HEADERS AUDIT")
|
|
print("=" * 60)
|
|
|
|
app = create_app("testing")
|
|
client = app.test_client()
|
|
|
|
results = {"passed": 0, "failed": 0, "warnings": 0}
|
|
|
|
for endpoint, method, _expected_status in TEST_ENDPOINTS:
|
|
print(f"\n[{method} {endpoint}]")
|
|
print("-" * 40)
|
|
|
|
if method == "GET":
|
|
resp = client.get(endpoint)
|
|
elif method == "POST":
|
|
resp = client.post(endpoint, data=b"test")
|
|
else:
|
|
continue
|
|
|
|
# Check required headers
|
|
for header, expected in REQUIRED_HEADERS.items():
|
|
actual = resp.headers.get(header, "")
|
|
if expected in actual:
|
|
print(f" ✓ {header}")
|
|
results["passed"] += 1
|
|
else:
|
|
print(f" ✗ {header}")
|
|
print(f" Expected: {expected}")
|
|
print(f" Got: {actual or '(missing)'}")
|
|
results["failed"] += 1
|
|
|
|
# Check forbidden headers
|
|
for header in FORBIDDEN_HEADERS:
|
|
if header in resp.headers:
|
|
print(f" ⚠ {header} should not be present")
|
|
results["warnings"] += 1
|
|
else:
|
|
print(f" ✓ No {header} header")
|
|
results["passed"] += 1
|
|
|
|
# Check X-Request-ID
|
|
if "X-Request-ID" in resp.headers:
|
|
print(" ✓ X-Request-ID present")
|
|
results["passed"] += 1
|
|
else:
|
|
print(" ✗ X-Request-ID missing")
|
|
results["failed"] += 1
|
|
|
|
# Summary
|
|
print("\n" + "=" * 60)
|
|
print("SUMMARY")
|
|
print("=" * 60)
|
|
print(f" Passed: {results['passed']}")
|
|
print(f" Failed: {results['failed']}")
|
|
print(f" Warnings: {results['warnings']}")
|
|
|
|
total = results["passed"] + results["failed"]
|
|
if results["failed"] == 0:
|
|
print(f"\n{results['passed']}/{total} checks passed")
|
|
return 0
|
|
else:
|
|
print(f"\nFAILED: {results['failed']}/{total} checks failed")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(run_audit())
|