ellite/Wallos · error · InvalidArgumentException

Invalid "secret" parameter.

Error message

Invalid "secret" parameter.

What it means

This is a validation guard inside OTPHP's ParameterTrait: getSecret() fetches the 'secret' parameter from the OTP parameters array and throws InvalidArgumentException when it is missing, not a string, or an empty string. It fires when an OTP (TOTP/LTOTP) object was constructed or loaded (e.g., from a provisioning URI or createFromUpdatedProvisioningUri) without a valid secret being set, and code then tries to read the secret via getSecret(). Fix by ensuring the object is created with a non-empty secret string before calling getSecret().

Solutions

  1. Always pass a non-empty base32 secret to TOTP::create()/HOTP::create().
  2. Check the configuration source: ensure the secret env var/DB field is populated before constructing the OTP.
  3. When parsing URIs, verify the query string contains secret=<base32>.
  4. Guard in calling code: if ($secret === '' || $secret === null) throw a domain-specific error.

Example fix

// before
$totp = TOTP::create($_ENV['OTP_SECRET'] ?? ''); // empty -> throws later
// after
$secret = $_ENV['OTP_SECRET'] ?? null;
if (!is_string($secret) || $secret === '') {
    throw new RuntimeException('OTP_SECRET is not configured');
}
$totp = TOTP::create($secret);
Defensive patterns

Strategy: validation

Validate before calling

if (!is_string($secret) || $secret === '') {
    throw new MissingConfigurationException('OTP secret is not configured');
}
$totp = TOTP::create($secret);

Type guard

function hasSecret(object $otp): bool {
    try { return $otp->getSecret() !== ''; } catch (InvalidArgumentException) { return false; }
}

Try / catch

try {
    $code = $otp->at(time());
} catch (InvalidArgumentException $e) {
    log_error('OTP secret missing or invalid', ['exception' => $e->getMessage()]);
    throw new OtpNotConfiguredException($e);
}

Prevention

When it happens

Trigger: Instantiating an OTP object without a secret (e.g. new TOTP() or an empty-string secret), loading a provisioning URI without secret= in the query, or a secret parameter that was somehow set to null/non-string.

Common situations: Empty env var or DB column for the secret; URI parse dropping the query string; factory misuse creating an object before assigning the secret.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at libs/OTPHP/ParameterTrait.php:50

    /**
     * @return array<non-empty-string, mixed>
     */
    public function getParameters(): array
    {
        $parameters = $this->parameters;

        if ($this->getIssuer() !== null && $this->isIssuerIncludedAsParameter() === true) {
            $parameters['issuer'] = $this->getIssuer();
        }

        return $parameters;
    }

    public function getSecret(): string
    {
        $value = $this->getParameter('secret');
        (is_string($value) && $value !== '') || throw new InvalidArgumentException('Invalid "secret" parameter.');

        return $value;
    }

    public function getLabel(): null|string
    {
        return $this->label;
    }

    public function setLabel(string $label): void
    {
        $this->setParameter('label', $label);
    }

    public function getIssuer(): null|string
    {
        return $this->issuer;
    }

View on GitHub (pinned to 52820e87ca)