Skip to content

Changelog

All notable changes to this project will be documented here.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.


[0.8.0]

Fixed

  • Unprotected routes table not updating after adding a policy: routes stayed visible in the "Unprotected Routes" section after a rate limit was set because the filter compared METHOD:/path display values against path-only strings stripped of the method prefix; comparison now uses the full composite key correctly.
  • Invalid rate limit strings accepted silently: submitting a malformed limit (e.g. 2/minutesedrr) was stored and caused a 500 on every request; the engine now validates the limit string before persisting and the dashboard modal surfaces an inline error with the correct format hint.

Added

  • Feature flags (api-shield[flags]): a full feature flag system built on the OpenFeature standard, supporting boolean, string, integer, float, and JSON flag types with multi-condition targeting rules, reusable user segments (explicit included/excluded lists plus attribute-based rules), percentage rollouts, prerequisite flags, individual user targeting, and a live SSE evaluation stream. Flags and segments are manageable from the admin dashboard (/shield/flags, /shield/segments) and the CLI (shield flags *, shield segments *) — including a new shield segments add-rule command and an "Add Rule" panel in the Edit Segment modal that lets operators add attribute-based targeting rules without touching code or the REST API directly.

  • SHIELD_SERVICE env var fallback on all --service CLI options: shield status, shield enable, shield disable, shield maintenance, and shield schedule all read SHIELD_SERVICE automatically — set it once with export SHIELD_SERVICE=payments-service and every command scopes itself to that service without repeating --service. An explicit --service flag always wins.

  • shield current-service command: shows the active service context from the SHIELD_SERVICE environment variable, or a hint to set it when the variable is absent.
  • shield services command: lists all distinct service names registered with the Shield Server, so you can discover which services are connected before switching context.
  • Dashboard "Unprotected Routes" section: the Rate Limits page now surfaces all routes that have no rate limit policy, with an "Add Limit" button per row that opens a modal to configure method, limit, algorithm, and key strategy in-place — no CLI required.
  • Route existence validation in set_rate_limit_policy(): attempting to add a rate limit policy for a route that does not exist now raises RouteNotFoundException immediately; the REST API returns 404 and the CLI prints a clear error, preventing phantom policies from accumulating.
  • ShieldSDK.rate_limit_backend parameter: pass a RedisBackend instance to share rate limit counters across all replicas of a service connected to the same Shield Server; without it each replica enforces limits independently.
  • Rate limit policy SSE propagation to SDK clients: policies set or deleted via the CLI or dashboard are now broadcast over the Shield Server's SSE stream as typed rl_policy envelopes and applied to every connected SDK client in real time — no restart required.
  • ShieldSDK auto-login (username / password params): pass credentials directly to ShieldSDK instead of a pre-issued token; on startup the SDK calls POST /api/auth/login with platform="sdk" and caches the resulting long-lived token for the life of the process — no manual token management required.
  • Separate SDK token lifetime (sdk_token_expiry): ShieldServer and ShieldAdmin now accept sdk_token_expiry (default 1 year) independently from token_expiry (default 24 h for dashboard / CLI users), so service apps can run indefinitely without re-authentication while human sessions remain short-lived.
  • platform field on POST /api/auth/login: the login endpoint now accepts "cli" (default) or "sdk" in the request body; "sdk" tokens use sdk_token_expiry and are intended for machine-to-machine service authentication.

0.7.0

Added

  • engine.sync — synchronous proxy for sync route handlers and background threads: every async engine method (enable, disable, set_maintenance, schedule_maintenance, set_env_only, enable_global_maintenance, disable_global_maintenance, set_rate_limit_policy, delete_rate_limit_policy, reset_rate_limit, get_state, list_states, get_audit_log) is now mirrored on engine.sync using anyio.from_thread.run(), the same mechanism the shield decorators use internally. Use engine.sync.* from plain def FastAPI handlers (which FastAPI runs in a worker thread automatically) and background threads — no event-loop wiring required.
  • Env-gate management from dashboard and CLI: routes can now have their environment gate set or cleared at runtime — without redeployment — via the dashboard "Env Gate" button (opens an inline modal) and the new shield env set / shield env clear CLI commands.
  • Global rate limit (engine.set_global_rate_limit): a single rate limit policy applied across all routes with higher precedence than per-route limits — checked first, so a request blocked globally never touches a per-route counter. Supports all key strategies, burst allowance, and per-route exemptions (exempt_routes). Configurable from the dashboard Rate Limits page and the new shield grl CLI command group (get, set, delete, reset, enable, disable).
  • Global rate limit pause / resume (engine.disable_global_rate_limit / engine.enable_global_rate_limit): suspend enforcement without removing the policy, then resume it later. Per-route policies are always unaffected.

Documentation

  • Reframed all docs and README as ASGI-first; expanded framework support tables to include Litestar, Starlette, Quart, and Django (ASGI) as planned adapters; added a dedicated Adapters overview page with a clear explanation of why WSGI frameworks (Flask, Django WSGI, Bottle) are out of scope for this project and will be supported in a separate dedicated library.

0.6.0

Fixed

  • OpenAPI schema stale across Gunicorn workers (RedisBackend): route filtering in /docs//redoc was inconsistent and the global maintenance banner never appeared after changes made by another worker. The _run_global_config_listener task was invalidating the in-process config cache but not bumping _schema_version, so the OpenAPI schema cache on receiving workers never expired. A new shield-route-state-listener background task now bumps _schema_version on every remote route state change too, ensuring all workers rebuild their schema on the next /openapi.json request.
  • RedisBackend crashes with "attached to a different loop" / "Event loop is closed" after worker restart: ConnectionPool was created once at __init__ time and shared for the process lifetime; after a gunicorn worker recycle or uvicorn --reload, all Redis calls failed because the pool's internal futures were bound to the replaced event loop. Fixed by replacing the single shared pool with a per-event-loop pool dict (dict[id(loop), (weakref.ref(loop), pool)]): pools are now created lazily on first use within each event loop and dead entries are pruned automatically when the loop is GC'd.

0.5.0

Fixed

  • Rate limit policies not synced across workers (RedisBackend): updating a policy via the admin API or CLI only updated the in-process dict of the worker that handled the request. Other workers continued enforcing the old (decorator-declared) limit until restart. Fixed by adding a shield:rl-policy-change Redis pub/sub channel, every set and delete now broadcasts the change to all instances, which apply it to their local _rate_limit_policies dict immediately via a new background listener task (shield-rl-policy-listener), mirroring the existing shield:global_invalidate pattern.
  • Rate limit policies reverted to decorator defaults on restart (ShieldRouter): register_shield_routes() called restore_rate_limit_policies() (via register_batch) before re-registering decorator-declared policies, so persisted admin/CLI updates were immediately overwritten on every app startup. Fixed by registering decorator policies first and running restore_rate_limit_policies() last, consistent with the order already used by scan_routes().
  • RedisRateLimitStorage.reset() blocked the event loop: the underlying limits library exposes a synchronous Redis client; calling .scan() and .delete() directly inside async def reset() stalled the event loop during every admin reset and route enable() call. Fixed by offloading the SCAN + DELETE loop to asyncio.to_thread().
  • enable() reset rate limit counters with seven sequential calls: a for _method in (…) loop called _rate_limiter.reset() once per HTTP method. Replaced with a single reset_all_for_path() call (method=None), which is the existing one-shot API on ShieldRateLimiter.

0.4.0

Added

  • Mobile & tablet responsive dashboard: all four tables (Routes, Audit, Rate Limits, Blocked) transform into stacked cards on screens narrower than 640 px using a CSS-only card layout with data-label attributes. Action buttons collapse to icon-only on small screens.
  • Back-to-top button: fixed button appears at the bottom-right after scrolling 200 px; hidden at the top.
  • Success toast notifications: 2.5 s toast appears after any mutating action (enable, disable, maintenance, schedule, rate limit edit/reset/delete).

Changed

  • CLI table pagination: shield status, shield log, shield rl list, and shield rl hits now support --page and --per-page (default 20) instead of a flat --limit; a footer shows the current slice and the next/prev page flag to run. The shield log status column now combines old and new state into a single old > new cell and shows a coloured label for rate limit audit actions. The shield rl hits table drops the Key and Method columns in favour of a single Path column showing METHOD /route.
  • @env_only now returns 403 with JSON: env-gated routes blocked by the wrong environment return 403 ENV_GATED with current_env, allowed_envs, and path instead of a silent empty 404.
  • Tailwind CSS v3 → v4: replaced tailwind.config.js with a CSS-first config in input.css (@import "tailwindcss", @source, @theme). Dashboard CSS is pre-built and committed; no Node.js required at install time.
  • No CDN dependency: shield.min.css is now served as a local static file instead of the Tailwind CDN script, eliminating the production warning and removing the runtime network dependency.

0.3.0

Added

  • Rate limiting (@rate_limit): per-IP, per-user, per-API-key, and global counters with fixed/sliding/moving window and token bucket algorithms. Supports burst allowance, tiered limits ({"free": "10/min", "pro": "100/min"}), exempt IPs/roles, and custom on_missing_key behaviour. Works as both a decorator and a Depends() dependency. Responses include X-RateLimit-Limit/Remaining/Reset and Retry-After headers. Requires api-shield[rate-limit].
  • Rate limit custom responses: response= on @rate_limit and responses["rate_limited"] on ShieldMiddleware for replacing the default 429 JSON body with any Starlette Response.
  • Rate limit dashboard: /shield/rate-limits tab showing registered policies with reset/edit/delete actions; /shield/blocked page for the blocked requests log. Policies can also be managed via the shield rl CLI commands (list, set, reset, delete, hits).
  • Rate limit audit log: policy changes (set, update, reset, delete) are recorded in the audit log alongside route state changes, with coloured action badges in the dashboard.
  • Rate limit storage: MemoryRateLimitStorage, FileRateLimitStorage (in-memory counters with periodic disk snapshot), and RedisRateLimitStorage (atomic Redis counters, multi-worker safe). Storage is auto-selected based on the main backend.
  • Custom responses for lifecycle decorators: response= on @maintenance, @disabled, and @env_only; responses= dict on ShieldMiddleware for "maintenance", "disabled", and "env_gated" states.
  • @deprecated as Depends(): injects Deprecation, Sunset, and Link headers directly without requiring middleware.
  • Webhook deduplication: RedisBackend uses SET NX EX to ensure only one instance fires webhooks per event in multi-instance deployments. Fails open on Redis error.
  • Distributed global maintenance: RedisBackend publishes to shield:global_invalidate on every set_global_config() call so all instances drop their cached config immediately.
  • Distributed Deployments guide (docs/guides/distributed.md): backend capability matrix, global maintenance cache invalidation, scheduler behaviour, webhook dedup, and production checklist.

Changed

  • Default SHIELD_ENV changed from "production" to "dev". Set SHIELD_ENV=production explicitly in production deployments.
  • @deprecated and all blocking decorators now support Depends() in addition to the decorator form.

0.2.0 — Admin Redesign & Docs

Added

Admin & Dashboard

  • ShieldAdmin: unified admin interface combining the HTMX dashboard UI and the REST API under a single mount point
  • REST API under /api/ for programmatic route management (enable, disable, maintenance, schedule, audit, global maintenance)
  • Token-based authentication with HMAC-SHA256 signing
  • Support for single user, multiple users, and custom ShieldAuthBackend auth backends
  • Automatic token invalidation when credentials change (auth fingerprint mixed into signing key)
  • HttpOnly session cookies for dashboard browser sessions
  • HTMX dashboard with SSE live updates, audit log table, and login page
  • Platform field in audit log entries ("cli" / "dashboard")

CLI

  • shield CLI redesigned as a thin HTTP client over the ShieldAdmin REST API
  • shield login / shield logout, shield status, shield enable, shield disable, shield maintenance, shield schedule, shield log
  • shield global subcommands: enable, disable, status, exempt-add, exempt-remove
  • shield config set-url and shield config show
  • Cross-platform config file at ~/.shield/config.json
  • Server URL auto-discovery via SHIELD_SERVER_URL env var, .shield file, or config file

Documentation

  • MkDocs Material documentation site with full tutorial, reference, guides, and adapter sections

Changed

  • CLI no longer accesses the backend directly; all operations go through the REST API
  • Custom auth header changed from Authorization: Bearer to X-Shield-Token
  • ShieldDashboard retained for backward compatibility; ShieldAdmin is now the recommended entry point

0.1.0 — Initial Release

Added

Core

  • RouteStatus, MaintenanceWindow, RouteState, AuditEntry Pydantic v2 models
  • ShieldException, MaintenanceException, EnvGatedException, RouteDisabledException
  • ShieldBackend ABC with full contract
  • MemoryBackend: in-process dict with asyncio.Queue subscribe
  • FileBackend: JSON file via aiofiles with asyncio.Lock
  • RedisBackend: redis-py async with pub/sub for live dashboard updates in multi-instance deployments
  • ShieldEngine: check, register, enable, disable, set_maintenance, set_env_only, get_state, list_states, get_audit_log
  • Fail-open guarantee: backend errors are logged; requests pass through
  • MaintenanceScheduler: asyncio.Task-based scheduler that auto-activates and auto-deactivates maintenance windows
  • Webhook support: add_webhook() and SlackWebhookFormatter

FastAPI adapter

  • @maintenance, @disabled, @env_only, @force_active, @deprecated decorators
  • ShieldRouter: drop-in APIRouter replacement with startup registration hook
  • ShieldMiddleware: ASGI middleware with structured JSON error responses and Retry-After header
  • @deprecated: injects Deprecation, Sunset, and Link response headers
  • apply_shield_to_openapi: runtime OpenAPI schema filtering
  • setup_shield_docs: enhanced /docs and /redoc with maintenance banners
  • Global maintenance mode: enable_global_maintenance, disable_global_maintenance, get_global_maintenance

Dashboard (original)

  • HTMX dashboard with SSE live updates and HTTP basic auth via ShieldDashboard
  • Route list with status badges, enable/maintenance/disable actions per route
  • Audit log table

CLI (original)

  • shield CLI with direct backend access
  • shield status, shield enable, shield disable, shield maintenance, shield schedule, shield log