{"record":{"id":"28c47eced3fc5b4c","repo":"lcobucci/jwt","slug":"leeway-cannot-be-negative-strictvalidat","errorCode":null,"errorMessage":"Leeway cannot be negative","messagePattern":"Leeway cannot be negative","errorType":"validation","errorClass":"LeewayCannotBeNegative","httpStatus":null,"severity":"error","filePath":"src/Validation/Constraint/StrictValidAt.php","lineNumber":30,"sourceCode":"use Psr\\Clock\\ClockInterface as Clock;\n\nfinal readonly class StrictValidAt implements ValidAtInterface\n{\n    private DateInterval $leeway;\n\n    public function __construct(private Clock $clock, ?DateInterval $leeway = null)\n    {\n        $this->leeway = $this->guardLeeway($leeway);\n    }\n\n    private function guardLeeway(?DateInterval $leeway): DateInterval\n    {\n        if ($leeway === null) {\n            return new DateInterval('PT0S');\n        }\n\n        if ($leeway->invert === 1) {\n            throw LeewayCannotBeNegative::create();\n        }\n\n        return $leeway;\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token instanceof UnencryptedToken) {\n            throw ConstraintViolation::error('You should pass a plain token', $this);\n        }\n\n        $now = $this->clock->now();\n\n        $this->assertIssueTime($token, $now->add($this->leeway));\n        $this->assertMinimumTime($token, $now->add($this->leeway));\n        $this->assertExpiration($token, $now->sub($this->leeway));\n    }\n","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/lcobucci/jwt/blob/375813049c24c7111bda8b6884c57b071ceb2fe7/src/Validation/Constraint/StrictValidAt.php#L12-L48","documentation":"StrictValidAt validates a token's iat/nbf/exp claims strictly against the current time, allowing an optional DateInterval leeway for clock skew. In its constructor, guardLeeway rejects a leeway interval created with invert=1 (a negative interval) because negative leeway is meaningless — the exception is LeewayCannotBeNegative. Leeway must be zero or a positive duration.","triggerScenarios":"new StrictValidAt($clock, new DateInterval('PT-5M')) or any DateInterval constructed via createFromDateString() with a negative spec (e.g. '-5 minutes') — DateInterval::invert is 1 for negative intervals.","commonSituations":"Typo like '-10 minutes' instead of '10 minutes' when configuring clock skew tolerance; computing the leeway by subtracting dates (DateTimeImmutable diff never does this, but manual math can); copying config from a library where negative leeway means 'past-only'.","solutions":["Use a positive DateInterval, e.g. new DateInterval('PT10M') for 10 minutes of leeway","Check the source string for a leading minus sign when using DateInterval::createFromDateString()","Pass null to use the default zero leeway if clock skew is not a concern"],"exampleFix":"// before\nnew StrictValidAt($clock, DateInterval::createFromDateString('-10 minutes'));\n// after\nnew StrictValidAt($clock, DateInterval::createFromDateString('10 minutes'));","handlingStrategy":"validation","validationCode":"$leeway = DateInterval::createFromDateString('10 minutes');\nif ($leeway->invert === 1) { throw new InvalidArgumentException('Leeway must not be negative'); }","typeGuard":"function isValidLeeway(?DateInterval $leeway): bool { return $leeway === null || $leeway->invert === 0; }","tryCatchPattern":"try {\n    $constraint = new StrictValidAt($clock, $leeway);\n} catch (LeewayCannotBeNegative $e) {\n    $leeway = new DateInterval('PT0S'); // or correct config\n}","preventionTips":["Never prefix leeway strings with '-' when constructing from date strings","Add config validation that rejects negative durations at boot time","Unit-test constraint construction with the exact values from your config files"],"tags":["jwt","leeway","dateinterval","configuration"],"backgroundTag":"invalid-argument-value","analyzedSha":"375813049c24c7111bda8b6884c57b071ceb2fe7","analyzedAt":"2026-09-14T11:12:28.004Z","contentChangedAt":"2026-09-14T11:12:28.004Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}