Check Implementations¶
Guard-core ships with 17 security checks that execute in this fixed order inside the SecurityCheckPipeline when all 17 are built. Each check is a subclass of SecurityCheck located in guard_core.core.checks.implementations. A given deployment's pipeline is usually a subset of the 17: build_default_pipeline filters the catalogue through each check class's applies_to(config, route_configs) classmethod before instantiating anything, and only the checks that return True for the effective configuration and registered routes are built. The base implementation returns True, so a check that does not override it always runs; a predicate that is uncertain what the configuration can trigger must also return True -- elimination is strictly an optimization, never a security decision. See Build-Time Elimination for the full mechanism, including why ip_security can never be eliminated.
Execution Order¶
| # | Check Name | Class | Blocks? | Passive-Aware? | Eliminated when |
|---|---|---|---|---|---|
| 1 | route_config |
RouteConfigCheck |
Strict | Yes | Never |
| 2 | emergency_mode |
EmergencyModeCheck |
Yes | Yes | emergency_mode is False and enable_dynamic_rules is False |
| 3 | https_enforcement |
HttpsEnforcementCheck |
Yes | Yes | enforce_https is False and no route requires HTTPS |
| 4 | request_logging |
RequestLoggingCheck |
Never | N/A | log_request_level is None |
| 5 | request_size_content |
RequestSizeContentCheck |
Yes | Yes | No route sets max_request_size or allowed_content_types |
| 6 | required_headers |
RequiredHeadersCheck |
Yes | Yes | No route sets required_headers |
| 7 | authentication |
AuthenticationCheck |
Yes | Yes | No route sets auth_required |
| 8 | referrer |
ReferrerCheck |
Yes | Yes | No route sets require_referrer |
| 9 | custom_validators |
CustomValidatorsCheck |
Yes | Yes | No route sets custom_validators |
| 10 | time_window |
TimeWindowCheck |
Yes | Yes | No route sets time_restrictions |
| 11 | cloud_ip_refresh |
CloudIpRefreshCheck |
Never | N/A | block_cloud_providers unset globally and per-route, and enable_dynamic_rules is False |
| 12 | ip_security |
IpSecurityCheck |
Yes | Yes | Never |
| 13 | cloud_provider |
CloudProviderCheck |
Yes | Yes | block_cloud_providers unset globally and per-route, and enable_dynamic_rules is False |
| 14 | user_agent |
UserAgentCheck |
Yes | Yes | blocked_user_agents empty globally and per-route, and enable_dynamic_rules is False |
| 15 | rate_limit |
RateLimitCheck |
Yes | Yes | enable_rate_limiting is False, endpoint_rate_limits unset, no route sets rate_limit/geo_rate_limits, and enable_dynamic_rules is False |
| 16 | suspicious_activity |
SuspiciousActivityCheck |
Yes | Yes | enable_penetration_detection is False, no route sets enable_suspicious_detection, and enable_dynamic_rules is False |
| 17 | custom_request |
CustomRequestCheck |
Yes | Yes | custom_request_check is None |
Passive-Aware means the check respects SecurityConfig.passive_mode -- it logs and emits events but does not return a blocking response.
Strict in the Blocks column means the check only blocks when a non-default setting turns it on; route_config blocks solely under SecurityConfig.route_resolution_strict.
Eliminated when describes the condition under which applies_to returns False and build_default_pipeline skips instantiating the check entirely. "No route sets X" only applies when the adapter can enumerate registered routes; when it cannot (route_configs is None), every route-driven check is kept. enable_rate_limiting and enable_penetration_detection both default to True, so rate_limit and suspicious_activity are built by default even with no other configuration. route_config and ip_security have no applies_to override and are never eliminated.
1. RouteConfigCheck¶
Purpose: Populates request.state with route configuration and client IP for all subsequent checks.
Blocks when: config.route_resolution_strict is True and the adapter set request.state.guard_route_unresolved, meaning it could not match the request to a route. Off by default, in which case this check never blocks.
Response: 500 Route resolution failed, alongside a suspicious-activity log and a route_unresolved event. Under passive_mode the log and event still fire and the request proceeds.
What it does:
- Calls
middleware.route_resolver.get_route_config(request)to resolve decorator-appliedRouteConfig. - Sets
request.state.route_config, which isNoneboth when no decorator is applied and when the adapter could not resolve the route. Only the adapter can tell those apart, which is why it reports the second case separately -- see Reporting a Failed Match. - Calls
extract_client_ip()and setsrequest.state.client_ip.
Must Run First
Every other check reads request.state.route_config and request.state.client_ip. Removing or reordering this check will break the pipeline.
2. EmergencyModeCheck¶
Purpose: Lockdown mode that blocks all traffic except whitelisted IPs.
Blocks when: config.emergency_mode is True and client_ip is not in config.emergency_whitelist.
Response: 503 Service temporarily unavailable
Configuration:
| Field | Type | Default |
|---|---|---|
emergency_mode |
bool |
False |
emergency_whitelist |
list[str] |
[] |
3. HttpsEnforcementCheck¶
Purpose: Redirects HTTP requests to HTTPS.
Blocks when: HTTPS is required (globally or per-route) and the request is not HTTPS.
Response: 307 redirect to the HTTPS URL (via response_factory.create_https_redirect).
HTTPS detection logic:
- Checks
request.url_scheme == "https". - If
trust_x_forwarded_protois enabled and the connecting IP is a trusted proxy, also checksX-Forwarded-Proto: https.
Configuration:
| Field | Type | Default |
|---|---|---|
enforce_https |
bool |
False |
trust_x_forwarded_proto |
bool |
False |
trusted_proxies |
list[str] |
[] |
Per-route: RouteConfig.require_https.
4. RequestLoggingCheck¶
Purpose: Logs every incoming request.
Blocks: Never.
Configuration: config.log_request_level controls the log level. Set to None to disable.
5. RequestSizeContentCheck¶
Purpose: Validates request size and content type against route-level limits.
Blocks when:
Content-LengthexceedsRouteConfig.max_request_size(returns413).Content-Typeis not inRouteConfig.allowed_content_types(returns415).
Configuration: Set via decorators on RouteConfig.
6. RequiredHeadersCheck¶
Purpose: Validates that required headers are present.
Blocks when: A header in RouteConfig.required_headers with value "required" is missing from the request.
Response: 400 Missing required header: {name}
7. AuthenticationCheck¶
Purpose: Validates the Authorization header format.
Blocks when: RouteConfig.auth_required is set and the header does not match the expected format ("bearer" expects Bearer ..., "basic" expects Basic ...).
Response: 401 Authentication required
Token Validation Not Included
This check only validates the header format, not the token itself. Actual token validation should be done in a custom validator or application logic.
8. ReferrerCheck¶
Purpose: Validates the Referer header against allowed domains.
Blocks when: RouteConfig.require_referrer is set and either the header is missing or the domain is not in the allowed list.
Response: 403 Referrer required or 403 Invalid referrer
9. CustomValidatorsCheck¶
Purpose: Runs user-defined async validator functions.
Blocks when: Any validator in RouteConfig.custom_validators returns a non-None GuardResponse.
Configuration: Validators are Callable[[GuardRequest], Awaitable[GuardResponse | None]] functions registered via decorators.
10. TimeWindowCheck¶
Purpose: Restricts access to specific time windows.
Blocks when: The current time falls outside the start/end range defined in RouteConfig.time_restrictions.
Response: 403 Access not allowed at this time
Time restriction format:
Supports overnight ranges (e.g., start: "22:00", end: "06:00").
11. CloudIpRefreshCheck¶
Purpose: Periodically refreshes cloud provider IP ranges.
Blocks: Never. The refresh runs as a single-flight background task (via cloud_handler.schedule_refresh, which invokes the middleware's refresh_cloud_ip_ranges()), so the request that crosses the interval is never delayed by provider fetches.
Triggers when: config.block_cloud_providers is set and cloud_ip_refresh_interval seconds have elapsed since the last refresh. If scheduling fails, the debounce timestamp is restored so the next request retries.
12. IpSecurityCheck¶
Purpose: Enforces IP-based access control at multiple levels.
Evaluation order:
- Banned IP check: Consults
IPBanManager. Returns403 IP address banned. - Route-level IP restrictions: If a
RouteConfigexists, evaluates itsip_blacklist,ip_whitelist,blocked_countries, andwhitelist_countriesfirst. Returns403 Forbiddenif the route denies the request. - Global IP restrictions: Always evaluated afterward, even when the route-level check ran and passed. Evaluates
config.blacklist,config.whitelist,config.blocked_countries, andconfig.block_cloud_providers. Returns403 Forbidden.
A route setting overrides the global gate only for the aspect it explicitly allows, and the IP and country aspects are evaluated independently: a route ip_whitelist match suppresses the global IP-list gate but does not exempt the request from country enforcement, and only an actual whitelist_countries match for the resolved country suppresses the global country gate. A route-level deny (ip_blacklist / blocked_countries) is enforced at the route step and never, by itself, disables the global IP or country rules. Within the IP aspect, a route ip_whitelist match wins over that same route's own ip_blacklist (v3.2.0 precedence, unchanged).
Also sets request.state.is_whitelisted — True only for a global config.whitelist match. A route-level ip_whitelist match grants access to that route but does not set is_whitelisted; it is access-only and still passes through rate limiting, user-agent filtering, cloud-provider blocking, and suspicious-activity detection.
13. CloudProviderCheck¶
Purpose: Blocks requests originating from cloud provider IP ranges (AWS, GCP, Azure).
Blocks when: client_ip belongs to a blocked cloud provider's network and the check is not bypassed.
Response: 403 Cloud provider IP not allowed
Skips: IPs whitelisted at the global level (request.state.is_whitelisted) — a route-level ip_whitelist match alone does not set this, so it does not skip this check.
14. UserAgentCheck¶
Purpose: Blocks requests from matching user agents.
Blocks when: The User-Agent header matches any pattern in RouteConfig.blocked_user_agents or config.blocked_user_agents (case-insensitive regex).
Response: 403 User-Agent not allowed
Skips: Whitelisted IPs.
15. RateLimitCheck¶
Purpose: Enforces request rate limits using a sliding window algorithm.
Evaluation order (first match wins):
- Endpoint-specific rate limits from
config.endpoint_rate_limits(set by dynamic rules). - Route-level rate limits from
RouteConfig.rate_limitandRouteConfig.rate_limit_window. - Geo-based rate limits from
RouteConfig.geo_rate_limits. - Global rate limits from
config.rate_limitandconfig.rate_limit_window.
Response: 429 Too many requests
Skips: Whitelisted IPs and bypassed routes.
16. SuspiciousActivityCheck¶
Purpose: Detects penetration attempts (SQLi, XSS, command injection, path traversal, etc.).
Blocks when: detect_penetration_patterns() finds a threat in query parameters, URL path, headers, or request body.
Behavior:
- Increments
suspicious_request_counts[client_ip]. - When the count reaches
auto_ban_threshold, bans the IP viaIPBanManagerforauto_ban_durationseconds. - Returns
403 IP has been bannedif auto-banned, otherwise400 Suspicious activity detected.
Skips: Whitelisted IPs and routes with detection disabled via decorator.
17. CustomRequestCheck¶
Purpose: Runs a global user-defined request check function.
Blocks when: config.custom_request_check returns a non-None GuardResponse.
Configuration: SecurityConfig.custom_request_check is a Callable[[GuardRequest], Awaitable[GuardResponse | None]].
Implementing a Custom Check¶
To add a new check, create a subclass of SecurityCheck:
from guard_core.core.checks.base import SecurityCheck
from guard_core.protocols.request_protocol import GuardRequest
from guard_core.protocols.response_protocol import GuardResponse
class GeoFenceCheck(SecurityCheck):
@property
def check_name(self) -> str:
return "geo_fence"
async def check(self, request: GuardRequest) -> GuardResponse | None:
client_ip = getattr(request.state, "client_ip", None)
if not client_ip:
return None
if self._is_outside_fence(client_ip):
if self.is_passive_mode():
self.logger.warning(f"Geo-fence violation: {client_ip}")
return None
return await self.create_error_response(403, "Access denied")
return None
Register it in the pipeline during middleware initialization: