Skip to content

3.0.0

Choose a tag to compare

@rennf93 rennf93 released this 27 Apr 07:22
· 64 commits to master since this release
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