Back to Blog

Secrets Belong in Git. Plaintext Does Not.

10 min read
On this page

Every GitOps rollout hits the same wall, usually in week two. The team agrees Git should be the source of truth. The cluster definition goes into a repo. The Helm values follow. Then someone tries to commit the Postgres password and the whole plan stalls in a meeting about “how we handle secrets,” and three sprints later half the configuration lives in Git and the other half lives in a vault nobody wired up, a CI variable nobody documented, and a .env file that gets passed around in Slack DMs.

We have written a lot on this blog about Git as the source of truth: pipeline agents that review cluster changes on pull requests, AI that proposes drafts instead of touching production, stacks that clone from a laptop to production. All of it assumes an answer to the question this post finally tackles head-on: if Git holds everything, where do the credentials go?

The answer is that they go in Git too, encrypted, with a workflow that makes the safe path the default path. Here is how that works, why the alternatives fail, and how Ankra makes it a one-time setup instead of a platform project.

The numbers got worse, and AI is making them worse faster

GitGuardian’s State of Secrets Sprawl report has been the industry’s yearly cold shower for half a decade. The 2026 edition is the coldest yet: 28.65 million new hardcoded secrets landed in public GitHub commits during 2025, a 34 percent jump year over year and the largest single-year increase they have ever recorded.

Two findings in that report matter specifically for infrastructure teams.

First, AI-assisted commits leak secrets at roughly double the baseline rate. Coding assistants reproduce the patterns they were trained on, and the training data is full of API keys pasted into examples and passwords hardcoded in test fixtures. As more of your YAML gets written by an agent, the odds of a credential slipping into a commit go up, not down.

Second, the report found six times more hardcoded secrets in internal repositories than in public ones. Teams behave as if a private repo is a security boundary. It is not, and there is a well-known incident that proves it.

”The repo is private” is not a secrets strategy

In April 2024, attackers got into the self-managed GitLab instance of Sisense, a business intelligence company whose product connects to its customers’ data sources. Inside that private repository they found hardcoded AWS credentials. Those credentials opened the company’s S3 buckets, and terabytes of customer data walked out: access tokens, passwords, SSL certificates. The breach was serious enough that CISA issued an alert telling every Sisense customer to rotate every credential they had ever shared with the platform.

Note what failed there. Not the firewall, not the cluster, not the cloud account. A private Git repository was treated as a safe place for plaintext credentials, and a single compromised repo converted into full cloud access.

The lesson is not “keep secrets out of Git.” Config that references secrets without containing them just moves the problem somewhere with less review, less history, and less audit trail. The lesson is that a secret in Git must be worthless to anyone who reads it.

And one more number, because it explains why leaks hurt for years instead of days: GitGuardian found that 64 percent of the secrets they detected in 2022 were still valid in January 2026. Most teams do not rotate. The secret that leaks today is the secret that gets used against you in three years.

Encrypt values, not files

The tool for this job is SOPS, and the detail that makes it the right tool is that it encrypts the values in a YAML file while leaving the keys and structure readable.

That distinction sounds small. It is everything. A fully encrypted blob cannot be reviewed: a pull request that changes it is a diff between two walls of base64, and your review process degrades to “approve and hope.” A SOPS-encrypted file keeps its shape:

apiVersion: v1
kind: Secret
metadata:
name: postgres-credentials
namespace: database
type: Opaque
stringData:
username: app_user
password: ENC[AES256_GCM,data:vxvYs7vKw+bPqNWO3tE=,iv:...,tag:...,type:str]
sops:
age:
- recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
encrypted_regex: ^(password)$
version: 3.9.4

A reviewer can see that this is a Secret, in the database namespace, that the username changed, and that the password is ciphertext. The commit is safe to push to any repo, public or private, because the value is encrypted with AES256 and only the holder of the private key can recover it.

For the key pair itself, Ankra uses AGE rather than PGP. AGE is a modern, deliberately boring encryption tool: one public key that encrypts, one private key that decrypts, no keyservers, no trust ceremonies, no twenty-year-old tooling to fight. The public key is shareable by design. The private key is the only thing that matters, and in Ankra’s setup, no human ever handles it.

The Ankra workflow: initialize once, encrypt by default

Most SOPS guides now ask you to generate keys, distribute them to every cluster, patch your CD tooling with a decryption plugin, and write a .sops.yaml policy file. That setup cost is exactly why teams postpone secrets hygiene until after the incident.

In Ankra it is one step. Go to Organisation Settings, open Encryption, and click Initialize Encryption. The platform generates an AGE key pair, shows you the public key, and stores the private key in its vault. From that moment:

  • Encryption is on by default. Any field you mark as encrypted is protected the moment you save.
  • Decryption is automatic. The Ankra agent on each cluster decrypts values at deploy time by running sops --decrypt directly, as part of Ankra’s native deployment engine. The AGE private key is fetched from the platform for the render and is not stored on the cluster, so there is no key Secret sitting in a namespace waiting to be read. There is nothing to configure per cluster, whether you run three clusters or fifty.

The flow, end to end: you type a plaintext value in the Stack Builder, Ankra encrypts it with your organisation’s public key on save, the ENC[AES256_GCM,...] ciphertext is what gets committed to your GitOps repository, and the agent decrypts it at deploy time. Plaintext exists in the editor and in the running workload. It never exists in Git, and the decryption key never lives on the cluster.

In the Stack Builder

When you edit a manifest or an addon’s Helm values, there is an Encrypted Keys (SOPS) section. Add the key names that are sensitive, password, apiKey, token, adminPassword, and enter values normally. Only the listed keys get encrypted; everything else stays readable, so reviews and debugging keep working.

From the CLI, for GitOps-first teams

If your cluster definition lives in a repo and you edit it locally, the Ankra CLI does the same thing in file mode. Encrypt a key in a Secret manifest before committing:

Terminal window
# Inspect the public key your org encrypts with
ankra cluster sops-config
# Encrypt the password in a manifest referenced by cluster.yaml
ankra cluster encrypt manifest db-secret --key password -f cluster.yaml
# Encrypt a sensitive Helm value on an addon
ankra cluster encrypt addon --name grafana --key adminPassword -f cluster.yaml

The CLI rewrites the referenced file with the encrypted value and records what it did in the cluster definition, so the platform knows which fields to decrypt at deploy time:

manifests:
- name: db-secret
from_file: "manifests/db-secret.yaml"
encrypted_paths:
- password
addons:
- name: grafana
chart_name: grafana
chart_version: 9.2.10
repository_url: https://grafana.github.io/helm-charts
namespace: monitoring
configuration:
from_file: "values/grafana.yaml"
encrypted_paths:
- adminPassword

Need to check what a value actually is? Decryption prints to stdout and nowhere else:

Terminal window
ankra cluster decrypt manifest db-secret -f cluster.yaml

Make plaintext a failed build, not a code review comment

Hygiene that depends on a reviewer noticing a credential at 5pm on a Friday is not hygiene. The check belongs in CI, where it is deterministic.

Ankra’s validation command has a flag for exactly this. In the pull request pipeline that reviews your cluster changes, run:

Terminal window
ankra cluster validate -f cluster.yaml --strict-secrets

Validation already checks your stack structure, chart references, and dependency wiring against the platform. With --strict-secrets, a plaintext Secret or an unencrypted sensitive value stops being a warning and becomes a failed job. The pull request goes red before a human ever looks at it, and the credential never reaches the default branch.

If you followed our pipeline agents guide, you already have this workflow running; the flag is in it. This is the piece most secrets tutorials skip, and it is the piece that actually changes outcomes, because it removes the human from the failure path.

The AI angle: agents that cannot leak what they never hold

Here is where the GitGuardian finding about AI-assisted commits stops being scary and starts being an architecture requirement. If AI writes a growing share of your configuration, and AI-assisted commits leak at twice the baseline rate, then the fix is not prompting the model to be careful. The fix is a pipeline where the unsafe output physically cannot land.

Ankra’s AI operates under the same contract as everyone else: it proposes drafts, humans approve, Git records the result. Sensitive values in those drafts are SOPS-encrypted before they leave the platform. The agent never holds your cloud credentials, and the commit it produces contains ciphertext, not keys. An AI that drafts a Grafana stack with an admin password produces an encrypted adminPassword, because the encryption step is in the platform, not in the model’s judgment.

Two related behaviors are worth knowing:

  • Secret variables are masking, not encryption. Ankra variables can be marked secret, which hides them in the UI. That is a convenience feature. For anything that must be encrypted at rest in Git, use SOPS. The platform docs are explicit about this distinction, and now you are too.
  • Clones do not carry secrets. When you clone a stack to another cluster or another organisation, encrypted values deliberately do not transfer. You reconfigure them in the target. Mildly annoying on purpose: the alternative is credentials silently propagating to environments they were never scoped for.

Rotate like you mean it

Remember the 64 percent of 2022’s leaked secrets that still worked in 2026? Encryption at rest solves the leak. Rotation solves the lifetime.

Ankra supports rotating the organisation’s AGE key pair from the same Encryption settings page: generate a new pair, re-encrypt existing content with the new public key, confirm, and the old private key is destroyed. Do it on a schedule, and do it when someone with access leaves. The workflow exists so that rotation is an afternoon, not a quarter.

The same discipline applies to the credentials inside the encrypted files. A database password that has been encrypted in Git for three years is still a three-year-old password. SOPS makes rotation safe to execute through the normal GitOps flow: change the value, the diff shows one key changed, the deploy rolls it out, and the history shows exactly when and by whom.

The five rules

If you take nothing else from this post:

  1. Secrets live in Git, encrypted. Anywhere else and you have two sources of truth, which is zero sources of truth.
  2. Encrypt values, not files. Reviews only work when reviewers can read the structure.
  3. No human touches the private key. Initialize once, let the platform hold it, let clusters decrypt automatically.
  4. Plaintext fails the build. --strict-secrets in CI, on every pull request that touches cluster config.
  5. Rotate on a schedule, not after an incident. Keys quarterly or on departure, credentials on their own clock.

A GitOps repository where every credential is ciphertext is a repository you can hand to a new hire, an auditor, or an AI agent without holding your breath. That is what the source of truth is supposed to feel like.

Set it up this afternoon: one click in Organisation Settings, Encryption, one flag in CI, done.


Get started: Create a free account on Ankra.

Join our community: Slack

Follow us on: LinkedIn | GitHub

Contact us: [email protected]

ShareXLinkedInHN

Get the next post in your inbox

Platform engineering guides and product updates. No spam, unsubscribe anytime.

Related Posts