Information Disclosure
Information Disclosure at a glance
Overview
Information disclosure covers a broad set of issues where data not intended for public consumption becomes accessible. Examples include debug pages revealing stack traces and environment variables, misconfigured storage (S3 buckets, object stores) that allow public reading, exposed management or diagnostic endpoints such as Spring Actuator, repository metadata (.git) served by web servers, and logs or telemetry that contain secrets.
Where it occurs
It occurs through misconfigurations or leftover debug settings, exposed management interfaces, public storage, leaked source files, or verbose logging, often introduced during rapid development or CI/CD setup.
Impact
Exposed information can leak API keys, database connection strings, internal endpoints, architecture details, and user data. Attackers use this information to escalate privileges, craft targeted exploits, and retrieve further secrets. The breach may require secrets rotation, forensic analysis, and customer notification.
Prevention
Prevent exposure by enforcing secure production defaults (DEBUG off, restricted management endpoints, private storage), redacting secrets from logs, protecting operational data, and automating scans for misconfigurations or leaked credentials.
Examples
Switch tabs to view language/framework variants.
Express, returns error stack traces to clients in responses
Error handler sends `err.stack` to the HTTP response, leaking internal file paths, code, and possibly secrets.
app.use((err, req, res, next) => {
res.status(500).send(err.stack); // BUG: leaks stack trace to client
});- Line 1: err.stack printed directly to the response exposes internal details
Returning stack traces reveals implementation details and can leak secrets or allow attackers to craft targeted exploits.
app.use((err, req, res, next) => {
// log details internally with appropriate redaction
logger.error('Unhandled error', { err });
// send a generic message to clients
res.status(500).send('internal server error');
});- Line 2: Log the stack server-side, return a generic error message to clients
Log full details internally (with redaction) and expose only generic errors to callers.
Engineer Checklist
-
Disable debug/verbose errors in production (e.g., Django DEBUG=False)
-
Restrict management and diagnostic endpoints to internal networks or require authentication
-
Block public access to object stores by default; use presigned URLs for temporary access
-
Avoid shipping .git or VCS metadata in production images or webroots; use .dockerignore and build artifacts
-
Redact sensitive fields from logs and control access to log storage and telemetry
-
Add deployment-time checks for common misconfigurations and run SCA/SAST to find exposed secrets
End-to-End Example
A team pushes a hotfix and leaves DEBUG enabled. An attacker triggers an exception and reads the detailed debug page showing environment variables including DB credentials and an S3 backup path. The attacker uses the credentials to download backups from S3.
// Node.js/Express - Vulnerable information disclosure
// VULNERABLE: Debug mode enabled in production
process.env.NODE_ENV = 'development'; // Should be 'production'!
// VULNERABLE: Detailed error handler that exposes stack traces
app.use((err, req, res, next) => {
// VULNERABLE: Sends full error details to client!
// Exposes file paths, library versions, env variables
res.status(500).json({
error: err.message,
stack: err.stack, // Full stack trace!
env: process.env, // ALL environment variables!
config: config, // Database URLs, API keys, etc.
request: {
headers: req.headers,
body: req.body,
user: req.user
}
});
});
// VULNERABLE: Debug endpoint exposed in production
app.get('/debug/env', (req, res) => {
// VULNERABLE: No authentication!
// Exposes all environment variables
res.json(process.env);
});
// VULNERABLE: Actuator/health endpoint with too much detail
app.get('/actuator/health', (req, res) => {
// VULNERABLE: Exposes internal architecture
res.json({
status: 'up',
database: {
url: process.env.DATABASE_URL, // Connection string!
version: '14.5',
host: 'db-prod-01.internal.example.com'
},
redis: {
host: process.env.REDIS_HOST,
password: process.env.REDIS_PASSWORD // Exposed!
},
services: {
payment: 'https://payment-service.internal.example.com',
auth: 'https://auth-service.internal.example.com'
},
secrets: {
stripeKey: process.env.STRIPE_SECRET_KEY,
jwtSecret: process.env.JWT_SECRET
}
});
});
// VULNERABLE: Logging sensitive data
app.post('/login', async (req, res) => {
const { email, password } = req.body;
// VULNERABLE: Logs passwords in plaintext!
console.log('Login attempt:', { email, password });
// VULNERABLE: Logs authorization headers
console.log('Request headers:', req.headers);
const user = await User.findOne({ email });
const valid = await bcrypt.compare(password, user.passwordHash);
if (valid) {
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
// VULNERABLE: Logs JWT token
console.log('Generated token:', token);
res.json({ token });
}
});
// VULNERABLE: Exposed .git directory
// Static file serving includes .git folder
app.use(express.static('public')); // public/.git is accessible!
// VULNERABLE: Backup files accessible
// .bak, .old, .backup files in webroot
app.get('/download', (req, res) => {
const file = req.query.file;
// No validation - can access .bak files
res.download(`/var/app/uploads/${file}`);
});
// VULNERABLE: Source maps in production
// main.js.map exposes original source code
app.use('/assets', express.static('dist/assets')); // Includes .map files!
// VULNERABLE: Detailed 404 pages
app.use((req, res) => {
res.status(404).json({
error: 'Not Found',
attempted_path: req.path, // Reveals routing logic
server: 'Express 4.18.2', // Version info
node_version: process.version,
available_routes: app._router.stack // ALL routes!
.filter(r => r.route)
.map(r => r.route.path)
});
});// Node.js/Express - Secure information handling
// SECURE: Force production mode
if (process.env.NODE_ENV !== 'production') {
throw new Error('NODE_ENV must be production');
}
// SECURE: Generic error handler that doesn't leak details
app.use((err, req, res, next) => {
// Log full error details server-side only
logger.error('Application error', {
error: err.message,
stack: err.stack,
requestId: req.id,
path: req.path,
method: req.method,
// Don't log sensitive data like passwords or tokens
userId: req.user?.id
});
// Send generic error to client - no stack trace or details
res.status(500).json({
error: 'Internal server error',
requestId: req.id // Only for support tracking
});
});
// SECURE: Redact sensitive fields from logs
const REDACTED_FIELDS = new Set([
'password',
'authorization',
'cookie',
'token',
'secret',
'apiKey',
'creditCard'
]);
function redactSensitiveData(obj) {
if (!obj || typeof obj !== 'object') return obj;
const redacted = Array.isArray(obj) ? [] : {};
for (const [key, value] of Object.entries(obj)) {
const lowerKey = key.toLowerCase();
const isSensitive = REDACTED_FIELDS.has(lowerKey) ||
lowerKey.includes('password') ||
lowerKey.includes('secret') ||
lowerKey.includes('token');
if (isSensitive) {
redacted[key] = '[REDACTED]';
} else if (typeof value === 'object') {
redacted[key] = redactSensitiveData(value);
} else {
redacted[key] = value;
}
}
return redacted;
}
// SECURE: Login with safe logging
app.post('/login', async (req, res) => {
const { email, password } = req.body;
// Safe logging - no password!
logger.info('Login attempt', {
email,
ip: req.ip,
userAgent: req.get('User-Agent')
});
const user = await User.findOne({ email });
const valid = await bcrypt.compare(password, user.passwordHash);
if (valid) {
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
// Don't log the actual token!
logger.info('Login successful', { userId: user.id });
res.json({ token });
} else {
logger.warn('Login failed', { email, ip: req.ip });
res.status(401).json({ error: 'Invalid credentials' });
}
});
// SECURE: Health endpoint with minimal info
app.get('/health', (req, res) => {
// Only return service status, no secrets or architecture
res.json({
status: 'healthy',
timestamp: new Date().toISOString()
// No database URLs, no internal hostnames, no secrets!
});
});
// SECURE: Detailed metrics only for authenticated internal requests
app.get('/actuator/metrics', requireInternalAuth, (req, res) => {
// Validate request comes from internal network
const ip = req.ip;
if (!isInternalIP(ip)) {
return res.status(403).json({ error: 'Forbidden' });
}
// Require authentication even for internal requests
if (!req.user || !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
// Return metrics but still redact secrets
res.json({
database: {
connected: true,
// Don't include URL or credentials
},
redis: {
connected: true
// Don't include host or password
}
});
});
function isInternalIP(ip) {
return ip === '127.0.0.1' ||
ip.startsWith('10.') ||
ip.startsWith('192.168.');
}
// SECURE: Prevent .git exposure with explicit blocking
app.use((req, res, next) => {
// Block access to version control and backup files
const blockedPatterns = [
/\.git/,
/\.svn/,
/\.env/,
/\.bak$/,
/\.backup$/,
/\.old$/,
/\.map$/ // source maps
];
if (blockedPatterns.some(pattern => pattern.test(req.path))) {
return res.status(404).send('Not found');
}
next();
});
// SECURE: Static files with restrictions
const serveStatic = express.static('public', {
dotfiles: 'deny', // Block .git, .env, etc.
index: false, // Don't list directories
setHeaders: (res, path) => {
// Prevent source map access in production
if (path.endsWith('.map')) {
res.status(404).end();
}
}
});
app.use(serveStatic);
// SECURE: Generic 404 handler
app.use((req, res) => {
// Don't reveal routing information
res.status(404).json({
error: 'Not found'
// No server version, no available routes, no stack info
});
});
// SECURE: Request logging middleware with redaction
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
// Log with redacted sensitive data
logger.info('Request', {
method: req.method,
path: req.path,
status: res.statusCode,
duration,
ip: req.ip,
// Redact sensitive headers
headers: redactSensitiveData(req.headers),
// Don't log body - may contain passwords
});
});
next();
});
// SECURE: Dockerfile best practices
/*
FROM node:18-alpine
WORKDIR /app
# .dockerignore includes:
# .git
# .env
# *.bak
# *.log
# node_modules
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
# Don't copy source maps to production image
RUN find dist -name "*.map" -delete
# Don't run as root
USER node
CMD ["node", "dist/server.js"]
*/Discovery
This vulnerability is discovered by analyzing application responses, error messages, debug endpoints, and source code comments for sensitive data like stack traces, database queries, internal paths, API keys, or user information that should not be exposed.
-
1. Probe error handling behavior
httpAction
Send malformed requests to trigger error pages and observe verbosity of responses
Request
GET https://app.example.com/api/invalid-endpointResponse
Status: 200Body:{ "note": "Error response reveals framework version, file paths, or stack traces" }Artifacts
http_response_body http_headers -
2. Check for exposed management endpoints
httpAction
Probe common diagnostic and management URLs for Spring Actuator, metrics, or admin consoles
Request
GET https://app.example.com/actuator/envResponse
Status: 200Body:{ "note": "Actuator endpoint returns full environment variables including DB credentials and API keys" }Artifacts
http_response_body exposed_secrets -
3. Test for VCS metadata exposure
httpAction
Check if version control metadata like .git directory is accessible via web server
Request
GET https://app.example.com/.git/configResponse
Status: 200Body:{ "note": "Git config file accessible, revealing repository URLs and internal structure" }Artifacts
http_response_body git_metadata -
4. Inspect cloud storage permissions
httpAction
Test if S3 buckets or blob storage containers allow public listing or anonymous access
Request
GET https://s3.amazonaws.com/company-backups/Response
Status: 200Body:{ "note": "XML bucket listing reveals backup files, database dumps, and sensitive documents" }Artifacts
bucket_listing exposed_files
Exploit steps
An attacker exploits this by collecting leaked sensitive information such as credentials, API keys, internal architecture details, or user data, using it to plan and execute more sophisticated attacks or directly access protected resources.
-
1. Extract credentials from debug output
Parse debug page for secrets
analysisAction
Extract database URLs, API keys, and AWS credentials from exposed error pages or actuator
Request
GET https://app.example.com/actuator/envResponse
Status: 200Body:{ "note": "Credentials extracted: DB_URL, AWS_ACCESS_KEY_ID, STRIPE_SECRET_KEY" }Artifacts
extracted_credentials environment_dump -
2. Access cloud storage with leaked credentials
Download backup files from S3
httpAction
Use leaked AWS credentials to access private S3 buckets and download database backups
Request
CLI N/A - Analysis or internal stepResponse
Status: 200Body:{ "note": "Database dump downloaded containing customer PII and payment information" }Artifacts
database_dump customer_records -
3. Download source code via .git exposure
Clone repository from exposed .git
httpAction
Reconstruct full application source code from publicly accessible .git directory
Request
CLI N/A - Analysis or internal stepResponse
Status: 200Body:{ "note": "Complete source code extracted including hardcoded secrets and internal logic" }Artifacts
source_code hardcoded_secrets -
4. Enumerate internal architecture
Map internal infrastructure
analysisAction
Use leaked stack traces, configs, and endpoints to map internal network and services
Request
GET https://app.example.com/actuator/mappingsResponse
Status: 200Body:{ "note": "Full API endpoint map, internal service URLs, and database schemas revealed" }Artifacts
api_documentation internal_endpoints
Specific Impact
Sensitive production credentials are exposed, enabling attackers to access backups and databases. This results in a data breach affecting customers and regulatory obligations to disclose.
Remediation includes disabling debug mode, rotating exposed credentials and keys, searching other systems for reuse of the compromised secrets, invalidating leaked backups, and notifying impacted parties.
Fix
Turn off debug and verbose outputs in production, redact and protect logs, restrict management endpoints to internal networks or require authentication, and rotate any secrets exposed during the incident. Add CI/deploy checks to detect and block DEBUG=true deploys and public bucket policies.
Detect This Vulnerability in Your Code
Sourcery automatically identifies information disclosure vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free