Breaking Changes
- API key secrets are now stored hashed instead of in plaintext
(HMAC-SHA256 peppered with the console encryption key). Existing keys
are hashed in place on upgrade, so keys already issued keep working.
Because the hash is keyed withENCRYPTION_KEY, changing that key now
invalidates stored API keys — in addition to the stored endpoint tokens
it already invalidates.
Features
- The application Variables tab now masks secret-looking values —
keys matching pass/secret/token/private/key/cred, URLs with
embedded credentials, and PEM private-key blocks under any
variable name — in both the variables list and the All
Variables block. Values appear only on explicit request: a per-row
Show/Hide toggle in the list, a Show secrets toggle on the block.
The same value heuristics the Service Keys page already uses; hosts and
ports in connection URLs stay readable. - The diagnostics resource waterfall now draws the document request itself
as its first row, segmented by phase (stall, DNS, TCP, TLS, server wait,
download). Previously the document was invisible — it is a navigation
entry, not a resource entry — so on a high-latency connection the chart
showed an unexplained void until the HTML arrived. Under the Stratos
clock the row collapses to just server wait + download, the part the
app can influence.
Bug Fixes
- WebSocket upgrades (application SSH and log streaming) now validate the
request Origin — same-origin, plus any host inALLOWED_ORIGINS—
instead of accepting connections from any origin, closing a cross-site
WebSocket hijacking vector. - The session cookie is now issued with
SameSite=Lax. - Jetstream no longer terminates when the Cloud Foundry info request fails
during SSO auto-connect at login. That one login fails instead of the
whole process exiting for every user. - Proxied requests that time out no longer leak a goroutine and its
buffered response body per endpoint. - The OAuth client secret and the application-SSH one-time code are no
longer written to the jetstream log. - Jetstream now warns at startup when
ENCRYPTION_KEYis left at the
well-known default value shipped inconfig.example. - The UAA token endpoint is now checked at the point the request is made,
rather than trusted from wherever it was built. It must be an absolute
http(s) URL with a plain host and path — no user info, query or
fragment. The first-run setup form supplies this value on an
unauthenticated request, so an unconfigured console could be pointed at
an arbitrary URL of a caller's choosing. - Signing in no longer flashes the Home page, blanks the console, and
reloads it. The login click handler and the existing-session check both
triggered the post-login redirect; the two navigations cancelled each
other and the loser fell back to a full page reload. The redirect now
runs once. - Jetstream serves its own HTTPS again where it terminates TLS itself.
Echo v5 resolves a certificate path through a filesystem rooted at the
working directory, which rejects an absolute path outright, and the
certificate was still being passed as a path — so no HTTPS listener
started at all. The Kubernetes chart, and the dev and CI configurations,
all use path shapes that hit this. Deployments on Cloud Foundry were
unaffected, since the router terminates TLS there. - Helm chart files and chart icons no longer 404 on Kubernetes. File
serving now goes through the same rooted filesystem, which accepts an
absolute path only beneath the working directory; the chart sets a cache
folder outside it, so every chart file and icon answered 404 for a file
plainly present on disk. Analysis reports and an absoluteUI_PATHhad
the same exposure whenever the process was not started from an ancestor
directory. - Jetstream logs through one logger again. Echo v5 logs via
log/slog
and defaults to writing JSON to standard output, which meant its
messages interleaved with Jetstream's own text-formatted logs on the
same stream and ignoredLOG_LEVEL. Echo's output is now routed into
the application logger, so format and level apply to everything.
API request logs keep the format they had under Echo v4 and remain
gated byLOG_API_REQUESTS; they now also respectLOG_LEVEL. - Deployed logs are no longer written with ANSI colour escapes. Colour was
forced on rather than detected, so every line from a deployed Jetstream
carried escape codes — visible incf logs, where nothing is a terminal.
A developer running the binary in a terminal still gets colour. The
analysis container had the same problem. - Two boot lines reporting the resolved Cloud Foundry paging overrides were
written before the log handler was installed, so they came out through
the standard library formatter. UnderLOG_TO_JSONa collector got two
unparseable records on every start. Boot output is JSON all the way down
now. - A
LOG_LEVELtypo used to silence the backend almost completely.
logrus returned its panic level for an unrecognised name and the
error was discarded, soLOG_LEVEL=inofsuppressed nearly every
message. An unusable value is now reported and the current level
kept. - Under
LOG_TO_JSON, the log level line and the API request log each
emitted a record with a duplicate key, because both passed an
attribute named for a key slog writes itself. Decoders generally take
the last occurrence, so an aggregator read the configured level
string in place of the record's real severity, and the request start
time in place of the record timestamp. - Deploying an application from a private Git repository wrote the
supplied access token to the debug log. The source information line
logged the whole client message, whose payload carries the token.
Only the source type is logged now. - Several failures were reported as success or reported without a
cause: a Helm chart icon whose cache folder could not be created was
returned as a valid path, a truncated chart archive was served as
though it had extracted cleanly, an analysis report that failed to
serialise returned HTTP 200, and both analysis runners logged
completion before checking whether the run had failed. - Two paths that could dereference a nil pointer are fixed: the Helm
client stored a configuration it had already failed to build, and the
Kubernetes dashboard proxy used a URL it had failed to parse. - Numerous log lines that named the wrong function, shared one message
between unrelated failures, or discarded the error they were
reporting now identify what actually happened. - The JSON schema form no longer emits a
<form>inside a<form>. The
renderer recurses into itself for nested object schemas and rooted every
level in a form element, which is invalid HTML — the DOM API builds it
anyway, and browsers scope submission and reset in ways the markup did
not intend. Nothing in that template submits, so it is a plain container. - Connecting a Kubernetes endpoint through OIDC or a kubeconfig works
again. RFC 7519 defines the token expiry claim as a number of seconds
since the epoch; it was being read as an RFC 3339 string, so no
conformant token could satisfy it and every attempt failed with
"can not get Access Token expiry time claim". - Artifact Hub is available again when it is enabled. The flag was renamed
from "enabled" to "disabled" when Artifact Hub support landed, but the
test against it was not inverted, so requests were rejected as disabled
precisely when the feature was on — which is the default. - A Helm release whose manifest contains a malformed custom resource is
now reported to the console as having manifest errors. That branch
logged the parse failure but left the flag unset, unlike the branch
beside it. - kube-score analysis jobs are evicted from the job map when they finish.
The busy flag was cleared for popeye runs and never for kube-score, and
the cleanup pass skips busy jobs, so the map grew for as long as the
analysis container ran. - Deploying an application no longer panics when a file check fails for
any reason other than the file being absent — a permission error, a
symlink loop, or an over-long name left a nil result that was then
dereferenced. - The fallback path for the plugin scripts folder contained a stray
character, so a layout that keeps the scripts under the plugin directory
never matched the candidate meant to find it. - The endpoint filter on the Applications, Marketplace and Services
toolbars lists endpoints by name. It was built by walking the endpoint
list in arrival order, which no query orders, so with more than one
endpoint of a kind the dropdown showed whatever order the rows came
back in. "All" stays at the top. The comparator is the one the sibling
dropdowns on those toolbars already use, so cf10 sorts after cf2.
Maintainability
- Jetstream now runs on Echo v5. Echo v4 is in security-and-bugfix-only
maintenance until the end of 2026, and v5 reworked enough of the API —
a concrete request context,log/slogfor logging, a rearranged error
handler — that the move had to happen in one step across the backend
and every plugin. - Retired four unmaintained or superseded dependencies:
SermoDigital/jose(no upstream release since 2019) is gone from the
Kubernetes auth plugin, which used it only to read one claim from a
token the platform had already issued;satori/go.uuid(abandoned) is
replaced bygoogle/uuid, which the backend already depended on for
the same purpose;golang/mock(archived) moves to its successor
go.uber.org/mock; andgorilla/context, obsolete since Go 1.7, is
gone along with the middleware that existed only to call it. - Dropped the archived AWS SDK v1. It was reachable only through
aws-iam-authenticator, which has since moved to SDK v2; the Kubernetes
IAM auth path moves with it. - Upgraded Helm to v3.21.4 and the Kubernetes client libraries to v0.36.
Kubernetes minor releases remove APIs, so these move as a set. This
also repairs dependency maintenance in the Kubernetes plugin, where
go mod tidycould not run at all: the previous Helm pulled a kubectl
that imports an API removed in k8s v0.36. - Jetstream now logs entirely through
log/slog. Echo v5 had already
moved to slog, which left the backend running two loggers with two
formats and two level settings; a bridge kept them on one stream
during the transition and is now gone. All nine Go modules are
converted, and no first-party code imports logrus anywhere. Log
messages are structured records rather than formatted strings, so
identifiers that used to be interpolated into message text — user,
endpoint and token GUIDs, org and space, chart and release names —
are now attributes that can be filtered and correlated.LOG_LEVEL
andLOG_TO_JSONbehave as before, and every level name logrus
accepted still works, includingtrace,fatalandpanic. - Every filesystem operation in the analysis container now re-checks, at
the point of use, that the path still sits beneath the reports
directory, resolving any ".." before comparing. The paths were already
built safely, but the guarantee lived in the constructors: an analyzer
read the job folder and temporary file names directly, so a later change
setting either from somewhere else would have reached the disk
unchallenged. The monocular and Artifact Hub cache paths are built from
validated segments in one place for the same reason.
Chores
- Jetstream now builds with Go 1.27. Go 1.27 rejects the invalid hash id
thatSermoDigital/joseregisters from an init function, which panics
beforemainand crash-loops the binary. The dependency is reached
through the CF CLI rather than our own code, so it is pinned to a fork
with that registration removed until the CF CLI moves to a maintained
JWT library. make check lintruns the Go linters again. golangci-lint supports
only a Go version at or below the one that built it, so the Go 1.27
bump left the packaged binary failing before it reached any linter.
It is now pinned and built from source against the toolchain named in
go.mod.- Frontend dependencies moved to their current releases: Angular and the
Angular CLI to 22.1.x,marked16 to 18,js-yaml4 to 5,sass-loader
13 to 17 and@playwright/testto 1.62. The Angular devkit packages have
to move as a set, and@angular-devkit/build-angularhad to be carried
with them by hand: it is deprecated at every published version, and
Dependabot filters deprecated releases out of the candidate list, so it
cannot bump that one package while bumping its siblings. Leaving it behind
resolves two copies of@angular-devkit/architectand the devkit builders
stop type-checking. - Two build scripts imported
js-yamlthrough a default export that
version 5 no longer provides, and failed at module load. - The frontend test matrix in CI listed seven of the eight vitest projects,
so thecf-autoscalerspecs ran locally and never in CI.