Releases: modern-python/lite-bootstrap
Release list
1.4.0
lite-bootstrap 1.4.0 — Litestar access logging off by default
1.4.0 is a minor release with a behavior change for Litestar services. A
second bootstrapper sharing an application also now fails loudly instead of
corrupting it — see Bug fixes below.
Behavior change
Litestar's LoggingMiddleware no longer logs requests and responses by
default. If your service is on Litestar and you rely on the HTTP Request
/ HTTP Response access log lines it used to emit, they stop appearing after
this upgrade until you opt back in:
LitestarConfig(
service_name="microservice",
litestar_logging_middleware_enabled=True,
)Why
LitestarLoggingInstrument registers Litestar's StructlogPlugin, and
Litestar's own default LoggingMiddlewareConfig logs full request and
response bodies. That meant:
- Any credential posted to the service — a login form's password, an API
key in a JSON body — landed in stdout verbatim. Litestar only obfuscates
theAuthorization/X-API-KEYheaders and thesessioncookie; request
and response bodies are never obfuscated. - Every offline Swagger asset served by
swagger_offline_docs=True
(swagger-ui-bundle.js,swagger-ui.css, up to ~150 KB) was logged as an
ordinary response body on every request. - Every k8s health probe and every Prometheus scrape produced its own log
line, unconditionally.
None of this was opt-in — it was a side effect of adding the plugin. Every
other bootstrapper (FastAPI, FastStream, FastMCP, Free) adds no
request/response logging middleware at all, so this brings Litestar in line
with the rest.
What the opt-in logs
With litestar_logging_middleware_enabled=True, access logs are metadata
only:
- Requests:
path,method,content_type,path_params. - Responses:
status_code.
No body, headers, cookies, or query — bodies and headers are where
secrets live, and query strings carry tokens often enough to not be worth the
diagnostic value. Note that path and path_params are still logged, so a
secret embedded in the URL itself (e.g. /reset-password/{token}) is
recorded; keep secrets in the body, never in the path.
The opt-in also excludes infrastructure routes from access logs: the Swagger
docs path, the offline Swagger static assets (when swagger_offline_docs is
on), the health-check path, and the Prometheus metrics path — each matched
whether or not the corresponding instrument is actually active, so a service
that disables health checks but serves its own route at the same path is
still excluded there.
Escape hatch
To take full control — including restoring Litestar's original body-logging
defaults — pass your own LoggingMiddlewareConfig via
litestar_logging_middleware_config. It replaces the hardened defaults
above wholesale, with no merging:
from litestar.middleware.logging import LoggingMiddlewareConfig
LitestarConfig(
service_name="microservice",
litestar_logging_middleware_enabled=True,
litestar_logging_middleware_config=LoggingMiddlewareConfig(
request_log_fields=("path", "method", "content_type"),
),
)Supplying litestar_logging_middleware_config while
litestar_logging_middleware_enabled is False is a no-op that emits a
warning — set the flag to actually turn logging on.
Bug fixes
- Litestar apps can read request bodies again.
Litestar.from_config()— which
LitestarBootstrapperuses — passes everyAppConfigfield explicitly, so the 10 MB
request_max_body_sizedefault thatLitestar(...)applies never reached the built
app. Every handler taking a request body returned
500: 'request_max_body_size' set to 'Empty' on all layersunless the caller set the
field themselves. The bootstrapper now fills it when the config leaves it unset; a
caller's own value, including an explicitNonefor no limit, is untouched. Reported
upstream as litestar#4296. - Structlog output follows a redirected stdout.
_MemoryLoggerFactoryConfig.log_stream
boundsys.stdoutonce, at import time, so a process that replacedsys.stdoutafter
importinglite_bootstrapbut before bootstrapping kept logging to the stale stream —
while the root-logger handler installed at bootstrap followed the new one. The stream is
now resolved at bootstrap, so both agree. Affects every bootstrapper. - The
litestarextra's floor moved from>=2.9to>=2.15.AppConfig.request_max_body_size,
which the fix above now reads, was only added in litestar 2.13; and
litestar.middleware.ASGIMiddleware, which the OpenTelemetry middleware already subclassed
before this release, was only added in litestar 2.15.>=2.9was never actually supported for
the OTel path — this just makes the declared floor honest. - A second bootstrapper sharing an application now fails loudly instead of corrupting it.
Constructing two bootstrappers (FastAPI, Litestar, FastStream, or FastMCP) against the same
application already warned at construction time, butbootstrap()on the second one applied
every instrument again anyway. Litestar died with an unrelated-looking
ImproperlyConfiguredException: Handler already registered for path '/health' and http method OPTIONS; FastAPI did not fail at all — the app's route count silently grew (e.g. from 6 to 8
for a default config), a shadowed duplicate of the health-check and metrics routes.
bootstrap()on the losing bootstrapper now raisesConfigurationErrornaming itself. If your
code relied on the FastAPI case appearing to "work", it will now raise. The ownership marker
behind this is never cleared, including byteardown()— once an application has been
bootstrapped, it stays owned for the life of the process; construct a fresh application rather
than reusing one that was already bootstrapped.
References
planning/changes/2026-08-10.01-litestar-middleware-logging.mdplanning/changes/2026-08-10.02-log-stream-bind-at-bootstrap.mdplanning/changes/2026-08-10.03-litestar-request-max-body-size.mdplanning/changes/2026-08-10.04-double-bootstrap-guard.md
1.3.2
lite-bootstrap 1.3.2 — fix lite-bootstrap[litestar] import
1.3.2 is a patch release. Fully backward compatible with 1.3.1.
Bug fixes
import lite_bootstrapno longer crashes underlite-bootstrap[litestar]
without prometheus-client.litestar_bootstrapperimported
litestar.plugins.prometheus(which requiresprometheus_client) under the
is_litestar_installedguard, but thelitestarextra does not install
prometheus-client — onlylitestar-metricsdoes. Sopip install lite-bootstrap[litestar]followed byimport lite_bootstrapraised litestar's
MissingDependencyException: prometheus_client. The import is now guarded by
prometheus-client presence too (its only uses are inside the metrics
instrument, already gated on that package). Found by a new per-extra isolation
install-check in CI.
References
planning/changes/2026-07-19.04-extra-isolation-install-check.md
1.3.1
lite-bootstrap 1.3.1 — fix bare-install import (typing-extensions)
1.3.1 is a patch release. Fully backward compatible with 1.3.0.
Bug fixes
-
Bare
import lite_bootstrapno longer crashes on a missing
typing_extensions. Core usestyping_extensionsat runtime (aSelf
return annotation, and aTypedDictFastAPI response model that pydantic
requires be atyping_extensions.TypedDicton Python < 3.12) but never
declared it, sopip install lite-bootstrapwith no extras followed by
import lite_bootstrapraisedModuleNotFoundError: No module named 'typing_extensions'.typing-extensionsis now declared as core's single
dependency. It is pure Python, so free-threaded support is unchanged, and this
is the leanest possible core.This corrects 1.3.0's note, which called core "zero-dependency": the code
genuinely needstyping_extensionswhile Python 3.10/3.11 are supported. The
bug was pre-existing (bare1.2.3failed the same way); every install with any
extra masked it, because extras pulltyping_extensionsin transitively.
Backwards compatibility
Fully compatible with 1.3.0. No API or configuration changes; the only
difference is that a bare-core install now resolves typing-extensions and
imports cleanly.
References
planning/changes/2026-07-19.03-core-zero-dep-typing-extensions.md
1.3.0
lite-bootstrap 1.3.0 — free-threaded Python support, orjson becomes opt-in
1.3.0 is a minor release. Backward compatible with 1.2.3, with one dependency
change (orjson moves from a mandatory core dependency to an opt-in extra).
It lands free-threaded CPython (3.13t/3.14t) support for core and most extras,
plus two import-safety fixes surfaced while verifying it.
Features
-
Free-threaded CPython (3.13t/3.14t) support.
lite-bootstrapis pure
Python; the only thing that ever blocked it on a free-threaded interpreter
was the mandatoryorjsondependency (below). Core,logging,sentry,
fastapi, andfaststream(plus their-sentry/-logging/-metrics
combos) now install and run on both 3.13t and 3.14t.litestar(+
litestar-metrics) andfastmcp(+fastmcp-metrics) land on 3.14t
only —msgspec(litestar) andcffi(fastmcp, via cryptography) both
gate free-threaded support to Python 3.14+ and fail to build from source on
3.13t. The gRPCotlexporter (grpciohas no ft wheels — use the new
otl-httpextra instead) andpyroscope(pyroscope-iois abi3-only and
unmaintained) remain unavailable on free-threaded builds pending upstream
fixes. See
architecture/free-threading.mdfor
the full support matrix andplanning/deferred.mdfor the
ecosystem blockers. -
OTLP-http exporter: new
opentelemetry_exporter_protocolconfig (grpc
default |http) and anotl-httpextra (nogrpcio) so OTLP trace export
works on free-threaded Python.otlnow pulls
opentelemetry-exporter-otlp-proto-grpcdirectly (was theopentelemetry-exporter-otlp
meta); grpc behavior is unchanged. -
orjsonis now an opt-in extra (lite-bootstrap[orjson]) instead of a
mandatory core dependency — it shipped no free-threaded wheels and its build
refuses to compile under a free-threaded interpreter, which meant nothing,
not even the pure extras, could install on ft before this change. The
logging serializer falls back to the stdlibjsonaccelerator whenorjson
is absent (byte-identical output for JSON-native values; ~2-5x slower;
non-JSON-native types in logextra, e.g. datetime/UUID, render via repr
instead of orjson's native encoding). Add[orjson]to keep the fast path
on a standard (GIL) build. If your code relied onimport lite_bootstrap
pullingorjsonin transitively, depend onorjsondirectly.
Bug fixes
import_checker's dottedfind_specchecks no longer crash on an
incomplete namespace.find_specimports a dotted name's parent package
first, so a present-but-incompleteopentelemetryinstall (e.g.
opentelemetry-apiwithoutopentelemetry-instrumentation) previously
raisedModuleNotFoundErrorinstead of returningFalse, crashingimport lite_bootstrap. Affected dotted checks now go through a
ModuleNotFoundError-safe helper.- The gRPC OTLP exporter is no longer imported unconditionally.
opentelemetry_instrument.pyused to import
opentelemetry.exporter.otlp.proto.grpc.trace_exporterwhenever bare
opentelemetry-apiresolved, crashingimport lite_bootstrapin any
environment withopentelemetry-apipresent but the exporter package
absent (e.g.lite-bootstrap[fastmcp], which pulls in bare
opentelemetry-apitransitively). The exporter import — and its use in
bootstrap()— now sit behind their ownis_otlp_grpc_exporter_installed
guard. Whenopentelemetry_endpointis set but the selected exporter package
is absent,bootstrap()emits anInstrumentDependencyMissingWarningnaming
the extra to install ([otl]for gRPC,[otl-http]for HTTP); see
architecture/instruments.md. - The OpenTelemetry instrument no longer assumes the SDK is present when only
the API is.is_opentelemetry_installed(find_spec("opentelemetry")) is
true with justopentelemetry-api, but the instrument imports
opentelemetry.sdk.*— a separate distribution. An api-only environment
(e.g.lite-bootstrap[fastmcp]) therefore still crashed at the sdk import
even after the exporter guard above. A newis_opentelemetry_sdk_installed
flag gates the sdk imports, andcheck_dependencies()requires both the api
and the sdk. With all three fixes,lite-bootstrap[fastmcp]imports and runs
on free-threaded 3.14t.
These bugs are not ft-specific — they affect any environment with a partial
opentelemetry stack — but ft verification work is what surfaced them.
Backwards compatibility
Fully backward compatible with 1.2.3 at the API level. The one dependency
change is the orjson move to opt-in described above; everything that imports
or configures lite-bootstrap continues to work unchanged on a standard (GIL)
build once orjson is installed (directly, or via the [orjson] extra).
References
- Design bundle:
planning/changes/2026-07-18.01-free-threaded-python-support.md,
planning/changes/2026-07-19.01-fix-otel-api-sdk-conflation.md,
planning/changes/2026-07-19.02-otlp-http-exporter.md.
1.2.3
lite-bootstrap 1.2.3 — release pipeline on PyPI Trusted Publishing
No library changes. The package is identical to 1.2.2; this release exercises the new publish path end-to-end.
CI
- Releases now authenticate to PyPI via Trusted Publishing (OIDC) instead of a long-lived
PYPI_TOKENsecret.uv publishauto-detects the GitHub Actions id-token; the release job runs under apypienvironment that scopes the trusted publisher (#145).
Downstream
No action required. Nothing about the installed package changes.
1.2.2
lite-bootstrap 1.2.2 — bound Litestar Prometheus path cardinality by default
1.2.2 is a patch release framed as a bug fix, with one observable behavior change. It flips the Litestar Prometheus path-label default from raw URLs to the route template, closing an unbounded-cardinality footgun that grew process memory without limit on parameterized routes.
Bug fix
-
Litestar
prometheus_group_pathnow defaults toTrue(PR #144). Litestar's ownPrometheusConfigdefaultsgroup_path=False, so thepathmetric label recorded the raw request URL. Any route with path parameters then minted one time series per distinct value (/users/1,/users/2, …), growing the metric registry without bound — visible in production as steadily climbing memory. The new default binds the label to the route template (/users/{id}), so cardinality is bounded by the number of routes, not the number of distinct URLs.The new
LitestarConfig.prometheus_group_pathfield is merged as{"group_path": <field>, **prometheus_additional_params}, soprometheus_additional_params["group_path"]still overrides it without a keyword collision — the previously-documented workaround keeps working unchanged. FastAPI is unaffected:prometheus-fastapi-instrumentatoralready labels by route template.
Behavior change
- The Litestar
pathmetric label now holds the route template instead of the raw URL. Dashboards, alerts, or recording rules keyed on raw parameterized paths (path="/users/1") will no longer match; they should key on the template (path="/users/{id}"). To restore the old raw-path behavior per application, setprometheus_group_path=False(orprometheus_additional_params={"group_path": False}).
Backwards compatibility
Fully backward compatible with 1.2.1 at the API level — the new field is additive and defaulted. The only observable difference is the metric-label value described above. Configs already passing group_path via prometheus_additional_params are unaffected (the dict still wins).
References
- PR #144
- Upstream issue proposing the same default flip / a warning in Litestar: litestar-org/litestar#4891
1.2.1
lite-bootstrap 1.2.1 — lift the FastAPI 0.137 cap
1.2.1 is a patch release. No public-API or behavior changes. It removes the temporary fastapi<0.137 ceiling introduced in 1.1.1, now that the upstream prometheus-fastapi-instrumentator fix has shipped.
Dependency constraints
fastapi<0.137cap removed from thefastapiextra (the single declaration everyfastapi-*extra composes from). FastAPI 0.137 and 0.138 now resolve.prometheus-fastapi-instrumentatorfloor raised on thefastapi-metricsextra:>=6.1→>=8.0.1. The crash that motivated the cap required FastAPI ≥0.137 and instrumentator ≤8.0.0, so lifting the FastAPI ceiling is paired with a floor that guarantees the fixed instrumentator wherever metrics are installed.
The original cap was added in 1.1.1 because prometheus-fastapi-instrumentator read route.path unconditionally and crashed on FastAPI 0.137's internal _IncludedRouter route type. That is fixed upstream in instrumentator v8.0.1 (issue #370, closed 2026-06-22). lite-bootstrap's own offline-docs guard (the isinstance(route, Route) filter shipped in 1.1.1) remains in place.
Verified end-to-end against FastAPI 0.138.0 + prometheus-fastapi-instrumentator 8.0.2: full suite green at 100% coverage, including the offline-docs and metrics paths that previously crashed.
Backwards compatibility
Fully backward compatible with 1.2.0. No public API or behavior changed; this is purely a loosening of resolution constraints. Installs that were held at FastAPI ≤0.136 by the cap will now resolve forward to current FastAPI.
References
- Upstream fix: prometheus-fastapi-instrumentator v8.0.1, issue #370
- Original cap: 1.1.1 (PR #122)
1.2.0
lite-bootstrap 1.2.0 — deeper seams: structured-log payload, unified teardown guard, OTel config tidy-up
1.2.0 is a minor release. Backward compatible with 1.1.1, with one observable behavior change (a new double-attach warning on Litestar and FastStream). It lands the shippable results of an architecture-deepening sweep: the structlog→Sentry contract gets a single owner, the teardown-on-shutdown guard is unified across all frameworks, and an OpenTelemetry config field moves to where it belongs.
Features
-
StructuredLogPayloadowns the structlog→Sentry contract (PR #129). The rendered-log-line shape that the Sentry instrument used to sniff and re-parse inline now lives in one value object (lite_bootstrap.instruments.logging_factory.StructuredLogPayload), with its meta-key vocabulary exposed as the publicSTRUCTLOG_META_KEYS. This closes a silent-drift failure mode: previously, renaming or adding a structlog meta-key could quietly leak it into Sentry'scontexts.structlogor drop the enrichment, with no test to catch it. Sentry output is unchanged for existing setups; the enrichment is now pinned by a round-trip test. -
opentelemetry_excluded_urlsnow lives onOpenTelemetryConfig(PR #132). The field was OpenTelemetry's own setting but had been declared separately on each of the FastAPI, Litestar, and FastStream configs. It now lives onOpenTelemetryConfig, so it is available wherever OpenTelemetry is configured and is read with typed access internally. The automatic exclusion of the metrics path and (unless health-check spans are enabled) the health-check path from traces is unchanged and now covered by a regression test.
Behavior changes
- The double-attach teardown guard now applies to Litestar and FastStream (PR #130). Teardown-on-shutdown wiring is unified behind a single
BaseBootstrapper._attach_teardown_onceseam. The safeguard that warns and skips when a second bootstrapper is constructed against the same application — previously present only on FastAPI and FastMCP — now covers Litestar and FastStream as well. If your code constructs two bootstrappers against one LitestarAppConfigor FastStream app, you will now see aUserWarningand the second teardown is skipped, where this was previously silent. This is non-breaking: the warning is informational, the second attach is skipped (not errored), and the first bootstrapper's teardown still runs on shutdown. Construct one bootstrapper per application.
Backwards compatibility
Fully backward compatible with 1.1.1. No public API was removed.
IGNORED_STRUCTLOG_ATTRIBUTESis retained insentry_instrumentas a silent alias ofSTRUCTLOG_META_KEYS, so existing imports keep working.opentelemetry_excluded_urlsis still set exactly as before (e.g.FastAPIConfig(opentelemetry_excluded_urls=[...])) — it is now inherited rather than locally declared.FreeConfigadditionally accepts the field now (inert there, since Free has no HTTP surface).- The only observable difference is the new Litestar/FastStream double-attach warning described above.
Internal-only changes that do not affect the public API: FastMCP's double-attach detection moved from scanning the provider list to the shared attribute marker, and FastAPI's internal lifespan marker was renamed to the unified _lite_bootstrap_teardown_attached.
References
1.1.1
lite-bootstrap 1.1.1 — FastAPI 0.137 compatibility
1.1.1 is a patch release. No public-API or behavior changes. It restores compatibility with FastAPI 0.137 and pins a transitive incompatibility, nothing more.
Bug fixes
- Offline docs no longer crash on FastAPI 0.137's
_IncludedRouter(PR #122). FastAPI 0.137.0 added the internal_IncludedRouterroute type (aBaseRoutesubclass with no.path) toapp.router.routes.enable_offline_docsfiltered routes via an uncheckedtyping.cast(Route, route).path, which raisedAttributeError: '_IncludedRouter' object has no attribute 'path'whenever a router was included (e.g. by the health-checks instrument). The filter now matches only realRouteinstances (isinstance(route, Route) and route.path in …), leaving_IncludedRouterand other route types untouched. Correct on both old and new FastAPI.
Dependency constraints
fastapi<0.137cap on thefastapiextra (temporary).prometheus-fastapi-instrumentator(≤ 8.0.0) has the same unguardedroute.pathaccess and is not yet fixed upstream, so any lite-bootstrap install that pulls FastAPI 0.137 would break metrics. The cap lives on the basefastapiextra — the single declaration every otherfastapi-*extra composes from — so the whole FastAPI surface resolves to a version tested end-to-end with no skew between extras. Lift the cap once a fixed instrumentator ships.- Upstream issue: trallnag/prometheus-fastapi-instrumentator#370
Backwards compatibility
Fully backward compatible with 1.1.0. No public API changed; the FastAPI fix is purely defensive. The only observable difference is the new fastapi<0.137 resolution ceiling, which holds installs at a known-good FastAPI until the upstream metrics fix lands.
References
- PR: #122
- Upstream issue: trallnag/prometheus-fastapi-instrumentator#370
1.1.0
lite-bootstrap 1.1.0 — Lifecycle hardening, config validation, CI gate
1.1.0 is a minor release. No intentional public-API breakage. The two behavior changes that could affect existing code are fixes to genuine bugs and are called out in Behavior changes below.
This release closes a 26-finding bug-audit cycle (audits + retro live under planning/specs/2026-06-05-bug-audit-v2*.md). The changes split across four shipped PRs:
- #108 — Lifecycle & teardown correctness (10 findings)
- #109 — Config UX & security validation (6 findings)
- #110 — Hygiene + CI gate (4 findings)
- #111 — Generalized
TeardownErroraggregation + cascade tests + README lifecycle docs (3 deferred follow-ups)
Test suite grew from 153 → 194 (+27%) at 100% line coverage throughout. pip-audit now runs on every PR and weekly via cron; a new filterwarnings config catches accidental InstrumentSkippedWarning emissions.
New features
- Injectable
prometheus_collector_registryonFastStreamConfig. Pass an existingprometheus_client.CollectorRegistryto expose counters registered elsewhere through FastStream's/metricsendpoint. Defaults to a fresh per-instance registry — fully backward compatible. opentelemetry_excluded_urlsfield onFastStreamConfig. Was agetattrfallback before; now a discoverable, IDE-completable config field matchingFastAPIConfigandLitestarConfig.SentryInstrument.teardown(). Callssentry_sdk.flush(timeout=2)thensentry_sdk.init()(no args) to reset the SDK to a no-op state. Previously the SDK stayed globally configured after bootstrapper teardown, leaking state across process-local tests.FastStreamLoggingInstrument.teardown(). Restoresbroker.config.logger.params_storageto its pre-bootstrap value. The bootstrap mutated broker state; teardown didn't reverse it.
Bug fixes
Lifecycle & teardown (PR #108)
- OpenTelemetry teardown now flushes spans and shuts down the tracer provider (LOG-1, LOG-2).
bootstrap()stored theTracerProvideronly as a local;teardown()couldn't reach it to callshutdown(). Buffered spans inBatchSpanProcessorwere dropped on graceful shutdown. Teardown also restores the two OTel-namespace stdlib loggers (opentelemetry.instrumentation.instrumentor,opentelemetry.trace) to their pre-bootstrapdisabledstate. LoggingInstrument.teardown()runs all cleanup steps even on partial failure (LOG-3). A raise from anyhandler.close()previously left remaining handlers attached, skipped the root-level reset, and never calledclose_handlers()on the memory factory. Now wrapped intry/finallywith per-handler error capture; all collected errors raise together viaTeardownError(errors).LitestarOpenTelemetryInstrumentationMiddlewarecache evicts dead refs (LOG-6). The olddict[int, ASGIApp]keyed byid()never evicted, holding wrapperOpenTelemetryMiddlewareinstances alive after Litestar droppednext_app. Replaced withweakref.WeakKeyDictionary; non-weakrefable apps fall through to the un-cached path.- Double-bootstrap on the same application is now detected and warned (LOG-7, LOG-8). Constructing two
FastMcpBootstrappers around the sameFastMCP(or twoFastAPIBootstrappers around the sameFastAPI) previously stacked teardown hooks. The second construction now emits aUserWarning, skips the re-attachment, and tells you the second bootstrapper'steardown()won't fire on ASGI shutdown. - Generalized
TeardownErroraggregation across all instruments (PR #111).OpenTelemetryInstrument,FastStreamLoggingInstrument, andSentryInstrumentnow run their full cleanup sequence even if an early step raises — aggregating errors into a singleTeardownErroror lettingsuper().teardown()run viatry/finally. Previously a misbehaving instrumentor / broker / Sentry flush silently skipped subsequent cleanup. - Pyroscope's precondition check survives
python -O(LOG-5/SEC-4). Replaced twoassertstatements (in_narrow_appandPyroscopeInstrument.bootstrap) with explicitraise TypeError(...)andraise RuntimeError(...).python -Ostrips asserts; the invariants now hold under all optimization levels. Closes the twobanditB101 findings.
Config & security (PR #109)
FastAPIConfigno longer stomps user-supplied app'stitle/debug/version(UX-1). Previously the three assignments ran unconditionally; a user passingFastAPI(title="My API", version="3.0.0")would silently have those clobbered by lite-bootstrap defaults. Now the assignments only run in theUnsetTypebranch (when lite-bootstrap constructed the app). See Behavior changes below.enable_offline_docsvalidatesrequest.scope["root_path"]against the existing path allowlist (SEC-1). Invalid root paths (e.g., HTML-injection payloads via a malicious upstream proxy'sX-Forwarded-Prefix) now fall back to empty and emit a warning instead of being reflected into Swagger/Redoc HTML script tags. Threat model: not an issue in default ASGI deployments; only matters ifProxyHeadersMiddlewaretrusts upstream prefix headers.- OpenTelemetry endpoint with
insecure=Trueemits a warning for non-local hosts (SEC-2). NewOpenTelemetryConfig.__post_init__parses the endpoint (handles bothhost:portandscheme://host:portforms, including IPv6 brackets andunix://) and warns when traces would ship unencrypted to a non-localhost/127.0.0.1/::1/unix://target. CorsConfigrejects unsafe wildcard + credentials combos at construction (SEC-3).cors_allowed_credentials=Truecombined withcors_allowed_origins=["*"](or a permissive regex like".*"/".+") is the canonical CORS misconfiguration — browsers reject the response. Now raisesConfigurationErrorimmediately instead of silently building a non-functional CORS layer. See Behavior changes below.
Hygiene & process (PR #110)
- Missing-dependency events now log via stdlib
loggingin addition towarnings.warn(UX-4). Users running underpython -W ignoreorPYTHONWARNINGS=ignorepreviously saw nothing when a configured instrument's optional dep was missing. The newlogger.warningline onlite_bootstrap.bootstrappers.baseis unaffected by warning filters.
Behavior changes
Two changes could affect existing code in ways the previous release wouldn't have. Both fix real bugs; surfaced here so you can audit.
-
User-supplied
FastAPIinstance retains its owntitle,debug,version. If you were relying on lite-bootstrap to overwrite these fromservice_name/service_debug/service_versionafter handing it a pre-builtFastAPI(), you'll now see your original values. Migration: set these on yourFastAPI()directly, or useFastAPIConfig(application_kwargs={...})to have lite-bootstrap construct the app. -
CorsConfig(cors_allowed_origins=["*"], cors_allowed_credentials=True)now raisesConfigurationError. This combo was never functional — browsers reject responses withAccess-Control-Allow-Credentials: trueandAccess-Control-Allow-Origin: *. If your code constructed this combo and worked anyway (because the credentials header was silently dropped by FastAPI's CORSMiddleware), construction now fails with a clear message. Migration: enumerate allowed origins explicitly, or setcors_allowed_credentials=False.
New constraints documented
Three lifecycle constraints surfaced by the audit are now documented in README.md and CLAUDE.md:
- One bootstrapper per application instance. Second construction emits a warning and skips re-attachment (see LOG-7/LOG-8 above).
- One
OpenTelemetryInstrumentper process. The OTel SDK enforcesset_tracer_provideras set-once via_TRACER_PROVIDER_SET_ONCE.do_once(...)(verified againstopentelemetry/trace/__init__.py:548-556);teardown()cannot reset the global pointer. __post_init__cascade invariant. Every config-class__post_init__must callsuper().__post_init__().BaseConfigships a no-op as the chain terminator.FastAPIConfiguses the explicitsuper(FastAPIConfig, self).__post_init__()form because@dataclass(slots=True)breaks baresuper().
CI changes
security-audit.ymlworkflow added.pip-auditruns on every PR and weekly via cron against the lockfile (uv export --all-extras --no-hashes). The default-branch run is purely informational; PRs that introduce CVEs will block until resolved.InstrumentSkippedWarningescalated to error in tests. Any unexpected emission outside apytest.warns(...)block now fails the test (registered viapytest_configure()intests/conftest.py; can't live inpyproject.tomlbecause that import order breaks pytest-cov tracing).
Backwards compatibility
Aside from the two Behavior changes called out above, every public API behaves identically. New fields default to their old behavior (prometheus_collector_registry=None → fresh registry as before; opentelemetry_excluded_urls=[] → empty set as before). New warnings/validators trigger only on configurations that were already broken or risky.
The 26-fix list with full file:line references and rationale is in:
planning/specs/2026-06-05-bug-audit-v2.md— the auditplanning/specs/2026-06-05-bug-audit-v2-sequencing.md— the 3-PR breakdownplanning/specs/2026-06-05-bug-audit-v2-retro.md— what the cycle taught us