A Kubernetes Secret isn't encrypted. It's base64. And yours might be in the web root.

There's a word in the Kubernetes object that does a lot of damage: Secret. It sounds protected. It implies the values inside are safe to commit, safe to copy, safe to leave lying around. They are not. A Kubernetes Secret stores its values in base64, and base64 is not encryption. It's an encoding, reversible by anyone, in one command, with no key. The name is the most expensive false sense of security in the ecosystem.

So when a secret.yaml ends up in a directory your web server hands to the public, every credential in it is readable. Not "potentially crackable." Readable. echo <value> | base64 -d and you have the database password, the API key, the TLS private key, the registry pull token, in plaintext, the same way the attacker does. The manifest was never meant to leave your cluster config. It ends up on the web more often than you'd think, and the encoding fools people into treating the leak as minor.

What's actually in a served Secret manifest

A Secret manifest is small and dense. Every line under data: is one credential, base64-encoded, and the type field tells you what kind.

request
GET /k8s/secret.yaml HTTP/1.1 Host: yoursite.com
response
HTTP/1.1 200 OK
apiVersion: v1
kind: Secret
type: Opaque
data:
DB_PASSWORD: cHJvZF9TM2NyM3RfMjAyNg==
STRIPE_KEY: c2tfbGl2ZV81MUh4UXAuLi4=
cHJvZF9TM2NyM3RfMjAyNg== decodes to prod_S3cr3t_2026. No key, no cracking. base64 -d and it's plaintext.

The data values decode straight to the real thing. A kubernetes.io/dockerconfigjson Secret hands over the credentials your cluster uses to pull private images. A kubernetes.io/basic-auth or a generic Opaque Secret holds whatever your app needed: database URLs, third-party API keys, JWT signing secrets, OAuth client secrets. The one type that's less alarming is kubernetes.io/tls, where the "secret" is a certificate plus a private key, still sensitive, but a different story, which is why a careful check treats it separately from the credential-bearing kinds.

How a cluster Secret lands on the public web

Nobody deploys a Secret manifest to their web root on purpose. It gets there through the boring paths, which is why it's worth knowing them.

The usual one is a Git checkout that became a docroot. Your deploy clones the repo, and the k8s/, manifests/, or deploy/ directory with all the YAML rides along, sitting under the public root because the web server points at the repo top instead of a public/ subfolder. Now /k8s/secret.yaml is a URL. Another path is a developer dropping a secret.yaml into static/ or public/ to "deploy it later" and forgetting. A third is a CI artifact or a backup that includes the manifests getting unpacked somewhere served. The common thread is that the file was authored for kubectl apply, not for HTTP, and something put it on the wrong side of the web root.

Two fixes, and one of them is mandatory

The immediate fix is to get the manifest off the web. The deeper fix is to stop storing live secrets in plaintext-decodable YAML at all.

# deploy clones the repo; web root = repo top
# /k8s/secret.yaml is now a public URL with base64 data:
data:
DB_PASSWORD: cHJvZF9TM2NyM3Q=   # decodes instantly
Keep manifests out of any served directory, and keep real secret values out of the manifest entirely.

First, point your web root at a dedicated public/ directory so the cluster config simply isn't in the served tree. Second, treat any Secret manifest that held real values as a credential leak: rotate everything in it, because base64 means whoever fetched it already has the cleartext. Third, and this is the durable fix, stop putting live secret values in data: at all. A Sealed Secrets operator or an external secrets manager lets the manifest you commit and deploy hold only an encrypted reference that's decrypted inside the cluster, so even a leaked file is inert. The manifest becomes genuinely safe to expose, which the base64 version never was.

This surfaces in a scan because the file, when it's served, is unmistakable: a kind: Secret with apiVersion: v1, a credential-bearing type, and a populated base64 data: value. SurfaceCheckr requests the common manifest paths and flags one only when all of those line up, with the canonical Kubernetes-docs placeholder values and the kubernetes.io/tls type filtered out, so a template or an example file doesn't fire. It reads enough of the YAML to confirm it's a real Secret and stops there; it never decodes your values or pulls the whole file. For the related case of cloud credentials sitting in the web root as their own files, terraform.tfstate, .aws/credentials, and the infra configs that leak is the neighbouring read, and why base64 keeps fooling people into shipping reversible secrets runs underneath both.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.