v0.6.5
Pre-releaseA patch for the k8s runner on managed, multi-tenant Kubernetes — where a platform team owns RBAC
and namespaces, a baseline default-deny NetworkPolicy already exists, the Postgres DSN Secret is
operator-owned, and packages come from an allowlist mirror. Reported by an enterprise adopter on
managed Kubernetes; every item below was verified against the code before it was fixed.
Security
golang.org/x/crypto0.54.0 → 0.55.0 (GO-2026-6303). The SSH gateway's
ssh.NewServerConnreaches the code path where the source-address critical
option was not enforced for non-public-key auth callbacks;govulncheck
reports it as reachable and would block the merge gate. Fixed upstream in
0.55.0;x/netandx/textmove with it as indirect dependencies.- The chart's NetworkPolicy governs the control-plane pod only. Its
podSelectormatched
app.kubernetes.io/name+instance— the two labels the shared helper puts on every pod the
release creates, and that sandbox pods can carry through caller-supplied labels. Any pod in the
release namespace carrying those two labels inherited the control plane's ingress rules (every non-http inbound port
denied) and, because NetworkPolicy allows are additive, its egress allowances. The selector now
also pinsapp.kubernetes.io/component: control-plane, which the pod template already carried; the
Deployment's own immutablespec.selectoris untouched, so upgrades apply cleanly.
Added
-
k8s.rbac.create(defaulttrue).falserenders no Role/RoleBinding/ClusterRole/
ClusterRoleBinding and nothing ink8s.runsNamespace, for a platform that provisions runner RBAC
out of band and refuses cluster-scoped objects from tenants. TheserviceAccount.create=false
without a name refusal stays either way. -
ingress.*— an optional Ingress for the console'shttpport (class, annotations, hosts,
TLS). Off by default; the render is unchanged until enabled. The UI-sandbox gateway keeps its own
hand-authored Ingress on its own hostname by design. -
secrets.ageKeySecretRef— the age identity from its own Secret, independent of the DSN
Secret, for a DSN a managed-Postgres operator owns and no one can add a key to. Naming it alongside
ageKeyFromSecret/ageKeyis refused at render: two Secrets, one identity, and booting under the
wrong one is unrecoverable. -
defaultPolicy— the default RunPolicy as JSON text (--set-file defaultPolicy=my.json),
rendered into a ConfigMap, mounted read-only, withWARDYN_DEFAULT_POLICYpointed at it and a
checksum annotation that rolls the pod on change. Until now the only chart-level choice was one of
the files baked into the image, whose shipped floor is CC2 — unadvertised on any cluster with no
k8s.runtimeClassespinned. -
WARDYN_K8S_ACK_AMBIENT_DEFAULT_DENY=1— an acknowledgement, distinct from
WARDYN_K8S_ALLOW_UNENFORCED_NETPOL, for the one canary shape a tenant cannot fix: the baseline
phase's pod ran and could not reach the API server because the namespace already carries a
default-deny NetworkPolicy the platform team owns. Boot proceeds, the log says loudly that
enforcement is acknowledged rather than proven, and the setup page shows it as awarnrow. A
canary pod that never started still refuses boot with no override. The refusal message now names
the acknowledgement next to the exemption it already named. -
A
confinement_floorsetup row that warns when the default policy's floor is a class this
runner does not advertise — every run on the default policy would be refused before launch — and
names the two remedies (lower the floor viadefaultPolicy/WARDYN_DEFAULT_POLICY, or pin a
RuntimeClass). -
A
not_runverdict for the setup connectivity probe, distinct fromblocked: the probe
sandbox never started (an image pull, a confinement class this host cannot enforce), so nothing was
learned about the network. The console says so instead of "fix the proxy". -
OIDC public clients.
WARDYN_OIDC_CLIENT_SECRETis optional; without it the token exchange
runs as a public client (client_idin the body, PKCE S256 — which every login already sent). -
GOPROXYbuild arg on every Go builder stage, plumbed throughmakeand Compose like
NPM_REGISTRY; empty is identical to unset. -
The release pipeline can be rehearsed.
release.ymlgains a
workflow_dispatchwithdry_run(default true): it builds every image, the
CLI cross-builds and the chart package, runs the SBOM merge and its zero-npm
assertion, and pushes, signs, attests and uploads nothing.This pipeline could previously only be exercised by tagging, so its bugs were
unobservable until a real tag pushed — which is why 0.6.2 shipped images with no
provenance and 0.6.3 shipped an SBOM that understated its own contents. Both
would have failed a dry run.make release-checkwas green every time, because
it validates the repository, not the workflow.
Fixed
- The setup connectivity probe blamed the proxy for its own failures. It pulled
agent-claude-code— an image the project deliberately stopped publishing in 0.6.2 — and dispatched
at the default policy's CC2 floor, so on a stock managed cluster it failed at the image pull or at
no confinement substrate can enforce class "CC2", and both surfaced as Blocked under the
proxy heading with "fix the proxy above". It now runs the publishedagent-baseimage (override
keybaseinWARDYN_AGENT_IMAGES) at the strongest class the runner actually advertises — the
probe tests egress, not the floor — and a sandbox that never started isnot_run, neverblocked.
agent-base'sagent-runstub honoursWARDYN_TASK_MODE=execso it can carry the probe;
make setupbuildswardyn/agent-base:localon the from-source path and the Compose stack maps
thebasekey to it. - A failed
run.completeread as a clean exit. The probe decoded the failure event's missing
exit_codeas0and reportedreachedfor a run whose watcher had errored. email_verifiedabsent is no longer "false". WithWARDYN_OIDC_EMAIL_DOMAINSset, an
id_token with noemail_verifiedclaim at all — the norm for Entra ID — denied every login with
the message for a claim the IdP had set tofalse. Absent is its ownemail_verified_absent
outcome: the operator gets a server-side warning naming the claim, the issuer and the variable; the
user is told the provider sent no claim and to ask for App Roles instead of "verify your email".
The sign-in copy also named a variable that does not exist (WARDYN_OIDC_ALLOWED_EMAIL_DOMAINS).- The setup barrier picker says it is a browser-local default. Its instruction read as if it
set the server's floor; it never did (the footnote below it already said so). NPM_REGISTRYwas bypassed by the npm self-upgrade. The agent image Dockerfiles ran
npm install -g npm@<version>beforenpm config set registry, so behind a mirror that does not
proxy the public registry the build failed on its first install. The registry is set first.
Not in this patch: an operator-configurable model-provider base URL (an internal OpenAI-compatible
gateway as a first-class provider) — the supported path today is the EgressRedirect header-injection
lane, documented in docs/OPERATIONS.md; a Gateway-API HTTPRoute variant of ingress.*.