Configuration
SecurityConfig is built through a single constructor of named arguments
(every field nullable with an engine default). Arguments belonging to features
this port does not implement, when set to an enabling value, throw
UnsupportedFeatureError (fail closed): guard agent telemetry, and dynamic
rules.
CORS
| Argument |
Default |
Notes |
enableCors |
false |
Enables the CORS handler over the engine |
corsAllowOrigins |
['*'] |
Exact origins; * allows every origin |
corsAllowMethods |
GET, POST, PUT, PATCH, DELETE, OPTIONS |
Uppercased at config time; an empty list falls back to GET |
corsAllowHeaders |
['*'] |
Lowercased at config time; * echoes the requested headers verbatim |
corsAllowCredentials |
false |
Incompatible with the * origin: that combination fails config construction |
corsExposeHeaders |
[] |
Joined into Access-Control-Expose-Headers on responses |
corsMaxAge |
600 |
A configured 0 falls back to 600 |
Behavior mirrors the reference CorsHandler (guard-core
handlers/cors_handler.py) and the adapter dispatch: a preflight (OPTIONS
carrying Access-Control-Request-Method) executes the security pipeline and
is then short-circuited with 200 OK (or 400 Disallowed CORS: origin,
method, headers), blocked responses compose the CORS headers on top of the
engine's blocked-response set, and disallowed origins simply get no CORS
headers (the browser enforces). For pass-through responses the adapter merges
GuardEngine::corsResponseHeaders($request) with its own outgoing headers.
| Argument |
Default |
Notes |
securityHeaders |
reference default block |
Reference-shaped dict: enabled, hsts (max_age/include_subdomains/preload), csp (ordered directive => sources), frame_options, content_type_options, xss_protection, referrer_policy, permissions_policy, custom |
Behavior mirrors the reference SecurityHeadersManager
(guard-core handlers/security_headers_handler.py): disabled configuration
emits no headers, the class defaults apply, an absent or null override key
keeps the class default, an empty permissions_policy removes that header
(the reference falsy check), an ordered csp block joins directives into
Content-Security-Policy, the hsts block builds
max-age/includeSubDomains/preload with the preload corrections
(preload requires at least one year and subdomains), and custom headers
land last and may override anything. Validation is fail-closed at config
construction (the reference configure() raising): RFC 7230 token names for
custom headers, CRLF rejected, values capped at 8192 bytes, control
characters sanitized away. Blocked responses carry the headers engine-side;
pass-through responses take GuardEngine::responseHeaders().
Per-route detection exclusions
RouteConfig mirrors the reference detection-exclusion fields:
enableSuspiciousDetection (default true; wins over the global
enablePenetrationDetection for routed requests), excludedDetectionParams,
excludedDetectionBodyFields, excludedDetectionHeaders,
enabledDetectionCategories and detectionScanBody. A null field inherits
the global config; a non-null set replaces the global one, except the header
set which always merges the hardcoded proxy-identity defaults with the
global set and the route set (suppressing ssrf address-chain false positives
only). A false detectionScanBody skips the request-body surface while the
URL path, query and headers still scan. Scheduling note: the engine's
statically-built pipeline schedules the check from the global flag; the
route-aware scheduling gate lives at the CheckFactory level (see
KNOWN_GAPS).
Behavior rules
| Argument |
Default |
Notes |
globalBehaviorRules |
[] |
Reference-shaped rule dicts (rule_type, threshold, window (default 3600), pattern, action (default log), ban_duration, correlate_with_detection) applied to every route |
behaviorScanResponseBody |
false |
Gates reading response bodies for non-status: return_pattern rules; construction rejects such rules while it is off (fail closed) |
behaviorMaxResponseBodyInspectBytes |
262144 |
Leading response-body prefix held for return_pattern inspection; bounds 1024..10485760, 0 normalizes to the default |
Route-level rules ride new RouteConfig(behaviorRules: [...]). Usage and
frequency rules run on requests the pipeline allowed; return_pattern rules
run when the adapter calls GuardEngine::processResponse($request,
$response). Windows are strictly-greater than the threshold, backed by
Redis over the reference key layout (behavior_usage/behavior_returns
with SHA-256 identity segments) and by bounded local maps otherwise.
Passive mode only logs; active ban uses the rule's ban_duration or the
3600s fallback. correlate_with_detection halves the effective threshold
of a global return rule while the IP has prior detection-category hits.
Geo country rules
| Argument |
Default |
Notes |
whitelistCountries |
[] |
ISO country codes, uppercased and deduplicated at construction. Non-empty is restrictive: only listed countries pass, and an unresolved country is denied |
blockedCountries |
[] |
ISO country codes that are always denied. Ignored while whitelistCountries is non-empty (construction warns via error_log) |
geoIpDbPath |
'' |
Path to a local MMDB database with top-level country records (the ipinfo country_asn.mmdb layout). Required when country rules are set and no handler is injected |
geoIpHandler |
null |
Injected CountryResolver (getCountry(ip): ?string); replaces the built-in MMDB reader |
Country rules run inside the ip_security check: after the global IP lists
and before the exempt-ips resolution, mirroring the reference
check_ip_access. A global whitelist match skips the country stage.
Loopback IPs are exempt from the country stage. An unresolvable country
fails closed in allowlist mode and open in blocklist mode. The engine does
not download databases: provision the MMDB file yourself or inject a
resolver. Exempt IPs are not exempt from country rules. Route-level country
rules are deferred (the PHP RouteConfig surface has no route-level IP
rule lists to combine with).
Client identity and proxy trust
| Argument |
Default |
Notes |
trustedProxies |
[] |
IPs or CIDRs whose forwarding headers are trusted |
trustedProxyDepth |
1 |
Must be >= 1 |
trustXForwardedProto |
false |
Honor X-Forwarded-Proto for HTTPS detection |
Access lists
| Argument |
Notes |
whitelist |
IPs or CIDRs, validated at config time |
blacklist |
IPs or CIDRs, validated at config time |
excludePaths |
Paths skipped by the pipeline (defaults: /docs, /redoc, /openapi.json, /openapi.yaml, /favicon.ico, /static) |
emergencyMode / emergencyWhitelist |
Blocks everything except the whitelist |
Redis
| Argument |
Default |
Notes |
enableRedis |
true |
Required for distributed bans and rate limits |
redisUrl |
redis://localhost:6379 |
Kept for parity; the connection uses REDIS_HOST / REDIS_PORT |
redisPrefix |
guard_core: |
Key prefix |
redisFailOpen |
false |
On Redis failure, allow traffic instead of blocking |
IP banning
| Argument |
Default |
Notes |
enableIpBanning |
true |
|
autoBanThreshold |
10 |
Violations before an auto-ban; must be >= 1 |
autoBanDuration |
3600 |
Auto-ban length in seconds |
threatBanConfig |
[] |
Per-category ['threshold' => .., 'duration' => ..] overrides |
enableRateLimitAutoBan |
false |
Count rate-limit violations toward auto-ban |
Rate limiting
| Argument |
Default |
Notes |
enableRateLimiting |
true |
|
rateLimit |
10 |
Requests per window |
rateLimitWindow |
60 |
Window length in seconds |
endpointRateLimits |
[] |
Exact-path overrides, e.g. ['/api' => ['limit' => 5, 'window' => 60]] |
Penetration detection
| Argument |
Default |
Notes |
enablePenetrationDetection |
true |
|
enabledDetectionCategories |
all 19 categories |
xss, sqli, cmd_injection, path_traversal, and more |
detectionSemanticThreshold |
0.7 |
Semantic model threshold, in [0.0, 1.0] |
logSensitiveHeaders |
[] |
Headers skipped by detection scanning and redacted from logs; use for the address headers (host, x-forwarded-for, ...) until a dedicated detection-exclusion knob lands |
Cloud provider blocking, user agents, auth
| Argument |
Notes |
blockCloudProviders |
Selectors AWS or AWS:!us-east-1 for a region carve-out; unknown names rejected |
cloudIpRefreshInterval |
Seconds, clamped to [60, 86400] |
blockedUserAgents |
Regex patterns, validated at config time |
authVerifier |
Closure(object $request, string $credential): mixed used by auth-required routes |
Logging
| Argument |
Default |
Notes |
logRequestLevel |
null (off) |
One of DEBUG, INFO, WARNING, ERROR, CRITICAL |
logSuspiciousLevel |
WARNING |
|
mutedCheckLogs |
[] |
Check names whose logs are suppressed |
logSensitiveHeaders / logSensitiveParams / logSensitiveBodyFields |
[] |
Values redacted from logs and skipped by detection scanning (headers) |
Custom behavior
| Argument |
Notes |
customErrorResponses |
Map of int status code to body message, used for every block verdict except 429 in this port |
onBlock |
Telemetry hook, see Usage |
customRequestCheck |
Final user-defined gate; a non-null response blocks |
passiveMode |
Log violations without blocking |
failSecure |
Default true; fail-closed on unresolvable client identity and internal errors |
routeResolutionStrict |
Reject requests whose route cannot be resolved |
Config values are immutable; with(['rate_limit' => 20]) returns a copy with
a bumped revision so the pipeline rebuilds its checks.