RedScore.ai

Fixes

Technology Fingerprinting · Updated 2026-05-02

Debug Indicators

Debug mode in production is the only critical fail in this check. X-Runtime / X-Request-ID warn. Disable debug, strip diagnostic headers.

Of all the Technology Fingerprinting checks, this is the one that catches a real, directly exploitable problem: debug mode left on in production. Debug pages and debug consoles expose source paths, stack traces with sensitive data, session tokens, environment variables, database queries, and (in the Werkzeug case) an interactive Python console that lets an attacker run arbitrary code on your server. Diagnostic headers like X-Runtime are scored separately as minor signals.

How the check works

Per primary host, the check looks at response headers and a snippet of the body for two tiers of signals:

Critical signals (score 0.0, debug_mode_detected)

  • X-Debug-Token header (Symfony Profiler).
  • X-Debug-Token-Link header (Symfony Profiler).
  • X-Symfony-Debug header.
  • Server: werkzeug (Flask/Werkzeug development server in production).
  • X-Powered-By: Express + body containing stack trace patterns (TypeError, ReferenceError with at lines, node_modules paths).
  • X-AspNet-Version header + body containing exception/stack and aspnet/server error keywords (verbose ASP.NET error page).

Minor signals (score 0.6, debug_borderline_signal)

  • X-Runtime header (Rails default in some configurations; reveals per-request timing).
  • X-Request-ID header (only counted if no critical signal also fires).

No signals scores 1.0 (full credit). Per-host scores are weighted (apex highest) and averaged across all primary hosts. Verdict thresholds: pass at 0.9 and above, warn at 0.45 and above, fail below.

How the verdict maps to evidence

  • Pass: no debug or diagnostic signals on any primary host.
  • Warn: minor signals (X-Runtime, X-Request-ID) on at least one host. Reason: debug_borderline_signal.
  • Fail: critical signal on any host. Reason: debug_mode_detected. Treat as immediate priority.

Special states

  • Not Applicable: domain redirects to a different site, or primary serves a default/empty placeholder.
  • Degraded: probe data unavailable. Fix Web Assessability first.

Critical: turn debug off in production

If the check found any critical signal, treat it as the highest-priority finding in this whole category. The signals are not just fingerprints; they are direct evidence that debug-mode functionality is exposed to the public internet. The Werkzeug case in particular has been linked to repeated full-RCE incidents.

Symfony

Disable debug in production (.env)

APP_ENV=prod
APP_DEBUG=0

Symfony's web profiler is loaded only when APP_ENV=dev (or APP_DEBUG=1 in older versions). Confirm public/index.php uses the production kernel and that profiler bundles are not in your prod composer install.

Flask / Werkzeug

Run via a real WSGI server, never the dev server

# WRONG (do not deploy this to production):
app.run(debug=True)
# Werkzeug exposes an interactive debugger on errors. RCE-grade.

# RIGHT (use a production WSGI server):
# gunicorn (Linux):
gunicorn --bind 0.0.0.0:8000 myapp:app

# uWSGI:
uwsgi --http :8000 --module myapp:app

# In Flask code, set DEBUG=False explicitly and never call app.run() in prod.

Express / Node.js

Production environment

# Set NODE_ENV=production. Most Express middleware and apps
# behave differently in production: error pages omit stack traces,
# views cache, etc.
NODE_ENV=production node server.js

# In code, gate verbose error pages on environment:
if (process.env.NODE_ENV !== "production") {
  app.use((err, req, res, next) => {
    res.status(500).send(err.stack);
  });
} else {
  app.use((err, req, res, next) => {
    res.status(500).send("Internal Server Error");
  });
}

ASP.NET

Disable verbose error pages

<!-- web.config -->
<system.web>
  <customErrors mode="On" defaultRedirect="/error" />
  <compilation debug="false" />
</system.web>

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="500" />
    <error statusCode="500" path="/error" responseMode="ExecuteURL" />
  </httpErrors>
</system.webServer>

ASP.NET Core

Use developer exception page only outside production

// Program.cs
if (app.Environment.IsDevelopment()) {
    app.UseDeveloperExceptionPage();
} else {
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Rails

Production config

# config/environments/production.rb
config.consider_all_requests_local = false
config.action_dispatch.show_exceptions = :rescuable

# Strip X-Runtime header (set by ActionDispatch::Executor by default in
# some configurations):
config.action_dispatch.x_runtime_header = nil

Django

Production settings

# settings.py
DEBUG = False
ALLOWED_HOSTS = ["yourdomain.tld", "www.yourdomain.tld"]

# Without DEBUG=False AND ALLOWED_HOSTS set, Django serves debug
# error pages with full request context to anyone who triggers an
# exception. Both must be set.

Minor: strip X-Runtime / X-Request-ID at the proxy

These are useful diagnostic headers internally but not required externally. The simplest fix is to strip them at your reverse proxy or CDN before responses leave your network.

nginx

proxy_hide_header X-Runtime;
proxy_hide_header X-Request-ID;

Apache

Header unset X-Runtime
Header unset X-Request-ID

Cloudflare (Transform Rules)

Modify Response Header → Remove → X-Runtime
Modify Response Header → Remove → X-Request-ID

Verify the fix

  • Trigger an error path on your site (a known-bad URL, an intentional bad parameter) and check the response. Stack traces, source paths, or interactive consoles in the response are critical.
  • curl -sI https://yourdomain.tld | grep -iE 'x-debug-token|x-symfony-debug|x-runtime|x-request-id|server' should return only your intended Server header.
  • If you saw a Werkzeug or Symfony Profiler signal, browse to /_profiler (Symfony) or trigger an exception (Werkzeug) and confirm those debug interfaces are no longer reachable.
  • Re-run the RedScore lookup. The verdict moves to pass when no debug or diagnostic signals appear on any primary host.

Common pitfalls

  • Werkzeug/Flask in "production" with debug=True. The Werkzeug debugger has a PIN-protected console, but the PIN is derived from machine details that can leak through other endpoints. Treat any Werkzeug-on-production finding as a likely RCE. Move to gunicorn/uWSGI immediately.
  • Symfony Profiler bundle still installed. Even with APP_ENV=prod, if the profiler bundle is in your composer install and you have a route that loads it, it can be reachable. Remove web-profiler-bundle from production dependencies.
  • Verbose ASP.NET errors when customErrors=Off. The customErrors=On setting hides stack traces from remote clients but still shows them locally. Make sure mode is On in production web.config and confirm .NET version supports the directive in your hosting model.
  • Stack traces in JSON API responses. Many APIs return error.stack on 500. The check looks at body_preview which includes API responses. Wrap exception handlers to return generic errors in production.
  • X-Runtime added by middleware you do not control. Some shared platforms (Heroku, certain PaaS) add X-Runtime as an outbound diagnostic. Strip at the CDN if you cannot remove it at the platform.
  • Re-enabling debug for short-lived investigations. If you flip debug on temporarily and forget to flip it off, the next scan catches it. Use feature-flag-style toggles or per-host dev environments instead of touching the live config.

What to do next

See how these recommendations apply to your site's current scan results.

Scan domain