Files
team-tryouts/tests/test_security_headers.py
T
GGThedandClaude Opus 5 7cec18c139 style: formater le depot avec ruff format
QUA-002, premiere moitie. **Ce commit ne fait que reformater** : aucun
changement de comportement, aucune ligne de logique touchee. 72 fichiers,
4 restaient deja conformes. Il est isole exprès, pour que `git log -p` sur
les commits voisins reste lisible.

`quote-style = "preserve"` etait deja pose dans pyproject.toml, ce qui
evite le brassage guillemets simples / doubles : le diff porte sur les
retours a la ligne, l indentation des appels longs et les virgules
finales, pas sur le style de chaine.

Verification : 263 tests passent avant et apres, ruff check propre.

L activation en CI arrive dans le commit suivant, separement, pour que ce
diff-ci ne contienne rien d autre.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-08 15:53:10 -04:00

78 lines
3.0 KiB
Python

"""HTTP hardening, error disclosure, and template escaping."""
from app.app import nl2br
class TestSecurityHeaders:
def test_core_headers_are_present(self, client):
headers = client.get('/auth/login').headers
assert headers['X-Content-Type-Options'] == 'nosniff'
assert headers['X-Frame-Options'] == 'DENY'
assert headers['Referrer-Policy'] == 'strict-origin-when-cross-origin'
assert 'frame-ancestors' in headers['Content-Security-Policy']
def test_deprecated_xss_auditor_header_is_not_sent(self, client):
"""X-XSS-Protection was removed: deprecated, and harmful in its
last implementations."""
assert 'X-XSS-Protection' not in client.get('/auth/login').headers
def test_csp_does_not_allow_inline_script(self, client):
"""SEC-WEB-001, closed: inline scripts are authorised by a
per-request nonce instead."""
csp = client.get('/auth/login').headers['Content-Security-Policy']
script_src = [d for d in csp.split(';') if d.strip().startswith('script-src')][0]
assert "'unsafe-inline'" not in script_src
assert "'nonce-" in script_src
class TestErrorDisclosure:
def test_health_reports_status_without_the_driver_message(self, app, client):
"""/health is unauthenticated. Driver exceptions routinely carry the
host, database name and user of the connection string."""
from app.extensions import db
def boom(*args, **kwargs):
raise RuntimeError(
'FATAL: password authentication failed for user "app" host=db.internal port=5432'
)
original = db.session.execute
db.session.execute = boom
try:
response = client.get('/health')
finally:
db.session.execute = original
assert response.status_code == 503
body = response.get_data(as_text=True)
assert response.get_json()['database'] == 'error'
for leaked in ('password', 'db.internal', '5432', 'FATAL'):
assert leaked not in body, f'{leaked!r} leaked through /health'
def test_health_reports_healthy_when_the_database_answers(self, client):
response = client.get('/health')
assert response.status_code == 200
assert response.get_json()['database'] == 'connected'
class TestTemplateEscaping:
def test_nl2br_escapes_markup(self, app):
"""Markup('<br>'.join(...)) marked attacker text as safe.
Markup('<br>').join(...) escapes each segment first."""
with app.app_context():
rendered = str(nl2br('<script>alert(1)</script>\nsecond'))
assert '<script>' not in rendered
assert '&lt;script&gt;' in rendered
assert '<br>' in rendered, 'the line break itself must stay markup'
def test_nl2br_still_joins_lines(self, app):
with app.app_context():
assert str(nl2br('a\nb')) == 'a<br>b'
def test_nl2br_handles_empty_input(self, app):
with app.app_context():
assert nl2br('') == ''
assert nl2br(None) == ''