Repository navigation
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 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
OPTIONSpreflights 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.CorsHandlermodule — 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_preflightremoved; replaced by_build_preflight_responseand_attach_cors_headershelpers that delegate toCorsHandler.
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.pywas importingBaseSecurityDecoratorandRouteConfigfromguard_core.decorators.base(the async path) when it should have been usingguard_core.sync.decorators.base. Sync/async protocol mismatches cascaded from there.cloud_handler.refresh(ttl=...)was being called with attlkwarg that the method does not accept; correct call isrefresh_async(). Also the call was missingawait.GuardResponsewas being assigned to aDjangoGuardResponse-typed slot; bridged withcast.- 4 tests passed
agent_model="..."toSecurityConfig— that field does not exist on the model.
All fixed at root.
Suppression cleanup
- Removed all six
[[tool.mypy.overrides]]suppression blocks. Installeddjango-stubsas dev dependency.redis7.x shipspy.typednatively;guard-core2.2.0 shipspy.typed;guard-agent2.2.0 shipspy.typed. Examples excluded via[tool.mypy] excluderather than per-module overrides. - Removed 8
# type: ignoremarkers from edited test files. Replaced with propercast(...),MagicMock(spec=...), or properly typed callback functions. - Stripped
[tool.uv.sources] guard-core = { path = ..., editable = true }from committed pyproject.toml. Useuv pip install -e ../guard-coreafteruv syncfor 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 unconstrainedguard-corein pyproject.toml; documented here for upgrade guidance). - guard-agent integration via
enable_agent=Truecontinues to work;guard-agentis a dev dependency and an optional runtime extra.
Migration
Before:
class GuardConfig(SecurityConfig):
pass
# Adapter handled OPTIONS preflight via internal short-circuitAfter:
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