Uh oh!
There was an error while loading. Please reload this page.
sec(secrets): resolve clientId/clientPassword from the existing Secret - #859
Conversation
Both were `required` in the template AND in values.schema.json's top-level
`required` with `minLength: 1`. So they had to be supplied as values on every
install and every upgrade -- which meant they were necessarily written into the
Helm release's user-supplied values, in cleartext, in EVERY retained revision
(10 by default). Rotating the credential did not clear the older revisions, and
anyone with `get secret` in the namespace could read all of them. Measured: 3
fleets x 10 revisions.
They are now resolved in three tiers, the shape this file already uses five
times over (podTokenSigningSecret, credmgrPassword, tbMetaPassword,
tbIngestPassword, bootstrapDbPassword):
1. explicit values, else
2. the CLIENT_ID / CLIENT_PASSWORD keys already in the live Secret, else
3. fail, naming both remedies.
Tier 3 is a HARD FAILURE, not `randAlphaNum`: the backend issues these, so the
chart must never invent one. An operator can now pre-create the Secret, or
install once with values and drop them afterwards, and the credential never
enters release values.
THE SCHEMA WAS THE REAL ENFORCEMENT. Changing only the template was a no-op --
JSON Schema validation runs BEFORE rendering, so an absent or empty value was
rejected before `lookup` could fire and the template's own refusal was dead
code. Removing clientId/clientPassword from `required` and dropping
`minLength: 1` is what actually makes tier 2 reachable. Enforcement moves to the
template, which is the only layer that can see the Secret; placeholder
rejection stays in the schema and is now also applied to the RESOLVED value, so
a badly pre-created Secret is caught too.
Tests. The unit suite CANNOT observe tier 2: `lookup` is inert under
helm-unittest, and deleting the whole tier-2 branch leaves all 30 unit tests
green. So the mechanism is covered where it can be -- a new path 5 in
scripts/tests/e2e-auto-upgrade.sh upgrades a real k3d install with
`--reset-values` (every user value discarded, so the credentials are genuinely
absent) and asserts they survive in the Secret. If they do, the lookup is the
only thing that could have supplied them.
Also fixes vacuous assertions found while doing this:
* `failedTemplate` with `errorPattern` is SILENTLY IGNORED by helm-unittest
0.5.2 -- a pattern appearing nowhere in the output still passes. Proven by
mutating two messages that such tests claim to assert: 49 tests stayed
green. Converted this file's two to `errorMessage` (honoured, exact) and
re-ran the same mutation: it now reddens. Six more live in five other test
files and are filed separately rather than dragged into this PR.
* "should reject empty credentials" was passing on SCHEMA validation, not on
anything in secrets.yaml -- its bare `failedTemplate: {}` could not tell the
difference, and this change moved which layer refuses it.
* Placeholder tests stay bare, deliberately: schema failures surface as a
chart-load error that no message assertion can match. Commented rather than
dressed up.
Evidence: 579/579 unit tests pass; helm lint clean; offline render without
credentials fails with the tier-3 message at secrets.yaml:40; with credentials
renders 56 documents carrying CLIENT_ID/CLIENT_PASSWORD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
saqlainsyed007
left a comment
There was a problem hiding this comment.
Direction is right and public-repo hygiene is clean — reading the credentials from an existing Secret so they never live in release values is exactly the backend#2571 goal. Requesting changes on one security-correctness gap, with two workflow issues to fix-or-scope. Details are in the three inline threads.
Blocker — empty stored key silently renders an empty credential (templates/secrets.yaml:38, and 46 for the password). The old code used required, which fails on an empty string. Tier 2 now takes the branch on hasKey $existingSecret.data "CLIENT_ID", and hasKey is true for a present-but-empty key — so a Secret carrying CLIENT_ID: "" resolves $clientId = "", the tier-3 fail (absent-key only) never fires, and the placeholder guard regexMatch "^<.*>$" doesn't match "". The chart then renders an empty credential and the client authenticates with nothing. Dropping minLength/required left no non-empty check on the resolved value. Please restore one (e.g. fail when the resolved $clientId/$clientPassword is empty), so the PR's own promise — "a badly pre-created Secret is caught" — actually holds.
Please address or explicitly scope in docs:
Greenfield adoption (
values.yaml:999): the documented preferred flow (pre-create the Secret out of band, empty values) fails a firsthelm install— Helm 3 won't adopt akubectl-created Secret that lacksapp.kubernetes.io/managed-by: Helm, aborting with "invalid ownership metadata". Tier 2 only works once Helm already owns the Secret (install-with-values-then-drop). Worth stating that ordering in the README, or the "preferred" label is misleading.GitOps /
helm template(secrets.yaml:40):lookupis inert underhelm template,--dry-run, and client-side ArgoCD/Flux rendering, so tier 2 can't fire there and operators must keep clientId/clientPassword in values — the cleartext-in-revisions problem this PR targets. If GitOps isn't a supported render path for this chart, a one-line caveat is enough; if it is, tier 2 alone doesn't cover it.
Two minor, non-blocking (not posted inline): tier 2 (the central mechanism) has no green test yet — its only coverage is e2e path 5 with the checkbox unchecked; per the repo's "evidence, not assertion" bar, land that run before merge. And path 5's helm upgrade --reset-values discards overrides set by earlier paths — harmless as the last path today, a latent trap if another is appended after it.
…dential Three Bugbot findings on #859, all real. Each one is a consequence of the same change -- dropping `required`/`minLength` so tier 2 could run -- so they are fixed together rather than singly. 1. HIGH: the preferred remedy could not work on a first install. secrets.yaml ALWAYS emits `<release>-secrets`, so the values.yaml advice to `kubectl create secret` before installing produced a Helm ownership error, and the tier-3 message named that same broken remedy. Helm adopts a pre-existing object carrying its three ownership fields, so values.yaml now gives the label + annotate commands alongside the create, says the names must match the install exactly, and leads with the simpler install-once-then-drop path. The fail message points at it. 2. HIGH: dropping clientId from values hid the live client. detect_installed_client read "values readable, no clientId" as NOT A CLIENT -- true while clientId was `required`, false the moment this chart told operators to drop it. The one-client guard compares on a non-empty id, so a client installed the new way was invisible and a re-run could re-point the machine. The id is now read from the release Secret when values do not carry it, and a client-chart release naming an id in NEITHER place is UNKNOWN (fail closed), not absent. Same fix in the PowerShell peer, which had the identical null-clientId `continue`. 3. MEDIUM: an empty Secret key counted as resolved. Tier 2 keyed off `hasKey`, so `CLIENT_ID: ""` resolved and helm shipped blank credentials -- the case `minLength: 1` used to catch before it had to come out. Tier 2 now tests the DECODED value, so an empty key reads as absent and falls to tier 3. TESTS. detect_installed_client's own scanning loop had no test at all -- every existing test stubs the function out, which is why nothing caught (2). Four added, and each is mutation-proven: removing the Secret fallback reddens 2 of them, dropping the fail-closed reddens the other 2, and restoring returns all four green. The empty-key path is unobservable to helm-unittest (`lookup` is inert there, as this PR already documents), so it is asserted in e2e-auto-upgrade.sh where tier 2 is real: blank the key, upgrade, require the failure AND require it to name clientId. Chart.yaml 1.9.72 -> 1.9.73 (version + appVersion) for the version-bump gate; manifest.sha256 regenerated for the two installer scripts. Verified: helm-unittest 579/579, make lint 0, make drift 20/20 guards green, helm-lint/vocab/template clean, the 4 new bats tests green + both mutations. NOT verified locally: Pester and the full bats file both hang on this Mac at `DEFAULT (TB_STORAGE_MODE unset)` -- reproduced on the UNMODIFIED PR head, so it is environmental, and neither runs on darwin in CI (ubuntu + windows only). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
…ts there Saqlain's third thread on #859, the one no code change answers: tier 2 is a `lookup`, so it needs a live cluster. The template already said that about `helm template`/`--dry-run`; it did not say the same limit applies to every CLIENT-SIDE RENDERER -- ArgoCD's default renderer and Flux post-render among them -- and that is where it actually bites someone. The asymmetry is the part worth writing down. The chart's other five credentials survive an inert `lookup` because their tier 3 is `randAlphaNum`, so a value-free render degrades to a generated secret. clientId/clientPassword cannot: the backend issues them, so tier 3 is a hard `fail` and the render STOPS rather than degrading. A GitOps install therefore has to keep both in its values -- which means the cleartext-in-revisions problem this PR fixes is NOT fixed for that deployment style. Said plainly in both places an operator reads, because a limitation discovered at install time is a support ticket: values.yaml (planning) and secrets.yaml (the mechanism). Both name what makes the value-free path reachable -- render against a live cluster -- and secrets.yaml names the out-of-scope remedy for the rest (an external secrets operator writing the Secret before render) so the gap is bounded rather than open. Docs only; no template logic changes, so the resolution tiers and their tests are untouched. Verified: make check green (parse, shellcheck, drift 20/20, helm-lint, chart-env-vocabulary 50/50) and helm-unittest 579/579 on this tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LukasWodka
commented
Aug 27, 2026
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 13ab07f. Configure here.
saqlainsyed007
left a comment
There was a problem hiding this comment.
Re-reviewed at 13ab07f. The blocker is resolved — thanks for the thorough fixes.
Blocker (empty stored key → empty credential): verified fixed. Tier 2 now gates on the decoded value's truthiness, not hasKey: a pre-created Secret carrying CLIENT_ID: "" decodes to $secretClientId = "", the {{- else if $secretClientId -}} branch is falsy, and resolution falls through to the tier-3 fail. An empty key correctly reads as ABSENT instead of silently rendering a blank credential into the pod. Same for clientPassword. The placeholder guard running on the resolved value is a good addition. All three of my threads (greenfield adoption ordering, the empty-key blocker, the GitOps/helm template caveat) are addressed and resolved — I'm satisfied on the code.
Holding my approval only on CI — and it's this PR's own test. The Pester suite is red (ubuntu + windows) with exactly one failure:
[-] values without a clientId key do not trip the guard — it aborts with InvalidOperationException: A 'break' or 'continue' statement with a label that does not match any enclosing loop, which per Pester #2669 kills the block with no result rather than asserting. That's the test covering the very guard this PR changes, so it needs to go green (the mislabeled break/continue looks like the immediate cause). Unit tests / bats / Bugbot are still pending too.
Get CI green and I'll approve — the code side is no longer blocked on me.
Pester went red on both ubuntu-latest and windows-latest at e0981f4 and it was a real regression, not the environment. The commit that fixed Bugbot's fail-open finding changed detect_installed_client's contract and left the PowerShell test asserting the OLD one: [-] values without a clientId key do not trip the guard That test read: a readable client release carrying no `clientId` is NOT a client, so the installer upgrades over it. True only while `clientId` was `required`, which made a clientId-free client release impossible. This PR drops that requirement and tells operators to remove clientId from values once the Secret holds it -- so the test was pinning the exact fail-open the finding was about. Both earlier attempts at this fix missed it, because Pester does not run on darwin. REPRODUCED LOCALLY FIRST, rather than inferred from the CI log. pwsh 7.5.2, filtered to the one test: the installer prints "Refusing to replace an unidentifiable existing client" and refuses -- which is the new contract working exactly as intended, failing a test that wanted the old one. The test is rewritten to the new contract, not the fix reverted: * renamed to what it now checks -- fails CLOSED on a readable client release with no clientId in values or Secret. `kubectl` has no cluster under Pester, so Get-ClientIdFromSecret returns "" and the both-places-empty path is what gets exercised, which is the case worth pinning. * NAMES THE REFUSAL (CLAUDE.md rule 10). Every other fail-closed path in this Describe also throws, so a bare `Should -Throw` would have passed on the wrong refusal -- an unreadable-values or garbage-list abort is indistinguishable from the one the test is named for. It asserts `Should -Invoke Err -ParameterFilter { $m -match 'unidentifiable existing client' }` as well as the throw and the absent upgrade. * carries the why above it, so the next reader does not "fix" it back. MUTATION-PROVEN, and the anchor was asserted rather than assumed: restoring the pre-fix `if ($null -eq $vals -or $null -eq $vals.clientId) { continue }` reddens it (PASSED=0 FAILED=1); restoring the fix greens it. The mutation script aborts if its anchor does not match, so an inert mutation cannot read as coverage. Also dropped the trailing bare `continue` e0981f4 added. It was the last statement of the loop body, so it bought nothing -- and PowerShell reported it escaping as an unmatched loop label (pester/Pester#2669), which aborts the whole run rather than failing one test. The two `continue`s above it are load-bearing and stay. manifest.sha256 regenerated for install-k8s.ps1. Verified on this tree: full Pester suite 784 total / 770 passed / 0 failed / 14 skipped (was 1 failed), and `make check` green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bugbot Medium on #859, and it is right that this PR is what makes it matter. `_client_id_from_secret` / `Get-ClientIdFromSecret` issue a fresh `kubectl get secret` with no `--request-timeout`, and kubectl's default is no timeout at all -- it waits forever. WHAT CHANGED THE RISK. Before this PR the fallback did not exist; the id was always in release values. This PR tells operators to DROP clientId from values, so once they do, every scanned client release reaches this call on every detect_installed_client / Get-InstalledClientInfo. That runs in the pre-provision pre-flight and the Helm-step one-client guard, both of which can run headless -- so a wedged API server hangs the install with no further output and no way to tell what it is waiting on. The enumeration around it is already bounded (Test-ApiReachable, and the `helm list` gate that documents exactly this hazard); this one call was the hole left in it. --request-timeout=5s on both twins. 5s is this repo's existing figure for a cheap existence probe rather than a new number -- install-k8s.ps1 already uses it for the namespace, daemonset and allocatable-GPU reads. A TIMEOUT LANDS IN A PATH THAT ALREADY EXISTS, which is why this is a one-flag change: kubectl exits non-zero, which both twins already treat as "could not read" (`|| return 0`, `return ""`). The caller turns that into an UNIDENTIFIABLE client, not an absent one, so a timeout fails CLOSED -- the guard refuses rather than waving through an install that re-points a live machine. The fail-open this PR set out to close is not reopened by the fix for it. Fixed in both languages. The bash and PowerShell readers are twins by design (they name each other), and fixing one would split them -- the divergence class backend#2220 found five of. VERIFIED THE STUBS STILL MATCH, because that was the real risk of adding an argument. install-client-helm.bats:875 keys its kubectl stub on POSITION -- `[ "$5" = "liverel-secrets" ]` -- so a flag inserted mid-vector would have silently broken three tests. The flag is appended last, leaving $1..$7 intact; exercised against a copy of that exact stub, `_client_id_from_secret liverel munich` still returns `uuid-from-secret`. Both no-kubectl and non-zero-kubectl (the timeout shape) still return empty with rc 0, as the contract requires. manifest.sha256 regenerated for both scripts. Verified: full Pester 784 total / 770 passed / 0 failed / 14 skipped; `make check` green; `bash -n` + `shellcheck -S warning -x` clean on the bash twin; install-k8s.ps1 parses clean. The full bats file hangs on this Mac before reaching these tests (reproduced on the unmodified PR head by a previous session, and bats runs on ubuntu in CI, not darwin) -- hence the direct stub-fidelity check above rather than a claim I did not make. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LukasWodka
commented
Aug 27, 2026
bugbot run |
LukasWodka
commented
Aug 27, 2026
@saqlainsyed007 — all three of your threads are addressed and resolved, plus two things CI found after your re-review. Summary of what moved since Your threads
Two found after that, both real
Verified: full Pester 784 total / 770 passed / 0 failed / 14 skipped (was 1 failed); helm-unittest 579/579; The PR was also retitled (ticket number out of the subject) and the body now says |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 77f86fb. Configure here.
LukasWodka
commented
Aug 27, 2026
@saqlainsyed007 CI is green at
Re-requesting your review so the change-request can clear. |
saqlainsyed007
left a comment
There was a problem hiding this comment.
Approving at 77f86fb. Everything I raised is resolved and CI is green.
- The blocker (empty stored key → empty credential) stays fixed: tier 2 gates on the decoded value's truthiness (
else if $secretClientId→ tier-3fail), andtemplates/secrets.yamlis unchanged since I verified it at 13ab07f. - CI is green. The Pester failure I flagged —
values without a clientId key do not trip the guard, aborting on the unmatched-loop-label bug (Pester #2669) — is fixed by dropping the no-op trailingcontinueinGet-InstalledClientInfo, keeping the two real ones above. - The delta since my review is sound and I'd own it. Adding
--request-timeout=5sto thekubectl get secret CLIENT_IDread on both peers (install-k8s.ps1andinstall-client-helm.sh) is the right call now that this read is on the common path once clientId leaves release values — an unbounded read against a wedged API would hang a headless install, and the timeout falls into the same fail-closedreturn ""/return 0(unidentified client, never absent).manifest.sha256re-pinned to match.
All three of my original threads (greenfield adoption ordering, the empty-key blocker, the GitOps caveat) are addressed and resolved. Nice work on the credential-hygiene hardening — LGTM.
Uh oh!
There was an error while loading. Please reload this page.
saadqbal
left a comment
There was a problem hiding this comment.
Answering the question you asked directly: yes, relaxing the schema is acceptable, and it's a net increase in enforcement rather than a trade. I checked the two things that decide it.
Nothing the schema used to catch is now uncaught. minLength: 1 refused an empty value; the template refuses an empty resolved credential by truthiness — if .Values.clientId / else if $secretClientId / else fail, where "" is falsy in both tiers, so an empty CLIENT_ID in a pre-created Secret reads as ABSENT and lands on the hard fail. Keying tier 2 off the decoded value rather than hasKey is what makes that true, and it's the difference between this and a version that ships blank credentials into the pods.
And the placeholder guards now run on $clientId/$clientPassword rather than .Values.*, so a Secret pre-created with <CLIENT_ID> is refused — a path the schema could never see. Strictly more coverage, one layer lower.
The rest I'd have raised is already in the file, which is why this reviewed quickly:
"The unit suite cannot observe tier 2." Deleting the entire tier-2 branch leaves all 30 unit tests green because lookup is inert under helm-unittest — measured, not assumed, and remedied by putting the coverage on real k3d with --reset-values. That's the check-that-can-observe-the-property problem, found and fixed by the author on their own PR. It's the single most common defect I've seen this week and normally it's the reviewer who has to find it.
The GitOps limit, stated rather than left to be discovered.lookup needs a live cluster, so ArgoCD's default renderer and Flux post-render hit tier 3 — and unlike the other five credentials there's no randAlphaNum to degrade to, so it stops rather than degrading. Saying outright that "a GitOps-rendered install must keep clientId/clientPassword in its values, and for it the cleartext-in-revisions problem this change fixes is NOT fixed" is the honest scoping, and naming the external-secrets mechanism that would close it while declaring it out of scope is the right place to stop.
Part of rather than Closes, because #2571 also carries rotations a PR can't perform — correct, and matching .github#354 which landed yesterday.
One thing I could not confirm:mergeStateStatus came back UNKNOWN/UNKNOWN on three separate reads several minutes apart, so I have no confirmation this is conflict-free — checks are green and there are no open threads, but treat the merge state as unverified rather than clean and re-check before merging. My approval is about the code. 👍
LukasWodka
commented
Aug 27, 2026
Correction to my own commit message on bats does not hang. It is slow — about 25 minutes on this machine — and completes: I inherited the claim from an earlier session's commit message on this branch ( This also retires a second inherited claim. What this changes about the evidence for this PR: nothing is weakened, and the four Secret-fallback tests are now covered behaviourally rather than by my hand-rolled stand-in. Test 68 is the one that mattered for the Full evidence for this head, all of it now actually run: bats 235/235, Pester 784 total / 770 passed / 0 failed / 14 skipped, helm-unittest 579/579, |
…9.74) Only manifest.sha256 conflicted (regenerated); develop #859 also reached chart 1.9.73, so bump to 1.9.74. GPU handling intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Part of tracebloc/backend#2571 — the structural half.
Deliberately not
Closes: #2571 also carries the Docker Hub PAT rotation and thecredential rotations, which are human actions this PR cannot perform. Auto-closing the
ticket on merge would retire those with it. Per org-standards (
.github#354), partialwork says
Part ofand keeps the ticket number out of the PR subject.The problem
clientIdandclientPasswordwererequiredin both the template andvalues.schema.json's top-levelrequired, withminLength: 1. They therefore had to be supplied as values on every install and every upgrade — which means they were necessarily written into the Helm release's user-supplied values, in cleartext, in every retained revision (10 by default).Measured on our own fleets: 3 fleets × 10 revisions, each a readable copy. Rotating the credential does not clear the older revisions, and anyone with
get secretin the namespace can read all of them. Unlike the chart's other five credentials, there was no way to avoid it.The fix
Three-tier resolution — the shape this file already uses for
podTokenSigningSecret,credmgrPassword,tbMetaPassword,tbIngestPasswordandbootstrapDbPassword:CLIENT_ID/CLIENT_PASSWORDkeys already in the live Secret, elseTier 3 is a hard failure rather than
randAlphaNum— the backend issues these, so the chart must never invent one. An operator can now pre-create the Secret, or install once with values and drop them on the next upgrade, and the credential never enters release values.The part worth reviewing closely: the schema was the real enforcement
Changing only the template was a no-op. JSON Schema validation runs before rendering, so an absent or empty value was rejected before
lookupcould fire — the template's own refusal was dead code and tier 2 was unreachable.So this PR removes both from the schema's
requiredand dropsminLength: 1. Enforcement moves to the template, which is the only layer that can see the Secret. Placeholder rejection stays in the schema and is now applied to the resolved value, so a badly pre-created Secret is caught too.I found this by mutation testing, not by reading. Worth a reviewer's eye on whether relaxing the schema is acceptable — my argument is that the template's tier 3 fails closed with a strictly better message and covers a path the schema cannot see.
Test coverage — including what is NOT covered
The unit suite cannot observe tier 2.
lookupis inert under helm-unittest: deleting the entire tier-2 branch leaves all 30 unit tests green. Measured, not assumed. Shipping on unit tests alone would have meant the central mechanism of this PR was unverified.So tier 2 is covered where it can be — a new path 5 in
scripts/tests/e2e-auto-upgrade.sh, which already installs on real k3d. It upgrades with--reset-values(every user value discarded, so the credentials are genuinely absent) and asserts they survive in the Secret. If they do, the lookup is the only thing that could have supplied them.Vacuous assertions fixed on the way
failedTemplatewitherrorPatternis silently ignored in helm-unittest 0.5.2. A pattern appearing nowhere in the output still passes. Proven by mutating two messages that such tests claim to assert — 49 tests stayed green. My own first draft usederrorPatternand was completely hollow.errorMessage(honoured, exact) and re-ran the same mutation: it now reddens.auto_upgrade,image_refresh,network_policy,priority_class_pdb,rbac). Filed separately rather than dragged into a credentials PR.should reject empty credentialswas passing on schema validation, not on anything insecrets.yaml. Its barefailedTemplate: {}could not tell the difference, and this change moved which layer refuses it.Test plan
helm unittest client— 579/579 pass, 34 suiteshelm lintcleansecrets.yaml:40CLIENT_ID/CLIENT_PASSWORDmysqlRootPassword must be alphanumeric→ reddens now, was green before the conversione2e-auto-upgrade.shpath 5 on a real k3d cluster — needs a CI run; GitHub runners were starved when this was writtenNote on rollout
This does not remove the credentials already in our fleets' release values. That is an operational follow-up on backend#2571, and it must be coordinated: dev and prod currently share the same
clientPassword, so it cannot be rotated per-fleet.Refs: backend#2571 · backend#947 · backend#1528
🤖 Generated with Claude Code
Note
High Risk
Changes how platform authentication credentials are sourced and relaxes JSON Schema while shifting enforcement to templates; installer one-client detection behavior inverts for id-only-in-Secret releases.
Overview
clientIdandclientPasswordno longer have to stay in Helm values on every upgrade. The secrets template resolves them in three tiers—explicit values, then keys on the live release Secret vialookup, then a hardfail—matching other credentials in the chart but without a generated fallback because the backend issues these values.Schema enforcement moved to the template.
clientId/clientPasswordwere removed from top-levelrequiredand lostminLength: 1so empty values can reach render and tier 2 can run; missing credentials are refused insecrets.yaml, and empty Secret keys count as absent so blank credentials cannot ship to pods.Install and upgrade guards follow the same contract. Bash (
_client_id_from_secret) and PowerShell (Get-ClientIdFromSecret) readCLIENT_IDfrom<release>-secretswhen it is missing fromhelm get values; a client-chart release with no id in values or Secret is treated as unidentifiable and blocks another install instead of being ignored.Tests and docs spell out limits. Helm unittest cannot exercise
lookup(tier 2); e2e-auto-upgrade path 5 upgrades with--reset-valuesto prove Secret resolution.values.yamldocuments Helm Secret adoption and that client-side GitOps renderers without cluster access must still keep these two in values.Reviewed by Cursor Bugbot for commit 77f86fb. Bugbot is set up for automated code reviews on this repo. Configure here.