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

  1. Verify the token still contains the separator before calling `unsign`; if the client may mangle it, re-encode/escape at transport boundaries.
  2. Ensure the same `sep` is used to sign and unsign; `Signer` rejects separators matching `[A-z0-9-_=]` for safety, so pick e.g. `'~'`.
  3. 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

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


AI-assisted analysis of django/django@b5388a3a80 (2026-08-10). Data as JSON: /api/errors/a782a453fbff0c68. Report an issue: GitHub.