1from __future__ import annotations
2
3from typing import TYPE_CHECKING
4
5from ..results import AuditResult, CheckResult
6from .base import Audit
7
8if TYPE_CHECKING:
9 from ..scanner import Scanner
10
11
12class ContentTypeOptionsAudit(Audit):
13 """Content type options header checks."""
14
15 name = "Content Type Options"
16 slug = "content-type-options"
17 description = "Prevents browsers from MIME-sniffing responses away from the declared content type. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options"
18
19 def check(self, scanner: Scanner) -> AuditResult:
20 """Check if X-Content-Type-Options header is present and configured properly."""
21 response = scanner.fetch()
22
23 # Check if header is present
24 header = response.headers.get("X-Content-Type-Options")
25
26 if not header:
27 # Header not detected
28 return AuditResult(
29 name=self.name,
30 detected=False,
31 required=self.required,
32 checks=[],
33 description=self.description,
34 )
35
36 # Header detected - validate value
37 checks = [
38 self._check_nosniff(header),
39 ]
40
41 return AuditResult(
42 name=self.name,
43 detected=True,
44 required=self.required,
45 checks=checks,
46 description=self.description,
47 )
48
49 def _check_nosniff(self, header: str) -> CheckResult:
50 """Check if X-Content-Type-Options is set to nosniff."""
51 header_value = header.strip().lower()
52
53 if header_value == "nosniff":
54 return CheckResult(
55 name="nosniff",
56 passed=True,
57 message="X-Content-Type-Options is set to nosniff",
58 )
59
60 return CheckResult(
61 name="nosniff",
62 passed=False,
63 message=f"X-Content-Type-Options has invalid value: '{header}' (expected: nosniff)",
64 )