Skip to content

Releases: Guard-Core/flaskapi-guard

v4.4.0

Choose a tag to compare

@rennf93 rennf93 released this 15 Oct 13:14
Immutable release. Only release title and notes can be modified.
b336fb0

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 use handle_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 403 under the default fail_secure=True. It used to skip the security pipeline and reach the view. If you depend on the old pass-through, set fail_secure=False in SecurityConfig.
  • Fixed - Blocked, preflight and bypass responses no longer emit request metrics and no longer get the pass-through response pass. _after_request runs 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.py drives 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 the 403.
  • Compatibility - The guard-core floor rises from >=4.1.0 to >=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_url drops /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

Choose a tag to compare

@rennf93 rennf93 released this 08 Oct 12:07
Immutable release. Only release title and notes can be modified.
3bb4211

Metadata release for the Guard-Core org migration: PyPI project URLs and documentation links now point at Guard-Core. No code changes.

v4.3.2

Choose a tag to compare

@rennf93 rennf93 released this 26 Sep 13:44
v4.3.2
4263b0e

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 passes mimetype="text/plain", matching the engine's reference response factory and the fastapi-guard fix (#144).
  • Compatibility - The guard-core floor moves from >=4.0.0 to >=4.1.0 in pyproject.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

Choose a tag to compare

@rennf93 rennf93 released this 23 Sep 23:26
v4.3.1
f69a37b

guard-core 4.0.x tracking: dependency floor to >=4.0.0, agent docs (v4.3.1)

  • Compatibility - The guard-core floor moves from >=3.15.0 to >=4.0.0 in pyproject.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-value require_headers() enforcement, excluded_detection_headers semantics) are documented in the guard-core changelog.
  • Documentation - Added AGENTS.md/CLAUDE.md agent 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

Choose a tag to compare

@rennf93 rennf93 released this 31 Aug 19:02
efe94ef

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 raised GuardRedisError straight out of FlaskAPIGuard(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 _initialized flag: a GuardRedisError raised there is caught and logged, and the request returns 503 with a Retry-After: 5 header instead of crashing the app, leaving the flag unset so the next request retries. redis_fail_open and fail_secure semantics are unchanged; guard-core still owns that policy (guard-core #76).
  • Fixed (tests) - test_request_without_client asserted the pre-3.14.0 behavior (403) for a request that resolves to the "unknown" client identity. guard-core 3.14.0 changed check_ip_access to treat "unknown" as no address available and allow the request unless whitelist or whitelist_countries is configured, since the blacklist, blocked_countries, and cloud-provider checks cannot match without an address (GHSA-634g-4wr8-xwxv); the test now asserts 200, and a new test_request_without_client_unknown_identity_with_whitelist_is_blocked covers the other half of the rule: an unknown identity is still blocked when a whitelist is configured. test_rate_limiter_redis_errors asserted the old silent in-memory fallback on a Redis failure; guard-core's rate limiter now honors redis_fail_open like every other check and raises GuardRedisError when it is False (the default), so the test now asserts the raise and adds a redis_fail_open=True case 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_initialization and test_redis_initialization_without_ipinfo_and_cloud keep their existing lazy_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_retries covers the new lazy-init path end to end: a first request with Redis unreachable gets 503 with Retry-After: 5 and 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

Choose a tag to compare

@rennf93 rennf93 released this 25 Aug 15:58
2f5c441

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

Choose a tag to compare

@rennf93 rennf93 released this 29 Jul 19:09
7376e39

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 setting request.state.guard_route_unresolved when request.endpoint is unset or its view function is missing. A matched view without _guard_route_id is left alone, since an undecorated view legitimately has no per-route config. With guard-core >= 3.7.0 and SecurityConfig.route_resolution_strict=True, unmatched requests are logged, emit a route_unresolved event, and are blocked with 500. 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_decorators likewise predated guard-core 3.5.0, where a route @require_ip match 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-message to actions/first-interaction@v3, which expects issue_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

Choose a tag to compare

@rennf93 rennf93 released this 27 May 23:56
231db82

Full Changelog: 4.0.0...4.0.1

4.0.0

Choose a tag to compare

@rennf93 rennf93 released this 30 Apr 15:25
3b888ea

v4.0.0 (2026-04-29)

Fail-secure by default (upstream), agent-stats surface, version reporting (v4.0.0)

  • Breaking (upstream) — SecurityConfig.fail_secure now defaults to True (inherited from guard-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 via SecurityConfig(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_stats read-only @property returning the agent's telemetry buffer state. Returns {"enabled": False} when no agent is wired; otherwise returns {"enabled": True, **agent_handler.get_stats()} exposing events_dropped, metrics_dropped, circuit_breaker_state, and other agent counters. No caching — fresh on each call. Access the property on the extension instance returned by FlaskAPIGuard(app, config=...) or via app.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 via importlib.metadata.version("flaskapi_guard") with a "0.0.0+unknown" fallback if the package is not installed (development from source). Pairs with guard-core >= 3.0.0's SecurityConfig.agent_guard_version so 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_ranges was calling cloud_handler.refresh(...) directly. When Redis was enabled, that method raises RuntimeError("Use refresh_async() when Redis is enabled") — the cloud-IP refresh check would surface as an exception inside the security pipeline. Under the old fail_secure=False default the exception was logged and the request fell through, so the bug stayed invisible. Under the new fail_secure=True default it would block every request with HTTP 500 in any deployment that combines enable_redis=True with block_cloud_providers. Switched to cloud_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_stats is purely additive; no existing API was changed. __version__ was previously absent; reading it before this release returned None via missing-attribute fallback in some integrations.

What's Changed

  • Fail secure default + agent version persistance by @rennf93 in #12

Full Changelog: 3.0.0...4.0.0

3.0.0

Choose a tag to compare

@rennf93 rennf93 released this 27 Apr 07:51
7d7e9fa

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_request now runs the security pipeline for every method including OPTIONS. 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.CorsHandler module.
  • 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_request injects CORS headers into normal responses.
  • The old _handle_preflight flow is gone; replaced by _attach_cors_to_blocked and _build_flask_preflight_response helpers.

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 = false block. Replaced with proper stubs (django-stubs if needed), inline-typed packages with py.typed (redis, guard-core, guard-agent).
  • Every # type: ignore marker in edited files. Replaced with cast(...), 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. Use uv pip install -e ../guard-core after uv sync for local-dev override.

Configuration

  • guard-agent added 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 unconstrained guard-core in pyproject.toml; documented here for upgrade guidance).
  • guard-agent is a dev dependency; runtime usage remains opt-in via SecurityConfig.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 ran

After — 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