ellite/Wallos · error · InvalidArgumentException

Label must not contain a colon.

Error message

Label must not contain a colon.

What it means

OTP::generateURI rejects labels containing a colon via hasColon(), throwing InvalidArgumentException. In the otpauth scheme the colon separates issuer from account in labels, and this library wants the issuer supplied separately (setIssuer) rather than embedded in the label.

Solutions

  1. Remove the colon from the label: use setLabel('alice@example.com').
  2. Set the issuer separately: $otp->setIssuer('My Company') — the library renders 'Issuer:account' correctly itself.
  3. Strip or replace colons: str_replace(':', ' ', $label) before setLabel().
  4. Validate labels with strpos($label, ':') === false before setting.

Example fix

// before
$otp->setLabel('Acme: alice@example.com');
// after
$otp->setIssuer('Acme');
$otp->setLabel('alice@example.com');
Defensive patterns

Strategy: validation

Validate before calling

if (str_contains($label, ':')) {
    [$issuer, $account] = explode(':', $label, 2);
    $otp->setIssuer(trim($issuer));
    $label = trim($account);
}
$otp->setLabel($label);

Type guard

function isCleanLabel(string $label): bool {
    return $label !== '' && !str_contains($label, ':');
}

Try / catch

try {
    $uri = $otp->getProvisioningUri();
} catch (InvalidArgumentException $e) {
    log_error('Bad OTP label', ['label' => $otp->getLabel()]);
    throw new InvalidOtpConfigurationException($e);
}

Prevention

When it happens

Trigger: setLabel('My Company: alice@example.com') followed by getProvisioningUri(); labels copied from other tools that embed 'Issuer:account' in one string.

Common situations: Migrating from libraries that expect 'Issuer:user' labels; concatenating issuer and username manually; copy-pasting full labels from decoded QR payloads.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of ellite/Wallos@52820e87ca (2026-09-13). Data as JSON: /api/errors/6a1689cea3932350. Report an issue: GitHub.

Appendix: source

Thrown at libs/OTPHP/OTP.php:103

            if (isset($options[$key]) && $default === $options[$key]) {
                unset($options[$key]);
            }
        }

        ksort($options);
    }

    /**
     * @param non-empty-string $type
     * @param array<non-empty-string, mixed> $options
     *
     * @return non-empty-string
     */
    protected function generateURI(string $type, array $options): string
    {
        $label = $this->getLabel();
        is_string($label) || throw new InvalidArgumentException('The label is not set.');
        $this->hasColon($label) === false || throw new InvalidArgumentException('Label must not contain a colon.');
        $options = [...$options, ...$this->getParameters()];
        $this->filterOptions($options);
        $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options, '', '&'));

        return sprintf(
            'otpauth://%s/%s?%s',
            $type,
            rawurlencode(($this->getIssuer() !== null ? $this->getIssuer() . ':' : '') . $label),
            $params
        );
    }

    /**
     * @param non-empty-string $safe
     * @param non-empty-string $user
     */
    protected function compareOTP(string $safe, string $user): bool
    {

View on GitHub (pinned to 52820e87ca)