juanfont/headscale · error · parseErr (ErrPreAuthKeyFailedToParse|ErrOAuthClientFailedToParse|ErrAccessTokenFailedToParse|ErrAPIKeyFailedToParse)

%w: secret contains invalid characters (expected base64 URL-

Error message

%w: secret contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)

What it means

The 64-character secret portion of a parsed credential contains characters outside the base64 URL-safe alphabet (A-Za-z0-9_-). Headscale generates secrets as hex, so invalid characters indicate corruption, replacement (e.g. shell expansion), or a foreign key format. The parseErr sentinel is wrapped for classification.

Source

Thrown at hscontrol/db/preauth_keys.go:288

		return "", "", fmt.Errorf(
			"%w: secret length mismatch, expected %d chars, got %d",
			parseErr,
			secretLen,
			len(secret),
		)
	}

	// Validate prefix contains only base64 URL-safe characters
	if !isValidBase64URLSafe(prefix) {
		return "", "", fmt.Errorf(
			"%w: prefix contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)",
			parseErr,
		)
	}

	// Validate secret contains only base64 URL-safe characters
	if !isValidBase64URLSafe(secret) {
		return "", "", fmt.Errorf(
			"%w: secret contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)",
			parseErr,
		)
	}

	return prefix, secret, nil
}

// isValidBase64URLSafe reports whether s contains only base64 URL-safe
// characters (A-Za-z0-9-_). Key material is now generated as hex, a subset of
// this alphabet, so this accepts both current hex keys and any legacy keys
// still stored in the database.
func isValidBase64URLSafe(s string) bool {
	return !strings.ContainsFunc(s, func(c rune) bool {
		return (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '-' && c != '_'
	})
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Always quote the key in single quotes in shell commands.
  2. Regenerate the key and transfer it via copy/paste without retyping.
  3. Verify with a quick regex that the secret matches ^[A-Za-z0-9_-]{64}$ before use.

Example fix

// before
AUTHKEY=hskey-node-abc123def456-secRet$1
headscale nodes register --key $AUTHKEY

// after
AUTHKEY='hskey-node-abc123def456-secRet$1' // single quotes, no expansion
headscale nodes register --key "$AUTHKEY"
Defensive patterns

Strategy: validation

Validate before calling

var base64URLSafe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
func validSecret(s string) bool {
    return len(s) == 64 && base64URLSafe.MatchString(s)
}

Prevention

When it happens

Trigger: Shell scripts where parts of the key were interpreted ($ expansion historically mangled old-format keys), keys passed through systems that transliterate characters (e.g. '0'/'O' swaps by humans), or non-headscale credentials fed to the parser.

Common situations: Single- vs double-quote mistakes in bash (unquoted $ sequences), email/chat autocorrect altering characters, manually retyped keys.

Understand the failure class

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/2089ac33f12833aa. Report an issue: GitHub.