Skip to content

Releases: Guard-Core/djapi-guard

v5.0.0

Choose a tag to compare

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

v5.0.0 (2026-10-15)

Engine routing parity with fastapi-guard, required GUARD_SECURITY_CONFIG (v5.0.0)

  • Breaking changes - A missing settings.GUARD_SECURITY_CONFIG now raises ImproperlyConfigured (#39, closes #38). DjangoAPIGuard used to fall back silently to SecurityConfig() defaults when the setting was absent or None, which ran the guard with a configuration you never chose. It now fails at startup with a message naming the setting.

  • Breaking changes - Upgrade action - Define GUARD_SECURITY_CONFIG = SecurityConfig(...) in your Django settings before upgrading. If you relied on the defaults, set GUARD_SECURITY_CONFIG = SecurityConfig() explicitly.

  • Fixed - Global behavioral return rules now run. global_behavior_rules are passed to the engine response pass at both response-pass call sites, as fastapi-guard does.

  • Documentation and tests - The README examples now build a real SecurityConfig instead of relying on the fallback, and the advanced example uses the engine request and response protocols and the status:404 return pattern syntax.

  • Documentation and tests - tests/test_engine_parity.py drives the middleware over Django's real URL resolution and views with the real engine bypass handler and response factory. The test that asserted the fallback now asserts the error.

  • Compatibility - The guard-core floor rises from >=4.1.0 to >=4.4.0, so upgrading djapi-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).

  • 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, a CODEOWNERS file was added, the family Discord relay workflow was added, and the upload-sarif action was bumped (#29, #30, #31, #32, #34, #36, #37).

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.
8dbb7f0

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:53
v4.3.2
160d8d9

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 (#22): the Django response factory left the content type unset, so middleware-generated 403/429/400 pages went out without an explicit content type and clients were left guessing. The factory now sets an explicit text/plain content type, matching the engine's reference response factory and the fastapi-guard (#144) and flaskapi-guard (#25) fixes.
  • 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 djapi-guard uses changed shape.

v4.3.1

Choose a tag to compare

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

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 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 djapi-guard package skill, and noted in the access-control decorator docs that an unknown bypass token now warns.
  • Fixed (tests) - test_blocked_user_agent_pattern configured blocked_user_agents=[r".*crawler.*"], which guard-core 4.x's ReDoS validator rejects (adjacent broad unbounded quantifiers), so the suite cannot construct a SecurityConfig against the 4.0.x line. The test now blocks through [Cc]rawler/\d+\.\d+, which still exercises regex-pattern matching against a real user agent while satisfying the validator.

v4.3.0

Choose a tag to compare

@rennf93 rennf93 released this 31 Aug 19:02
9273d8d

v4.3.0 (2026-08-31)

guard-core 3.15.0 compatibility: lazy Redis initialization, fail-secure client-address rejection (v4.3.0)

  • Fixed - DjangoAPIGuard.__init__() called HandlerInitializer.initialize_redis_handlers() eagerly. Django's load_middleware() runs this before convert_exception_to_response is installed, so a Redis outage at process start raised GuardRedisError as a raw unhandled exception at the WSGI boundary instead of a styled response, and since __init__ runs once per worker process, recovery needed a restart. Initialization is now lazy: __call__ runs it once, on the first request, through the new _ensure_handlers_initialized(). A GuardRedisError on that first attempt is caught, logged, and answered with HttpResponse(status=503) and Retry-After: 5; the initialized flag stays unset, so the next request retries instead of waiting on a restart. redis_fail_open and fail_secure keep their existing guard-core meaning throughout; no new policy was introduced.
  • Compatibility (GHSA-634g-4wr8-xwxv) - A request with no client address is now rejected by default (fail_secure=True) instead of running the security pipeline with an "unknown" identity; this is guard-core 3.15.0's own fix, this adapter's test suite is brought in line with it here. test_passthrough_request_no_client_host now expects 403 instead of 200.
  • Fixed (tests) - test_rate_limiter_redis_errors asserted that a Redis error during rate limiting always falls back to the in-memory window. guard-core 3.15.0's check_rate_limit now honors redis_fail_open like every other check: with the default redis_fail_open=False it raises GuardRedisError instead of returning None silently, and only falls back in-memory once redis_fail_open=True is set. The test now asserts the raise on the default and the fallback only after opting in.
  • Internal - The no-client-host 403 above is returned before call_next is ever reached, so the wrapped_call_next closure in __call__ lost the only test path that invoked it. It is not dead: handle_security_bypass still calls it when a route bypasses all checks (@security.bypass(["all"])) outside passive_mode. Coverage is restored with a test that exercises that route instead of removing the closure. Splitting the lazy-init check into _ensure_handlers_initialized() and the CORS-preflight block into _handle_preflight() also keeps __call__ at cyclomatic complexity rank B.
  • Compatibility - guard-core moves from an unconstrained dependency to guard-core>=3.15.0 in pyproject.toml; no upper bound, the floor moves with each lockstep release. No public API of this adapter changed.

v4.2.0

Choose a tag to compare

@rennf93 rennf93 released this 25 Aug 15:59
8dc2026

guard-core 3.13.0 compatibility: call_next bypass contract, bounded body readers, behavior-scan 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. DjangoAPIGuard.call now builds a wrapped_call_next closure over self.get_response(request) and passes it to both bypass methods. Django can supply a real downstream callable because its middleware exposes get_response.
  • Added: DjangoGuardRequest/DjangoGuardResponse.read_body_prefix(max_bytes) implement guard-core 3.12.0 SyncBoundedBodyReader/SyncBoundedResponseBodyReader protocols (StreamingHttpResponse returns b"").
  • Fixed (tests): behavior_scan_response_body fixture, detection-cache seam patch (utils.detect_penetration_attempt), VALID_CLOUD_PROVIDERS expansion, honeypot read_body_prefix, --cov-branch enforced.
  • 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:05
416d950

Unresolvable paths are reported to guard-core (v4.1.0)

  • Added — _populate_guard_state() swallowed Resolver404 and left request.state untouched, 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. Django resolves URLs itself, so this adapter has no equivalent matching bug, but it now reports the failure by setting request.state.guard_route_unresolved. With guard-core >= 3.7.0 and SecurityConfig.route_resolution_strict=True, those 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. 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 #13
  • Bump actions/setup-python from 6 to 7 by @dependabot[bot] in #14
  • Bump actions/labeler from 6 to 7 by @dependabot[bot] in #15
  • feat(middleware): report an unresolvable path to guard-core (v4.1.0) by @rennf93 in #16

Full Changelog: 4.0.1...4.1.0

4.0.1

Choose a tag to compare

@rennf93 rennf93 released this 27 May 23:58
3a4f656

Full Changelog: 4.0.0...4.0.1

4.0.0

Choose a tag to compare

@rennf93 rennf93 released this 30 Apr 15:15
e722fad

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 djapi-guard major bump tracks this upstream change so deployments see a clear signal.
  • Added — DjangoAPIGuard.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. Lets app teams build health endpoints that surface agent-side drops and circuit-breaker trips without scraping the agent directly.
  • Added — from djangoapi_guard import __version__ — package version is now exported via importlib.metadata.version("djapi-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 djapi-guard version through to the agent for SaaS-side telemetry attribution: SecurityConfig(agent_guard_version=__version__).
  • Compatibility — DjangoAPIGuard.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 #11

New Contributors

Full Changelog: 3.0.0...4.0.0

3.0.0

Choose a tag to compare

@rennf93 rennf93 released this 27 Apr 07:22
087d6f6

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 Django adapter.

Breaking change — preflight OPTIONS requests are now subject to the security pipeline. Migrate by ensuring you're on guard-core>=2.2.0 (no other code changes required).

Pipeline / CORS

  • The middleware no longer short-circuits OPTIONS preflights ahead of the security pipeline. Banned IPs, rate-limited clients, and suspicious patterns now apply to preflight just like every other request.
  • CORS preflight construction and response-header injection moved to the shared guard_core.sync.handlers.cors_handler.CorsHandler module — single source of truth across all adapters.
  • Blocked responses now carry CORS headers when the origin is allowed, so cross-origin clients see the real status code (403, 429, etc.) rather than an opaque CORS error.
  • _handle_preflight removed; replaced by _build_preflight_response and _attach_cors_headers helpers that delegate to CorsHandler.

Internal — defects surfaced by removing suppressions

The pre-existing [[tool.mypy.overrides]] ignore_missing_imports = true and follow_imports = "skip" blocks for guard_core.* and friends had been hiding real defects:

  • middleware.py was importing BaseSecurityDecorator and RouteConfig from guard_core.decorators.base (the async path) when it should have been using guard_core.sync.decorators.base. Sync/async protocol mismatches cascaded from there.
  • cloud_handler.refresh(ttl=...) was being called with a ttl kwarg that the method does not accept; correct call is refresh_async(). Also the call was missing await.
  • GuardResponse was being assigned to a DjangoGuardResponse-typed slot; bridged with cast.
  • 4 tests passed agent_model="..." to SecurityConfig — that field does not exist on the model.

All fixed at root.

Suppression cleanup

  • Removed all six [[tool.mypy.overrides]] suppression blocks. Installed django-stubs as dev dependency. redis 7.x ships py.typed natively; guard-core 2.2.0 ships py.typed; guard-agent 2.2.0 ships py.typed. Examples excluded via [tool.mypy] exclude rather than per-module overrides.
  • Removed 8 # type: ignore markers from edited test files. Replaced with proper cast(...), MagicMock(spec=...), or properly typed callback functions.
  • Stripped [tool.uv.sources] guard-core = { path = ..., editable = true } from committed pyproject.toml. Use uv pip install -e ../guard-core after uv sync for local-dev override.

Test coverage

  • 258 tests, 100% line + 100% branch coverage across djangoapi_guard/ (397 statements, 106 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 integration via enable_agent=True continues to work; guard-agent is a dev dependency and an optional runtime extra.

Migration

Before:

class GuardConfig(SecurityConfig):
    pass
# Adapter handled OPTIONS preflight via internal short-circuit

After:

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,
    cors_expose_headers=["X-Trace"],
)

The cors_* fields drive everything; preflight is now subject to all security checks; CORS headers are emitted on success, on preflight, and on blocked responses for legitimate origins.

What's Changed

Full Changelog: 2.2.0...3.0.0