From 17dabcf21a7f9974c8d102ecc9876473e6b872eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:30:58 +0000 Subject: [PATCH 1/5] Initial plan From 3b74f6eea5a2d3ae3e3b5fd9c1596832fc7b60be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:33:22 +0000 Subject: [PATCH 2/5] Update version to 1.0.0 and remove experimental markers Co-authored-by: Hexagon <419737+Hexagon@users.noreply.github.com> --- LICENSE | 2 +- README.md | 8 +++----- deno.jsonc | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/LICENSE b/LICENSE index b23e82a..49d9831 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 cross-org +Copyright (c) 2024-2026 cross-org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b2370f1..e760060 100644 --- a/README.md +++ b/README.md @@ -120,12 +120,10 @@ const { privateKey, publicKey } = await generateKeyPair("RS512"); const key = await generateKeyPair({ algorithm: "RS512" }); ``` -- **`exportPEMKey(key: CryptoKey, filePathOrOptions?: string | ExportPEMKeyOptions): Promise`** (Experimental) -- **`importPEMKey(keyDataOrPath: string, algorithm: SupportedKeyPairAlgorithms): Promise`** (Experimental) +- **`exportPEMKey(key: CryptoKey, filePathOrOptions?: string | ExportPEMKeyOptions): Promise`** +- **`importPEMKey(keyDataOrPath: string, algorithm: SupportedKeyPairAlgorithms): Promise`** ```javascript -// Experimental. - // Generate and export RS512 keys in PEM-format. (filePath and write mode can be supplied as optional second parameter at export) const { privateKey, publicKey } = await generateKeyPair("RS512"); await exportPEMKey(privateKey, "./private_key_RS512.pem"); @@ -431,7 +429,7 @@ const insecureString = "shortString"; const key = await generateKey(insecureString, keyOptions); ``` -Export/import a key pair to and from local files. (Experimental) +Export/import a key pair to and from local files. ```javascript // Generate and export RS512 keys in PEM-format. diff --git a/deno.jsonc b/deno.jsonc index 11d458b..4226bd3 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,6 +1,6 @@ { "name": "@cross/jwt", - "version": "0.6.0", + "version": "1.0.0", "exports": "./mod.ts", "tasks": { From f58b6f361485d8aa9a35eb48ddea26cacfd36e55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:34:39 +0000 Subject: [PATCH 3/5] Fix PEM filename inconsistency in README example Co-authored-by: Hexagon <419737+Hexagon@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e760060..f99e67f 100644 --- a/README.md +++ b/README.md @@ -434,8 +434,8 @@ Export/import a key pair to and from local files. ```javascript // Generate and export RS512 keys in PEM-format. const { privateKey, publicKey } = await generateKeyPair("RS512"); -await exportPEMKey(privateKey, "./private_key_RS512a.pem"); -await exportPEMKey(publicKey, "./public_key_RS512a.pem"); +await exportPEMKey(privateKey, "./private_key_RS512.pem"); +await exportPEMKey(publicKey, "./public_key_RS512.pem"); // Import RS512 keys from PEM-format. const importedPrivateKey = await importPEMKey("./private_key_RS512.pem", "RS512"); From 546f3fb6975fcb28a2bb918eef40af18457ad5c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:37:21 +0000 Subject: [PATCH 4/5] Fix security issues: RSA-PSS key mutation and error message sanitization Co-authored-by: Hexagon <419737+Hexagon@users.noreply.github.com> --- src/core/validate.ts | 21 ++++++++++++--------- src/crypto/sign-verify/rsapss.ts | 14 ++++++++++---- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/core/validate.ts b/src/core/validate.ts index 902dcff..28d4833 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -92,7 +92,12 @@ export async function validateJWT( } } - const payload = JSON.parse(textDecode(decodeBase64Url(jwtParts[1]))); + let payload: JWTPayload; + try { + payload = JSON.parse(textDecode(decodeBase64Url(jwtParts[1]))); + } catch (error) { + throw new JWTParseError("Invalid JWT payload: malformed JSON"); + } validateClaims(payload, options); @@ -252,11 +257,10 @@ export function unsafeParseJWT(jwt: string): JWTPayload { const payload = JSON.parse(textDecode(decodeBase64Url(jwtParts[1]))); return payload; } catch (error) { - if (error instanceof Error) { - throw new JWTParseError(error.message); - } else { - throw new JWTParseError("An unknown error occurred while parsing the JWT."); + if (error instanceof JWTFormatError || error instanceof JWTParseError) { + throw error; } + throw new JWTParseError("Invalid JWT format or malformed payload"); } } @@ -273,10 +277,9 @@ export function unsafeParseJOSEHeader(jwt: string): JOSEHeader { const payload = JSON.parse(textDecode(decodeBase64Url(jwtParts[0]))); return payload; } catch (error) { - if (error instanceof Error) { - throw new JWTParseError(error.message); - } else { - throw new JWTParseError("An unknown error occurred while parsing the JOSE header."); + if (error instanceof JWTFormatError || error instanceof JWTParseError) { + throw error; } + throw new JWTParseError("Invalid JWT format or malformed header"); } } diff --git a/src/crypto/sign-verify/rsapss.ts b/src/crypto/sign-verify/rsapss.ts index 29ae0da..ceec22c 100644 --- a/src/crypto/sign-verify/rsapss.ts +++ b/src/crypto/sign-verify/rsapss.ts @@ -12,9 +12,12 @@ import type { JWTOptions } from "../../types/options.ts"; * @returns {Promise} A promise resolving to the base64url-encoded RSA-PSS signature. */ export async function signWithRSAPSS(key: CryptoKey, data: string, options?: JWTOptions) { - (key.algorithm as RsaPssParams).saltLength = options?.saltLength || 32; + const algorithm = { + name: key.algorithm.name, + saltLength: options?.saltLength || 32, + }; const signature = await crypto.subtle.sign( - key.algorithm, + algorithm, key, textEncode(data) as BufferSource, ); @@ -32,9 +35,12 @@ export async function signWithRSAPSS(key: CryptoKey, data: string, options?: JWT * @returns {Promise} A promise resolving to `true` if the signature is valid, `false` otherwise. */ export async function verifyWithRSAPSS(key: CryptoKey, data: string, signature: string, options?: JWTOptions) { - (key.algorithm as RsaPssParams).saltLength = options?.saltLength || 32; + const algorithm = { + name: key.algorithm.name, + saltLength: options?.saltLength || 32, + }; const isValid = await crypto.subtle.verify( - key.algorithm, + algorithm, key, decodeBase64Url(signature), textEncode(data) as BufferSource, From 7c10da318d15559335ac17115b5062f24f501797 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:45:07 +0000 Subject: [PATCH 5/5] Fix lint error and test compatibility for error handling Co-authored-by: Hexagon <419737+Hexagon@users.noreply.github.com> --- src/core/validate.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/core/validate.ts b/src/core/validate.ts index 28d4833..df9832d 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -95,7 +95,7 @@ export async function validateJWT( let payload: JWTPayload; try { payload = JSON.parse(textDecode(decodeBase64Url(jwtParts[1]))); - } catch (error) { + } catch (_error) { throw new JWTParseError("Invalid JWT payload: malformed JSON"); } @@ -256,10 +256,7 @@ export function unsafeParseJWT(jwt: string): JWTPayload { const jwtParts = validateParts(jwt); const payload = JSON.parse(textDecode(decodeBase64Url(jwtParts[1]))); return payload; - } catch (error) { - if (error instanceof JWTFormatError || error instanceof JWTParseError) { - throw error; - } + } catch (_error) { throw new JWTParseError("Invalid JWT format or malformed payload"); } } @@ -276,10 +273,7 @@ export function unsafeParseJOSEHeader(jwt: string): JOSEHeader { const jwtParts = validateParts(jwt); const payload = JSON.parse(textDecode(decodeBase64Url(jwtParts[0]))); return payload; - } catch (error) { - if (error instanceof JWTFormatError || error instanceof JWTParseError) { - throw error; - } + } catch (_error) { throw new JWTParseError("Invalid JWT format or malformed header"); } }