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:/pathdisplay 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 newshield segments add-rulecommand 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_SERVICEenv var fallback on all--serviceCLI options:shield status,shield enable,shield disable,shield maintenance, andshield scheduleall readSHIELD_SERVICEautomatically — set it once withexport SHIELD_SERVICE=payments-serviceand every command scopes itself to that service without repeating--service. An explicit--serviceflag always wins. shield current-servicecommand: shows the active service context from theSHIELD_SERVICEenvironment variable, or a hint to set it when the variable is absent.shield servicescommand: 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 raisesRouteNotFoundExceptionimmediately; the REST API returns404and the CLI prints a clear error, preventing phantom policies from accumulating. ShieldSDK.rate_limit_backendparameter: pass aRedisBackendinstance 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_policyenvelopes and applied to every connected SDK client in real time — no restart required. ShieldSDKauto-login (username/passwordparams): pass credentials directly toShieldSDKinstead of a pre-issued token; on startup the SDK callsPOST /api/auth/loginwithplatform="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):ShieldServerandShieldAdminnow acceptsdk_token_expiry(default 1 year) independently fromtoken_expiry(default 24 h for dashboard / CLI users), so service apps can run indefinitely without re-authentication while human sessions remain short-lived. platformfield onPOST /api/auth/login: the login endpoint now accepts"cli"(default) or"sdk"in the request body;"sdk"tokens usesdk_token_expiryand 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 onengine.syncusinganyio.from_thread.run(), the same mechanism the shield decorators use internally. Useengine.sync.*from plaindefFastAPI 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 clearCLI 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 newshield grlCLI 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//redocwas inconsistent and the global maintenance banner never appeared after changes made by another worker. The_run_global_config_listenertask was invalidating the in-process config cache but not bumping_schema_version, so the OpenAPI schema cache on receiving workers never expired. A newshield-route-state-listenerbackground task now bumps_schema_versionon every remote route state change too, ensuring all workers rebuild their schema on the next/openapi.jsonrequest. RedisBackendcrashes with "attached to a different loop" / "Event loop is closed" after worker restart:ConnectionPoolwas created once at__init__time and shared for the process lifetime; after a gunicorn worker recycle oruvicorn --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 ashield:rl-policy-changeRedis pub/sub channel, everysetanddeletenow broadcasts the change to all instances, which apply it to their local_rate_limit_policiesdict immediately via a new background listener task (shield-rl-policy-listener), mirroring the existingshield:global_invalidatepattern. - Rate limit policies reverted to decorator defaults on restart (
ShieldRouter):register_shield_routes()calledrestore_rate_limit_policies()(viaregister_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 runningrestore_rate_limit_policies()last, consistent with the order already used byscan_routes(). RedisRateLimitStorage.reset()blocked the event loop: the underlyinglimitslibrary exposes a synchronous Redis client; calling.scan()and.delete()directly insideasync def reset()stalled the event loop during every admin reset and routeenable()call. Fixed by offloading the SCAN + DELETE loop toasyncio.to_thread().enable()reset rate limit counters with seven sequential calls: afor _method in (…)loop called_rate_limiter.reset()once per HTTP method. Replaced with a singlereset_all_for_path()call (method=None), which is the existing one-shot API onShieldRateLimiter.
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-labelattributes. 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, andshield rl hitsnow support--pageand--per-page(default 20) instead of a flat--limit; a footer shows the current slice and the next/prev page flag to run. Theshield logstatus column now combines old and new state into a singleold > newcell and shows a coloured label for rate limit audit actions. Theshield rl hitstable drops theKeyandMethodcolumns in favour of a singlePathcolumn showingMETHOD /route. @env_onlynow returns 403 with JSON: env-gated routes blocked by the wrong environment return403 ENV_GATEDwithcurrent_env,allowed_envs, andpathinstead of a silent empty 404.- Tailwind CSS v3 → v4: replaced
tailwind.config.jswith a CSS-first config ininput.css(@import "tailwindcss",@source,@theme). Dashboard CSS is pre-built and committed; no Node.js required at install time. - No CDN dependency:
shield.min.cssis 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 customon_missing_keybehaviour. Works as both a decorator and aDepends()dependency. Responses includeX-RateLimit-Limit/Remaining/ResetandRetry-Afterheaders. Requiresapi-shield[rate-limit]. - Rate limit custom responses:
response=on@rate_limitandresponses["rate_limited"]onShieldMiddlewarefor replacing the default 429 JSON body with any StarletteResponse. - Rate limit dashboard:
/shield/rate-limitstab showing registered policies with reset/edit/delete actions;/shield/blockedpage for the blocked requests log. Policies can also be managed via theshield rlCLI 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), andRedisRateLimitStorage(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 onShieldMiddlewarefor"maintenance","disabled", and"env_gated"states. @deprecatedasDepends(): injectsDeprecation,Sunset, andLinkheaders directly without requiring middleware.- Webhook deduplication:
RedisBackendusesSET NX EXto ensure only one instance fires webhooks per event in multi-instance deployments. Fails open on Redis error. - Distributed global maintenance:
RedisBackendpublishes toshield:global_invalidateon everyset_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_ENVchanged from"production"to"dev". SetSHIELD_ENV=productionexplicitly in production deployments. @deprecatedand all blocking decorators now supportDepends()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
ShieldAuthBackendauth backends - Automatic token invalidation when credentials change (auth fingerprint mixed into signing key)
HttpOnlysession 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¶
shieldCLI redesigned as a thin HTTP client over theShieldAdminREST APIshield login/shield logout,shield status,shield enable,shield disable,shield maintenance,shield schedule,shield logshield globalsubcommands: enable, disable, status, exempt-add, exempt-removeshield config set-urlandshield config show- Cross-platform config file at
~/.shield/config.json - Server URL auto-discovery via
SHIELD_SERVER_URLenv var,.shieldfile, 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: BearertoX-Shield-Token ShieldDashboardretained for backward compatibility;ShieldAdminis now the recommended entry point
0.1.0 — Initial Release¶
Added¶
Core¶
RouteStatus,MaintenanceWindow,RouteState,AuditEntryPydantic v2 modelsShieldException,MaintenanceException,EnvGatedException,RouteDisabledExceptionShieldBackendABC with full contractMemoryBackend: in-process dict withasyncio.QueuesubscribeFileBackend: JSON file viaaiofileswithasyncio.LockRedisBackend:redis-pyasync with pub/sub for live dashboard updates in multi-instance deploymentsShieldEngine: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()andSlackWebhookFormatter
FastAPI adapter¶
@maintenance,@disabled,@env_only,@force_active,@deprecateddecoratorsShieldRouter: drop-inAPIRouterreplacement with startup registration hookShieldMiddleware: ASGI middleware with structured JSON error responses andRetry-Afterheader@deprecated: injectsDeprecation,Sunset, andLinkresponse headersapply_shield_to_openapi: runtime OpenAPI schema filteringsetup_shield_docs: enhanced/docsand/redocwith 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)¶
shieldCLI with direct backend accessshield status,shield enable,shield disable,shield maintenance,shield schedule,shield log