hashicorp/nomad · error
failed to parse signed token: %w
Error message
failed to parse signed token: %w
What it means
Fires in Encrypter.VerifyClaim when jwt.ParseSigned cannot parse the presented token string — the token is malformed/truncated or not a valid JWS compact serialization, so signature verification never starts.
Source
Thrown at nomad/encrypter.go:362
return "", "", err
}
}
raw, err := jwt.Signed(sig).Claims(claims).CompactSerialize()
if err != nil {
return "", "", err
}
return raw, cs.rootKey.Meta.KeyID, nil
}
// VerifyClaim accepts a previously signed encoded claim and validates
// it before returning the claim.
func (e *Encrypter) VerifyClaim(tokenString string) (*structs.IdentityClaims, error) {
token, err := jwt.ParseSigned(tokenString)
if err != nil {
return nil, fmt.Errorf("failed to parse signed token: %w", err)
}
// Find the Key ID
keyID, err := joseutil.KeyID(token)
if err != nil {
return nil, err
}
// Find the key material
pubKey, err := e.waitForPublicKey(keyID)
if err != nil {
return nil, err
}
typedPubKey, err := pubKey.GetPublicKey()
if err != nil {
return nil, err
}View on GitHub (pinned to 482b49bf1a)
Solutions
- Regenerate the token (nomad alloc status / job re-submit) rather than repairing it
- Verify the full compact JWT (three dot-separated base64url segments) reaches VerifyClaim — no truncation or whitespace
- Confirm the caller is passing a signed workload identity claim, not an ACL secret ID
- Check secret templates for rendering bugs that cut the token
Example fix
// before: template truncates token
NOMAD_JWT = "{{ with secret "nomad/vars/foo" }}{{ .Data.token | truncate 60 "" }}{{ end }}"
// after: render the full value
NOMAD_JWT = "{{ with secret "nomad/vars/foo" }}{{ .Data.token }}{{ end }}" Defensive patterns
Strategy: validation
Validate before calling
import "strings"
// quick shape check before VerifyClaim
func looksLikeCompactJWT(tok string) bool {
parts := strings.Split(strings.TrimSpace(tok), ".")
return len(parts) == 3 && len(parts[0]) > 0 && len(parts[1]) > 0 && len(parts[2]) > 0
} Type guard
func isProbablyJWS(s string) bool {
s = strings.TrimSpace(s)
parts := strings.Split(s, ".")
if len(parts) != 3 { return false }
for _, p := range parts {
if p == "" { return false }
for _, c := range p {
ok := c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
if !ok { return false }
}
}
return true
} Try / catch
claims, err := encrypter.VerifyClaim(token)
if err != nil && strings.Contains(err.Error(), "failed to parse signed token") {
return fmt.Errorf("token malformed (got %d chars) — re-issue the workload token: %w", len(token), err)
} Prevention
- Pass tokens verbatim — no trimming, truncation, or template post-processing
- Store the full token in templates and env vars (watch for line-wrapping in files)
- Distinguish ACL secret IDs from signed identity claims in code
- Validate token shape at the boundary where the token is received
When it happens
Trigger: Encrypter.VerifyClaim(tokenString) is called with an empty, truncated, base64-corrupt, or non-JWT string; parse fails before any key lookup.
Common situations: Workload passes a truncated NOMAD_TOKEN-style value, a config template captured only part of the token, whitespace/newlines injected by a secret template, or a caller passes an opaque ACL token where a signed identity claim was expected.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- invalid signature: %w
- invalid claims: %w
- node does not have a JWT identity token
- ErrNoKeyID
- users: unable to parse uid/gid from username
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/bba09a5cd390489b.
Report an issue: GitHub.