Safeguard
Security Guides

Secrets Management for Python Applications

Hard-coded API keys and .env files committed to git are still the fastest route into a Python app. Here is how to keep secrets out of your code and your history.

Priya Mehta
Security Researcher
5 min read

A stolen credential does not trip alarms the way an exploit does. It just logs in. That is why leaked secrets, an API key in a config file, a database password in git history, a cloud token printed to a log, remain one of the most reliable ways attackers get into Python applications. The defense is not clever; it is disciplined. Keep secrets out of code, out of version control, and short-lived enough that a leak has a short shelf life.

The hierarchy of where secrets should live

Not all secret storage is equal. Ranked from worst to best:

  1. Hard-coded in source. Never. It leaks the moment the repo is cloned, forked, or made public.
  2. A .env file committed to git. Also never; committing it is the same as hard-coding, and it lingers in history forever.
  3. Environment variables from an uncommitted .env. Acceptable for local development, with .env in .gitignore.
  4. A dedicated secret manager such as AWS Secrets Manager, Google Secret Manager, HashiCorp Vault, or Azure Key Vault. This is the production answer.

Read secrets from the environment, fail loudly

In application code, read secrets from the environment and refuse to start if a required one is missing. A crash at boot is far better than a service running with a default password:

import os

class Config:
    def __init__(self):
        # Raises immediately if the secret is not set - no silent fallback
        self.database_url = os.environ["DATABASE_URL"]
        self.api_key = os.environ["PAYMENTS_API_KEY"]

config = Config()

Never provide a hard-coded default for a secret. A line like os.environ.get("SECRET", "dev-secret-123") is how a development placeholder ends up authenticating production traffic.

Prefer a secret manager in production

Environment variables are fine, but a secret manager gives you rotation, audit logs, and access control that env vars cannot. Fetch at startup and cache in memory rather than baking the value into an image or config file:

import boto3, json
from functools import lru_cache

@lru_cache(maxsize=None)
def get_secret(name: str) -> dict:
    client = boto3.client("secretsmanager")
    resp = client.get_secret_value(SecretId=name)
    return json.loads(resp["SecretString"])

db = get_secret("prod/database")["password"]

The payoff is rotation. When a secret manager rotates a database password on a schedule, a credential that leaked last month is already dead, which shrinks the window an attacker has to use it.

Do not leak secrets in logs and errors

Secrets escape through the side door constantly. A full-request log, an exception that serializes the config object, or a debug dump of environment variables can all print a live key. Redact deliberately, and scrub anything that renders an object you would not want in plaintext:

def redact(value: str) -> str:
    if not value:
        return value
    return value[:4] + "***"   # enough to identify, not to reuse

logger.info("Using API key %s", redact(config.api_key))

Also disable framework debug modes in production, since interactive tracebacks routinely expose local variables that hold secrets.

Catch leaks before and after they happen

Prevention is a scan you run early and often. Add a secret scanner as a pre-commit hook so a key never reaches a commit, and scan the full history for anything that already slipped in:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

If a secret is found in history, rotate it first, then purge it. Rotation matters more than deletion: once a value has been pushed, assume it is compromised and burn it, because removing it from history does not un-leak it.

Prefer short-lived credentials

The best secret is one that expires before it can be abused. Wherever your platform supports it, move from static, long-lived keys to dynamically issued, short-lived credentials. Cloud workload identity lets a service assume a role and receive temporary tokens with no stored key at all; a secrets manager can broker database credentials that live for minutes. This changes the economics of a leak: a token that dies in fifteen minutes is worth far less to an attacker than a password that has been valid for two years. When you cannot go fully dynamic, at least set a rotation schedule and enforce it, because an unrotated secret is a leak waiting for its moment.

Checklist

ControlPurpose
No secrets in source or committed .envPrevent the leak at the source
Env vars locally, secret manager in prodRotation, audit, access control
Fail-fast on missing secretsNo silent insecure defaults
Redaction in logs and errorsClose the side channels
Pre-commit secret scanningStop leaks before they land
Rotate on any suspected exposureShrink the usable window

How Safeguard helps

Choosing a secret manager and wiring rotation are decisions and infrastructure you own; a supply-chain platform does not replace Vault or Secrets Manager, and we would not claim otherwise. Safeguard's role is the surrounding attack surface. Our software composition analysis flags vulnerable versions of the SDKs you use to reach those secret stores, such as cloud client libraries and HTTP layers, since a bug there can undermine the whole chain. You can run policy and dependency checks in CI and locally with the Safeguard CLI, and a dynamic scan of your deployed service helps confirm that debug endpoints and verbose error pages are not quietly exposing configuration. For prioritizing which exposed-credential-adjacent findings to fix first, Griffin AI adds reachability context so the urgent items rise to the top.

Get started

Move your secrets into the environment or a manager and add a pre-commit scanner today. Then let Safeguard cover the dependencies around them. Sign up free at app.safeguard.sh/register and read the guide at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.