Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
Added¶
Changed¶
Deprecated¶
Removed¶
Fixed¶
Security¶
3.1.0 - 2026-06-01¶
Added¶
NEW password hashing helpers in
kstlib.secure(hash_password,verify_password,needs_rehash), backed by Argon2id via the optionalargon2-cffidependency (pip install kstlib[passwords]). Cost parameters follow thekwargs > kstlib config > defaultscascade with OWASP-aligned defaults (RFC 9106 low-memory:time_cost=3,memory_cost=65536KiB,parallelism=4). Values resolved below the OWASP minimum baseline (19 MiB /t=2) are clamped up and aWARNING [SECURITY]is logged. Passwords longer thanMAX_PASSWORD_LENGTH(4096 bytes) are rejected as an anti-DoS guard, and passwords and hashes are never written to the logs.NEW exceptions
PasswordErrorandInvalidPasswordHashError(kstlib.secure), both underKstlibError.verify_passwordreturnsFalseon a wrong password and raisesInvalidPasswordHashErroronly when the stored hash is corrupt or not a valid Argon2 hash.
3.0.0 - 2026-05-22¶
BREAKING changes :
WebSocketManager.close()semantic has changed (see Changed section below for full details and migration hint).
Added¶
NEW
WebSocketManager.is_recoverableproperty for external watchdog consumers. Returns True when the connection is dead but NOT intentionally shutdown (is_dead and not is_shutdown). Helps distinguish accidental disconnects (gracefulclose()end-of-scope or reactivekill()) from intentional terminal shutdowns (force_close()orshutdown()). Watchdog consumers should use this property rather thanis_deadto avoid restarting an intentional shutdown loop.Logging caller context in
debugandtracepreset file format (src/kstlib/kstlib.conf.yml) : log records emitted with these presets now include[<filename>:<lineno> <funcName>]for diagnostic visibility on post-mortem incident investigation. Console output remains unchanged (Richshow_path=truealready provides visual caller location). Other presets (dev,prod,trace_mail) are not affected so clean production logs are preserved.MinifyRequiresRawError(RapiError)exception (kstlib.rapi.exceptions) : exported viakstlib.rapi.__all__. Surfaces the constraint that compact JSON (minify) is incompatible with Rich console rendering, so callers must request raw output too. The CLI emits the same constraint viatyper.BadParameterfor native Typer error formatting ; this exception is reserved for callers that need to raise it programmatically (for example, future pipeline steps exposing equivalentminifyandoutsemantics). Default message exposed as theDEFAULT_MESSAGEclass attribute and contains the canonical hint pointing to--raw --minify.AuthExpiredError(AuthError)exception (kstlib.auth.errors) : exported viakstlib.auth.__all__. Distinct from the existingTokenExpiredError(TokenError). The two cover different lifecycle points :AuthExpiredErroris raised by consumers (typicallykstlib.rapi.client) on receiving an HTTP 401 at runtime, signalling that a token which was valid at send time has been expired or invalidated by the identity provider during the session ;TokenExpiredErroris raised bykstlib.authwhen a loaded token is detected as already expired before the request is sent (client-side pre-flight check). The new class carries two optional attributestoken_source(label such as'~/.sas/credentials.json','env:KSTLIB_TOKEN','sops:secrets/api.sops.json') andsuggested_action(re-auth hint such as'Run: sas-admin auth login -u <user>'), mirrored into thedetailsdict for introspection. Used by upcoming runtime 401 detection inkstlib.rapi.client.
Changed¶
WebSocketManager.close()semantic now graceful (BREAKING v3.0.0) : previously an alias forforce_close()(State=CLOSED terminal, cannot reconnect). Now closes gracefully : State=DISCONNECTED non-terminal, reconnection remains possible via explicitconnect()or auto-reconnect. Restores Pythonasync withconvention so__aexit__no longer prevents reconnection. The_auto_reconnectflag is preserved. The method is idempotent (no-op if state is already CLOSED or DISCONNECTED). Migration : useforce_close()explicitly for emergency stop, orshutdown()for intentional SIGINT-like shutdown.WebSocketManager.force_close()now marksis_shutdown=True: the intentional emergency stop now sets the shutdown event in addition to flippingauto_reconnect=Falseand moving to State=CLOSED. Helps watchdog consumers distinguish reactive disconnect (viakill(),is_shutdown=False) from intentional terminal shutdown (viaforce_close()orshutdown(),is_shutdown=True). The newis_recoverableproperty (see Added section above) leverages this distinction.CLI
rapi call --minifywithout--rawnow fails fast : the combination was previously silent (Rich console rendering reformats output regardless of compact JSON flags, so--minifyalone had no visible effect). The CLI now rejects the combination at command entry viatyper.BadParameterwith a hint message pointing to--raw --minify. The validation runs before any RapiClient construction or endpoint resolution so no I/O occurs on a rejected invocation.rapi.clientnow detects access token expiration on HTTP 401RapiClient.call(and its async counterpart) now inspects every parsed response and raisesAuthExpiredErrorwhen the response indicates token expiration (heuristic : body containsexpired,invalid_token, ortoken expiredcase-insensitive, orWWW-Authenticateheader containsinvalid_tokenper RFC 6750). The exception carries a sanitizedtoken_sourcelabel and a contextualsuggested_actionhint derived from the credential source (file path, env var, SOPS path, or provider name) without ever exposing the secret material itself. Backward compat preserved : HTTP 401 responses that lack expiration markers continue to surface asRapiResponse(ok=False). A taggedWARNING [SECURITY]log entry is emitted before the raise (status code, content type, credential source label only ; never the response body or the token).
rapi.clientnow also emits a user-facingERRORlog with the re-auth hint before raisingAuthExpiredError. TheWARNING [SECURITY]audit entry is preserved untouched ; the newERRORline targets human operators consuming kstlib as a library (the actionable hint stays visible even if the exception is swallowed or generically caught upstream). Same sanitization contract applies : the message references the credential source label (file path / env var name / SOPS path / provider name) but never the token value, the response body, or theAuthorizationheader.CLI
rapi call:AuthExpiredErrornow triggers exit code 4 (distinct from the generic exit code 1 used for other errors). The error message displays the actionable re-authentication hint on a dedicatedHint:line alongside the token source on aSource:line. Shell scripts wrappingkstlib rapican now detect token expiration specifically and trigger an automated re-login flow without having to parse the message body.
Deprecated¶
Removed¶
Fixed¶
fix-rapi-minify-requires-raw: the--minifyCLI flag was silently ineffective without--rawbecause Rich console rendering reformats output regardless of compact JSON flags. Add_validate_output_flags()rejection at command entry with an explicit hint instead of swallowing the user intent.fix-rapi-auth-expired-error-detection:RapiClientno longer swallows expired SAS Viya (or any OAuth/OIDC) tokens behind a genericRapiResponse(ok=False)exit. The detection helper_check_auth_expired()invoked inside both_execute_with_retryand_execute_with_retry_asyncsurfacesAuthExpiredErrorwith a contextual re-authentication hint (sas-admin auth loginfor SAS Viya credentials files, env var refresh hint for env-based credentials, SOPS file path hint for SOPS-backed credentials,kstlib auth loginfor provider-backed credentials). The CLI exit code mapping to a distinct value forAuthExpiredErrorships in a follow-up commit.Watchdog timeout test determinism :
tests/resilience/test_watchdog.py::test_raise_on_timeoutrelied on a realtime.sleep(1.2)plus the background monitor thread, making it timing-sensitive and flaky under runtime load (notably on Python 3.14). Rewritten to mocktime.monotonicand drive the timeout check synchronously, making the test deterministic and independent of runtime speed.SecretErrorandConfigExportErrornow inherit fromKstlibError: both classes previously extended onlyRuntimeError, so they escaped the library-wideexcept KstlibErrorcatch-all honored by every other kstlib exception.SecretError(kstlib.secrets, base ofSecretNotFoundErrorandSecretDecryptionError) now extends(KstlibError, RuntimeError), andConfigExportError(kstlib.config) now extends(ConfigError, RuntimeError).RuntimeErroris retained in both cases, so existingexcept RuntimeErrorhandlers andisinstancechecks are unaffected : the change only widens catchability.
Security¶
idnalower bound raised to>=3.15(transitive viahttpx) : addresses CVE-2026-45409, a quadratic-time denial of service on oversized input that bypassed the CVE-2024-3651 mitigation. Fixed upstream in idna 3.14 ; the floor is set to 3.15 (the Dependabot target, which also adds an early DNS-length cap on labels).authliblower bound raised to>=1.6.12: upstream fix for an open redirect to an unvalidatedredirect_urionInvalidScopeErrorin the OpenID implicit and hybrid grants (authlib 1.6.12). No CVE assigned upstream. The locked and tested authlib version is 1.7.2 ; authlib 1.7.x delegates JOSE operations tojoserfc, a new transitive dependency.
2.7.1 - 2026-05-13¶
Fixed¶
Throttle adversarial tests determinism :
tests/mail/test_throttle_adversarial.pywas sensitive to runtime speed (logging overhead, CPU contention, accumulated test runtime), causing off-by-one assertion failures on slower runs (regression-prone on Python 3.10-3.13). Add autouse fixture_frozen_clockthat monkeypatchestime.monotonicto0.0for all tests in the file, making tests deterministic and independent of runtime speed (no token refill during the test window). Harden two ceiling assertions now made strict by the frozen clock:test_recursion_via_notify_is_throttled:<= 5->== 5test_combined_stress_does_not_exceed_rate_per_window:<= 6->== 5
Cross-platform LF line endings enforced via
.gitattributes: expliciteol=lfon*.py,*.pyi,*.toml,*.yml,*.yaml,*.json,*.ini,*.cfg,*.conf,*.md,*.rst,*.txtand onLICENSE,CHANGELOG,READMEdocumentation files. Without this, Windows checkout produced CRLF in worktree whileruff formatenforces LF (pyproject.tomlline-ending = "lf"), causing systematicruff format --checkfailures on dev environments.uv.lockline endings enforced as LF : new# --- Lockfiles ---section in.gitattributesaddsuv.lock text eol=lf. uv writes LF natively butcore.autocrlf=trueon Windows would re-introduce CRLF at checkout, creating churn. Explicit target marks the lockfile’s criticality (source of truth for resolved dep versions)..coveragercuntracked from repository : the file was previously committed but is generated locally for test coverage measurement. Removed from git tracking and added to.gitignore.Pre-commit hook documentation hardening (Windows OneDrive users) :
CONTRIBUTING.mdadds an optional Windows hardening section recommending to host the virtualenv outside OneDrive sync via theUV_PROJECT_ENVIRONMENTenvironment variable (session isolation: defined explicitly per terminal session, never persisted globally). Symlinks are NOT recommended because OneDrive (and most folder-sync tools) traverse symbolic links and sync the target content anyway, defeating the purpose.
Security¶
deps: bump urllib3 2.6.3 -> 2.7.0 (CVE-2026-44431, CVE-2026-44432) - Two HIGH severity vulnerabilities patched by urllib3 2.7.0:
CVE-2026-44431 :
ProxyManager.connection_from_urldid not strip sensitive headers (Retry.remove_headers_on_redirect) on cross-host redirect.CVE-2026-44432 : decompression-bomb safeguards bypass on
read(amt=N)with Brotli compression +drain_conn()after partial read (CWE-409, client resource DoS).
Lockfile resolution stayed conservative (urllib3 only, zero collateral).
2.7.0 - 2026-05-09¶
Added¶
Mail throttle (anti-spam kill switch) :
kstlib.mail.MailThrottleenforces a token-bucket rate limit on everyMailBuilder.send(), including indirect calls via the@mail.notifydecorator. Stops runaway batch loops, recursive sends, exception-handler floods, and concurrent thread/asyncio races at their first overrun. Built on top of the existingkstlib.resilience.RateLimiter(Token Bucket, thread-safe and asyncio-safe).Configuration cascade, highest priority first :
Per-builder kwarg
MailBuilder(throttle=False)(disable),MailBuilder(throttle={"rate": 100, "per": 3600.0})(custom).mail.presets.<name>.throttle.<key>(preset-level YAML override).mail.throttle.<key>(mail-wide YAML default).Code defaults :
enabled=true,rate=20,per=60.0,on_exceed=raise.
Each key cascades independently. Two policies are supported on bucket empty :
raise(emitsWARNING [SECURITY]and raisesMailThrottledError) andwarn(emitsWARNING [SECURITY]and drops the send silently). Modedropis intentionally rejected at init : a security event must never be silent (kstlib logging convention).A singleton
MailThrottleis shared across all builders that use the same preset, including_snapshot()copies taken by@mail.notify, preventing bypass via creating many builder instances. The registry persists acrosskstlib.config.clear_config()calls : the throttle is operational, not a preference.Hard limits enforced at init (
ratein[1, 1000],perin[1.0, 86400.0]) reuse the existing alerts hard-limit constants. YAML keys outside{enabled, rate, per, on_exceed}are rejected (anti-typo). Invalid types and out-of-bounds values raiseMailConfigurationErrorat builder init, not at send time.MailThrottledErrorexception : raised byMailBuilder.send()in moderaisewhen the throttle bucket is empty. Inherits fromMailError. Carries the throttle parameters in the message to help the caller decide on a backoff strategy.mail.throttleYAML section : new section insrc/kstlib/kstlib.conf.ymlwith documented defaults and an inline example showing per-preset override.Logging instrumentation for mail throttle : every
MailBuilder.__init__emits aDEBUGlog on thekstlib.mail.throttlelogger reporting the resolvedrate,per,on_exceed, source level (preset/mail/default) and preset name. Every blocked send emits exactly oneWARNING [SECURITY]log line (no batching). The log never includes the message body, sender, recipients, or attachments : only a sanitized subject (truncated to 80 chars, null-byte filtered) is included.Sphinx documentation : new section “Throttle (anti-spam protection)” in
docs/source/features/mail/index.md(labelmail-throttle) covering the four real-world spam scenarios, the cascade resolution, the two on_exceed modes, the singleton-per-preset contract, the hard limits, and recommendations for production usage. Cross-link added from the@mail.notify()decorator section.
Changed¶
Mail sends are now throttled by default at 20 mails per 60 seconds, raising
MailThrottledErroron overrun. This is a behavior change versus 2.6.0 where mail sends were unbounded.To restore the previous unbounded behavior on a single builder, pass
MailBuilder(throttle=False). To restore globally, setmail.throttle.enabled: falseinkstlib.conf.yml. Both are recommended only for tests, scripts that send a single mail and exit, or specific business cases where the upstream system already enforces a rate limit.See
docs/source/features/mail/index.mdsection “Throttle” for cascade tuning and operational recommendations.
Deprecated¶
Removed¶
Fixed¶
Security¶
Anti-spam kill switch on mail sends : closes a real robustness gap where a buggy
forloop,@mail.notifyon a hot function, unbounded recursion, or exception handler that mailed could blacklist an SMTP relay’s source IP, exhaust an external provider quota, or saturate a destination inbox. The throttle bounds the blast radius toratemails perperseconds and emitsWARNING [SECURITY]logs on every blocked send so the event is observable in operational dashboards.
2.6.0 - 2026-05-01¶
Added¶
--log-modulefactorized syntax : the CLI flag now supports three syntaxes for module-level logging configuration :--log-module kstlib.rapi.config=TRACE(existing, fully-qualified)--log-module rapi.config=TRACE(NEW :kstlib.auto-prepended)--log-module DEBUG=foo.bar,baz.qux(NEW : reverse mode, level groups module list)
Mode is decided on the LEFT side : a known level token (case- insensitive, in
TRACE,DEBUG,INFO,SUCCESS,WARNING,ERROR,CRITICAL) triggers reverse mode, anything else is treated as a module name. Levels are case-insensitive on both sides. Whitespace around commas and inside the module list is trimmed. Empty entries skipped silently. Repeated--log-moduleflags accumulate ; later entries on the same logger overwrite earlier ones.Logger name validation uses the regex
^kstlib\.[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*$after the optional auto-prepend, rejecting double dots, leading dots, embedded whitespace, and non-identifier characters. Invalid entries emit aWARNING(orWARNING [SECURITY]for malformed logger names) on thekstlib_logging_internallogger and the offending occurrence is skipped without aborting the command. Activate the observer vialogging.getLogger("kstlib_logging_internal").setLevel(logging.WARNING).
Changed¶
Sphinx logging introspection guide : cascade priority schema reverted to the clean three-layer model (YAML defaults, preset.modules, CLI
--log-module), new section “Module mutes are persistent by design” documenting the v2.6.0 behaviour, new “CLI module overrides” section listing the three syntaxes, and updated behavior matrix illustrating the persistent-mute interplay between handler level and per-logger level.
Fixed¶
Logging modules cascade : YAML default mutes (
kstlib.rapi.config: WARNING,kstlib.config.loader: WARNING) are now preserved when CLI verbosity flags (-v/-vv/-vvvor--log-level) are used. Previously in v2.5.0, these mutes were reset by verbosity flags (the so-called “Option A” design), defeating the purpose of having defaults for verbose modules. The embedded mutes are now persistent by design ; verbosity flags only adjust the root handler level. To explicitly bypass a mute under verbosity, use--log-module name=<level>(or any of the new factorized syntaxes).Friction reproducer that motivated the change :
# In v2.5.0 : noisy 'rapi.config' was un-muted by -vvv, drowning # the output the user wanted to see. kstlib -vvv rapi list # In v2.6.0 : 'rapi.config' stays at WARNING under -vvv ; the rest # of the kstlib hierarchy runs at TRACE as requested. kstlib -vvv rapi list # Explicit bypass when the user does want rapi.config at TRACE. kstlib -vvv --log-module rapi.config=TRACE rapi list
--log-moduleparser robustness : pathological inputs (no=, empty side, unknown level, malformed logger name, only commas in reverse mode, embedded whitespace or punctuation in a module name) no longer abort the command with an exception. The CLI now warns onkstlib_logging_internaland skips the offending entry, applying the remaining valid entries normally.
2.5.0 - 2026-05-01¶
Added¶
kstlib._shared.redactionmodule consolidating sanitization helpers used across kstlib :redact_sensitive()(regex-based, AWS ARN, AKIA keys, paths),mask_webhook_url()(Slack/Discord/Teams tokens),mask_url()(URL with inline credentials or sensitive query parameters). Replaces local helpers previously scattered insecrets/providers/sops.pyandalerts/channels/slack.py. Available atfrom kstlib._shared.redaction import redact_sensitive, mask_webhook_url, mask_url.kstlib.logging.modulesYAML configuration for per-logger level override. Cascade : global config -> active preset -> CLI--log-moduleflag (replaces total) -> CLI verbosity flags (-v/-vv/-vvvor--log-level) reset the YAML cascade when no--log-moduleis supplied. Default sans entries means no per-module filtering. Validation on YAML load : invalid logger names (must start withkstlib.) and invalid levels are skipped with WARNING.CLI flag
--log-module name=levelrepeatable, formatname=level. When at least one is supplied, it REPLACES the YAML modules cascade.Default YAML mutes for verbose modules at startup :
kstlib.rapi.config: WARNING(silences ~1300+ DEBUG/TRACE per Viya startup) andkstlib.config.loader: WARNING(silences cascade verbose with multiple includes). Override available via userkstlib.conf.yml, presets, or CLI flags.devvsdebugpresets differentiation :devkeepsoutput: console/ levelDEBUG(rapid iteration),debugswitches tooutput: both(consolepersisted file for grep) / level
TRACE(deep investigation).
Sphinx documentation : new section “User responsibility” (6 cases : NotifyCollector, transform callable, StepResult, NotifyResult, AlertMessage, WebSocket callbacks) + new “Logging introspection guide” (convention, cascade, presets, recipes, HTTPTraceLogger reference).
MailBuilder.notifyredact_user_data: bool = True: default-on opt-in redaction ofexception/return_value/traceback_strinNotifyCollectorsummary HTML. User can opt-out withredact_user_data=Falsefor cases where exception messages are known safe.NotifyCollector.render_html: new redaction toggle aligned withMailBuilder.notify.WARNING [SECURITY]tag systematically applied before raising on security events across modules : path traversal insecure/fs.py, drive escape, null byte injection indb/cipher.py,db/aiosqlcipher.py,pipeline/_base.py,auth/providers/oauth2.py(state mismatch CSRF),ssl.py(null byte CA bundle),ops/validators.py,rapi/config.py,rapi/client.py,utils/validators.py. Total : 15+ occurrences.kstlib_logging_internalinternal logger (underscore-prefixed, outsidekstlib.*hierarchy) for logging theLogManagersetup itself without recursion. Used bykstlib.logging.managerto break the silent fail-paths.~80 logging seeds across kstlib modules (rapi, config, auth, transform, pipeline, secrets, mail, alerts, websocket, db, secure, ssl, cache, helpers, limits, ui, utils, logging) following the convention 7 levels (TRACE/DEBUG/ INFO/SUCCESS/WARNING/ERROR/CRITICAL). INFO and SUCCESS used sparingly, TRACE/DEBUG split between detail (per-item, firehose) and synthesis (decisional branching).
Smoke tests :
tests/security/test_log_no_secret_leak.py(validates 4 HIGH fix points),tests/smoke/test_logging_noise_reduction.py(asserts-vvvproduces < 200 useful lines on typical rapi call),tests/smoke/test_logging_modules_kill_switch.py(validates YAML mute silences verbose modules).Furo theme consistency tweaks :
.sd-cardand.admonitionbackground forced to--lokaal-surfacefor visual coherence with code blocks. 3 Furo gotchas inscribed indevelopment/quality.md(no{contents}directive with Furo, card cascade override needs!important, build local verification).
Changed¶
HTTPTraceLogger(inkstlib.utils.http_trace) generalized as the official infrastructure for HTTP redaction across all httpx-based modules. Existing adopters (auth/providers/oauth2.py,mail/transports/gmail.py,mail/transports/resend.py) unchanged. New module additions are encouraged to import and use it instead of building local redaction.Anti-pattern “stderr-in-WARNING” remediation (~9 occurrences) : Option C split-levels applied (short WARNING + redacted detail in TRACE) on
auth/providers/base.py,auth/config.py,config/loader.py,mail/transports/*,alerts/channels/slack.py. Pipeline subprocess user applies Option A (drop stderr from log, kept inStepResult.stderrfor user inspection).Anti-pattern “raise sans log on security events” remediation :
[SECURITY]tag applied before each raise across the 15+ identified occurrences.pipeline.runnerlog levels reclassified following convention 7 levels : step skipped nowDEBUG(wasINFO), pipeline completed nowSUCCESS(wasINFO), step result successful nowSUCCESS. Reduces INFO bias on long pipelines.secrets/providers/sops.pydrops local_redact_sensitive_output()helper, importsredact_sensitivefrom_shared/redaction.py. Behavior identical with finer granularity ([REDACTED_ARN],[REDACTED_AKIA],[REDACTED_PATH]instead of generic[REDACTED]).alerts/channels/slack.pydrops local_mask_webhook_url(), importsmask_webhook_urlfrom_shared/redaction.py. Behavior identical.Sphinx Furo theme : visual consistency tweaks on cards and admonitions. No content change.
Fixed¶
Security:
auth/callback.py:116: OAuth authorization code (?code=...query parameter) was leaked in DEBUG via therequestlineforwarded byBaseHTTPRequestHandler.log_request. Fix :log_messagenow logs only HTTP method + status, never the full requestline.Security:
websocket/manager.py:449: WebSocket connect URL was logged in clear at INFO level. URLs with inline auth (wss://user:pass@host) or sensitive query parameters (?token=xxx) leaked credentials. Fix : URL is now redacted viamask_url()from_shared/redaction.pybefore logging.Security:
pipeline/steps/_base.py:70-76: subprocess stderr was forwarded in WARNING on FAILED status. Stderr from arbitrary user shell commands (psql,curl,ssh,mysql -p) could contain credentials. Fix : stderr is dropped from log (kept inStepResult.stderrfor user inspection). Documented in Sphinx user-responsibility.Security:
pipeline/steps/shell.py:59,62+pipeline/steps/python.py:55, 59: shell command line was logged in DEBUG (and INFO on dry-run), exposingAuthorization: Bearer xxx,--password=xxx,sshpass -p xxx, inline credentials in URLs. Fix : new helper_sanitize_command()(regex- based, best-effort) redacts known sensitive patterns before logging. Users should still preferenv:mapping over command-line credentials when possible (documented in Sphinx).examples/db/02_encrypted_sqlite_demo.py: 4 sqlite3/sqlcipher3 connect call sites refactored to usecontextlib.closingso the connection is released even when an intermediateexecute()raises. Production impact is nil (this is an executable demo) but the file doubled as a copy-paste template that taught the wrong shape.tests/smoke/test_logging_noise_reduction.py: module docstring and assert message no longer reference internal audit documents and sprint labels; failure-mode hint is rewritten in self-contained terms (logger and level the user controls).tests/websocket/test_manager.py: drop unusedAsyncMockstub causingRuntimeWarning: coroutine was never awaitedunder Python 3.14.
Security¶
Important : this release fixes 4 HIGH severity logging leaks identified by an internal audit covering 15 kstlib modules. Operators using
kstlib >= 2.0.0with logging enabled atDEBUGor below in production are encouraged to upgrade to v2.5.0.
OAuth callback authorization code leak (HIGH) : pre-v2.5.0,
auth/callback.pyforwarded the full HTTP requestline tolog.debug, exposing the OAuth authorization code (10-minute window typically). Severity mitigated by PKCE in PKCE-enabled flows but still a policy violation. Fixed in v2.5.0.WebSocket URL credentials leak (HIGH) : pre-v2.5.0,
websocket/manager.pylogged the full URL in INFO including userinfo and query string. URLs likewss://user:pass@hostorwss://exchange.com/?token=...exposed credentials. Fixed in v2.5.0.Pipeline subprocess stderr leak (HIGH) : pre-v2.5.0,
pipeline/steps/_base.pyforwarded full subprocess stderr in WARNING on FAILED. Stderr from user shell commands could contain credentials. Fixed in v2.5.0.Pipeline shell command leak (HIGH) : pre-v2.5.0,
pipeline/steps/shell.pyandpipeline/steps/python.pylogged the full command line including Authorization headers, passwords, and inline credentials. Fixed in v2.5.0 via_sanitize_command().Sanitization defenses inscribed as systematic conventions in the Sphinx “Logging introspection guide” (12 model patterns) and “User responsibility” guide (6 cases). New module contributions must respect the pre-commit checklist documented there.
2.4.0 - 2026-04-26¶
Added¶
MailBuilder.notifyacceptsmode="ok" | "ko" | "both"(case-insensitive) andon_success_only=Truefor symmetric filtering, completing the pre-existingon_error_onlyshortcut.MailBuilder.notify(collector=...)captures every result that passes the active filter into aNotifyCollector, enabling run-summary emails. Capture follows the active mode, so the double-decorator pattern records exactly one entry per execution.NotifyCollector: thread-safe, FIFO-bounded collector ofNotifyResultinstances, withrender_html/render_plain/to_monitor_table/to_contexthelpers.MailBuilder.send_summary(collector, *, subject=None, format="html")shortcut to send a summary email built from a collector. Operates on an internal snapshot so the original builder state is preserved.kstlib._shared.jinjamodule exposingrender_jinja(source, context, *, autoescape=False)andrender_jinja_file(path, context, *, autoescape=False, encoding="utf-8")helpers. Internal cross-module helpers; not part of the public top-level kstlib API. Used byMailBuilderfor template rendering.Documentation table for
NotifyCollector.to_context()keys with types, plus a “Custom Jinja2 templates with NotifyCollector” section showing a loop-over-results example.
Changed¶
MailBuilder.message(template=...)now renders templates with real Jinja2 (loops, conditions, filters, attribute access) instead of the previous regex-based{{ var }}substitution. Backward compatible for simple{{ var }}templates. Two small semantic changes worth noting: missing variables now render as empty strings (Jinja2ChainableUndefined) instead of preserving the{{ var }}literal, and non-scalar variables now render via Pythonstr()instead of the"[object]"placeholder.
Deprecated¶
Removed¶
Fixed¶
Security¶
2.3.1 - 2026-04-25¶
Fixed¶
Circular import when
from kstlib.mail import ...(or any import path entering throughkstlib.limits) is the first kstlib import in a fresh Python process. Affected 2.3.0 users running scripts from command line or Jupyter kernels just afterRestart Kernel. The cycle was triggered bykstlib.config.loaderandkstlib.config.sopsreading constants fromkstlib.limitswhile the latter was still initialising (transitively reached throughkstlib.utils.formatting->kstlib.utils.validators->kstlib.config.exceptions). Fix reorderskstlib.limitsso allHARD_*andDEFAULT_*constants are defined before the cross-module import. Workaround for users still on 2.3.0:import kstlib.configbefore the firstfrom kstlib.mail import .... Upgrading to 2.3.1 removes the need for the workaround.TypeError: 'NoneType' object is not iterableon any kstlib command that loads the configuration when a user-provided YAML carried an emptyinclude:key (orinclude: null, or a list whose items had been commented out).dict.pop("include", [])returnsNonewhen the key is present with anullvalue, and the downstreamfor inc in includes:loop exploded. Fix routes the raw value through a new private helper_normalize_includesinkstlib.config.loaderwith four rules: empty/None/whitespace is silent[], single string is wrapped after strip, list entries that areNone/empty/whitespace are dropped with a singleWARNING kstlib.config.loaderlog naming the source file, drop count, and original indices, and any other type (or non-string list item) raisesConfigFormatErrorconsistent with the rest of the loader’s validation.
Tests¶
New
tests/test_no_circular_imports.py: 32 parametrised tests that import every publickstlib.*top-level module, a curated set of public symbols, and historical cycle-guard submodules (kstlib.config.sops) in isolated Python subprocesses. Subprocesses start with an emptysys.modules, which is the only condition under which import cycles surface (pytest’s own init pre-loads enough of the package to mask them). Regression guard for the 2.3.0 cycle above.New
tests/config/test_loader_include_normalization.py: 22 tests covering every branch of_normalize_includes(silent empty cases, string stripping, list filtering withcaplogassertions on the warning message, and every illegitimate type path) plus two integration smoke tests that exercise_load_with_includesagainst tmp_path YAML files.
2.3.0 - 2026-04-24¶
Added¶
kstlib.config.reload_config(): ergonomic alias for “flush the singleton cache and reload from disk”. Equivalent toclear_config()+get_config()but expresses the intent in a single call, designed for Jupyter / REPL sessions where the YAML files have been edited mid-session. Also re-exported at the top level askstlib.reload_configfor consistency withget_config/clear_config. Purely additive, no backward-compat break.Mail preset SSL context with 4-level cascade. SMTP presets now resolve their
ssl.SSLContextthrough a cascade (preset >mail.ssl.*> rootssl.*> Python default). The two keysssl_verifyandssl_ca_bundlecascade independently, so a preset can disable verification while a higher level provides a CA bundle.ssl_ca_bundlepaths are validated viakstlib.ssl.validate_ca_bundle_path()(7-layer hardening: type/null-byte/empty/exists/file/readable/PEM). Newmail.ssl: {verify, ca_bundle}defaults shipped in the packagedkstlib.conf.yml. Unlocks enterprise/internal PKI usage that was previously impossible through presets (users had to bypassMailBuilderand expose secrets).Mail preset envelope defaults (
sender,reply_to). A preset may now declare adefaults:subsection undermail.presets.<name>.defaults. Both fields are applied automatically when the builder is created viaMailBuilder(preset="...")or whenmail.defaultresolves to a preset with defaults. User-provided values via.sender()/.reply_to()always override. ExplicitMailBuilder(transport=...)short-circuits the logic entirely. Scope is intentionally narrow:to/cc/bccare refused by design to prevent silent accidental sends. Unknown keys underdefaultsare logged once as WARNING and ignored (forward-compat).
Security¶
WARNING log on
ssl_verify=falsefor mail transports. Every SMTP preset built with a resolvedssl_verify=False(at any cascade level) now emits a single WARNING naming the source level (preset,mail.ssl,ssl (root)ordefault). Helps operators detect accidental verification disablement. Precedence: when both a CA bundle andssl_verify=falseare present, the CA bundle wins and verification stays enabled (CERT_REQUIRED) - documented.
2.2.1 - 2026-04-17¶
Fixed¶
WebSocket reconnect counter resets only after
stable_connection_time(default 60s) of uninterrupted connection, not on every successful handshake. Exponential backoff now accumulates correctly during prolonged server outages.WebSocket code 1013 (“Try Again Later”) is now detected explicitly and forces a
server_unavailable_delay(default 30s) before any reconnect, instead of being treated as a generic server close.WebSocket disconnect alert throttle: new
on_disconnect_alertcallback with built-indisconnect_alert_interval(default 300s) and aggregated disconnect count. Prevents alert storms during server outages (e.g. 3918 Slack alerts in 19 minutes during a Kraken trading engine outage).deps: authlib>=1.6.11 (GHSA-jj8c-mmj3-mmgv) - JWT algorithm confusion vulnerability.
2.2.0 - 2026-04-16¶
Added¶
System-wide config cascade via platformdirs. New fallback level between package defaults and user home config:
XDG_CONFIG_DIRSon Linux, native paths on macOS/Windows.LogManager
register=True. Unified bootstrap API; replaces the previous dual-object pattern.init_logging()is kept as a backward-compatible wrapper.MailBuilder config-driven presets.
mail.presetsinkstlib.conf.yml; cascade istransport=kwarg >preset=kwarg >mail.default.Auto-activation of kstlib internal logs via
kstlib.logging.enabledinkstlib.conf.yml(silent cascade, never breaks the host app).
Fixed¶
Exception hierarchy consolidated. All subpackage base exceptions now inherit from
KstlibError:mail,metrics,rapi,resilience,ui,secure,utils.except KstlibErrornow catches everything kstlib raises.pytest 9.0.3 bump (was capped at
<9; pulls in the CVE-2025-71176 temp dir fix).defusedxmlapplied to all XML parsing paths (transform.primitives,transform.chain,utils.serialization).auth/providersasserts removed.assertstatements replaced by explicitConfigurationErrorraises so missingtoken_url/issuer/ JWKS still raise cleanly underpython -O.Pipeline
CallableStephardened.DANGEROUS_MODULESblacklist + optionalallowed_moduleswhitelist wired throughPipelineConfig.allowed_callable_modules; blocksos,subprocess,ctypes,pickle, … from being invoked via YAML config.age-keygen private key now has an explicit
chmod 0o600after generation (umask fallback was0o644on Unix).HTTP trace logger redacts
access_token,refresh_token,id_token, andclient_secretin TRACE-level response bodies (matching the existing request redaction).
Refactored¶
pipeline/steps: shared_run_subprocessextracted intopipeline/steps/_base.py;shell.pyandpython.pynow delegate instead of duplicating the execution skeleton.utils/validators:CALLABLE_TARGET_PATTERN+MAX_CALLABLE_TARGET_LENGTHconsolidated; pipeline and transform validators share_validate_callable_target_str.alerts/manager:_create_email_transportsplit into_create_smtp_transport,_create_resend_transport,_create_ses_transport; dispatcher dropped to ~10 lines.cli/commands/ops/common:get_session_managerdecomposed into_detect_backend,_scan_tmux_sockets,_build_session_manager(complexity 15 -> 8, blanketC901/PLR0912suppressions removed).cli/commands/rapi:_load_config_or_exit()shared bylist,show, andcall.LOGGING_LEVELre-exported fromkstlib.logging.ParamSpecmigrated fromtyping_extensionstotyping(resilience/rate_limiter,resilience/circuit_breaker; Python 3.10+).
Security¶
Response-body token redaction in HTTP trace logger (audit-security F1).
0o600enforced on age private key (audit-security F2).defusedxmleverywhere (audit-security F4).auth/providersasserts replaced byConfigurationError(audit-security F5, safe underpython -O).CallableStepblacklist + whitelist (audit-security F6).
2.1.4 - 2026-04-01¶
Fixed¶
Batch audit fixes: pre-compile regex, extract helpers, fix resource leaks
Pre-compile regex patterns at module level (logging, monitoring, delivery, ops/validators, sops) to avoid re-compilation on hot paths
Extract helpers to reduce duplication:
_normalize_scopes/_normalize_redirect_uri(auth/config),_validate_token_options(cli/token),_upgrade_to_tls/_authenticate/_trace_envelope(smtp),_check_response_size/_handle_retry_error(rapi/client)Add
OAuth2Provider.close()and__exit__to release HTTP connection poolAdd JWKS TTL cache (3600s) in OIDC provider for key rotation
Add
py.typedmarker to package-data (PEP 561)
2.1.3 - 2026-04-01¶
Fixed¶
deps: bump Pygments >= 2.20.0 (CVE-2026-4539) - Regular Expression Denial of Service (ReDoS) due to inefficient regex for GUID matching. Transitive dependency via Sphinx. Lower bound enforced in dev, docs, and all extras.
2.1.2 - 2026-03-29¶
Fixed¶
deps: enforce transitive dep lower bounds for CVE remediation - Previous CVE fixes only bumped lockfiles (uv.lock, pylock.toml) but did not set minimum versions in pyproject.toml. Users installing via
pip install kstlibcould resolve vulnerable transitive dependencies. Now pyproject.toml enforces patched versions for all security-critical transitive deps.Runtime dependencies added:
cryptography>=46.0.6(CVE-2026-26007, CVE-2026-34073) - SECT curve subgroup validation + DNS name constraint enforcement (via authlib)requests>=2.33.0(CVE-2024-47081, CVE-2026-25645) - cookie handling + temp path replacement (via httpx)urllib3>=2.6.3(CVE-2025-50182, CVE-2025-50181, CVE-2025-66418, CVE-2025-66471, CVE-2026-21441) - 5 vulnerabilities in HTTP client (via requests)
Dev dependencies added:
filelock>=3.20.3(CVE-2026-22701) - via tox/virtualenvjaraco-context>=6.1.0(CVE-2026-23949) - via keyringvirtualenv>=20.36.1(CVE-2026-22702) - via toxwheel>=0.46.2(CVE-2026-24049) - build-system
2.1.1 - 2026-03-26¶
Fixed¶
deps: bump requests 2.32.5 -> 2.33.0 (CVE-2026-25645) -
requests.utils.extract_zipped_pathsvulnerable to malicious file replacement via deterministic temp paths. Indirect dependency only (via sphinx, requests-toolbelt), no direct impact on kstlib.
2.1.0 - 2026-03-25¶
Added¶
RAPI: multipart/form-data upload - Endpoints with
Content-Type: multipart/form-datain their headers now auto-handle file uploads via httpx nativefiles=parameter. MIME type auto-detected from extension, boundary auto-generated.MultipartConfigdataclass for fine-tuning (field_name, content_type)FilePayloaddataclass for programmatic uploads without files on diskEndpointConfig.is_multipartproperty, optionalmultipart:YAML sectionTRACE-level logging: boundary, parts, field names, sizes (
-vvv)HARD_MAX_RAPI_UPLOAD_SIZE(100 MiB) deep defense limit
RAPI CLI:
--rawflag - Output raw JSON without Rich formatting, pipeable tojqRAPI CLI:
--minifyflag - Output compact single-line JSON, combinable with--out
Improved¶
RAPI: endpoint collision warnings - Now show collision count, first source file, and actionable tips instead of just the endpoint name
Documentation¶
Multipart upload section with YAML config and Python examples
--rawand--minifyin CLI options tablesFilePayloadandMultipartConfigin API reference autodoc
2.0.1 - 2026-03-17¶
Fixed¶
RAPI: null query params no longer sent as empty strings - YAML query parameters with
nullvalue (e.g.filter: null) were converted to empty strings by httpx, causing server-side errors (SAS Viya error 1104:The filter '' is not valid). Null params are now excluded from the HTTP request. Kwargs can still override them at call time (e.g.filter="name eq 'foo'"), and passingNoneas a kwarg explicitly removes a default param._extract_query_params()filters outNonevalues from endpoint configEndpointConfig.querytype hint corrected todict[str, str | None]
2.0.0 - 2026-03-07¶
Breaking Changes¶
Major version bump due to security hardening that changes runtime behavior. Rollback:
pip install "kstlib<2.0.0"
validate_command()strict blocklist - Now blocks;,&&,||,|,>,<,eval,source,exec, null bytes in ops commands (container run/exec). Pipeline steps usestrict=Falseand are not affected.validate_env()dangerous keys denylist - BlocksLD_PRELOAD,PYTHONPATH,NODE_OPTIONSand similar environment variables in ops functions.send_keys()input validation - Blocks pipes, semicolons, redirections in tmux send_keys. Tmux control sequences (C-c, Enter, Escape) remain allowed.OIDC ID token validation mandatory -
exchange_code()now raisesTokenValidationErroron validation failure instead of logging a warning.authlib required for OIDC - Missing
authlibraisesTokenValidationErrorinstead of falling back to unverified JWT decode.Config
includepath traversal blocked - Include paths escaping the config directory raiseConfigFormatError. Symlinks in includes are also rejected to prevent TOCTOU bypass of the path confinement check.SOPS binary must be a simple name - Paths in
sops_binaryraiseConfigSopsError.RAPI URL scheme validation - Only
http://andhttps://accepted.JSON heuristic removed - Response body parsed as JSON only when content-type confirms it (
application/jsonorapplication/vnd.*+json).shlex.split()for container commands - Replacesstr.split()for proper quoting support.Redirect URI validation -
AuthProviderConfigvalidates scheme (http/https) and warns on non-localhost hosts.TmuxRunner socket_name validation - Rejects paths and null bytes in socket names.
Security¶
47 security items hardened across 9 phases (8 CRITICAL, 17 HIGH, 15 MEDIUM, 7 LOW)
Timing-safe CSRF state comparison (
hmac.compare_digest)Thread-safe token refresh with double-checked locking
Config file size limit (10 MiB) before YAML/JSON parse
Bounded recursion depth (max 32) for
deep_mergeand SOPS scanNaN/Inf rejection in config values
Path traversal and null byte checks in RAPI parameters
SOPS decrypted secrets cache TTL (5 minutes)
WAL checkpoint forced on DB pool close
Log injection sanitization (CRLF/ANSI/null stripped)
SMTP AUTH/PASS redacted in debug trace
Token/SecretRecord/SMTPCredentials
repr=FalseExplicit
follow_redirects=Falsein httpx clientsCallbackHandler state cleared on stop
.gitignorepatterns for secrets (*.env,*.key,*.pem,credentials.*)
1.7.8 - 2026-03-06¶
Fixed¶
Oops:
ops attach/stopnow auto-discover custom tmux sockets - v1.7.7 added custom socket discovery forops list, but we didn’t follow through:attachandstopstill only checked the default socket, sokstlib ops attach orionfailed with “Session not found” even thoughops listshowed it just fine. Nowget_session_manager()scans custom sockets automatically when no--socketflag is provided, solist,attach, andstopall share the same discovery logic.Move
discover_tmux_sockets()from CLI layer tokstlib.ops.tmux(reusable)get_session_manager()scans custom sockets whensocket_nameis NoneValidated on EC2:
kstlib ops attach orionworks without--socket
1.7.7 - 2026-03-06¶
Fixed¶
tmux custom socket discovery -
TmuxRunnernow supports sessions started withtmux -L <name>(custom sockets). Previously,kstlib ops listonly discovered sessions on the default socket, making multi-bot setups (e.g. Orion on-L orion) invisible.TmuxRunner(socket_name=...)injects-Lflag in all tmux commands includingattachSessionStatus.socket_nametracks the origin socket for each session_discover_tmux_sockets()auto-scans/tmp/tmux-{uid}/for non-default socketskstlib ops listshows a new Socket column andsocket_namein JSON outputNew
--socket / -LCLI option onstart,stop,attach,logs,statusSessionManagerandauto_detect_backendpropagate socket context end-to-end
1.7.6 - 2026-03-06¶
Security¶
Harden
ContainerRunner.exec()- Addvalidate_command()input validation before execution, consistent withSessionConfig.__post_init__validationHarden
TmuxRunner.send_keys()- Addvalidate_session_name()input validation before passing session name to tmux subprocessPre-commit SOPS guard - Add
check_sops_files()to pre-commit hook to block commits containing decrypted SOPS files (missingsops:metadata block)
1.7.5 - 2026-03-05¶
Security¶
Pin authlib>=1.6.9 - Tighten minimum version to exclude versions vulnerable to
alg: nonesignature verification bypass (CVE-2026-28802)Pin jinja2>=3.1.5 - Tighten minimum version to exclude versions vulnerable to sandbox bypass leading to RCE (CVE-2024-56326, CVE-2024-56201)
Upgrade werkzeug 3.1.5 to 3.1.6 - Fix
safe_join()path traversal on Windows (dependabot #7, transitive dependency via moto)
Changed¶
Sync
uv.lockandpylock.tomlwith updated dependency constraints
1.7.4 - 2026-03-04¶
Security¶
Upgrade authlib 1.6.6 to 1.6.9 - Fix
alg: nonesignature verification bypass (GHSA-7wc2-qxgw-g8gg)
1.7.3 - 2026-02-27¶
Changed¶
Full quality sweep - Comprehensive code quality audit and fixes across 35 files
Add
__all__exports to 5 modules (cache/decorator, cache/strategies, logging/manager, utils/dict, config/export)Expose
opsmodule via lazy loading in root__init__.pyFix all D401 (imperative mood) docstring violations in 12 src/ files
Add ~137 missing test function docstrings (D103) across 9 test files
Simplify
MANIFEST.in(useprune .*instead of individual exclusions)Add
coverage.jsonto.gitignore
Fixed¶
3 mypy –strict errors - Remove stale
type: ignorecomments for authlib, fix duplicate__all__in config/export, fix unreachable code in test_auth_clitest_rejects_code_exceeding_max_length- Use_MAX_CODE_LENGTH + 1instead of hardcoded value that became too small after limit was raised to 2048CLI test coverage - token.py (28% to 100%), attach.py (35% to 100%), login.py (47% to 99%)
1.7.2 - 2026-02-27¶
Fixed¶
secrets doctorfalse ERROR on missing keyring - The doctor command reported ERROR (exit code 1) when the optionalkeyringpackage was not installed, even though the encryption backend (age) was fully operational. Keyring is optional (used for credential caching, not encryption), so its absence now produces a WARNING instead.
1.7.1 - 2026-02-25¶
Fixed¶
WebSocket
ConnectionClosedOKsilent data loss - When Binance (or any server) closes the WebSocket cleanly (code 1000, e.g. after 24h),_receive_loopnow triggers_handle_disconnect(SERVER_CLOSED)instead of only logging a debug message. This restores auto-reconnect behavior on clean server closures, preventing silent data loss.Deprecated
websocketsAPI - Replacee.code/e.reasonwithe.rcvd.code/e.rcvd.reason(deprecated since websockets 13.1) in bothConnectionClosedOKandConnectionClosedErrorhandlers.
1.7.0 - 2026-02-22¶
Added¶
AlertLevel.SUCCESS- New alert severity level for positive confirmationsValue 11 (between INFO=10 and WARNING=20), filtered like INFO by
min_levelSlack emoji
:white_check_mark:(green checkmark), color#36a64f(green)_parse_level("success")supported for config-driven channel setupConsumer: astro trading bot for
ws_reconnect,heartbeat_ok,order_filled
Fixed¶
Prod preset now defaults
tracebacks_show_localstoFalse- Reduces default exposure surface in production tracebacks. Local variables are still available by switching to thedebugpreset or settingtracebacks_show_locals: Truein config. The fallback default also changed fromTruetoFalseso unlisted presets inherit the safer behavior.
1.6.2 - 2026-02-18¶
Fixed¶
get_logger()missing.trace()and.success()methods - Child loggers returned byget_logger(__name__)are standardlogging.Loggerinstances that lack custom level methods. Callinglogger.trace(...)raisedAttributeError. Nowinit_logging()patches thelogging.Loggerclass once at startup so all child loggers support.trace()and.success().
1.6.1 - 2026-02-16¶
Fixed¶
kstlib auth checkSSL context - HTTP client now reuses the provider’s SSL config (ca_bundle,ssl_verify) instead of creating a barehttpx.Client(). Falls back to the global SSL cascade fromkstlib.conf.ymlwhen no provider config is available.
Added¶
Shell examples -
kstlib-auth-check.sh(wrapper script) andtoken_check.sh(raw curl/openssl verification) for auth check usage without PythonCLI tests - New test suite for
kstlib auth checkCLI command (80 lines, covers SSL context propagation)
Documentation¶
Updated examples gallery with shell script references
1.6.0 - 2026-02-14¶
Added¶
kstlib auth check- JWT token validation with cryptographic proofTokenCheckerclass with 6-step validation chain: decode JWT structure, discover issuer endpoints, fetch JWKS, extract public key, verify RSA signature, validate claimsWorks with any RSA-signed JWT (RS256/RS384/RS512) whose issuer exposes an OIDC discovery endpoint (
.well-known/openid-configuration)Key metadata extraction: type (RSA), size (2048/4096-bit), SHA-256 fingerprint
X.509 certificate parsing from JWKS
x5cfield when published by the IDPCLI command with
--verbose,--json,--access-tokenoptionsRich panel output with step-by-step validation results
Exit codes: 0 (valid), 1 (invalid), 2 (system error)
Delegated trust support: issuer and JWKS server can be different hosts
Documentation¶
Cryptographic proof chain diagram with RSA verification steps
Manual verification guide (without kstlib, using curl/openssl/PowerShell)
Issuer vs JWKS server (delegated trust) explanation
Examples gallery:
token_check.py(Python) andtoken_check.ps1(PowerShell raw RSA)Updated auth feature guide, API reference, CLI reference
1.5.0 - 2026-02-11¶
Added¶
kstlib.pipeline- Declarative, config-driven pipeline execution moduleThree step types:
ShellStep(subprocess, shell=True),PythonStep(python -m),CallableStep(importlib import + call)Conditional execution:
always,on_success,on_failurestep conditionsError policies:
fail_fast(aborts with on_failure cleanup) orcontinueTimeout cascade: step timeout overrides pipeline
default_timeoutDry-run mode: simulate execution without side effects
Config-driven pipelines: define workflows in
kstlib.conf.ymlunderpipeline.pipelinesPipelineRunner.from_config(name)to load and run named pipelinesDeep input validation reusing
kstlib.ops.validators(dangerous pattern detection)PipelineLimitsinlimits.pywith hard caps (max 50 steps, 1-3600s timeout)Multi-line shell commands supported via YAML
>-(folded) and|(literal) scalars
Fixed¶
SQLite
isolation_level- Align plain SQLite path with cipher path (isolation_level=Nonefor autocommit consistency)
Documentation¶
Pipeline feature guide, API reference, exception catalog
3 example scripts + example
kstlib.conf.ymlUpdated landing page, features/api/examples indexes
1.4.1 - 2026-02-11¶
Security¶
Upgrade
cryptography46.0.4 -> 46.0.5 (CVE-2026-26007, subgroup attack on SECT curves)
1.4.0 - 2026-02-10¶
Added¶
Multi-backend
secrets init- Auto-detect best available encryption backendPriority order: age > GPG > error with clear guidance
--backend/-boption for explicit backend selection (ageorgpg)GPG flow: reads fingerprint from keyring, generates
.sops.yamlwithpgp:keyAge flow: unchanged (backward compatible)
Doctor backend mismatch hint - Actionable guidance when configured backend is unavailable but an alternative exists on the system (e.g., “Configured backend (age) is not available, but gpg is. Run:
kstlib secrets init --backend gpg”)
Fixed¶
secrets doctorscan - Detect available backends (age/gpg/kms) by binary presenceDeep checks for configured backends, lightweight checks for unconfigured
New
available_backendsfield in doctor payload
1.3.0 - 2026-02-08¶
Added¶
SQLite performance PRAGMAs for self-maintaining databases
PRAGMA auto_vacuum=INCREMENTALon new file databases (set before WAL to ensure it takes effect; skipped on existing databases and:memory:)PRAGMA optimizeexecuted on each connection at pool close (updates internal statistics for better query planner decisions)
1.2.1 - 2026-02-07¶
Fixed¶
Binance testnet WebSocket URL - Update deprecated
wss://testnet.binance.vision/wstowss://stream.testnet.binance.vision/ws(officially changed by Binance in May 2024, old endpoint kept working beyond scheduled removal but is now unreliable)
Documentation¶
README and Sphinx index: add AWS SES to mail transports list (from v1.2.0)
1.2.0 - 2026-02-06¶
Added¶
SesTransport - AWS SES email delivery via
send_raw_emailAsync transport using
run_in_executorfor boto3 compatibilityRaw MIME passthrough (preserves HTML, attachments, all headers)
Default credential chain support (EC2 instance profiles, env vars)
Explicit credentials option (
aws_access_key_id/aws_secret_access_key)Deep validation: region, credential pairs, timeout
TRACE-level logging for request metadata
Error mapping:
ClientError,NoCredentialsError,EndpointConnectionError
[ses]extras -pip install kstlib[ses]installs boto3Alert manager integration -
type: "ses"in transport factory
Fixed¶
Sphinx docs -
're_'in ResendTransport docstring caused reST warning
Documentation¶
Sphinx features/mail: AWS SES Transport section with install + examples
Sphinx api/mail: autodoc for SesTransport, ResendTransport, GmailTransport
Installation guide:
[ses]extra in bundles list
1.1.1 - 2026-02-04¶
Fixed¶
SOPS cascading search - Fix
.sops.yamldiscovery from source file directoryPreviously only checked
~/.sops.yaml, ignoring local configsNow walks up directory tree from source file, then falls back to home
1.1.0 - 2026-02-01¶
Added¶
RAPI Safeguards - Protection for dangerous endpoints
SafeguardConfig: configure which HTTP methods require confirmation
Runtime confirmation via
confirm=parameterConfig-time validation for DELETE/PUT endpoints
RAPI Environment Variables - Dynamic configuration
${VAR}syntax for required env vars${VAR:-default}syntax with fallback valuesRecursive expansion in all string values
RAPI Nested Includes - Modular API definitions
Include sub-modules from root
.rapi.ymlfilesDefaults inheritance (base_url, credentials, headers)
Endpoint collision detection with strict mode
RAPI CLI Improvements
--filteroption for endpoint search--short-descflag for truncated descriptionsVerbose mode shows full descriptions by default
SAS Viya POC - Example with ~1250 endpoints
Validates nested includes architecture at scale
Body schemas approximate (not validated)
1.0.2 - 2026-01-31¶
Changed¶
Upgrade dev dependencies (security fixes)
1.0.1 - 2026-01-31¶
Fixed¶
Use absolute URL for logo in README
1.0.0 - 2026-01-30¶
First public release of kstlib, a config-driven Python toolkit for building resilient applications.
Core Modules¶
Configuration (kstlib.config)¶
Cascading YAML/JSON configuration with file includes
SOPS-encrypted secrets integration (Age, GPG, AWS KMS)
Environment variable interpolation
Box-based dot notation access
Auto-discovery with customizable search paths
Secrets Management (kstlib.secrets)¶
Multi-provider secret resolution (env, keyring, SOPS, AWS KMS)
Kwargs provider for runtime injection
Sensitive value redaction in logs
Provider priority chain with fallbacks
Logging (kstlib.logging)¶
Rich console output with colors and formatting
Rotating file handlers with compression
TRACE level for verbose debugging
Structlog integration for structured logging
Context-aware logging with correlation IDs
Authentication (kstlib.auth)¶
OAuth2/OIDC with PKCE support
Automatic token refresh
Secure token storage (keyring integration)
Callback server for authorization flows
Provider abstraction (Keycloak, generic OIDC)
Mail (kstlib.mail)¶
Fluent MailBuilder API
Jinja2 template rendering
Multiple transports: SMTP, Gmail API, Resend
Attachment handling with filesystem guardrails
Async transport support
Alerts (kstlib.alerts)¶
Multi-channel delivery (Slack, Email)
Severity levels with routing rules
Throttling to prevent alert fatigue
Template-based message formatting
WebSocket (kstlib.websocket)¶
Resilient connection management
Automatic reconnection with backoff
Heartbeat and ping/pong handling
Watchdog for connection health
Message deduplication
REST API Client (kstlib.rapi)¶
Config-driven endpoint definitions
HMAC request signing
Automatic retry with backoff
Response validation
Rate limiting integration
Monitoring (kstlib.monitoring)¶
Metric collectors with Jinja rendering
Multiple delivery targets (file, mail)
Scheduled collection runs
Custom collector plugins
Resilience (kstlib.resilience)¶
Circuit breaker pattern
Token bucket rate limiter
Graceful shutdown handling
Heartbeat monitoring
Watchdog timers
Operations (kstlib.ops)¶
Session manager (tmux integration)
Container operations (Docker/Podman)
Process management utilities
Cache (kstlib.cache)¶
TTL-based caching
LRU eviction strategy
File-based persistent cache
Decorator API for easy integration
Database (kstlib.db)¶
Connection pooling
Field-level encryption (cipher)
Migration support
UI Components (kstlib.ui)¶
Rich panels and tables
Progress spinners
Formatted output helpers
Utilities (kstlib.utils, kstlib.helpers)¶
TimeTrigger for scheduled operations
Input validators
Text formatting utilities
Secure file deletion
HTTP request tracing
CLI¶
Command-line interface for common operations (
kstlib --help)
Documentation¶
Comprehensive Sphinx documentation with Furo theme
API reference with autodoc
Examples gallery with runnable code
Development guides (testing, contributing, secrets management)
Quality¶
95%+ test coverage per module
16,000+ tests across 5 Python versions (3.10-3.14)
Strict mypy type checking
Ruff linting and formatting
Pre-commit hooks with tox validation
CI/CD with GitHub Actions
Security¶
Supply chain verification with PEP 751 lockfile (pylock.toml)
SHA256 hash verification for all dependencies
SOPS integration for secrets at rest
Sensitive value redaction in logs and errors
Filesystem guardrails for attachments