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 StatusCodeAudit(Audit):
13    """HTTP status code checks."""
14
15    name = "HTTP Status Code"
16    slug = "status-code"
17    description = "Checks that the server returns a successful HTTP status code and not an error response."
18    required = True  # Server errors are always a problem
19
20    def check(self, scanner: Scanner) -> AuditResult:
21        """Check if the final response has a valid status code."""
22        response = scanner.fetch()
23
24        # Get status code from response
25        status_code = response.status_code if response else None
26
27        if status_code is None:
28            return AuditResult(
29                name=self.name,
30                detected=True,
31                required=self.required,
32                checks=[
33                    CheckResult(
34                        name="status-code",
35                        passed=False,
36                        message="Unable to determine HTTP status code",
37                    )
38                ],
39                description=self.description,
40            )
41
42        # Check for server errors (5xx)
43        if 500 <= status_code < 600:
44            return AuditResult(
45                name=self.name,
46                detected=True,
47                required=self.required,
48                checks=[
49                    CheckResult(
50                        name="status-code",
51                        passed=False,
52                        message=f"Server returned {status_code} error - cannot perform complete security audit",
53                    )
54                ],
55                description=self.description,
56            )
57
58        # Check for client errors (4xx) - informational, not required to pass
59        if 400 <= status_code < 500:
60            return AuditResult(
61                name=self.name,
62                detected=True,
63                required=False,  # 4xx might be expected (like 404 for a test page)
64                checks=[
65                    CheckResult(
66                        name="status-code",
67                        passed=False,
68                        message=f"Server returned {status_code} client error",
69                    )
70                ],
71                description=self.description,
72            )
73
74        # Success (2xx) or redirect (3xx) - all good
75        return AuditResult(
76            name=self.name,
77            detected=True,
78            required=self.required,
79            checks=[
80                CheckResult(
81                    name="status-code",
82                    passed=True,
83                    message=f"Server returned {status_code} status code",
84                )
85            ],
86            description=self.description,
87        )