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
- Always pass a non-empty base32 secret to TOTP::create()/HOTP::create().
- Check the configuration source: ensure the secret env var/DB field is populated before constructing the OTP.
- When parsing URIs, verify the query string contains secret=<base32>.
- 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
- Fail fast at boot if the OTP secret env var/DB field is empty
- Never construct OTP objects from unvalidated config values
- Assert secrets are non-empty base32 in a health check
- Centralize OTP construction in one factory that validates inputs
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
- The label is not set.
- Unable to decode the secret. Is it correctly base32 encoded?
- Unsupported " " OTP type
- Invalid "counter" parameter.
- The counter must be at least 0.
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)