Quick Answer
AI-generated code is full of console.log() statements that dump user credentials, API responses, session tokens, and personal data to the browser console and server logs. In production, these debug statements become security vulnerabilities - anyone with browser DevTools can see sensitive data, and server logs often end up in third-party monitoring services without proper redaction.
How Debug Statements Become Data Leaks
When you prompt an AI tool to build a login form, it adds console.log() for debugging. When you ask for Stripe integration, it logs the full webhook payload. When you build an API, it logs every request body. These statements are invisible to the UI, but they create persistent data exposure in two places: the browser console (client-side) and server/container logs (server-side).
GitGuardian's State of Secrets Sprawl 2023 found 10 million new secrets exposed on GitHub in 2022 alone. Security logging and alerting failures are A09 in the OWASP Top 10 (2025), but excessive logging of sensitive data is equally dangerous.
What AI-Generated Code Logs
// ❌ BAD - Typical AI-generated auth handler
app.post('/api/login', async (req, res) => {
console.log('Login request:', req.body);
// Logs: { email: "user@email.com", password: "MyP@ssw0rd!" }
const user = await db.users.findByEmail(req.body.email);
console.log('Found user:', user);
// Logs: { id: 1, email: "...", passwordHash: "...", ssn: "..." }
const token = generateToken(user);
console.log('Generated token:', token);
// Logs: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
res.json({ token });
});
// ✅ GOOD - Structured logging without sensitive data
import pino from 'pino';
import bcrypt from 'bcrypt';
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
app.post('/api/login', async (req, res) => {
logger.info({ email: req.body.email }, 'Login attempt');
// Logs: { email: "user@email.com", msg: "Login attempt" }
// Never logs password, token, or full user object
const user = await db.users.findByEmail(req.body.email);
const valid = user && await bcrypt.compare(req.body.password, user.passwordHash);
if (!valid) {
logger.warn({ email: req.body.email }, 'Login failed');
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = generateToken(user);
logger.info({ userId: user.id }, 'Login successful');
// Only logs a non-sensitive identifier
res.json({ token });
});
The Three Places Your Logs Leak
| Location | Who Can See It | Risk |
|---|---|---|
| Browser console | Any user with DevTools (F12) | Exposes tokens, API keys, user data in the client |
| Server stdout/stderr | Anyone with server access | Passwords, full request bodies in container logs |
| Log aggregation services | Third-party vendor (Datadog, LogRocket, Sentry) | Sensitive data stored in external systems you do not control |
Sensitive Data Types Found in Logs
| Data Type | Typical AI Log Statement | GDPR / PCI Impact |
|---|---|---|
| Passwords | console.log('body:', req.body) |
Direct credential exposure |
| JWT tokens | console.log('token:', token) |
Session hijacking vector |
| Credit card numbers | console.log('payment:', payload) |
PCI-DSS violation |
| Email / phone / SSN | console.log('user:', userData) |
Risks breaching GDPR Article 5 (data minimisation) |
| API keys / secrets | console.log('config:', config) |
Full system compromise |
How to Remove and Prevent Log Leaks
- Search your entire codebase for
console.log,console.debug,console.warn, andconsole.error. Remove any that log request bodies, user objects, tokens, or API responses. - Use a structured logger like Pino or Winston. Structured loggers make it easy to control what fields are included and support log-level filtering per environment.
- Add an ESLint rule:
"no-console": "error"to catch newconsole.logstatements at build time. - Configure log levels: Use
debuglevel for development andinfoorwarnfor production. Never deploy with debug-level logging enabled. - Redact sensitive fields in your logger configuration. Pino supports
redact: ['req.headers.authorization', 'req.body.password']. - Scan automatically. VibeDoctor's Vibe Check flags every
console.logandconsole.debugcall in JavaScript and TypeScript code outside tests, scripts, docs and examples, with the file and line. In Python web apps it flags files with more than fiveprint()calls and no logging library (CFG-006). It flags the call, not what it logs, so the redaction steps above still matter. Free to sign up.
ESLint Configuration to Block console.log
ESLint 10 only reads the flat config file, eslint.config.js. The old .eslintrc.json format is no longer supported.
// eslint.config.js
export default [
{
rules: {
'no-console': ['error', { allow: ['warn', 'error'] }],
},
},
];
// For files where you need console.log (dev scripts, CLIs):
// eslint-disable-next-line no-console
console.log('This is intentional');
FAQ
Is console.log() a real security vulnerability?
Yes. Logging sensitive data (passwords, tokens, PII) to the browser console or server logs is classified as CWE-532 (Insertion of Sensitive Information into Log File). It can lead to credential theft, session hijacking, and regulatory violations (GDPR, PCI-DSS, HIPAA). It is not theoretical - it is a documented, exploitable vulnerability class.
Can I just strip console.log in production builds?
Build tools like Terser can remove console.log from client-side bundles, but this does not help with server-side code (Node.js, Express, Fastify). Server-side console.log still writes to container logs. You need a proper logging library with redaction and level filtering for full coverage.
What should I log instead?
Log events, not data. Log "Login attempt for user 42" instead of "Login with body: {email, password}". Log "Payment processed, order 123" instead of "Stripe payload: {card_number, ...}". Log identifiers and outcomes, never raw input or credentials.
How many console.log statements are too many?
In production code, the answer is generally zero. Every console.log in production should be replaced with a structured logger call at an appropriate log level. Development-only debug logging should be removed or gated behind a debug flag before deployment.