Juicy Shop
Security Misconfiguration
Most breaches don't need a clever exploit — just a default password, a verbose error, or an exposed admin panel. Learn the common misconfigurations and how secure-by-default settings close them.
The default-and-forgot problem
Frameworks, databases, and cloud services ship with permissive defaults to make getting started easy: sample accounts, debug modes, open ports, world-readable buckets. Security misconfiguration is the gap between 'works on my machine' and 'hardened for production'. The fix is a repeatable hardening process applied to every environment, not a one-off checklist someone runs by hand.
Verbose errors and stack traces
A stack trace returned to the browser tells an attacker your framework, versions, file paths, and sometimes secrets or SQL. In production, log the full error server-side and return a generic message with a correlation id to the client. Disable debug endpoints, directory listings, and detailed error pages before you ship.
Security headers and CORS
Missing response headers are a classic misconfiguration. Set a strict Content-Security-Policy, X-Content-Type-Options: nosniff, and a sensible Referrer-Policy; enable HSTS. For CORS, never reflect arbitrary Origins or use Access-Control-Allow-Origin: * together with credentials — allow-list the exact origins you trust.
Least functionality
Reduce attack surface: remove unused features, sample apps, and default accounts; change every default credential; close ports you don't need; and don't expose internal admin tools to the public internet. The fewer doors exist, the fewer can be left unlocked.
Insecure vs secure
const app = express();
app.use(errorhandler()); // full stack traces to client
app.use(cors({ origin: true, credentials: true }));
// reflects ANY origin and allows credentialsStack traces leak internals to attackers, and reflecting any Origin with credentials lets any site make authenticated cross-origin calls.
const app = express();
app.use(helmet()); // sets CSP, HSTS, nosniff, etc.
app.use(cors({ origin: ALLOWED_ORIGINS, credentials: true }));
app.use((err, _req, res, _next) => {
log.error(err);
res.status(500).json({ id: correlationId() });
});Helmet applies secure headers, CORS is restricted to an explicit allow-list, and clients get a generic error with a correlation id instead of a stack trace.
Key takeaways
- Permissive defaults, not exotic exploits, cause many breaches.
- Never return stack traces to clients in production.
- Set security headers (CSP, HSTS, nosniff) and lock down CORS to an allow-list.
- Remove unused features, sample apps, and default credentials.
- Harden every environment with a repeatable, automated process.