Repository navigation
Releases: Guard-Core/flaskapi-guard
Release list
v4.4.0
v4.4.0 (2026-10-15)
Engine routing parity with fastapi-guard (v4.4.0)
- Changed - Passthrough, the route all-checks bypass and the response pass now go through the guard-core engine like fastapi-guard (#40, closes #39). Passthrough uses the engine
BypassHandler.handle_passthrough, and@security.bypass_all()routes usehandle_security_bypass, which calls the real view and applies the response modifier. Excluded paths stay scoped: the checks that apply to them still run instead of being skipped. - Changed (behavior) - A request with no client address is now answered with
403under the defaultfail_secure=True. It used to skip the security pipeline and reach the view. If you depend on the old pass-through, setfail_secure=FalseinSecurityConfig. - Fixed - Blocked, preflight and bypass responses no longer emit request metrics and no longer get the pass-through response pass.
_after_requestruns the engine response pass only for responses that came out of the view. - Fixed - Global behavioral return rules (
global_behavior_rules) are now passed to the engine response pass, so they run. - Added (tests) -
tests/test_engine_parity.pydrives the extension with the Flask test client against the real engine handlers. The test that asserted the old no-client-address pass-through now asserts the403. - Compatibility - The
guard-corefloor rises from>=4.1.0to>=4.4.0, so upgrading flaskapi-guard also brings the guard-core 4.4.0 detection and dynamic rule fixes (Cookie header scanned per pair, the per-request scan budget, the semantic-only header opt-out and dynamic rule versions applied over the base config). No public API of this adapter changed. - Chore - The sponsor button points at the maintainer's Sponsors page, the mkdocs
site_urldrops/latest/so canonical URLs resolve, the README markdown lint errors that blocked the docs deploy are fixed and the README was brought to content parity (#32, #35, #37, #38), a CODEOWNERS file was added and the family Discord relay workflow was added (#31, #33).
v4.3.3
v4.3.2
v4.3.2 (2026-09-26)
Block response Content-Type, guard-core 4.1.0 tracking (v4.3.2)
- Fixed - Blocked responses now declare
Content-Type: text/plain(#25): the Flask response factory left Flask's default mimetype in place, so middleware-generated 403/429/400 pages went out without an explicit content type and clients were left guessing. The factory now passesmimetype="text/plain", matching the engine's reference response factory and the fastapi-guard fix (#144). - Compatibility - The
guard-corefloor moves from>=4.0.0to>=4.1.0inpyproject.toml(no upper bound), so this adapter tracks the guard-core 4.1.0 engine (the exempt_ips skip-list, the recon leading-separator gate, the raw-view recon scan, the cross-engine interop harness). The adapter delegates all security behavior to the engine through the unasync-generated sync mirror; no sync API flaskapi-guard uses changed shape.
v4.3.1
guard-core 4.0.x tracking: dependency floor to >=4.0.0, agent docs (v4.3.1)
- Compatibility - The
guard-corefloor moves from>=3.15.0to>=4.0.0inpyproject.toml(no upper bound), so this adapter tracks the guard-core 4.0.x engine line and is verified against guard-core 4.0.4. No adapter code changed: the delta since 4.3.0 is docs and CI only, hence the patch bump. Since the adapter delegates all security behavior to the engine, no other change is required for the 4.0.x line; guard-core 4.0.0's own breaking changes (grammar-based secret redaction, exact-valuerequire_headers()enforcement,excluded_detection_headerssemantics) are documented in the guard-core changelog. - Documentation - Added
AGENTS.md/CLAUDE.mdagent guidance and the flaskapi-guard package skill, and noted in the access-control decorator docs that an unknown bypass token now warns.
v4.3.0
v4.3.0 (2026-08-31)
guard-core 3.15.0 lockstep: lazy Redis initialization, unknown-identity access change (v4.3.0)
- Fixed -
FlaskAPIGuard.init_app()called_initialize_handlers()eagerly at construction, so an unreachable Redis raisedGuardRedisErrorstraight out ofFlaskAPIGuard(app, config=...)and the app object never finished constructing; recovering needed a process restart. Initialization is now lazy, running at the top of_before_request()guarded by an_initializedflag: aGuardRedisErrorraised there is caught and logged, and the request returns503with aRetry-After: 5header instead of crashing the app, leaving the flag unset so the next request retries.redis_fail_openandfail_securesemantics are unchanged; guard-core still owns that policy (guard-core #76). - Fixed (tests) -
test_request_without_clientasserted the pre-3.14.0 behavior (403) for a request that resolves to the "unknown" client identity. guard-core 3.14.0 changedcheck_ip_accessto treat "unknown" as no address available and allow the request unlesswhitelistorwhitelist_countriesis configured, since the blacklist,blocked_countries, and cloud-provider checks cannot match without an address (GHSA-634g-4wr8-xwxv); the test now asserts200, and a newtest_request_without_client_unknown_identity_with_whitelist_is_blockedcovers the other half of the rule: an unknown identity is still blocked when a whitelist is configured.test_rate_limiter_redis_errorsasserted the old silent in-memory fallback on a Redis failure; guard-core's rate limiter now honorsredis_fail_openlike every other check and raisesGuardRedisErrorwhen it isFalse(the default), so the test now asserts the raise and adds aredis_fail_open=Truecase for the fallback. Tests that inspected state built by_initialize_handlers()(composite agent handler wiring, behavior tracker threading, the rate limiter's Redis handler, Lua script loading, Redis handler call assertions) now call the extension's_ensure_initialized()explicitly, since that state no longer exists immediately after construction;test_redis_initializationandtest_redis_initialization_without_ipinfo_and_cloudkeep their existinglazy_init=False(guard-core 3.13.0's daemon-thread race) alongside the explicit call. - Added (tests) -
test_redis_unavailable_at_first_request_returns_503_and_retriescovers the new lazy-init path end to end: a first request with Redis unreachable gets503withRetry-After: 5and leaves the extension uninitialized, and a second request against a reachable Redis succeeds and completes initialization. - Compatibility - Requires guard-core 3.15.0 (
guard-core>=3.15.0, previously unconstrained). No public API of this adapter changed. Lockstep with fastapi-guard 7.8.2 on the guard-core 3.15.0 line.
v4.2.0
guard-core 3.13.0 compatibility: inline bypass replication, bounded body readers, behavior-scan and auth-verifier lockstep.
- Fixed: guard-core 3.12.0 (30f43944) made BypassHandler.handle_passthrough and handle_security_bypass require a call_next callable, so calls without it raised TypeError on every request and cascaded to 500. Flask before_request cannot invoke the downstream view, so _check_passthrough and _check_security_bypass now replicate the bypass decision inline (flask.g.guard_passthrough + early short-circuit), matching fastapi-guard.
- Added: FlaskGuardRequest/FlaskGuardResponse.read_body_prefix(max_bytes) implement guard-core 3.12.0 SyncBoundedBodyReader/SyncBoundedResponseBodyReader protocols.
- Fixed (tests): behavior_scan_response_body fixture, detection-cache seam patch (helpers.detect_penetration_attempt), auth_verifier fixture, honeypot read_body_prefix, 72 pre-existing mypy errors.
- Compatibility: requires guard-core 3.13.0. No public API change.
See CHANGELOG.md for full details.
v4.1.0
Unmatched requests are reported to guard-core (v4.1.0)
- Added —
_populate_guard_state()returned silently when Flask matched no rule, which is indistinguishable from a view that simply carries no decorators. guard-core reads both as "nothing to enforce", so a resolution failure silently skips every per-route check — the fail-open half of GHSA-f2vm-w8gq-h378, reported against the Starlette adapter. Flask routes requests itself, so this adapter has no equivalent matching bug, but it now reports the failure by settingrequest.state.guard_route_unresolvedwhenrequest.endpointis unset or its view function is missing. A matched view without_guard_route_idis left alone, since an undecorated view legitimately has no per-route config. With guard-core >= 3.7.0 andSecurityConfig.route_resolution_strict=True, unmatched requests are logged, emit aroute_unresolvedevent, and are blocked with500. Behaviour is unchanged by default and on older guard-core, which ignores the attribute. - Fixed (tests) — The IPv6 and mixed IPv4/IPv6 allow-list tests still encoded pre-3.2.0 guard-core semantics, where the blacklist was evaluated before the whitelist. Since guard-core 3.2.0 an explicit whitelist match overrides the blacklist, so an address inside both a whitelist CIDR and a blacklist entry is now served rather than blocked.
test_multiple_decoratorslikewise predated guard-core 3.5.0, where a route@require_ipmatch grants the IP aspect only, so a route-blocked country still denies the request. CI floats guard-core to the latest release and had not run here since 2026-05-28, so these only surfaced now; the adapter itself was correct throughout. - Fixed (CI) — The greetings workflow passed
issue-message/pr-messagetoactions/first-interaction@v3, which expectsissue_message/pr_message, so the job failed on every first-time issue and pull request.
What's Changed
- Bump actions/checkout from 6 to 7 by @dependabot[bot] in #14
- Bump actions/setup-python from 6 to 7 by @dependabot[bot] in #15
- Bump actions/labeler from 6 to 7 by @dependabot[bot] in #16
- feat(extension): report an unmatched request to guard-core (v4.1.0) by @rennf93 in #17
Full Changelog: 4.0.1...4.1.0
4.0.1
4.0.0
v4.0.0 (2026-04-29)
Fail-secure by default (upstream), agent-stats surface, version reporting (v4.0.0)
- Breaking (upstream) —
SecurityConfig.fail_securenow defaults toTrue(inherited fromguard-core >= 3.0.0). When any security check raises an unhandled exception, the request is now blocked with HTTP 500 instead of logging and falling through. Bugs in checks that previously slipped past as silent fail-open responses now surface immediately. Restore the old behavior on deployments that depend on it viaSecurityConfig(fail_secure=False). Recommended migration: keep the new default, surface any check exceptions in your monitoring, and fix them — the previous default could mask serious bugs. The flaskapi-guard major bump tracks this upstream change so deployments see a clear signal. - Added —
FlaskAPIGuard.agent_statsread-only@propertyreturning the agent's telemetry buffer state. Returns{"enabled": False}when no agent is wired; otherwise returns{"enabled": True, **agent_handler.get_stats()}exposingevents_dropped,metrics_dropped,circuit_breaker_state, and other agent counters. No caching — fresh on each call. Access the property on the extension instance returned byFlaskAPIGuard(app, config=...)or viaapp.extensions["flaskapi_guard"]["guard"].agent_stats. Lets app teams build Flask health endpoints that surface agent-side drops and circuit-breaker trips without scraping the agent directly. - Added —
from flaskapi_guard import __version__— package version is now exported viaimportlib.metadata.version("flaskapi_guard")with a"0.0.0+unknown"fallback if the package is not installed (development from source). Pairs withguard-core >= 3.0.0'sSecurityConfig.agent_guard_versionso application code can wire the flaskapi-guard version through to the agent for SaaS-side telemetry attribution:SecurityConfig(agent_guard_version=__version__). - Fixed (latent) —
FlaskAPIGuard.refresh_cloud_ip_rangeswas callingcloud_handler.refresh(...)directly. When Redis was enabled, that method raisesRuntimeError("Use refresh_async() when Redis is enabled")— the cloud-IP refresh check would surface as an exception inside the security pipeline. Under the oldfail_secure=Falsedefault the exception was logged and the request fell through, so the bug stayed invisible. Under the newfail_secure=Truedefault it would block every request with HTTP 500 in any deployment that combinesenable_redis=Truewithblock_cloud_providers. Switched tocloud_handler.refresh_async(...)which dispatches Redis-aware vs non-Redis paths internally (matches djangoapi-guard's v3.0.0 fix and tornadoapi-guard's existing dispatch). Same fix shape; pre-existing bug. - Compatibility —
FlaskAPIGuard.agent_statsis purely additive; no existing API was changed.__version__was previously absent; reading it before this release returnedNonevia missing-attribute fallback in some integrations.
What's Changed
Full Changelog: 3.0.0...4.0.0
3.0.0
Hardening release routing CORS through the security pipeline.
Companion to guard-core 2.2.0 and guard-agent 2.3.0. Closes the audit's preflight-bypass finding for the Flask adapter.
Breaking change — preflight OPTIONS requests are now subject to the security pipeline.
Pipeline / CORS
_before_requestnow runs the security pipeline for every method includingOPTIONS. Banned IPs, rate-limited clients, and suspicious patterns apply to preflight just like every other request.- Preflight response construction and response-header injection moved to the shared
guard_core.sync.handlers.cors_handler.CorsHandlermodule. - Blocked responses now carry CORS headers when the origin is allowed — cross-origin clients see the real status code instead of an opaque CORS error.
_after_requestinjects CORS headers into normal responses.- The old
_handle_preflightflow is gone; replaced by_attach_cors_to_blockedand_build_flask_preflight_responsehelpers.
Internal — suppression cleanup
The release-wide no-suppression rule applied here too. Removed:
- Every
[[tool.mypy.overrides]] ignore_missing_imports = true/follow_imports = "skip"/disallow_untyped_decorators = falseblock. Replaced with proper stubs (django-stubsif needed), inline-typed packages withpy.typed(redis, guard-core, guard-agent). - Every
# type: ignoremarker in edited files. Replaced withcast(...),MagicMock(spec=...), or properly typed callbacks. - The
[tool.uv.sources] guard-core = { path = ..., editable = true }block from committed pyproject.toml — that's per-developer config, not project metadata. Useuv pip install -e ../guard-coreafteruv syncfor local-dev override.
Configuration
guard-agentadded to dev dependencies. Previously only mentioned in deptry's ignore list, which meant tests exercising agent integration relied on the package being installed by coincidence.
Test coverage
- 214 tests, 100% line + 100% branch coverage across
flaskapi_guard/(376 statements, 70 branches, 0 missed). Zero warnings under-W error.
Compatibility
- Python 3.10–3.14.
- Requires
guard-core>=2.2.0(declared as unconstrainedguard-corein pyproject.toml; documented here for upgrade guidance). guard-agentis a dev dependency; runtime usage remains opt-in viaSecurityConfig.enable_agent=True.
Migration
Before — explicit preflight bypass behavior assumed:
config = SecurityConfig(enable_cors=True, cors_allow_origins=["https://app.example.com"], ...)
FlaskAPIGuard(app, config)
# Preflights short-circuited inside the extension before any check ranAfter — preflight goes through the same checks as every other request:
config = SecurityConfig(
enable_cors=True,
cors_allow_origins=["https://app.example.com"],
cors_allow_methods=["GET", "POST"],
cors_allow_headers=["X-Custom"],
cors_allow_credentials=True,
cors_max_age=600,
)
FlaskAPIGuard(app, config)The cors_* fields drive everything; CORS is configured purely via SecurityConfig and activates inside the FlaskAPIGuard extension. There is no separate configure_cors entry point.
What's Changed
Full Changelog: 2.2.0...2.3.0