{"record":{"id":"beff630aa985f531","repo":"ellite/Wallos","slug":"counter-must-be-at-least-0","errorCode":null,"errorMessage":"Counter must be at least 0.","messagePattern":"Counter must be at least 0\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"libs/OTPHP/HOTP.php","lineNumber":98,"sourceCode":"        }\n\n        return $this->verifyOtpWithWindow($otp, $counter, $window);\n    }\n\n    public function setCounter(int $counter): void\n    {\n        $this->setParameter('counter', $counter);\n    }\n\n    /**\n     * @return array<non-empty-string, callable>\n     */\n    protected function getParameterMap(): array\n    {\n        return [...parent::getParameterMap(), ...[\n            'counter' => static function (mixed $value): int {\n                $value = (int) $value;\n                $value >= 0 || throw new InvalidArgumentException('Counter must be at least 0.');\n\n                return $value;\n            },\n        ]];\n    }\n\n    private function updateCounter(int $counter): void\n    {\n        $this->setCounter($counter);\n    }\n\n    /**\n     * @param null|0|positive-int $window\n     */\n    private function getWindow(null|int $window): int\n    {\n        return abs($window ?? self::DEFAULT_WINDOW);\n    }","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/ellite/Wallos/blob/52820e87ca5a6e105fdbb7f1c0c681bc0cfee2fd/libs/OTPHP/HOTP.php#L80-L116","documentation":"HOTP::getParameterMap registers a normalizer for the 'counter' parameter that casts to int and throws InvalidArgumentException if the result is negative. This runs whenever a parameter is set or a provisioning URI is loaded, enforcing counter >= 0 at write time.","triggerScenarios":"Setting the counter parameter with a negative value ($hotp->setParameter('counter', -5)) or loading a provisioning URI containing counter=-1, so the map callback throws during parameter hydration.","commonSituations":"Parsing malicious/malformed otpauth://hotp URIs; importing OTP state from external systems where counters were stored as signed or corrupted values.","solutions":["Fix the source value: ensure counter is >= 0 before setParameter() or in the provisioning URI.","Clamp on import: max(0, (int) $value) when hydrating counters from external data.","Sanitize URIs before loadFromProvisioningUri(): regex-check counter=\\d+.","Reject at your own input-validation layer with a clear user-facing message."],"exampleFix":"// before\n$hotp->setParameter('counter', $userInput); // could be -3\n// after\n$hotp->setParameter('counter', max(0, (int) $userInput));","handlingStrategy":"validation","validationCode":"if ((int) $value < 0) {\n    throw new DomainException('Counter must be >= 0');\n}\n$hotp->setParameter('counter', (int) $value);","typeGuard":"function sanitizeCounter(mixed $value): int {\n    return max(0, (int) $value);\n}","tryCatchPattern":"try {\n    $otp = $factory->loadFromProvisioningUri($uri);\n} catch (InvalidArgumentException $e) {\n    log_error('Malformed counter in provisioning URI', ['uri' => $uri]);\n    throw new InvalidProvisioningUriException($uri, $e);\n}","preventionTips":["Sanitize counters imported from external systems before hydration","Regex-validate counter=\\d+ (no minus) in URIs before parsing","Use unsigned DB columns for counters","Reject negative counters at your API input layer"],"tags":["php","otp","hotp","parameter-validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"52820e87ca5a6e105fdbb7f1c0c681bc0cfee2fd","analyzedAt":"2026-09-13T14:09:30.873Z","contentChangedAt":"2026-09-13T14:09:30.873Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}