ci: add security headers audit to pipeline

This commit is contained in:
Username
2025-12-26 16:56:03 +01:00
parent 63034e17fe
commit 93a4dd2f97
3 changed files with 112 additions and 1 deletions

View File

@@ -191,6 +191,9 @@ jobs:
- name: Race condition tests
run: python tests/security/race_condition_test.py
- name: Security headers audit
run: python tests/security/headers_audit.py
memory:
name: Memory Leak Check
runs-on: ubuntu-latest

View File

@@ -191,7 +191,7 @@ Not tested (no signature defined):
[ ] Add remaining MIME test results to security assessment
[ ] Document rate limiting behavior under attack
[ ] Create threat model diagram
[ ] Add security headers audit to CI pipeline
[x] Add security headers audit to CI pipeline
```
---

View File

@@ -0,0 +1,108 @@
#!/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())