Securing Sensitive Credentials, Passwords, and Tokens
🏷️ Final Capstone Engineer Script project / Code Review Checklist
When building automation scripts or tools, one of the most common mistakes is accidentally exposing sensitive information like API keys, database passwords, or authentication tokens. Hardcoding these values directly into your Python files creates a serious security risk, especially when code is shared, committed to version control, or deployed across environments. This section covers practical techniques to keep credentials safe while still making them accessible to your scripts.
🧠 Why Hardcoding Credentials Is Dangerous
- Version control exposure – If you commit a file containing a password or token, it stays in the git history forever, even if you remove it later.
- Accidental sharing – Sending a script to a colleague or posting it in a shared repository can leak secrets unintentionally.
- Environment inflexibility – Hardcoded values force you to edit the script every time credentials change or when moving between development, staging, and production environments.
- Compliance violations – Many security standards (like SOC2, PCI-DSS) explicitly forbid storing secrets in source code.
🛠️ Recommended Approaches for Storing Secrets
| Approach | Best For | Key Advantage |
|---|---|---|
| Environment variables | Quick scripts and local development | Simple to set up, no extra files to manage |
.env files with python-dotenv |
Projects with multiple secrets | Keeps secrets in one file outside your code |
| Configuration files (JSON/YAML) with restricted permissions | Structured settings | Easy to parse and organize |
| Secret management tools (HashiCorp Vault, AWS Secrets Manager) | Production and enterprise systems | Centralized, audited, and rotated automatically |
⚙️ Using Environment Variables
Environment variables are a standard way to pass configuration to your application without embedding secrets in code.
- Set a variable in your terminal session: export DB_PASSWORD="my_secret_pass"
- Access it in Python using the os module: os.getenv("DB_PASSWORD")
- If the variable is missing, os.getenv returns None by default, or you can provide a fallback value: os.getenv("DB_PASSWORD", "default_value")
Example usage in a script:
- Import os at the top of your file.
- Store the value in a variable: password = os.getenv("DB_PASSWORD")
- Check if the value exists before using it: if not password: raise ValueError("Missing DB_PASSWORD environment variable")
This approach keeps your code clean and your credentials out of the repository.
📄 Using a .env File with python-dotenv
For local development, a .env file is a convenient way to manage multiple secrets without setting environment variables manually each time.
- Create a file named .env in your project root.
- Add each secret on a new line using KEY=VALUE format: API_TOKEN=abc123xyz
- Install the python-dotenv library using pip install python-dotenv
- At the start of your script, load the file: from dotenv import load_dotenv then load_dotenv()
- Access values the same way as environment variables: os.getenv("API_TOKEN")
Critical rule: Add .env to your .gitignore file immediately so it is never committed to version control.
🔐 Setting File Permissions for Configuration Files
If you choose to store secrets in a JSON or YAML configuration file, restrict who can read it.
- Store the file outside your project directory if possible, for example: /home/your_user/secrets/config.json
- Set file permissions so only your user can read it: chmod 600 config.json
- In Python, read the file and parse it: import json then with open("config.json") as f: config = json.load(f) then access values like config["db_password"]
This method is useful when you need structured configuration beyond simple key-value pairs.
🕵️ Avoiding Common Mistakes
- Never print or log credentials during debugging. Use placeholder values like ""* if you must show that a value exists.
- Do not pass secrets as command-line arguments. Other users on the system can see them in process lists.
- Avoid storing secrets in environment variables that are automatically loaded by your shell profile (like .bashrc) unless the file is properly secured.
- When using version control, run a scan with tools like git-secrets or truffleHog to detect accidentally committed secrets.
✅ Code Review Checklist for Credential Security
When reviewing a script or project, check for these items:
- Are there any hardcoded strings that look like passwords, tokens, or API keys in the code?
- Is there a .gitignore file that excludes .env, config.json, or similar files?
- Are secrets loaded from environment variables or a secure configuration file?
- Is there a README or documentation explaining how to set up required environment variables?
- Are file permissions restricted for any configuration files containing secrets?
- Are there any debug print statements or logging calls that might expose sensitive values?
📌 Summary
Protecting sensitive credentials is a fundamental responsibility when writing automation scripts. By using environment variables, .env files, or restricted configuration files, you keep secrets separate from your code and reduce the risk of accidental exposure. Always treat credentials as confidential data, never hardcode them, and make credential management a standard part of your development workflow.
This section shows how to keep passwords, API keys, and tokens out of your Python code so they aren't accidentally shared or exposed.
🔐 Example 1: Hardcoded credential (what NOT to do)
This example shows the unsafe practice of writing a password directly into your script — never do this in real code.
username = "admin"
password = "SuperSecret123!"
api_key = "abc123def456"
print(f"Logging in as {username}")
📤 Output: Logging in as admin
🔐 Example 2: Reading credentials from environment variables
This example shows how to pull a password from the operating system's environment variables instead of writing it in code.
import os
db_password = os.environ.get("DB_PASSWORD")
print(f"Database password loaded: {db_password is not None}")
📤 Output: Database password loaded: True
🔐 Example 3: Using a .env file with python-dotenv
This example shows how to load multiple secrets from a separate .env file that is never committed to version control.
from dotenv import load_dotenv
import os
load_dotenv()
api_token = os.getenv("API_TOKEN")
secret_key = os.getenv("SECRET_KEY")
print(f"API token: {api_token[:4]}...")
print(f"Secret key: {secret_key[:4]}...")
📤 Output: API token: sk-12...
📤 Output: Secret key: 9a8b...
🔐 Example 4: Prompting for a password at runtime
This example shows how to ask the user for a password without displaying it on screen using getpass.
import getpass
username = input("Enter your username: ")
password = getpass.getpass("Enter your password: ")
print(f"User {username} authenticated successfully")
📤 Output: User jdoe authenticated successfully
🔐 Example 5: Storing tokens in a JSON config file (with file permissions)
This example shows how to read a token from a local JSON file that only the engineer running the script can read.
import json
import os
config_path = "secrets.json"
if os.path.exists(config_path):
with open(config_path, "r") as f:
config = json.load(f)
token = config.get("github_token")
print(f"GitHub token loaded: {token[:6]}...")
else:
print("No secrets file found — using fallback")
📤 Output: GitHub token loaded: ghp_ab...
🔐 Example 6: Using keyring for OS-level credential storage
This example shows how to store and retrieve a password using the operating system's built-in credential manager.
import keyring
service_name = "my_engineer_tool"
username = "deploy_bot"
keyring.set_password(service_name, username, "s3cur3P@ss!")
retrieved = keyring.get_password(service_name, username)
print(f"Retrieved password: {retrieved[:4]}...")
📤 Output: Retrieved password: s3cu...
📊 Comparison: Credential Storage Methods
| Method | Where secret lives | Safe to commit? | Requires extra setup? |
|---|---|---|---|
| Hardcoded in code | In the script file | ❌ No | ❌ No |
| Environment variable | OS environment | ✅ Yes (if not in code) | ❌ No |
| .env file | Separate file | ❌ No (add to .gitignore) | ✅ Yes (python-dotenv) |
| Runtime prompt | User input only | ✅ Yes | ❌ No |
| JSON config file | Local file | ❌ No (set file permissions) | ❌ No |
| OS keyring | OS credential manager | ✅ Yes | ✅ Yes (keyring library) |
When building automation scripts or tools, one of the most common mistakes is accidentally exposing sensitive information like API keys, database passwords, or authentication tokens. Hardcoding these values directly into your Python files creates a serious security risk, especially when code is shared, committed to version control, or deployed across environments. This section covers practical techniques to keep credentials safe while still making them accessible to your scripts.
🧠 Why Hardcoding Credentials Is Dangerous
- Version control exposure – If you commit a file containing a password or token, it stays in the git history forever, even if you remove it later.
- Accidental sharing – Sending a script to a colleague or posting it in a shared repository can leak secrets unintentionally.
- Environment inflexibility – Hardcoded values force you to edit the script every time credentials change or when moving between development, staging, and production environments.
- Compliance violations – Many security standards (like SOC2, PCI-DSS) explicitly forbid storing secrets in source code.
🛠️ Recommended Approaches for Storing Secrets
| Approach | Best For | Key Advantage |
|---|---|---|
| Environment variables | Quick scripts and local development | Simple to set up, no extra files to manage |
.env files with python-dotenv |
Projects with multiple secrets | Keeps secrets in one file outside your code |
| Configuration files (JSON/YAML) with restricted permissions | Structured settings | Easy to parse and organize |
| Secret management tools (HashiCorp Vault, AWS Secrets Manager) | Production and enterprise systems | Centralized, audited, and rotated automatically |
⚙️ Using Environment Variables
Environment variables are a standard way to pass configuration to your application without embedding secrets in code.
- Set a variable in your terminal session: export DB_PASSWORD="my_secret_pass"
- Access it in Python using the os module: os.getenv("DB_PASSWORD")
- If the variable is missing, os.getenv returns None by default, or you can provide a fallback value: os.getenv("DB_PASSWORD", "default_value")
Example usage in a script:
- Import os at the top of your file.
- Store the value in a variable: password = os.getenv("DB_PASSWORD")
- Check if the value exists before using it: if not password: raise ValueError("Missing DB_PASSWORD environment variable")
This approach keeps your code clean and your credentials out of the repository.
📄 Using a .env File with python-dotenv
For local development, a .env file is a convenient way to manage multiple secrets without setting environment variables manually each time.
- Create a file named .env in your project root.
- Add each secret on a new line using KEY=VALUE format: API_TOKEN=abc123xyz
- Install the python-dotenv library using pip install python-dotenv
- At the start of your script, load the file: from dotenv import load_dotenv then load_dotenv()
- Access values the same way as environment variables: os.getenv("API_TOKEN")
Critical rule: Add .env to your .gitignore file immediately so it is never committed to version control.
🔐 Setting File Permissions for Configuration Files
If you choose to store secrets in a JSON or YAML configuration file, restrict who can read it.
- Store the file outside your project directory if possible, for example: /home/your_user/secrets/config.json
- Set file permissions so only your user can read it: chmod 600 config.json
- In Python, read the file and parse it: import json then with open("config.json") as f: config = json.load(f) then access values like config["db_password"]
This method is useful when you need structured configuration beyond simple key-value pairs.
🕵️ Avoiding Common Mistakes
- Never print or log credentials during debugging. Use placeholder values like ""* if you must show that a value exists.
- Do not pass secrets as command-line arguments. Other users on the system can see them in process lists.
- Avoid storing secrets in environment variables that are automatically loaded by your shell profile (like .bashrc) unless the file is properly secured.
- When using version control, run a scan with tools like git-secrets or truffleHog to detect accidentally committed secrets.
✅ Code Review Checklist for Credential Security
When reviewing a script or project, check for these items:
- Are there any hardcoded strings that look like passwords, tokens, or API keys in the code?
- Is there a .gitignore file that excludes .env, config.json, or similar files?
- Are secrets loaded from environment variables or a secure configuration file?
- Is there a README or documentation explaining how to set up required environment variables?
- Are file permissions restricted for any configuration files containing secrets?
- Are there any debug print statements or logging calls that might expose sensitive values?
📌 Summary
Protecting sensitive credentials is a fundamental responsibility when writing automation scripts. By using environment variables, .env files, or restricted configuration files, you keep secrets separate from your code and reduce the risk of accidental exposure. Always treat credentials as confidential data, never hardcode them, and make credential management a standard part of your development workflow.
Interactive Views
You are currently in 📚 All-in-One mode. Use the tabs at the top to switch to 📖 Theory Only or 💻 Code Only views.
This section shows how to keep passwords, API keys, and tokens out of your Python code so they aren't accidentally shared or exposed.
🔐 Example 1: Hardcoded credential (what NOT to do)
This example shows the unsafe practice of writing a password directly into your script — never do this in real code.
username = "admin"
password = "SuperSecret123!"
api_key = "abc123def456"
print(f"Logging in as {username}")
📤 Output: Logging in as admin
🔐 Example 2: Reading credentials from environment variables
This example shows how to pull a password from the operating system's environment variables instead of writing it in code.
import os
db_password = os.environ.get("DB_PASSWORD")
print(f"Database password loaded: {db_password is not None}")
📤 Output: Database password loaded: True
🔐 Example 3: Using a .env file with python-dotenv
This example shows how to load multiple secrets from a separate .env file that is never committed to version control.
from dotenv import load_dotenv
import os
load_dotenv()
api_token = os.getenv("API_TOKEN")
secret_key = os.getenv("SECRET_KEY")
print(f"API token: {api_token[:4]}...")
print(f"Secret key: {secret_key[:4]}...")
📤 Output: API token: sk-12...
📤 Output: Secret key: 9a8b...
🔐 Example 4: Prompting for a password at runtime
This example shows how to ask the user for a password without displaying it on screen using getpass.
import getpass
username = input("Enter your username: ")
password = getpass.getpass("Enter your password: ")
print(f"User {username} authenticated successfully")
📤 Output: User jdoe authenticated successfully
🔐 Example 5: Storing tokens in a JSON config file (with file permissions)
This example shows how to read a token from a local JSON file that only the engineer running the script can read.
import json
import os
config_path = "secrets.json"
if os.path.exists(config_path):
with open(config_path, "r") as f:
config = json.load(f)
token = config.get("github_token")
print(f"GitHub token loaded: {token[:6]}...")
else:
print("No secrets file found — using fallback")
📤 Output: GitHub token loaded: ghp_ab...
🔐 Example 6: Using keyring for OS-level credential storage
This example shows how to store and retrieve a password using the operating system's built-in credential manager.
import keyring
service_name = "my_engineer_tool"
username = "deploy_bot"
keyring.set_password(service_name, username, "s3cur3P@ss!")
retrieved = keyring.get_password(service_name, username)
print(f"Retrieved password: {retrieved[:4]}...")
📤 Output: Retrieved password: s3cu...
📊 Comparison: Credential Storage Methods
| Method | Where secret lives | Safe to commit? | Requires extra setup? |
|---|---|---|---|
| Hardcoded in code | In the script file | ❌ No | ❌ No |
| Environment variable | OS environment | ✅ Yes (if not in code) | ❌ No |
| .env file | Separate file | ❌ No (add to .gitignore) | ✅ Yes (python-dotenv) |
| Runtime prompt | User input only | ✅ Yes | ❌ No |
| JSON config file | Local file | ❌ No (set file permissions) | ❌ No |
| OS keyring | OS credential manager | ✅ Yes | ✅ Yes (keyring library) |