Plain is headed towards 1.0! Subscribe for development updates →

 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        )