From 72dd99ebb5b65d707cebb213952d75c5391d9047 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 4 Aug 2026 23:53:55 +0200 Subject: [PATCH 1/2] docs: point Star History at our self-hosted instance [skip ci] Charts now render from https://stats-github.ever.works/svg instead of the shared public api.star-history.com, so they are backed by our own GitHub PAT quota and our own uptime, and are cached for 24h at the edge. Also fixes the repo name: the chart pointed at ever-co/directory-web-template, which is not this repo (it lives in ever-works). README-only change, so [skip ci] keeps it off the build runners. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11a1ceaea..964f1dbab 100644 --- a/README.md +++ b/README.md @@ -791,7 +791,7 @@ You can also view a full list of our [contributors tracked by Github](https://gi ## ⭐ Star History -[![Star History Chart](https://api.star-history.com/svg?repos=ever-co/directory-web-template&type=Date)](https://star-history.com/#ever-co/directory-web-template&Date) +[![Star History Chart](https://stats-github.ever.works/svg?repos=ever-works/directory-web-template&type=Date)](https://stats-github.ever.works/#ever-works/directory-web-template&Date) ## ❤️ Powered By From ca6927d44b5d70b73a1ae421a7b813915da8447e Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Fri, 7 Aug 2026 15:36:42 +0200 Subject: [PATCH 2/2] fix(ci): stop leaking secrets into k8s metadata via client-side kubectl apply (#1006) Both secret-creating steps in deploy_k8s.yaml piped into a plain `kubectl apply -f -`. Client-side apply stamps the entire object into a `kubectl.kubernetes.io/last-applied-configuration` annotation, so for a Secret the payload ends up a second time, in cleartext-base64, in the object's METADATA - where it leaks through `-o yaml` dumps, Velero snapshots and any tool that redacts `data:` but renders annotations verbatim. This was found live on 2026-08-07: 109 of 122 dockerconfigjson secrets on ever-k8s carried the annotation with a usable GHCR token in it. - `${WORK_SLUG}-pull` duplicated the registry credential. - `${WORK_SLUG}-runtime-env` is worse: it materialises EVERY forwarded repo secret (DATABASE_URL, AUTH_SECRET, COOKIE_SECRET, ...), so it duplicated the app's whole secret set. Both now use `kubectl apply --server-side --force-conflicts`, which records ownership in `.metadata.managedFields` and writes no annotation. `replace --force` also avoids it but deletes and recreates the object, leaving a window where a starting pod finds no pull secret. The namespace, deployment, service and ingress applies are deliberately left as client-side apply - they carry no secret material. Co-authored-by: Claude Opus 5 --- .github/workflows/deploy_k8s.yaml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_k8s.yaml b/.github/workflows/deploy_k8s.yaml index f827713ce..20345454a 100644 --- a/.github/workflows/deploy_k8s.yaml +++ b/.github/workflows/deploy_k8s.yaml @@ -239,12 +239,22 @@ jobs: # the chain for back-compat with older platform pushes. REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD || secrets.GITHUB_READ_PACKAGES_TOKEN || secrets.GITHUB_TOKEN }} run: | + # 🛑 --server-side is REQUIRED here, do not "simplify" it back to a plain + # `kubectl apply -f -`. Client-side apply stamps the ENTIRE object into a + # `kubectl.kubernetes.io/last-applied-configuration` annotation — for a Secret + # that means a second, cleartext-base64 copy of the registry credential in the + # object's METADATA, where it leaks through `-o yaml` dumps, Velero snapshots + # and any tool that redacts `data:` but renders annotations verbatim. + # Found across 109 secrets on ever-k8s, 2026-08-07. Server-side apply records + # ownership in `.metadata.managedFields` instead and writes no annotation. + # `replace --force` also avoids the annotation but deletes and recreates the + # object, leaving a window where a starting pod finds no pull secret. kubectl create secret docker-registry "${WORK_SLUG}-pull" \ --docker-server="$REGISTRY_HOST" \ --docker-username="$REGISTRY_USERNAME" \ --docker-password="$REGISTRY_PASSWORD" \ -n "$NAMESPACE" \ - --dry-run=client -o yaml | kubectl apply -f - + --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts -f - - name: Provision runtime-env secret # Materialize every repo secret as a Kubernetes Secret that the @@ -304,8 +314,13 @@ jobs: ARGS+=(--from-literal=COOKIE_DOMAIN="${INGRESS_HOST}") fi if [[ ${#ARGS[@]} -gt 0 ]]; then + # 🛑 --server-side is REQUIRED here — see the note on the image-pull secret + # above. This one matters even more: this Secret carries EVERY forwarded repo + # secret (DATABASE_URL, AUTH_SECRET, COOKIE_SECRET, …), so a client-side + # `apply` would duplicate the app's entire secret set in cleartext-base64 in + # the object's metadata annotation, not just a registry credential. kubectl create secret generic "${WORK_SLUG}-runtime-env" -n "$NAMESPACE" \ - "${ARGS[@]}" --dry-run=client -o yaml | kubectl apply -f - + "${ARGS[@]}" --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts -f - # Log keys (not values) so we have a paper trail of what # was forwarded on this deploy. printf 'Provisioned %s-runtime-env (%d keys): %s\n' \