3.4. Environment Variables#
Environment variables control how your system and programs behave. But with great power comes great responsibility—misconfigured environment variables are a common security vulnerability.
3.4.1. Environment Variables Basics Review#
Note: Chapter 2 (0205) introduces environment variables and how to set them. This section focuses on security implications and safe practices. You should be familiar with basic concepts like
export PATH="$PATH:..."before continuing.
A quick reminder:
# View a variable
$ echo $HOME
/Users/user
# List all variables
$ env
# Set temporarily
$ export MYVAR="value"
# Set permanently
$ echo 'export MYVAR="value"' >> ~/.bashrc
3.4.2. Security Risks from Environment Variables#
3.4.2.1. PATH Manipulation Attacks#
The Problem:
# If PATH includes current directory...
$ export PATH=".:$PATH"
# Attacker creates malicious 'ls'
$ mkdir /tmp/trap
$ cat > /tmp/trap/ls << 'EOF'
#!/bin/bash
echo "Trojan horse!"
rm -rf ~/* # Or worse...
EOF
# When you cd into /tmp/trap and run ls...
$ cd /tmp/trap
$ ls
Trojan horse! # Running the attacker's code!
Solution:
# Never include current directory in PATH
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
# No ".:" at the beginning or middle
# If you need to run local scripts, be explicit
$ ./my_script.sh # Clear intent
$ /full/path/to/script.sh
3.4.2.2. LD_PRELOAD and LD_LIBRARY_PATH#
These variables control which libraries programs use. Attackers can inject malicious libraries:
# Dangerous: Never allow untrusted code to set these
$ export LD_PRELOAD=/path/to/malicious.so
$ some_program # Loads malicious library instead
# Secure practice: Don't export these in shared environments
# Use absolute paths if needed, and only in controlled contexts
3.4.2.3. Environment Variable Information Disclosure#
Environment variables can leak secrets:
# BAD: Storing secrets in variables
$ export DATABASE_PASSWORD="secret123"
$ export API_KEY="sk_live_xxxxx"
# Anyone running 'env' can see them
$ env | grep PASSWORD
DATABASE_PASSWORD=secret123
# Running 'ps aux' might expose them
$ ps aux | grep python
user 12345 python app.py --password=secret123
Better approach:
# Use secure files with restricted permissions
$ cat ~/.db_password
secret123
$ chmod 600 ~/.db_password
# Read from file instead
$ python app.py << 'EOF'
with open(os.path.expanduser('~/.db_password')) as f:
password = f.read().strip()
EOF
# Or use a secrets manager (Vault, LastPass, etc.)
3.4.3. Securing Environment Variables#
3.4.3.1. Audit Your Environment#
# See what's exposed
$ env | sort
# Check for dangerous patterns
$ env | grep -E "(PASSWORD|SECRET|KEY|TOKEN)"
# Check for suspicious PATH entries
$ echo $PATH | tr ':' '\n' | grep -E '^\.|~'
# Should be empty (no current dir or home shortcuts)
3.4.3.2. Protect Sensitive Variables#
# Option 1: Use files with restricted permissions
$ echo "secret_value" > ~/.config/myapp/secret
$ chmod 600 ~/.config/myapp/secret
$ chown you:you ~/.config/myapp/secret
# Option 2: Use environment files that are sourced
$ cat > ~/.env.local << 'EOF'
export API_KEY="sk_live_xxxxx"
EOF
$ chmod 600 ~/.env.local
# Source it when needed (not exported globally)
$ source ~/.env.local
3.4.3.3. Prevent Leaks in Logs and Process Lists#
# BAD: Password visible in history and process list
$ mysql -u user -p"password" database
# GOOD: Prompt for password (not in command line)
$ mysql -u user -p
Enter password: [hidden]
# For scripts, read from file
$ mysql -u user --password="$(cat ~/.mysql_password)" database
$ chmod 600 ~/.mysql_password
3.4.3.4. Temporary Environment Variables#
When you need to set environment variables temporarily without polluting your shell:
# Set only for one command (not exported to shell)
$ MYVAR="value" ./my_script.sh
# Set for a session, unset when done
$ export TEMP_VAR="secret"
$ # ... use it ...
$ unset TEMP_VAR
3.4.3.5. Container and CI/CD Security#
For Docker and CI/CD systems:
# BAD: Secrets in image
FROM ubuntu
ENV DATABASE_PASSWORD="secret123"
# GOOD: Use build arguments and secrets
FROM ubuntu
ARG DB_PASSWORD
# Use docker secrets or pass at runtime
# Deploy with secrets
$ docker run -e DATABASE_PASSWORD="secret123" myapp
# Or use Docker secrets
$ docker secret create db_password -
# Reference in compose file
3.4.4. Environment Variables as a Security Surface#
Environment variables are inherited by child processes. This means:
# When you run a command, it inherits all environment variables
$ export MY_VAR="secret"
$ ./script.sh
# script.sh can read $MY_VAR
# This is intentional BUT dangerous if you're not careful
# Never assume your secrets are private
3.4.4.1. What Gets Inherited?#
# These are inherited by default
$ export MY_VAR="data"
$ ./child_script.sh
# You can explicitly NOT inherit
$ env -i ./script.sh # Minimal environment
$ env -u MY_VAR ./script.sh # Remove specific variable
3.4.4.2. Permission-Based Restrictions#
Since environment is a shell feature, use file permissions instead:
# Store secret in a file
$ cat > /usr/local/secrets/api_key << 'EOF'
sk_live_xxxxx
EOF
# Restrict access by user/group
$ sudo chown apiuser:apiuser /usr/local/secrets/api_key
$ sudo chmod 400 /usr/local/secrets/api_key # Read-only for owner
# Only the API user can access it
$ sudo -u apiuser cat /usr/local/secrets/api_key
sk_live_xxxxx
# Regular user cannot
$ cat /usr/local/secrets/api_key
cat: /usr/local/secrets/api_key: Permission denied
3.4.5. Secure Environment Checklist#
Before deploying any system, verify:
# ✓ No current directory in PATH
$ echo $PATH | grep -E '^:|:\.:|:$' # Should be empty
$ # Or just check that it starts with /
$ echo $PATH | grep "^/"
# ✓ No secrets in environment
$ env | grep -v '^(DISPLAY|LANG|HOME|PATH|USER|SHELL|TERM)'
# ✓ Dangerous libraries not loaded
$ echo $LD_PRELOAD # Should be empty or carefully controlled
$ echo $LD_LIBRARY_PATH # Should only have trusted paths
# ✓ Temporary files are restricted
$ ls -la /tmp
# Should show permissions like drwxrwxrwt (everyone can use, but only owner can delete)
# ✓ Secrets in files, not variables
$ find ~ -name "*.password" -o -name "*secret*" -o -name "*token*" | \
xargs ls -l # Check permissions are 600 or 400
3.4.6. Secure Project Setup#
Now that you understand permissions, ownership, and users, let’s apply it to real projects. A properly secured project prevents accidental damage and protects sensitive data.
3.4.6.1. Single Developer Project#
3.4.6.1.1. Setup#
# Create project directory
$ mkdir ~/projects/myapp
$ cd ~/projects/myapp
# Restrict to you only
$ chmod 700 ~/projects/myapp
# Create subdirectories
$ mkdir src tests config data logs
$ mkdir -p data/{raw,processed}
# Set appropriate permissions
$ chmod 700 config # Secrets go here
$ chmod 755 src tests # Readable by others for sharing
$ chmod 700 data # Private data
# Set default permissions for new files
$ umask 0077
3.4.6.1.2. File Permissions#
# Source code - readable but not executable
$ chmod 644 src/*.py
# Scripts - executable
$ chmod 755 scripts/deploy.sh
# Config with secrets - very restrictive
$ chmod 600 config/database.ini
# Data files - depends on sensitivity
$ chmod 600 data/raw/*.csv # Private
$ chmod 644 data/processed/*.csv # Shareable
3.4.6.2. Team Project#
3.4.6.2.1. 1. Create Team Group#
$ sudo groupadd -g 2000 myproject-team
# Add team members
$ sudo usermod -aG myproject-team alice
$ sudo usermod -aG myproject-team bob
$ sudo usermod -aG myproject-team charlie
3.4.6.2.2. 2. Create Project with Group Ownership#
# Create with group ownership
$ sudo mkdir /home/projects/myapp
$ sudo chown :myproject-team /home/projects/myapp
# Set permissions: owner can do anything, group can read/write
$ sudo chmod 2770 /home/projects/myapp
# ││└── Others: no access
# │└─── Group: read, write, execute
# └──── Owner: read, write, execute
# 2 = setgid (new files inherit group)
# Verify
$ ls -ld /home/projects/myapp
drwxrws--- 2 root myproject-team 4096 Jan 15 myapp
↑
setgid - new files inherit group
3.4.6.2.4. 4. Sensitive Files Within Team Project#
# Database password - only database user can read
$ sudo touch /home/projects/myapp/.db_password
$ sudo chown www-data:myproject-team /home/projects/myapp/.db_password
$ sudo chmod 400 /home/projects/myapp/.db_password
# Only www-data (the app) can read it
# API keys - restrict to ops team
$ sudo mkdir /home/projects/myapp/secrets
$ sudo chown :ops-team /home/projects/myapp/secrets
$ sudo chmod 750 /home/projects/myapp/secrets
# Group 'ops-team' can access, others cannot
3.4.6.3. Audit and Verify#
# Check project ownership
$ ls -ld /home/projects/myapp
drwxrws--- 2 alice myproject-team 4096 Jan 15 myapp
# Check file permissions
$ find /home/projects/myapp -type f -exec ls -l {} \;
# Check group membership
$ getent group myproject-team
myproject-team:x:2000:alice,bob,charlie
# Verify no overly permissive files
$ find /home/projects/myapp -type f -perm /077
# Should show nothing (no group/other access)
# Check for world-readable secrets
$ find /home/projects/myapp -name "*secret*" -o -name "*password*" | \
xargs ls -l
# Permissions should be 600 or 400, not 644 or 755