django/django · error · BadSignature
No "%s" found in value
Error message
No "%s" found in value
What it means
`Signer.unsign()` (signing.py:247-249) raises `BadSignature('No "%s" found in value' % self.sep)` when the separator (default `':'`) does not appear anywhere in the supplied signed string. Because `unsign` splits on the separator to recover the value and signature, a missing separator means the input is structurally invalid as a signed token.
Source
Thrown at django/core/signing.py:249
self.__class__.__name__,
)
self.algorithm = algorithm or "sha256"
if _SEP_UNSAFE.match(self.sep):
raise ValueError(
"Unsafe Signer separator: %r (cannot be empty or consist of "
"only A-z0-9-_=)" % sep,
)
def signature(self, value, key=None):
key = key or self.key
return base64_hmac(self.salt + "signer", value, key, algorithm=self.algorithm)
def sign(self, value):
return "%s%s%s" % (value, self.sep, self.signature(value))
def unsign(self, signed_value):
if self.sep not in signed_value:
raise BadSignature('No "%s" found in value' % self.sep)
value, sig = signed_value.rsplit(self.sep, 1)
for key in [self.key, *self.fallback_keys]:
if constant_time_compare(sig, self.signature(value, key)):
return value
raise BadSignature('Signature "%s" does not match' % sig)
def sign_object(self, obj, serializer=JSONSerializer, compress=False):
"""
Return URL-safe, hmac signed base64 compressed JSON string.
If compress is True (not the default), check if compressing using zlib
can save some space. Prepend a '.' to signify compression. This is
included in the signature, to protect against zip bombs.
The serializer is expected to return a bytestring.
"""
data = serializer().dumps(obj)
# Flag for if it's been compressed or not.View on GitHub (pinned to b5388a3a80)
Solutions
- Verify the token still contains the separator before calling `unsign`; if the client may mangle it, re-encode/escape at transport boundaries.
- Ensure the same `sep` is used to sign and unsign; `Signer` rejects separators matching `[A-z0-9-_=]` for safety, so pick e.g. `'~'`.
- Catch `BadSignature` at the call site and treat the input as untrusted/invalid rather than letting it surface as a 500.
Example fix
// before
value = signer.unsign(raw) # BadSignature: No ":" found in value
// after
if signer.sep not in raw:
raise ValueError('not a signed token')
try:
value = signer.unsign(raw)
except BadSignature:
value = None Defensive patterns
Strategy: try-catch
Validate before calling
from django.core.signing import BadSignature
def is_well_formed(token: str, sep: str = ':') -> bool:
return bool(token) and sep in token Type guard
def looks_signed(token: str, sep: str = ':') -> bool:
return isinstance(token, str) and sep in token and token.count(sep) >= 1 Try / catch
from django.core.signing import BadSignature
try:
value = signer.unsign(token)
except BadSignature as exc:
# 'No ":" found in value' means structurally invalid; treat as unsigned
value = None Prevention
- Use consistent separators across sign and verify (Signer rejects [A-z0-9-_=] separators).
- URL-encode signed tokens at transport boundaries so ':' is preserved.
- Always catch BadSignature at the trust boundary instead of letting it raise.
When it happens
Trigger: Calling `signer.unsign(token)` or `signing.loads(token)` with a string that contains no `:` (or whichever custom `sep` the Signer was constructed with). Examples: a truncated token, a raw unsigned value, a token built with a different separator than the verifier uses.
Common situations: Client truncated or URL-decoded a signed cookie/query param incorrectly (e.g. the `:` stripped); a signed value constructed with a custom `sep` being verified by a Signer using the default (or vice versa); passing an unsigned plaintext to `loads()`; mismatched separators across `dumps`/`loads` (note: only `Signer.unsign` exposes `sep`; high-level `dumps`/`loads` use the default).
Related errors
- Signature "%s" does not match
- Signature age %s > %s seconds
- The SECRET_KEY setting must not be empty.
- The field %s cannot be referenced.
- The field %s cannot be referenced.
AI-assisted analysis of django/django@b5388a3a80 (2026-08-10).
Data as JSON: /api/errors/a782a453fbff0c68.
Report an issue: GitHub.