Release Notes¶
v3.0.0 (2026-04-29)¶
Fail-secure by default, broader cloud-provider coverage, agent encryption + version propagation (v3.0.0)¶
Breaking changes¶
SecurityConfig.fail_securenow defaults toTrue. Any unhandled exception inside a security check now blocks the request with HTTP 500 instead of logging the error and falling through. Bugs in checks that previously slipped past as silent fail-open responses now surface immediately. To restore the old behavior on deployments that depend on it, setfail_secure=Falseexplicitly:
Recommended migration: keep the new default and fix any check exceptions that surface — the previous default could mask serious bugs.
Added¶
fetch_digitalocean_ip_ranges()— pulls the DigitalOcean geofeed CSV fromhttps://www.digitalocean.com/geo/google.csvand returns the set of CIDRs (IPv4 + IPv6).fetch_linode_ip_ranges()— pulls the Linode/Akamai RFC8805 CSV fromhttps://geoip.linode.com/.fetch_vultr_ip_ranges()— pulls the Vultr/Constant geofeed JSON fromhttps://geofeed.constant.com/?json.- All three providers wired into
_ALL_PROVIDERS, theCloudManagersingleton initializer, and the three provider→fetcher dispatch maps (_refresh_providers,refresh_async,_refresh_providers_via_redis_handler). Sync mirrors updated in lockstep usingrequestsinstead ofaiohttp. - Each fetcher gracefully returns an empty
set()on any HTTP / parse failure withlogging.error(...). Malformed CIDR rows in CSV feeds are skipped silently rather than discarding the entire feed. SecurityConfig.agent_project_encryption_key: str | None— per-project AES-256-GCM key the framework adapter passes through to the agent. When set, the agent posts to/api/v1/events/encryptedwith the encrypted payload; whenNone, the agent uses the plaintext ingest path. Required for any API key whose SaaS-side configuration enforces payload encryption — without it the SaaS rejects every batch and the agent's ingestion breaker stays tripped.to_agent_config()propagates this directly toAgentConfig.project_encryption_key.SecurityConfig.agent_guard_version: str | None— framework wrapper version (e.g.fastapi_guard.__version__) propagated to the agent so the SaaS can attribute telemetry to the wrapper version, not just the agent version.to_agent_config()propagates this toAgentConfig.guard_version. Pairs withguard-agent >= 2.4.0'sEventBatch.guard_versionfield; older agents silently drop the kwarg via Pydantic's defaultextra='ignore'.
Notes¶
- Alibaba was evaluated for inclusion but no reliable official public IP-range feed could be confirmed. Deferred to a follow-up rather than ship a guessed URL.
v2.2.2 (2026-04-29)¶
Safer failures, observability, and truthful copy (v2.2.2)¶
- Fixed — Decode iteration cap raised from 3 to 7 in
ContentPreprocessor.decode_common_encodingsto cover up to 7-layer polyglot encoding evasion (base64(base64(base64(base64(payload))))and similar). The loop still terminates onif content == original: break, so it stays bounded. Sync mirror updated in lockstep. - Fixed —
IPInfoManager.get_countryno longer raisesRuntimeError("Database not initialized")when the MaxMind reader is unset; it now logs a WARNING and returnsNone. Callers no longer need to wrap every geo lookup in a defensivetry/except. Sync mirror. - Fixed —
ErrorResponseFactory.apply_modifiercatches exceptions raised by the user-suppliedcustom_response_modifier, logs vialogger.exception, and returns the unmodified response. A buggy modifier can no longer crash the request pipeline. Sync mirror. - Added —
IPBanManager.banned_ipsis now an_ObservableTTLCachethat exposesevictions_counton the manager and emits a WARNING every 100 overflow evictions. Only overflow evictions are counted; TTL-expiry deletions are excluded (verified againstcachetoolssource —expire()usesCache.__delitem__, notpopitem). Sync mirror. - Added —
HandlerInitializer.initialize_dynamic_rule_manageremits a WARNING whenenable_dynamic_rules=Truebut no agent handler is reachable, so the silent fall-back to static config is now visible to operators. The opt-out path (enable_dynamic_rules=False) remains silent. Sync mirror. - Changed — README and CHANGELOG copy aligned with what the engine actually does. Replaced "intelligent / behavioral analysis / anomaly detection / penetration detection" framing with signature-based detection plus multi-metric semantic scoring. Added a "How Detection Works" section to the README walking through the decode → regex match → semantic-score → ReDoS-guard pipeline.
v2.2.1 (2026-04-27)¶
RedisManager singleton hardening (v2.2.1)¶
- Fixed —
RedisManager.__new__always created a new instance and overwrote the class-level_instancereference, breaking the singleton contract. When middleware or a fixture calledRedisManager(config)more than once, each successive call orphaned the previous instance — but each instance owned an independent_redisconnection set by its owninitialize(). The orphaned connection had no closer; on garbage collection it surfaced asResourceWarning: unclosed Connection(and the underlying socket / asyncio transport). Underpytest -W errorthis manifested as cascadingPytestUnraisableExceptionWarningfailures across any test suite that constructedRedisManagermore than once. __new__now follows the same true-singleton pattern asRateLimitManager: create the instance once, updateconfigon every call, return the same instance. Connections are owned by a single live instance andclose()actually closes them.- Mirror fix applied to
guard_core.sync.handlers.redis_handler.RedisManager. - No behavior change for production callers that construct
RedisManageronce at startup. Test suites that previously leaked redis connections across fixtures now run clean under-W error.
v2.1.0 (2026-04-25)¶
lazy_init: background warmup instead of first-request stall¶
Changed¶
lazy_init=Truenow schedules the IPInfo MMDB download and cloud-IP provider fetches as a background task duringinitialize_redis_handlers(), instead of triggering them synchronously on the first request that needs them. Eliminates the multi-second latency spike on the first user request. During the warmup window (typically a few seconds at startup), cloud-provider blocking and country-based geo checks are inert; rate limiting, IP banning, pattern detection, and all other security layers remain fully active. After the background task completes, the geo/cloud layers activate seamlessly.HandlerInitializerexposes_lazy_init_task, theasyncio.Task(orthreading.Threadin the sync mirror) that runs the deferred cloud and geo bootstrap whenlazy_init=True. Failures inside the background task are caught and logged vialogging.getLogger("guard_core.core.initialization")(guard_core.sync.core.initializationfor the sync mirror) atWARNINGlevel; they never propagate.CloudIpRefreshCheck.check()no longer triggers a synchronouscloud_handler.refresh_async(...)when ranges are empty underlazy_init=True. The interval-based scheduled refresh path is now the only refresh path inside the request lifecycle.
Compat notes¶
lazy_init=False(the default) is unchanged — eager init at startup.- Users who opted into
lazy_init=Truein 2.0.0 see only an upside: the first-request latency that 2.0.0 imposed is replaced with a brief startup-time warmup window where cloud/geo layers are inert. No code changes required. lazy_init=Trueusers with strict cloud-provider blocking who can't tolerate any warmup window should stay onlazy_init=False(or continue usinglazy_init=Truewith a Kubernetes/ALB warmup probe that hits a health endpoint before real traffic).
v2.0.0 (2026-04-25)¶
Operator-facing security controls and pluggable IP lifecycle (v2.0.0)¶
Highlights¶
- Detection exclusion knobs — global and per-route opt-out for headers, query params, and JSON body fields, plus per-category disablement for the 16 known threat categories (XSS, SQLi, dir traversal, cmd injection, …). The detection engine itself is unchanged (regex set + bag-of-words token-overlap scorer); this release adds operator-facing controls on top of it.
DetectionResultreplacestuple[bool, str]. Bothdetect_penetration_attempt()anddetect_penetration_patterns()now return a dataclass carryingis_threat,trigger_info,threat_categories, andthreat_scores. Callers that unpacked the tuple must migrate.- Per-category ban thresholds and durations. New
ThreatBanConfig(threshold, duration)model andSecurityConfig.threat_ban_config: dict[str, ThreatBanConfig]. The check increments per-category counts; the first category that crosses its own threshold short-circuits the flat-threshold fallback. Reasons are tagged"penetration_attempt:<category>"for category bans and"penetration_attempt"for flat fallback. - Global behavior rules.
SecurityConfig.global_behavior_rules: list[BehaviorRuleConfig]lets users configure 404-noise correlation and other behavioural patterns without decorators. Whencorrelate_with_detection=Trueand the IP has any positive entry insuspicious_request_counts, the rule's effective threshold is halved (floor 1). - Lazy IP lifecycle + pluggable cloud-IP store.
SecurityConfig.lazy_init=Truedefers IPInfo MMDB download and cloud-IP fetches until the first request.SecurityConfig.cloud_ip_storeaccepts aCloudIpStoreProtocol; default is in-memory, automatically upgraded to Redis-backed when Redis is wired. Horizontally-scaled deployments can pre-populate the store and skip per-instance cold starts. - Strict protocol typing.
redis_handlerandagent_handlerparameters inIPInfoManagerandCloudManagerare typed againstRedisHandlerProtocol/AgentHandlerProtocolinstead ofAny. - Test posture. 3124 tests, 100% line + 100% branch coverage on every touched file, zero pytest warnings, vulture clean (10 pre-existing findings fixed at the root), pre-commit chain (ruff, mypy, vulture, bandit, radon, xenon, deptry) all green.
Added¶
DetectionResultdataclass atguard_core.detection_result(sync mirror underguard_core.sync.detection_result).ALL_DETECTION_CATEGORIES(frozenset of 16 labels) andCATEGORY_CONTEXT_MAPinguard_core.handlers.suspatterns_handler.SecurityConfigfields:excluded_detection_headers,excluded_detection_params,excluded_detection_body_fields,enabled_detection_categories(default = fullALL_DETECTION_CATEGORIESset; rejects unknown labels).RouteConfigoverride fields for the four detection-exclusion knobs (defaultNone= inherit fromSecurityConfig).ContentFilteringMixin.detection_exclusion(headers=, params=, body_fields=, categories=)decorator;Noneargs leave the correspondingRouteConfigfield unchanged.ThreatBanConfig(threshold, duration)model +SecurityConfig.threat_ban_config. Validator rejects unknown categories.BehaviorRule.ban_duration: int | None(consumed by_execute_ban_action, defaults to 3600 when unset).BehaviorRule.correlate_with_detection: bool = False.BehaviorTracker.track_return_pattern(..., effective_threshold=)override.BehaviorRuleConfigmodel +SecurityConfig.global_behavior_rules: list[BehaviorRuleConfig]. Module-levelconfig_to_rule(cfg) -> BehaviorRulehelper.BehavioralContext.middleware: Any = Nonefield.BehavioralProcessor.process_global_return_rules()uses the existing_behavior_tracker()precedence helper (context tracker first, decorator tracker fallback) and short-circuits cleanly when neither is reachable.ErrorResponseFactory.process_response()accepts an optionalprocess_global_behavioral_rulescallback and runs it alongside the existing route-specific path.client_ipis extracted once and shared across both paths.SecurityConfig.lazy_init: bool = False.SecurityConfig.geo_ip_db_max_age: int = 86400(validated 3600 ≤ x ≤ 604800).SecurityConfig.cloud_ip_store: CloudIpStoreProtocol | None = None.GeoIPHandlerprotocol gained asyncrefresh()and syncclose().IPInfoManager(token, db_path, max_age=...)with arefresh()method._max_agereplaces the hardcoded 86400 in disk-freshness checks and Redis TTL writes.CloudIpStoreProtocol(andSyncCloudIpStoreProtocolmirror) withget/set/clearmethods.InMemoryCloudIpStoreandRedisCloudIpStoredefault implementations underguard_core.handlers.cloud_ip_stores.CloudManager.set_store().refresh_async()reads from the store first, falls back to API fetch + write-back. Legacyredis_handler-only path preserved when_store is None.HandlerInitializer.initialize_redis_handlers()wirescloud_handler.set_store(config.cloud_ip_store)after Redis bootstrap when an explicit store is configured. Cloud + geo bootstrap now skipped whenlazy_init=True;CloudIpRefreshChecktriggers a one-shot init on the first request that needs cloud data.
Changed¶
detect_penetration_attempt(request, config=None, route_config=None)→DetectionResultinstead oftuple[bool, str].detect_penetration_patterns(...)→DetectionResultinstead oftuple[bool, str].GuardMiddlewareProtocol.suspicious_request_counts: dict[str, dict[str, int]](wasdict[str, int]). IP → category → count. Existing total-count semantics preserved viasum(values())everywhere they were read.SusPatternsManager.compiled_patternsand_pattern_definitionsentries are 3-tuples(regex, contexts, category)(were 2-tuples). Every regex threat dict returned by_check_regex_pattern()now carriescategory. Custom patterns are tagged"custom"and bypassenabled_categoriesfiltering.SusPatternsManager.detect()and_check_regex_patterns()accept anenabled_categories: set[str] | None = Nonefilter._check_value_enhanced()/_check_request_component()now returntuple[bool, str, list[dict]](added the raw threats list so the public detector can extract categories and scores).- Cloud-IP cache Redis namespace moved from
cloud_ranges(comma-separated values) toguard:cloud_ip(JSON-encoded sorted list). See Compat notes below.
Fixed¶
setup_custom_loggingnow closes each handler before removing it, instead of relying onlogger.handlers.clear(). Closes aResourceWarningfor_io.FileIOthat surfaced underpytest -W error::ResourceWarning.- Vulture clean. Removed 10 pre-existing dead-code findings:
schemeparameter onGuardRequest.url_replace_schemeis now whitelisted (Protocol method body is...; renaming would break callers passing the kwarg by name); the fourunreachable code after raisefindings intests/test_handlers_integration.py(and sync mirrors) replaced their@asynccontextmanagermocks with class-based async/sync context managers that don't need a structurally-required deadyield. - Pydantic mypy plugin is now wired (
plugins = ["pydantic.mypy"]in[tool.mypy]). Removed 10 obsolete# type: ignoremarkers and 2 stale# TODO: Add type hints to the decoratorcomments above@field_validator/@model_validatordecorators inguard_core/models.py. Also dropped the now-unneeded[[tool.mypy.overrides]] module = "pydantic.*" follow_imports = "skip"block that was masking the plugin. unasync.pygained a multi-linefrom tests.conftest import (...)rewrite rule and a substitution rule for the newcloud_ip_store_protocolimport path. The sync mirror now correctly renamesCloudIpStoreProtocol→SyncCloudIpStoreProtocol, matching the project'sSync*-prefix convention for sync protocols.
BREAKING¶
-
detect_penetration_attempt()anddetect_penetration_patterns()returnDetectionResult. Tuple-unpacking callers must migrate: -
GuardMiddlewareProtocol.suspicious_request_counts: dict[str, dict[str, int]]. Code that reads or writes this attribute must use the nested-dict shape:# Before self.suspicious_request_counts[ip] += 1 # After (per-category increment) self.suspicious_request_counts.setdefault(ip, {}) self.suspicious_request_counts[ip][category] = self.suspicious_request_counts[ip].get(category, 0) + 1 # Reading the total total = sum(self.suspicious_request_counts.get(ip, {}).values()) -
SusPatternsManagercompiled-pattern tuples are 3-tuples.get_all_compiled_patterns()returnstuple[Pattern, frozenset[str], str]instead oftuple[Pattern, frozenset[str]]. Direct callers that iterate this collection must unpack three elements. -
_check_value_enhanced/_check_request_componentreturn 3-tuples. External callers (none in the framework adapters; flagged here in case downstream code reaches in). -
Cloud-IP cache namespace migration:
cloud_ranges→guard:cloud_ip. Any ops tooling or dashboards reading those Redis keys directly must switch over. The new format is JSON-encoded sorted list of CIDRs per provider, written under namespaceguard:cloud_ip. The legacy comma-separated path is still reachable for users who explicitly set_store = Noneon theCloudManagersingleton, but the default and theRedisCloudIpStorewiring use the new namespace.
Compat notes¶
- All four framework adapters (fastapi-guard, flaskapi-guard, djapi-guard, tornadoapi-guard) need a release pinning
guard-core>=2.0.0and a small migration: any adapter middleware that readsuspicious_request_counts[ip]as an int must readsum(suspicious_request_counts[ip].values())(the protocol now reflects the per-category shape). Adapters that calleddetect_penetration_attempt/detect_penetration_patternsand unpacked the 2-tuple must consumeDetectionResult.is_threat/.trigger_info. lazy_init=Falseis the default and preserves existing eager startup. Existing deployments do not need to opt in.enabled_detection_categoriesdefaults to the fullALL_DETECTION_CATEGORIESset, so detection coverage is unchanged unless the user explicitly narrows it.threat_ban_configdefaults to an empty dict and falls back to the existingauto_ban_threshold/auto_ban_durationflat behaviour — existing configurations behave identically until per-category entries are added.- Pydantic mypy plugin was a typing tooling change; it does not affect runtime behaviour or installed dependencies.
Tooling¶
make sync(powered byscripts/unasync.py) regenerates the entireguard_core/sync/**tree plus matchingtests/test_sync/**. Hand-edits are limited to files inunasync.py:TEMPLATE_FILES(a few sync protocol files); everything else is regenerated and verified viapython scripts/unasync.py --checkin pre-commit.tests/conftest.pyredis_cleanupfixture now teardowns Redis state afteryieldin addition to before it. Removes a previously-hidden test-order dependency that surfaced when running tests across many invocations.
v1.2.1 (2026-04-24)¶
Integration fixes caught by end-to-end smoke test (v1.2.1)¶
Fixed¶
OtelHandler.start()now normalizes the configuredotel_exporter_endpointby appending/v1/tracesand/v1/metricswhen the base URL lacks the signal path. Previously, users who setotel_exporter_endpoint="http://collector:4318"received 404 Not Found from every OTLP receiver. Matches the semantics of theOTEL_EXPORTER_OTLP_ENDPOINTenvironment variable. Also correctly rewrites explicit signal suffixes (/v1/traces,/v1/metrics,/v1/logs) so the traces exporter always gets/v1/tracesand the metrics exporter always gets/v1/metricsregardless of which signal-specific path the user configured.HandlerInitializer.build_enricher()now owns aBehaviorTrackerinstance when the user'sSecurityDecoratordoes not supply one, and caches it asHandlerInitializer.behavior_trackerfor reuse. Without this fix,guard.behavior.correlation_keyandguard.behavior.recent_event_countnever populated for adapters that instantiate the middleware and decorator separately (all four current adapters).BehavioralContextgained an optionalbehavior_trackerfield andBehavioralProcessornow threads writes throughcontext.behavior_trackerwhen present, falling back toguard_decorator.behavior_trackerotherwise. This closes the architectural gap where the enricher read from one tracker while writes went to another —guard.behavior.recent_event_countnow populates end-to-end when adapters thread theHandlerInitializer.behavior_trackerthrough theirBehavioralContextconstruction (shipping in the next adapter releases).
Compat notes¶
- No public API changes.
OtelHandler._otlp_signal_endpointis an internal helper.BehavioralContext.behavior_trackerhas a default ofNoneso existing callers continue to work unchanged. - Adapters should bump their
guard-core>=1.2.1pin to pick up all three fixes. See the matchingfastapi-guard 5.1.1,flaskapi-guard,djapi-guard,tornadoapi-guardreleases — those ship the adapter-side threading changes that complete the behaviour-correlation wiring.
v1.2.0 (2026-04-24)¶
Enriched telemetry: client-side EventEnricher gated on guard-agent (v1.2.0)¶
Highlights¶
- Two-tier telemetry model. Raw OTel/Logfire signal stays free and unchanged. A new enriched tier — gated on
enable_agent=True+enable_enrichment=True— adds project identity, deterministic threat scores, dynamic-rule correlation, and per-IP behavioural correlation to every event and metric the composite fans out. Every exporter (guard-agent, OTel, Logfire) sees the same enriched payload. EventEnricher. Newguard_core.core.events.enricher.EventEnricher+EnrichmentContextrun insideCompositeAgentHandler.send_event/.send_metricbetween the mute filter and fan-out. Four independent strategies, each fails soft — a faulty strategy never blocks emission. Async + sync mirror parity maintained viascripts/unasync.py.- Eight
guard.*enrichment keys.guard.project_id,guard.service.name,guard.deployment.environment,guard.threat_score,guard.rule.id,guard.rule.version,guard.behavior.correlation_key,guard.behavior.recent_event_count. All nullable, all absent unless the corresponding context exists. - Deterministic threat score.
ThreatScorer.score_for(event_type)maps 16 event types to 0-100 scores that match the SaaS'sEVENT_SEVERITY(penetration_attempt=90,ip_banned=70, medium events=50,rate_limited=20, default=20). No ML, no server-side recomputation. - Dynamic-rule correlation.
DynamicRuleManager.match_event(event)checks the cached rule against the event's IP / country / event-type and returns(rule_id, version) | None. The enricher attaches both keys when matched. - Behavioural correlation key. 16-char SHA-256 prefix of
ip | service | floor(now/300), stable within a 5-minute rolling window. Combined with a newBehaviorTracker.get_recent_event_count(ip, window)that aggregates in-memory usage counters, dashboards can group correlated attack chains by IP. - OTel + Logfire forward
guard.*metadata as span attributes.OtelHandler.send_eventandLogfireHandler.send_eventnow walkevent.metadataand attach everyguard.*key (excepttraceparent/tracestate, which are still used for parent-context extraction only). - 100% line + branch coverage. 2751 tests passing, zero skips, zero
# pragma: no cover.
Added¶
guard_core.core.events.enricher.EventEnricher+EnrichmentContextdataclass (sync mirror underguard_core/sync/).guard_core.core.events.enricher.ThreatScorer.score_for(event_type)+ deterministic_THREAT_SCORE_MAP.- Eight
ENRICHMENT_KEY_*constants inguard_core.core.events.event_types(async + sync). SecurityConfig.enable_enrichment: boolfield with avalidate_agent_configmodel validator that raisesValidationErrorwhen enrichment is requested withoutenable_agent=True.HandlerInitializer.build_enricher()factory.build_composite_handler()now passes the enricher intoCompositeAgentHandler;shutdown_agent_integrations()clears the enricher reference. The early-exit guard ininitialize_agent_integrationsnow accounts forenable_enrichment.CompositeAgentHandler(..., enricher=...)parameter;send_event/send_metricinvoke the enricher between the mute filter and handler fan-out.DynamicRuleManager.match_event(event) -> tuple[str, int] | Nonereturning(rule_id, version)when the cached rule matches.BehaviorTracker.get_recent_event_count(ip, window_seconds) -> intaggregating usage counts across all endpoints for the given IP.OtelHandler.send_event+LogfireHandler.send_eventforwardguard.*metadata keys as span attributes.
Docs¶
docs/architecture/telemetry.mdupdated with: the two-tier model table, the newenable_enrichmentconfig field, an enrichment-fields reference table, a dedicated "Enabling enrichment" section, documentation of dynamic-rule correlation matching, and documentation of the behavioural correlation key algorithm.
Compat notes¶
- All new fields / layers are strictly additive. Existing configurations with
enable_otel=Trueand/orenable_logfire=Truecontinue to emit raw signal unchanged. - Adapters built against 1.1.0 continue to work against 1.2.0 without code changes — the enricher only activates when
enable_enrichment=True, and that flag is False by default.
v1.1.0 (2026-04-24)¶
Telemetry v1: OpenTelemetry, Logfire, and composable muting (v1.1.0)¶
Highlights¶
- OpenTelemetry export — opt-in via
enable_otel=True. Emits guard events as spans and request metrics as OTLP-compatible instruments (guard.request.duration,guard.request.count,guard.error.count). Includesotel_service_name,otel_exporter_endpoint, andotel_resource_attributesfor deployment/env/version tagging. Requires theguard-core[otel]extra. - Logfire export — opt-in via
enable_logfire=True. Events aslogfire.span("guard.event.<type>", ...), metrics as structuredlogfire.infocalls. Requires theguard-core[logfire]extra. - W3C trace-context propagation — incoming
traceparentandtracestateheaders are forwarded so guard spans become children of the caller's trace across the whole request lifecycle. - Composable muting at three layers —
muted_event_types,muted_metric_types, andmuted_check_logsonSecurityConfig. Applied insideCompositeAgentHandlerso every exporter (guard-agent, OTel, Logfire) sees the same mute rules.muted_check_logsalso suppresses in-checklog_activity()output, not just the pipeline logs. CompositeAgentHandler+EventFilter— every telemetry exporter runs through one handler chain with a shared filter, so new exporters get muting / propagation for free.- Factory methods for adapters —
HandlerInitializer.build_event_bus()and.build_metrics_collector()so framework adapters route through the composite instead of constructingSecurityEventBus/MetricsCollectordirectly. See Adapter upgrade notes below. - Validated mute values —
muted_event_types,muted_metric_types, andmuted_check_logsall validate at config time againstEVENT_TYPE_VALUES/METRIC_TYPE_VALUES/CHECK_NAME_VALUES. Typos raiseValidationErrorwith the full set of valid values in the message. - Idempotent handler lifecycle —
OtelHandler/LogfireHandlerstart()/stop()are safe to call repeatedly;stop()nulls provider references so subsequent calls don't double-shutdown. - 100% line + branch coverage on every module touched (2597 tests, zero skips, zero
# pragma: no cover).
Added¶
SecurityConfig.muted_event_types,muted_metric_types,muted_check_logs(validatedset[str]fields).SecurityConfig.enable_otel,otel_service_name,otel_exporter_endpoint,otel_resource_attributes.SecurityConfig.enable_logfire,logfire_service_name.guard_core.core.events.otel_handler.OtelHandler(async + sync mirror).guard_core.core.events.logfire_handler.LogfireHandler(async + sync mirror).guard_core.core.events.composite_handler.CompositeAgentHandler— composes guard-agent + OTel + Logfire behind oneAgentHandlerProtocol, appliesEventFilterat fan-out.guard_core.core.events.event_types.EventFilter+EVENT_TYPE_VALUES/METRIC_TYPE_VALUES/CHECK_NAME_VALUESfrozensets (30 / 3 / 17 members).HandlerInitializer.build_event_bus(),.build_metrics_collector(),.build_composite_handler(),.shutdown_agent_integrations()— factory + lifecycle API for adapters.SecurityCheck.log_if_allowed()— check-awarelog_activitywrapper that honoursmuted_check_logs.docs/architecture/telemetry.md— full field reference, troubleshooting, and adapter wiring guidance.[otel]and[logfire]optional extras inpyproject.toml.
Fixed¶
logfire.metric(...)never existed — replaced withlogfire.info("guard.metric.<type>", ...)for structured metric logs.send_metricnow warns (once per unknown type) instead of silently dropping when handed a metric_type outsideMETRIC_TYPE_VALUES.OtelHandler.stop()is now idempotent (nulls_tracer/_meter) so shutdown hooks can call it safely on re-entry.- Sync mirror under
guard_core/sync/fully covers every async change (behavior, decorators, detection engine, handlers, checks, events, initialization, responses, routing, validation, bypass, behavioral).
Adapter upgrade notes¶
Framework adapters (fastapi-guard, flaskapi-guard, djapi-guard, tornadoapi-guard) must switch from constructing SecurityEventBus(agent_handler, ...) / MetricsCollector(agent_handler, ...) directly to calling initializer.build_event_bus() / initializer.build_metrics_collector() after initializer.initialize_agent_integrations(). Direct construction routes events to the bare agent handler and bypasses the composite entirely — meaning OTel, Logfire, and the event filter never see pipeline-level events or request metrics. Each adapter will publish a matching minor version pinning guard-core>=1.1.0,<2.0.0 with this wiring fix.
Docs¶
- New
docs/architecture/telemetry.mdcovering the two-tier model (raw OTel/Logfire signal; guard-agent as a parallel enriched exporter), mute field reference with all valid values, incomingtraceparent/tracestatebehaviour, and troubleshooting for missing spans / inactive mutes /logfire.configure()warnings. - Install and extras documentation moved to uv-first tabs (
uv add "guard-core[otel]", then poetry, then pip) acrossdocs/index.md,docs/llms.txt,docs/architecture/telemetry.md.
v1.0.3 (2026-04-05)¶
Added¶
- Guard processing time instrumentation on all request-scoped
SecurityEventobjects viaget_pipeline_response_time(). Covers events fromSecurityEventBus,SecurityCheckPipeline,RateLimitManager,BaseSecurityDecorator, andsend_agent_event(). Timing starts at pipeline entry and lazily initializes for events fired before or after the pipeline (bypass, behavioral). No adapter-level changes required
v1.0.2 (2026-04-05)¶
Fixed¶
- Removed
_check_ip_spoofing()which incorrectly flagged every request withX-Forwarded-Forheaders as a spoofing attempt whentrusted_proxieswas not configured (the default) - Added IP caching in
extract_client_ipto avoid redundant lookups across the request lifecycle
Added¶
- Guard processing time instrumentation on all request-scoped
SecurityEventobjects viaget_pipeline_response_time(). Covers events fromSecurityEventBus,SecurityCheckPipeline,RateLimitManager,BaseSecurityDecorator, andsend_agent_event(). Timing starts at pipeline entry and lazily initializes for events fired before or after the pipeline (bypass, behavioral). No adapter-level changes required
v1.0.1 (2026-03-28)¶
Fixed¶
- Removed false-positive suspicious patterns that blocked legitimate web traffic:
- Static file extensions (
.html,.js,.css,.png,.jpg,.svg,.webp,.bmp,.pl,.properties) - Common API prefixes (
/api/,/rest/,/v1/,/v2/,/status/,/config/) - Authentication paths (
/login,/signin,/account/login) - Admin paths (
/admin) - Static asset directories (
/images/,/css/,/img/,/scripts/) - Retained detection for actual recon indicators: legacy server extensions (
.asp,.aspx,.jsp,.cfm,.cgi, etc.), and suspicious management endpoints (/management,/config_dump,/credentials)
v1.0.0 (2026-03-25)¶
Added¶
- Complete synchronous API (
guard_core.sync) generated viascripts/unasync.py, including sync versions of all 17 security checks, handlers, decorators, protocols, detection engine, and utilities scripts/unasync.pytransformation tool converting async code to sync (async deftodef,awaitremoved,aiohttptorequests,redis.asynciotoredis,asyncio.Locktothreading.Lock)- Sync protocols:
SyncGuardRequest,SyncGuardMiddlewareProtocol, and sync versions of all handler protocols - PEP 561 type stub markers (
guard_core/py.typed,guard_core/sync/py.typed) - Project governance files:
CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md README.mdwith project documentation, badges, and ecosystem overview.safety-project.inifor dependency vulnerability scanningMANIFEST.inand.gitattributesfor packaging.python-versionspecifying supported Python versions (3.10-3.14)- Comprehensive edge-case test suites for cloud provider, HTTPS enforcement, IP security, rate limiting, and time window checks
docs/llms.txtfor LLM-assisted development context- Complete sync test suite (
tests/test_sync/) mirroring the async test structure
Changed¶
- Restructured and consolidated the entire test suite into organized directories (
test_agent/,test_core/,test_decorators/,test_features/,test_handlers/, etc.) - Enhanced
CloudManagerwith IP range change logging and improved provider refresh logic - Updated
SusPatternsManagerwith additional detection logic - Enhanced
BehavioralProcessor,ErrorResponseFactory, andRouteConfigResolverinternals - Minor updates to
IPInfoManagerhandler - Updated
BaseSecurityDecoratorroute config handling - Added mypy override for
guard_core.sync.*(type suppression for generated sync code) - Documentation fully standardized and verified for accuracy against source code
- Disabled safety pre-commit hook temporarily
Fixed¶
- Suspicious pattern handling in
detect_penetration_attempt
v0.1.0 (2026-03-23)¶
New Features (v0.1.0)¶
- Initial release: Guard Core extracted as a framework-agnostic security library for Python web applications.
- Protocol-based architecture: Uses
GuardRequestandGuardResponseprotocols for framework independence. - Full feature parity: All security features available through framework-agnostic APIs.
- IP Management: Whitelisting, blacklisting, geolocation, cloud provider blocking.
- Rate Limiting: Sliding window algorithm with in-memory and Redis backends.
- Penetration Detection: Enhanced detection engine with pattern matching, semantic analysis, and performance monitoring.
- Security Decorators: Route-level security controls for access control, authentication, rate limiting, behavioral analysis, content filtering, and advanced features.
- Security Headers: Comprehensive HTTP security header management following OWASP best practices.
- Redis Integration: Distributed state management for multi-instance deployments.
- Behavioral Analysis: Usage monitoring, return pattern detection, and frequency analysis.