Skip to content

Releases: modern-python/lite-bootstrap

1.4.0

Choose a tag to compare

@github-actions github-actions released this 10 Aug 18:29
1.4.0
2a1066d

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
    the Authorization / X-API-KEY headers and the session cookie; 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
    LitestarBootstrapper uses — passes every AppConfig field explicitly, so the 10 MB
    request_max_body_size default that Litestar(...) applies never reached the built
    app. Every handler taking a request body returned
    500: 'request_max_body_size' set to 'Empty' on all layers unless the caller set the
    field themselves. The bootstrapper now fills it when the config leaves it unset; a
    caller's own value, including an explicit None for no limit, is untouched. Reported
    upstream as litestar#4296.
  • Structlog output follows a redirected stdout. _MemoryLoggerFactoryConfig.log_stream
    bound sys.stdout once, at import time, so a process that replaced sys.stdout after
    importing lite_bootstrap but 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 litestar extra's floor moved from >=2.9 to >=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.9 was 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, but bootstrap() 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 raises ConfigurationError naming 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 by teardown() — 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.md
  • planning/changes/2026-08-10.02-log-stream-bind-at-bootstrap.md
  • planning/changes/2026-08-10.03-litestar-request-max-body-size.md
  • planning/changes/2026-08-10.04-double-bootstrap-guard.md

1.3.2

Choose a tag to compare

@github-actions github-actions released this 19 Jul 15:18
56ba454

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_bootstrap no longer crashes under lite-bootstrap[litestar]
    without prometheus-client.
    litestar_bootstrapper imported
    litestar.plugins.prometheus (which requires prometheus_client) under the
    is_litestar_installed guard, but the litestar extra does not install
    prometheus-client — only litestar-metrics does. So pip install lite-bootstrap[litestar] followed by import lite_bootstrap raised 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

Choose a tag to compare

@github-actions github-actions released this 19 Jul 14:13
65845e0

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_bootstrap no longer crashes on a missing
    typing_extensions.
    Core uses typing_extensions at runtime (a Self
    return annotation, and a TypedDict FastAPI response model that pydantic
    requires be a typing_extensions.TypedDict on Python < 3.12) but never
    declared it, so pip install lite-bootstrap with no extras followed by
    import lite_bootstrap raised ModuleNotFoundError: No module named 'typing_extensions'. typing-extensions is 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 needs typing_extensions while Python 3.10/3.11 are supported. The
    bug was pre-existing (bare 1.2.3 failed the same way); every install with any
    extra masked it, because extras pull typing_extensions in 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

Choose a tag to compare

@github-actions github-actions released this 19 Jul 13:46
f84ffbe

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-bootstrap is pure
    Python; the only thing that ever blocked it on a free-threaded interpreter
    was the mandatory orjson dependency (below). Core, logging, sentry,
    fastapi, and faststream (plus their -sentry/-logging/-metrics
    combos) now install and run on both 3.13t and 3.14t. litestar (+
    litestar-metrics) and fastmcp (+ fastmcp-metrics) land on 3.14t
    only
    msgspec (litestar) and cffi (fastmcp, via cryptography) both
    gate free-threaded support to Python 3.14+ and fail to build from source on
    3.13t. The gRPC otl exporter (grpcio has no ft wheels — use the new
    otl-http extra instead) and pyroscope (pyroscope-io is abi3-only and
    unmaintained) remain unavailable on free-threaded builds pending upstream
    fixes. See
    architecture/free-threading.md for
    the full support matrix and planning/deferred.md for the
    ecosystem blockers.

  • OTLP-http exporter: new opentelemetry_exporter_protocol config (grpc
    default | http) and an otl-http extra (no grpcio) so OTLP trace export
    works on free-threaded Python. otl now pulls
    opentelemetry-exporter-otlp-proto-grpc directly (was the opentelemetry-exporter-otlp
    meta); grpc behavior is unchanged.

  • orjson is 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 stdlib json accelerator when orjson
    is absent (byte-identical output for JSON-native values; ~2-5x slower;
    non-JSON-native types in log extra, 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 on import lite_bootstrap
    pulling orjson in transitively, depend on orjson directly.

Bug fixes

  • import_checker's dotted find_spec checks no longer crash on an
    incomplete namespace.
    find_spec imports a dotted name's parent package
    first, so a present-but-incomplete opentelemetry install (e.g.
    opentelemetry-api without opentelemetry-instrumentation) previously
    raised ModuleNotFoundError instead of returning False, crashing import lite_bootstrap. Affected dotted checks now go through a
    ModuleNotFoundError-safe helper.
  • The gRPC OTLP exporter is no longer imported unconditionally.
    opentelemetry_instrument.py used to import
    opentelemetry.exporter.otlp.proto.grpc.trace_exporter whenever bare
    opentelemetry-api resolved, crashing import lite_bootstrap in any
    environment with opentelemetry-api present but the exporter package
    absent (e.g. lite-bootstrap[fastmcp], which pulls in bare
    opentelemetry-api transitively). The exporter import — and its use in
    bootstrap() — now sit behind their own is_otlp_grpc_exporter_installed
    guard. When opentelemetry_endpoint is set but the selected exporter package
    is absent, bootstrap() emits an InstrumentDependencyMissingWarning naming
    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 just opentelemetry-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 new is_opentelemetry_sdk_installed
    flag gates the sdk imports, and check_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

Choose a tag to compare

@github-actions github-actions released this 01 Jul 20:44
7369e10

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_TOKEN secret. uv publish auto-detects the GitHub Actions id-token; the release job runs under a pypi environment that scopes the trusted publisher (#145).

Downstream

No action required. Nothing about the installed package changes.

1.2.2

Choose a tag to compare

@github-actions github-actions released this 01 Jul 18:30
8c5b1cd

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_path now defaults to True (PR #144). Litestar's own PrometheusConfig defaults group_path=False, so the path metric 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_path field is merged as {"group_path": <field>, **prometheus_additional_params}, so prometheus_additional_params["group_path"] still overrides it without a keyword collision — the previously-documented workaround keeps working unchanged. FastAPI is unaffected: prometheus-fastapi-instrumentator already labels by route template.

Behavior change

  • The Litestar path metric 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, set prometheus_group_path=False (or prometheus_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

1.2.1

Choose a tag to compare

@lesnik512 lesnik512 released this 24 Jun 09:47
3bb26ad

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.137 cap removed from the fastapi extra (the single declaration every fastapi-* extra composes from). FastAPI 0.137 and 0.138 now resolve.
  • prometheus-fastapi-instrumentator floor raised on the fastapi-metrics extra: >=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

1.2.0

Choose a tag to compare

@lesnik512 lesnik512 released this 24 Jun 09:34
1b0860e

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

  • StructuredLogPayload owns 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 public STRUCTLOG_META_KEYS. This closes a silent-drift failure mode: previously, renaming or adding a structlog meta-key could quietly leak it into Sentry's contexts.structlog or 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_urls now lives on OpenTelemetryConfig (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 on OpenTelemetryConfig, 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_once seam. 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 Litestar AppConfig or FastStream app, you will now see a UserWarning and 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_ATTRIBUTES is retained in sentry_instrument as a silent alias of STRUCTLOG_META_KEYS, so existing imports keep working.
  • opentelemetry_excluded_urls is still set exactly as before (e.g. FastAPIConfig(opentelemetry_excluded_urls=[...])) — it is now inherited rather than locally declared. FreeConfig additionally 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

  • PRs: #129, #130, #132
  • Design bundles and the decisions behind the rejected/limited options: planning/changes/2026-06-23.01-structured-log-payload/, planning/changes/2026-06-24.01-unify-teardown-attach/, planning/changes/2026-06-24.02-otel-excluded-urls-home/, and planning/decisions/.

1.1.1

Choose a tag to compare

@lesnik512 lesnik512 released this 14 Jun 19:51
3d2bdc0

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 _IncludedRouter route type (a BaseRoute subclass with no .path) to app.router.routes. enable_offline_docs filtered routes via an unchecked typing.cast(Route, route).path, which raised AttributeError: '_IncludedRouter' object has no attribute 'path' whenever a router was included (e.g. by the health-checks instrument). The filter now matches only real Route instances (isinstance(route, Route) and route.path in …), leaving _IncludedRouter and other route types untouched. Correct on both old and new FastAPI.

Dependency constraints

  • fastapi<0.137 cap on the fastapi extra (temporary). prometheus-fastapi-instrumentator (≤ 8.0.0) has the same unguarded route.path access and is not yet fixed upstream, so any lite-bootstrap install that pulls FastAPI 0.137 would break metrics. The cap lives on the base fastapi extra — the single declaration every other fastapi-* 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.

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

1.1.0

Choose a tag to compare

@lesnik512 lesnik512 released this 05 Jun 19:00
558b1d5

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 TeardownError aggregation + 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_registry on FastStreamConfig. Pass an existing prometheus_client.CollectorRegistry to expose counters registered elsewhere through FastStream's /metrics endpoint. Defaults to a fresh per-instance registry — fully backward compatible.
  • opentelemetry_excluded_urls field on FastStreamConfig. Was a getattr fallback before; now a discoverable, IDE-completable config field matching FastAPIConfig and LitestarConfig.
  • SentryInstrument.teardown(). Calls sentry_sdk.flush(timeout=2) then sentry_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(). Restores broker.config.logger.params_storage to 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 the TracerProvider only as a local; teardown() couldn't reach it to call shutdown(). Buffered spans in BatchSpanProcessor were dropped on graceful shutdown. Teardown also restores the two OTel-namespace stdlib loggers (opentelemetry.instrumentation.instrumentor, opentelemetry.trace) to their pre-bootstrap disabled state.
  • LoggingInstrument.teardown() runs all cleanup steps even on partial failure (LOG-3). A raise from any handler.close() previously left remaining handlers attached, skipped the root-level reset, and never called close_handlers() on the memory factory. Now wrapped in try/finally with per-handler error capture; all collected errors raise together via TeardownError(errors).
  • LitestarOpenTelemetryInstrumentationMiddleware cache evicts dead refs (LOG-6). The old dict[int, ASGIApp] keyed by id() never evicted, holding wrapper OpenTelemetryMiddleware instances alive after Litestar dropped next_app. Replaced with weakref.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 same FastMCP (or two FastAPIBootstrappers around the same FastAPI) previously stacked teardown hooks. The second construction now emits a UserWarning, skips the re-attachment, and tells you the second bootstrapper's teardown() won't fire on ASGI shutdown.
  • Generalized TeardownError aggregation across all instruments (PR #111). OpenTelemetryInstrument, FastStreamLoggingInstrument, and SentryInstrument now run their full cleanup sequence even if an early step raises — aggregating errors into a single TeardownError or letting super().teardown() run via try/finally. Previously a misbehaving instrumentor / broker / Sentry flush silently skipped subsequent cleanup.
  • Pyroscope's precondition check survives python -O (LOG-5/SEC-4). Replaced two assert statements (in _narrow_app and PyroscopeInstrument.bootstrap) with explicit raise TypeError(...) and raise RuntimeError(...). python -O strips asserts; the invariants now hold under all optimization levels. Closes the two bandit B101 findings.

Config & security (PR #109)

  • FastAPIConfig no longer stomps user-supplied app's title/debug/version (UX-1). Previously the three assignments ran unconditionally; a user passing FastAPI(title="My API", version="3.0.0") would silently have those clobbered by lite-bootstrap defaults. Now the assignments only run in the UnsetType branch (when lite-bootstrap constructed the app). See Behavior changes below.
  • enable_offline_docs validates request.scope["root_path"] against the existing path allowlist (SEC-1). Invalid root paths (e.g., HTML-injection payloads via a malicious upstream proxy's X-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 if ProxyHeadersMiddleware trusts upstream prefix headers.
  • OpenTelemetry endpoint with insecure=True emits a warning for non-local hosts (SEC-2). New OpenTelemetryConfig.__post_init__ parses the endpoint (handles both host:port and scheme://host:port forms, including IPv6 brackets and unix://) and warns when traces would ship unencrypted to a non-localhost/127.0.0.1/::1/unix:// target.
  • CorsConfig rejects unsafe wildcard + credentials combos at construction (SEC-3). cors_allowed_credentials=True combined with cors_allowed_origins=["*"] (or a permissive regex like ".*"/".+") is the canonical CORS misconfiguration — browsers reject the response. Now raises ConfigurationError immediately instead of silently building a non-functional CORS layer. See Behavior changes below.

Hygiene & process (PR #110)

  • Missing-dependency events now log via stdlib logging in addition to warnings.warn (UX-4). Users running under python -W ignore or PYTHONWARNINGS=ignore previously saw nothing when a configured instrument's optional dep was missing. The new logger.warning line on lite_bootstrap.bootstrappers.base is 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.

  1. User-supplied FastAPI instance retains its own title, debug, version. If you were relying on lite-bootstrap to overwrite these from service_name/service_debug/service_version after handing it a pre-built FastAPI(), you'll now see your original values. Migration: set these on your FastAPI() directly, or use FastAPIConfig(application_kwargs={...}) to have lite-bootstrap construct the app.

  2. CorsConfig(cors_allowed_origins=["*"], cors_allowed_credentials=True) now raises ConfigurationError. This combo was never functional — browsers reject responses with Access-Control-Allow-Credentials: true and Access-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 set cors_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 OpenTelemetryInstrument per process. The OTel SDK enforces set_tracer_provider as set-once via _TRACER_PROVIDER_SET_ONCE.do_once(...) (verified against opentelemetry/trace/__init__.py:548-556); teardown() cannot reset the global pointer.
  • __post_init__ cascade invariant. Every config-class __post_init__ must call super().__post_init__(). BaseConfig ships a no-op as the chain terminator. FastAPIConfig uses the explicit super(FastAPIConfig, self).__post_init__() form because @dataclass(slots=True) breaks bare super().

CI changes

  • security-audit.yml workflow added. pip-audit runs 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.
  • InstrumentSkippedWarning escalated to error in tests. Any unexpected emission outside a pytest.warns(...) block now fails the test (registered via pytest_configure() in tests/conftest.py; can't live in pyproject.toml because 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 audit
  • planning/specs/2026-06-05-bug-audit-v2-sequencing.md — the 3-PR breakdown
  • planning/specs/2026-06-05-bug-audit-v2-retro.md — what the cycle taught us

References

Read more