The extension uses libsodium for all cryptographic operations:
| Purpose | Algorithm | Justification |
|---|---|---|
| Key Derivation | Argon2id | Memory-hard, winner of Password Hashing Competition |
| Symmetric Encryption | XSalsa20-Poly1305 | Modern AEAD cipher, part of NaCl/libsodium |
The original dotenvx uses ECIES with secp256k1 (Bitcoin's curve). We chose a different approach:
- Library Availability: libsodium is bundled with PHP; secp256k1 requires external linking
- Complexity: Public-key crypto adds key management complexity
- Use Case: For file encryption, passphrase-based is often more practical
- Security Margin: Argon2id provides excellent brute-force resistance
// Argon2id parameters (MODERATE)
crypto_pwhash_OPSLIMIT_MODERATE // 3 iterations
crypto_pwhash_MEMLIMIT_MODERATE // 256 MB memoryThese settings provide:
- ~0.7 seconds per derivation on modern hardware
- Resistance against GPU/ASIC attacks
- Balance between security and usability
SFDOTENV | Ver | Reserved | Salt (16) | Nonce (24) | Ciphertext + MAC
- Magic bytes: Identify encrypted files, prevent format confusion
- Version: Enable future format upgrades
- Salt: Unique per-file, prevents rainbow tables
- Nonce: Random per-encryption, prevents nonce reuse
- MAC: 16-byte Poly1305 tag for authentication
// After use, key material is zeroed
sodium_memzero(key, sizeof(key));
// Decrypted content is zeroed before free
sf_crypto_secure_zero(plaintext, plaintext_len);- Encryption keys
- Decryption keys
- Raw file content after parsing
- Intermediate buffers during crypto operations
- PHP string zvals (managed by Zend engine)
- Return values (visible to PHP code)
Each request:
- Initializes fresh module globals (
RINIT) - Parses files independently
- Cleans up on shutdown (
RSHUTDOWN)
No data persists between requests.
// Tracked for cleanup
static sf_putenv_tracker_t putenv_tracker;
// Freed on request shutdown
sf_env_cleanup_putenv();Note: Due to POSIX limitations, environment strings passed to putenv() cannot be safely unset. They are freed when the PHP process exits.
// Only valid env var names accepted
bool sf_env_validate_key(const char *key, size_t key_len)
{
// Must match: [a-zA-Z_][a-zA-Z0-9_]*
}- No path traversal prevention (OS handles this)
- Caller is responsible for validating paths
- Extension reads files in binary mode
On any decryption error:
- All sensitive buffers are zeroed
- Exception is thrown
- No partial data is returned
if (crypto_err != SF_CRYPTO_OK) {
sf_crypto_secure_zero(plaintext, plaintext_len);
efree(plaintext);
zend_throw_exception(...);
RETURN_THROWS();
}Error messages are designed to:
- Provide enough info for debugging
- Not leak sensitive data
- Not reveal internal state
Example: "Decryption failed: wrong key or tampered data"
- Does NOT say which one
- Does NOT reveal expected/actual values
// Uses libsodium's constant-time compare
int sf_crypto_compare(const void *a, const void *b, size_t len)
{
return sodium_memcmp(a, b, len);
}Used for:
- Magic byte verification
- MAC verification (handled by secretbox_open)
Once decrypted, secrets exist in PHP memory:
- Accessible via
$_ENV, return values - May be swapped to disk
- Visible in process memory
Mitigation: Use short-lived processes, enable swap encryption
Environment variables may leak via:
/proc/[pid]/environ(Linux)ps eww(some systems)- Child processes
Mitigation: Use export => false when possible
PHP may log:
- Error messages containing paths
- Exception stack traces
Mitigation: Configure error logging carefully in production
Process crashes may dump memory including secrets.
Mitigation: Disable core dumps in production (ulimit -c 0)
# Generate strong passphrase
openssl rand -base64 32
# Store in secret manager, not in repo
vault kv put secret/app DOTENV_KEY=...# .env files
chmod 600 .env
chown www-data:www-data .env
# .env.encrypted (can be less restrictive)
chmod 644 .env.encrypted# Never commit unencrypted .env
echo ".env" >> .gitignore
# Encrypted files are safe to commit
git add .env.encryptedLog failed decryption attempts:
try {
$env = \Signalforge\dotenv('.env.enc');
} catch (\Signalforge\DotenvException $e) {
// Log for security monitoring
error_log("SECURITY: dotenv decryption failed: " . $e->getCode());
throw $e;
}If you discover a security vulnerability:
- Do NOT open a public issue
- Email: security@signalforge.example.com
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We aim to respond within 48 hours and patch within 7 days for critical issues.