Information Disclosure

Data LeakageSensitive Data Exposure

Information Disclosure at a glance

What it is: Unintended exposure of internal implementation details, secrets, or user data to unauthorised parties via error pages, public storage, logs, debug endpoints, or misconfigured services.
Why it happens: Information disclosure occurs when debug settings, exposed management interfaces, public storage, leaked source files, or verbose logs reveal sensitive data due to configuration or deployment mistakes.
How to fix: Disable debug output in production, restrict diagnostic and storage access, redact sensitive logs, harden bucket permissions, and limit management endpoints to internal networks.

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.

sequenceDiagram participant Browser participant App as App Server Browser->>App: GET /bad-input?debug=1 App-->>Browser: 500 Internal Server Error (stack trace shows DB_URL and file paths) note over Browser: Secrets exposed in error response
A potential flow for a Information Disclosure exploit

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.

Vulnerable
JavaScript • Express — Bad
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.

Secure
JavaScript • Express — Good
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.

Vulnerable
JAVASCRIPT
// 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)
  });
});
Secure
JAVASCRIPT
// 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. 1. Probe error handling behavior

    http

    Action

    Send malformed requests to trigger error pages and observe verbosity of responses

    Request

    GET https://app.example.com/api/invalid-endpoint

    Response

    Status: 200
    Body:
    {
      "note": "Error response reveals framework version, file paths, or stack traces"
    }

    Artifacts

    http_response_body http_headers
  2. 2. Check for exposed management endpoints

    http

    Action

    Probe common diagnostic and management URLs for Spring Actuator, metrics, or admin consoles

    Request

    GET https://app.example.com/actuator/env

    Response

    Status: 200
    Body:
    {
      "note": "Actuator endpoint returns full environment variables including DB credentials and API keys"
    }

    Artifacts

    http_response_body exposed_secrets
  3. 3. Test for VCS metadata exposure

    http

    Action

    Check if version control metadata like .git directory is accessible via web server

    Request

    GET https://app.example.com/.git/config

    Response

    Status: 200
    Body:
    {
      "note": "Git config file accessible, revealing repository URLs and internal structure"
    }

    Artifacts

    http_response_body git_metadata
  4. 4. Inspect cloud storage permissions

    http

    Action

    Test if S3 buckets or blob storage containers allow public listing or anonymous access

    Request

    GET https://s3.amazonaws.com/company-backups/

    Response

    Status: 200
    Body:
    {
      "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. 1. Extract credentials from debug output

    Parse debug page for secrets

    analysis

    Action

    Extract database URLs, API keys, and AWS credentials from exposed error pages or actuator

    Request

    GET https://app.example.com/actuator/env

    Response

    Status: 200
    Body:
    {
      "note": "Credentials extracted: DB_URL, AWS_ACCESS_KEY_ID, STRIPE_SECRET_KEY"
    }

    Artifacts

    extracted_credentials environment_dump
  2. 2. Access cloud storage with leaked credentials

    Download backup files from S3

    http

    Action

    Use leaked AWS credentials to access private S3 buckets and download database backups

    Request

    CLI N/A - Analysis or internal step

    Response

    Status: 200
    Body:
    {
      "note": "Database dump downloaded containing customer PII and payment information"
    }

    Artifacts

    database_dump customer_records
  3. 3. Download source code via .git exposure

    Clone repository from exposed .git

    http

    Action

    Reconstruct full application source code from publicly accessible .git directory

    Request

    CLI N/A - Analysis or internal step

    Response

    Status: 200
    Body:
    {
      "note": "Complete source code extracted including hardcoded secrets and internal logic"
    }

    Artifacts

    source_code hardcoded_secrets
  4. 4. Enumerate internal architecture

    Map internal infrastructure

    analysis

    Action

    Use leaked stack traces, configs, and endpoints to map internal network and services

    Request

    GET https://app.example.com/actuator/mappings

    Response

    Status: 200
    Body:
    {
      "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