The golden rule: secrets never belong in source code
This should be obvious, but we still find it in production during penetration tests. API keys hardcoded in JavaScript files. Database credentials sitting in a config.py committed to a public GitHub repo. Paystack or Flutterwave secret keys embedded in mobile app bundles that anyone with a decompiler can extract in under two minutes.
The problem isn't just that the secret is visible. It's that once it's in your git history, deleting the file doesn't remove it. The secret lives in every clone, every fork, every CI cache. Rotating the credential becomes the only option, and most teams don't realise the exposure until it's too late.
Hardcoded secrets in git history
In a recent engagement for a Lagos-based lending platform, we recovered production database credentials from a git commit made 14 months earlier. The file had been deleted in a subsequent commit, but the secret was still fully recoverable. The credentials were still active.
Where secrets should NOT live
Let's be explicit about what doesn't work, because each of these is something we encounter regularly during vulnerability assessments:
- Hardcoded in source files — any language, any framework. If
git logcan find it, an attacker can too. .envfiles committed to version control — adding.envto.gitignoreafter it's been committed does nothing for history. See our deep dive on .env file security.- Docker image layers — copying a
.envfile into a build stage and deleting it later still leaves the secret in intermediate layers. Use multi-stage builds with--secretflags or runtime injection. - Client-side code — any secret shipped to a browser or mobile app is a public secret. Full stop. See can hackers see your source code for the full breakdown.
- Error logs and stack traces — unhandled exceptions that dump environment variables to Sentry, LogRocket, or stdout in production.
Approach 1: vault services
Dedicated secret management platforms are the gold standard. They centralise storage, handle encryption at rest, provide audit trails, enforce access policies, and support automatic rotation.
HashiCorp Vault
Vault provides dynamic secrets, meaning your application requests a database credential at runtime, uses it, and Vault automatically revokes it after a TTL expires. This eliminates long-lived credentials entirely. For Nigerian fintechs running on AWS or GCP, Vault integrates directly with IAM roles for authentication, so your app never handles a master secret.
Cloud-native options
AWS Secrets Manager, Google Secret Manager, and Azure Key Vault all provide managed secret storage with automatic rotation, fine-grained IAM policies, and SDK-level integration. If you're already on one of these clouds, this is the lowest-friction option. Your app fetches secrets via the SDK at startup or on-demand, and the cloud provider handles encryption, replication, and access logging.
Approach 2: CI/CD injection
If a vault feels like overkill for your current stage, CI/CD secret injection is the minimum acceptable approach. GitHub Actions Secrets, GitLab CI Variables, and Vercel Environment Variables all store encrypted values that are injected into your build or runtime environment without touching source code.
The key discipline here: scope secrets to the environment that needs them. Your staging Paystack test key should never be accessible in your production pipeline. Most CI platforms support environment-scoped secrets — use them.
Approach 3: runtime environment variables
For server-side applications, reading secrets from environment variables at runtime is a well-established pattern. Your deployment platform (Heroku, Render, Railway, or a Docker orchestrator like Kubernetes) injects the variable into the process environment. The secret never touches the filesystem.
In Kubernetes, use Secrets objects mounted as environment variables or volume files, and ensure RBAC policies restrict who can read them. Never store Kubernetes Secrets in plaintext manifests committed to git — use Sealed Secrets or integrate with an external vault.
Not sure if your secrets management is production-ready? We can audit your configuration and deployment pipeline.
Book a security reviewApproach 4: the mobile backend proxy pattern
Mobile apps present a unique challenge. Unlike server-side code, anything bundled into an APK or IPA is extractable. Decompiling a Flutter, React Native, or native Android app takes minutes. Obfuscation slows an attacker down slightly — it does not prevent extraction.
The correct pattern: your mobile app authenticates the user, then calls your own backend API. Your backend holds the third-party API keys (Paystack, Mono, Flutterwave) and makes the upstream call on behalf of the mobile client. The mobile app never sees the third-party secret.
This is mandatory for any fintech handling payments or KYC/BVN data. If your mobile app directly calls a payment processor with a secret key embedded in the binary, you are one decompilation away from financial exposure.
Secret rotation and lifecycle
Storing secrets properly is half the problem. The other half is rotation. Every secret should have a defined lifetime. API keys should be rotated on a schedule — quarterly at minimum, monthly for high-value credentials. When a team member leaves, rotate every secret they had access to. No exceptions.
Automate rotation where possible. AWS Secrets Manager and Vault both support automatic rotation with zero-downtime deployment patterns. For secrets that can't be auto-rotated, maintain a runbook that your team can execute in under 30 minutes. This matters for CBN compliance and incident response readiness.
Quick reference by stack
Here's what we recommend based on where you are:
- Solo developer / early startup — CI/CD secrets (GitHub Actions, Vercel) + runtime env vars. Zero cost, minimal setup.
- Growth-stage fintech — Cloud-native secret manager (AWS/GCP/Azure) with IAM scoping and rotation policies.
- Enterprise / regulated — HashiCorp Vault or cloud vault with dynamic secrets, audit logging, and integration with your ISO 27001 or PCI DSS controls.
- Mobile apps — Backend proxy pattern. No exceptions. No "but we obfuscate" justifications.
Secrets management is a spectrum, not a binary
You don't need Vault on day one. But you do need to get secrets out of source code on day one. Start with CI/CD injection, graduate to a managed vault as you scale. The critical thing is that secrets are never in code, never in git history, and never shipped to clients.
Related reading
Blog: .env files in production · Can hackers see your source code? · Secure your fintech API
Guides: Fintech security checklist · OWASP for fintech · PCI DSS compliance
Services: Penetration testing · Secure architecture review · API security