v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
HIGH AI PREVALENCE: MEDIUM auth.go.flow.oauth-credential-in-log

An OAuth/OIDC credential from the HTTP request flows into a logging call.

Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.

Why this matters

The tainted value is an authorization code, an access_token / refresh_token / id_token, a bearer token, a client_secret, or the raw Authorization header, and the sink is a log.*, slog.*, fmt.Print*, or logger.* call. Logs are written to files, shipped to aggregators (Datadog, Splunk, CloudWatch) and read by people and systems that should never see live credentials. A leaked authorization code or token can be replayed to impersonate the user or complete the OAuth exchange (CWE-532).

Never log the raw credential. Redact or mask it before logging, log a non-sensitive identifier instead (a user id, a key id), or drop the field entirely.

VULNERABLE
vulnerable.go
package main

import (
	"fmt"
	"log"
	"log/slog"
	"net/http"
)

// Inline: the authorization code logged straight from the query string.
func logCode(w http.ResponseWriter, r *http.Request) {
	// ruleid: auth.go.flow.oauth-credential-in-log
	log.Printf("received code=%s", r.URL.Query().Get("code"))
}

// Indirection: access token stored in a local, then logged via slog.
func logAccessToken(w http.ResponseWriter, r *http.Request) {
	at := r.FormValue("access_token")
	// ruleid: auth.go.flow.oauth-credential-in-log
	slog.Info("token exchange", "token", at)
}

// The raw Authorization header written to stdout via fmt.
func logAuthHeader(w http.ResponseWriter, r *http.Request) {
	auth := r.Header.Get("Authorization")
	// ruleid: auth.go.flow.oauth-credential-in-log
	fmt.Println("auth:", auth)
}

// A client_secret form value logged through a named logger receiver.
func logClientSecret(w http.ResponseWriter, r *http.Request, logger *log.Logger) {
	// ruleid: auth.go.flow.oauth-credential-in-log
	logger.Printf("client_secret=%s", r.PostFormValue("client_secret"))
}

func main() {
	http.HandleFunc("/code", logCode)
	http.HandleFunc("/at", logAccessToken)
	_ = http.ListenAndServe(":8080", nil)
}
SAFE
safe.go
package main

import (
	"log"
	"net/http"
)

// redact replaces all but the first few characters of a secret.
func redact(s string) string {
	if len(s) <= 4 {
		return "****"
	}
	return s[:4] + "****"
}

// Safe: the credential is masked before it reaches the log sink.
func logRedacted(w http.ResponseWriter, r *http.Request) {
	// ok: auth.go.flow.oauth-credential-in-log
	log.Printf("code=%s", redact(r.URL.Query().Get("code")))
}

// Safe trap: a benign, non-credential request field logged verbatim must not
// fire — the source list is scoped to OAuth credential names only.
func logPage(w http.ResponseWriter, r *http.Request) {
	// ok: auth.go.flow.oauth-credential-in-log
	log.Printf("page=%s", r.URL.Query().Get("page"))
}

func main() {
	http.HandleFunc("/r", logRedacted)
	http.HandleFunc("/p", logPage)
	_ = http.ListenAndServe(":8080", nil)
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.go.flow.oauth-credential-in-log -- <reason>

References

https://cwe.mitre.org/data/definitions/532.html ↗https://datatracker.ietf.org/doc/html/rfc6749#section-10.3 ↗https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/ ↗