Inclusion of Sensitive Information in Test Code
Description
Inclusion of Sensitive Information in Test Code is a vulnerability where test applications or test code contains sensitive information that becomes accessible to unauthorized parties. Developers often include real credentials, API keys, configuration details, and other sensitive data in test code without anticipating that this code might be deployed to production, included in repositories, or otherwise made accessible. Test code commonly contains administrative functions, hardcoded usernames and passwords, session identifiers, database connection strings, and detailed system configuration information that can be exploited if exposed.
Risk
Test code with sensitive information creates multiple security risks. Real credentials or API keys in test files may be committed to version control and exposed through repository leaks. Test code accidentally deployed to production provides attackers with administrative access or debugging capabilities. Unit tests may contain valid database credentials that provide unauthorized data access. Integration tests might expose internal API endpoints or authentication bypass mechanisms. Test fixtures and sample data may include real personal information. The informal nature of test code often leads to weaker security practices, making it a rich source of sensitive information for attackers.
Solution
Remove all test code, test pages, and debugging functionality before deploying applications to production. Never use real credentials or production secrets in test code - use mock data, test credentials, or environment-specific configuration. Implement automated checks in CI/CD pipelines to detect test code or test artifacts in production deployments. Use separate test databases and test API keys that have no access to production resources. Store test configuration separately from production configuration. Review test code for sensitive information before committing to repositories. Implement .gitignore rules to exclude test credentials.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Adversaries can access sensitive information embedded in test code, including credentials, API keys, personal data, and system configuration details. |
Example Code
Vulnerable Code
// Vulnerable: Test class with hardcoded credentials
package com.example.tests;
import org.junit.Test;
import static org.junit.Assert.*;
public class VulnerableDatabaseTest {
// Vulnerable: Real production credentials in test!
private static final String DB_HOST = "prod-db.company.com";
private static final String DB_USER = "admin";
private static final String DB_PASSWORD = "Production_P@ssw0rd_2024!";
// Vulnerable: Real API keys
private static final String API_KEY = "sk_live_51ABC123XYZ";
private static final String SECRET_KEY = "whsec_secret123456";
@Test
public void testDatabaseConnection() {
// Test uses real production credentials
Connection conn = DriverManager.getConnection(
"jdbc:mysql://" + DB_HOST + "/production",
DB_USER,
DB_PASSWORD
);
assertTrue(conn.isValid(5));
}
@Test
public void testApiIntegration() {
// Test uses real production API key
ApiClient client = new ApiClient(API_KEY, SECRET_KEY);
Response response = client.makeRequest("/live/endpoint");
assertEquals(200, response.getStatus());
}
}
# Vulnerable: Test file with sensitive information
# tests/test_integration.py
import unittest
import requests
# Vulnerable: Real AWS credentials in test file
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
AWS_BUCKET = "production-user-data"
# Vulnerable: Real database credentials
DATABASE_URL = "postgresql://admin:[email protected]:5432/users"
# Vulnerable: Admin test accounts with real passwords
TEST_ADMIN = {
"username": "[email protected]",
"password": "AdminPass123!", # Real admin password!
"role": "superuser"
}
class IntegrationTests(unittest.TestCase):
def test_admin_login(self):
# Vulnerable: Uses real admin credentials
response = requests.post(
"https://app.company.com/login",
json=TEST_ADMIN
)
self.assertEqual(response.status_code, 200)
def test_s3_access(self):
# Vulnerable: Uses real AWS credentials
import boto3
s3 = boto3.client('s3',
aws_access_key_id=AWS_ACCESS_KEY,
aws_secret_access_key=AWS_SECRET_KEY
)
# Accesses real production bucket!
objects = s3.list_objects(Bucket=AWS_BUCKET)
// Vulnerable: Test configuration with secrets
// tests/config.test.js
const testConfig = {
// Vulnerable: Production API keys
stripe: {
publishableKey: 'pk_live_51ABC123',
secretKey: 'sk_live_51ABC123XYZ789',
webhookSecret: 'whsec_realwebhooksecret'
},
// Vulnerable: Real OAuth credentials
oauth: {
clientId: 'real-production-client-id',
clientSecret: 'real-production-client-secret',
redirectUri: 'https://app.company.com/callback'
},
// Vulnerable: Real admin credentials
adminUser: {
email: '[email protected]',
password: 'Admin123!',
totpSecret: 'JBSWY3DPEHPK3PXP' // TOTP seed exposed!
},
// Vulnerable: Internal API endpoints
internalApis: {
userService: 'http://internal-user-api:8080',
paymentService: 'http://internal-payment-api:8080',
adminPanel: 'https://admin.internal.company.com'
}
};
module.exports = testConfig;
<?php
// Vulnerable: Test page deployed to production
// /var/www/html/test.php (should not exist in production!)
// Vulnerable: Debug mode enabling
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Vulnerable: Hardcoded test credentials
$test_admin_user = 'admin';
$test_admin_pass = 'admin123'; // Weak test password that works!
// Vulnerable: Database test credentials
$test_db_config = [
'host' => 'localhost',
'user' => 'root',
'pass' => 'root_password_123',
'database' => 'production'
];
// Vulnerable: Test function exposing system info
function debug_system_info() {
phpinfo(); // Exposes all PHP configuration
echo "<pre>";
print_r($_SERVER); // Server information
print_r($_ENV); // Environment variables
echo "</pre>";
}
// Vulnerable: Authentication bypass for testing
if ($_GET['test_mode'] === 'enabled') {
$_SESSION['authenticated'] = true;
$_SESSION['role'] = 'admin';
}
?>
Fixed Code
// Fixed: Test class using mock data and test credentials
package com.example.tests;
import org.junit.Test;
import org.junit.BeforeClass;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class SecureDatabaseTest {
// Fixed: Use environment variables for test credentials
private static String testDbHost;
private static String testDbUser;
private static String testDbPassword;
@BeforeClass
public static void setup() {
// Fixed: Load from test-specific environment
testDbHost = System.getenv("TEST_DB_HOST");
testDbUser = System.getenv("TEST_DB_USER");
testDbPassword = System.getenv("TEST_DB_PASSWORD");
// Fixed: Validate we're not using production
if (testDbHost != null && testDbHost.contains("prod")) {
throw new IllegalStateException(
"Tests must not run against production!"
);
}
}
@Test
public void testDatabaseConnection() {
// Fixed: Use test database, not production
// Or better, use an in-memory database for unit tests
Connection conn = DriverManager.getConnection(
"jdbc:h2:mem:testdb",
"sa",
""
);
assertTrue(conn.isValid(5));
}
@Test
public void testApiIntegration() {
// Fixed: Mock the API client for unit tests
ApiClient mockClient = mock(ApiClient.class);
when(mockClient.makeRequest("/endpoint"))
.thenReturn(new Response(200, "OK"));
Response response = mockClient.makeRequest("/endpoint");
assertEquals(200, response.getStatus());
}
}
# Fixed: Test file using proper test configuration
# tests/test_integration.py
import unittest
import os
from unittest.mock import patch, MagicMock
# Fixed: Load credentials from environment, never hardcode
def get_test_config():
"""Get test configuration from environment."""
env = os.environ.get('TEST_ENV', 'test')
# Fixed: Ensure we're not in production
if env == 'production':
raise RuntimeError("Tests cannot run in production!")
return {
'db_url': os.environ.get('TEST_DATABASE_URL'),
'api_key': os.environ.get('TEST_API_KEY'),
}
# Fixed: Use test fixtures with fake data
TEST_USER = {
"username": "[email protected]", # Fake email
"password": "TestPassword123", # Test-only password
"role": "user"
}
class IntegrationTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Fixed: Verify test environment
if os.environ.get('ENVIRONMENT') == 'production':
raise RuntimeError("Integration tests cannot run in production!")
cls.config = get_test_config()
@patch('requests.post')
def test_user_login(self, mock_post):
# Fixed: Mock external calls
mock_post.return_value = MagicMock(status_code=200)
response = requests.post(
"https://test.example.com/login",
json=TEST_USER
)
self.assertEqual(response.status_code, 200)
@patch('boto3.client')
def test_s3_access(self, mock_boto):
# Fixed: Mock AWS client
mock_s3 = MagicMock()
mock_boto.return_value = mock_s3
mock_s3.list_objects.return_value = {'Contents': []}
import boto3
s3 = boto3.client('s3')
objects = s3.list_objects(Bucket='test-bucket')
# Never touches real AWS
self.assertIsNotNone(objects)
// Fixed: Test configuration using environment variables
// tests/config.test.js
// Fixed: Load from environment, with test-specific defaults
const testConfig = {
stripe: {
// Fixed: Test mode keys only
publishableKey: process.env.TEST_STRIPE_PK || 'pk_test_placeholder',
secretKey: process.env.TEST_STRIPE_SK || 'sk_test_placeholder',
webhookSecret: process.env.TEST_STRIPE_WEBHOOK || 'whsec_test'
},
// Fixed: Mock OAuth for tests
oauth: {
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
redirectUri: 'http://localhost:3000/callback'
},
// Fixed: Test user with fake data
testUser: {
email: '[email protected]', // Fake domain
password: 'TestOnly123', // Test-only password
// No TOTP secrets in code
},
// Fixed: Use localhost or mock URLs
apis: {
userService: 'http://localhost:8080',
paymentService: 'http://localhost:8081',
}
};
// Fixed: Validation to prevent production use
if (process.env.NODE_ENV === 'production') {
throw new Error('Test config must not be used in production!');
}
module.exports = testConfig;
<?php
// Fixed: No test pages in production deployment
// Fixed: Test endpoints in separate test directory not deployed
// tests/helpers/test_utils.php (NOT in webroot)
/**
* Test utilities - NEVER DEPLOY TO PRODUCTION
*/
// Fixed: Environment check at the top of all test files
if (getenv('ENVIRONMENT') === 'production') {
die('Test files cannot run in production!');
}
// Fixed: Test credentials from environment only
function getTestCredentials() {
return [
'user' => getenv('TEST_USER') ?: 'testuser',
'pass' => getenv('TEST_PASS') ?: 'testpass',
];
}
// Fixed: Test database uses separate, empty database
function getTestDatabaseConfig() {
$config = [
'host' => getenv('TEST_DB_HOST') ?: 'localhost',
'user' => getenv('TEST_DB_USER') ?: 'test_user',
'pass' => getenv('TEST_DB_PASS') ?: '',
'database' => getenv('TEST_DB_NAME') ?: 'test_db',
];
// Fixed: Prevent accidental production use
if (strpos($config['database'], 'prod') !== false) {
throw new Exception('Cannot use production database for tests!');
}
return $config;
}
# Fixed: CI/CD pipeline preventing test code deployment
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Fixed: Remove test files before deployment
- name: Remove test code
run: |
rm -rf tests/
rm -rf test/
rm -rf spec/
rm -rf __tests__/
rm -f **/test_*.py
rm -f **/*_test.py
rm -f **/*.test.js
rm -f **/*.spec.js
rm -f **/Test*.java
rm -f **/*Test.java
# Fixed: Scan for test patterns in remaining code
- name: Check for test code
run: |
if grep -rn "test_password\|TEST_API_KEY\|pk_test_\|sk_test_" --include="*.py" --include="*.js" --include="*.php" --include="*.java" .; then
echo "ERROR: Test credentials found in deployment!"
exit 1
fi
# Fixed: Check for debug code
- name: Check for debug code
run: |
if grep -rn "phpinfo()\|var_dump(\|console.log(\|debugger;" --include="*.php" --include="*.js" .; then
echo "WARNING: Debug code found - review before deploying"
fi
- name: Deploy
run: ./deploy.sh
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is extremely common:
- Exposed API keys in GitHub repositories
- Test pages left in production deployments
- Credentials in test files committed to public repos
References
- MITRE Corporation. "CWE-531: Inclusion of Sensitive Information in Test Code." https://cwe.mitre.org/data/definitions/531.html
- OWASP. "Testing Guide - Test Code Review."
- GitHub. "Secret Scanning Documentation."