Git, CI/CD & GitOpsCV · GitOps Anti-PatternsPlaintextSecrets
Plaintext secrets in Git — the secrets that the repository remembers
What you'll learn
- Identify the four shapes a plaintext secret in a GitOps repository takes
- Apply pre-commit and CI scanning to catch secrets before they reach the default branch
- Distinguish Sealed Secrets, SOPS, and External Secrets as the three replacement patterns
- Design a vault-bound reference for credentials the controller cannot store locally
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
Git remembers. A secret committed to a Git repository in plaintext is a secret the repository returns to every clone, every fork, every CI runner that has ever fetched it, every backup the team has ever taken, every laptop that has ever held a working copy. The credential does not have to be exfiltrated by an attacker; it is exfiltrated by the act of being committed.
The four shapes the violation takes
A plaintext secret in a GitOps repository can take any of four shapes, each equally recoverable by anyone who clones the repository at any point in its history.
flowchart LR
A["Kubernetes Secret in YAML"] --> E["Plaintext in repo"]
B[".env file committed"] --> E
C["Helm values.yaml credential"] --> E
D["Cloud key in CI variable"] --> E
E --> F["Every clone inherits"]
E --> G["Every CI runner fetches"]
E --> H["History retains after removal"]
- Kubernetes Secret manifests. A
Secretresource withstringData.password: hunter2. .envfiles. A.envfile in the chart’s templates directory.- Helm
values.yamlcredentials. Avalues.production.yamlwith database credentials. - CI variable files. A
terraform.tfvarscommitted for “convenience”.
All four converge: the credential lives in a Git commit, and the Git commit lives forever.
The encrypted or referenced replacement
The replacement is one of three patterns. Each replaces the value in the repository with a reference whose key holder lives outside the repository.
sops --encrypt --age "$SOPS_AGE_RECIPIENT" \
secret.yaml > secret.enc.yaml
- Sealed Secrets. A controller-side key holder. The
controller runs
kubesealagainst the plaintext to produce aSealedSecretwhose ciphertext decrypts only on the target cluster. Cluster-bound; not portable. - SOPS. A file-level encryption tool. The repository holds the ciphertext; the controller holds the private key. Multi-recipient; portable; diff-friendly.
- External Secrets. A controller that synchronises
secrets from an external vault (AWS Secrets Manager,
HashiCorp Vault). The repository holds an
ExternalSecretmanifest.
The choice depends on the trust boundary. The wrong choice is to commit plaintext and call the choice “temporary”.
The pre-commit and CI gates
The replacement patterns assume the secret never reaches the repository. The gates that enforce that assumption:
gitleaks protect --staged --redact
gitleaks protect --staged runs against the staged changes
in a pre-commit hook and blocks the commit if a secret is
detected. The same scanner runs in CI on every pull request;
the merge button is disabled when a secret is found.
The discipline has three layers:
- Pre-commit. The developer’s first defence.
- CI scan. The repository’s defence.
- Forge scanning. The vendor’s defence. The alert arrives after the merge.
Production discipline
- No plaintext secrets in the repository. Ever. The exception process is the rotation, not the commit.
- Pre-commit, CI, and forge scanning are all on.
- The reference pattern matches the trust boundary. Sealed Secrets for cluster-bound, SOPS for multi-cluster, External Secrets for credentials that already live in a vault.
- Rotation is the response to a leak. Removing the secret in a later commit is not the response.
Cross-course references
- This course, Part LXXXII (GitOpsSecrets) - Sealed Secrets, SOPS, External Secrets in depth.
- This course, Part CIII (CommittedSecrets) - pre-commit and CI scanning, the rotation response.
Quiz
Knowledge check · 4 questions
Q1. An engineer commits a Kubernetes Secret with a database password to the GitOps repository in plaintext, then removes the file in a later commit. What is the state of the credential?
Q2. A pre-commit hook is a sufficient defence against plaintext secrets in a GitOps repository.
Q3. Name the three replacement patterns for plaintext secrets in a GitOps repository and the trust boundary each fits.
Q4. Diagnose a plaintext-secret incident and recommend the architecture that prevents it.
An engineer commits a `.env.production` file with a database password to the GitOps repository's templates directory. The commit is merged. A week later, a former contractor's laptop, which still holds a clone from three months ago, is compromised. The contractor never had access to the production database; the credential they exfiltrated did.
Passing score: 75%. Answers are checked in this browser.