Skip to content

Add unstable JOSE modules (Jwa, Jwk, Jws, Jwt, Jwe)#6566

Open
leonitousconforti wants to merge 7 commits into
Effect-TS:mainfrom
leonitousconforti:feat/unstable-jose
Open

Add unstable JOSE modules (Jwa, Jwk, Jws, Jwt, Jwe)#6566
leonitousconforti wants to merge 7 commits into
Effect-TS:mainfrom
leonitousconforti:feat/unstable-jose

Conversation

@leonitousconforti

@leonitousconforti leonitousconforti commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Effect Schema definitions and WebCrypto-backed operations for the JOSE family: RFC 7518 algorithm identifiers with separate import/signature parameter sets, RFC 7517 JWK/JWK Set schemas, RFC 7515 JWS in all three serializations with multi-signature sign/verify and extensible critical headers validated at the type level, and a high-level RFC 7519 JWT layer with JWK Set verification and registered-claim validation.

Keys embedded in tokens (jwk/jku header parameters) are ignored during verification unless explicitly opted into.

Type

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

Related

  • Related Issue #
  • Closes #

Summary by CodeRabbit

  • New Features
    • Added experimental, unstable JOSE modules for JWS signing/verification, JWE encryption/decryption (including compact serialization), and JWK parsing plus compatibility helpers.
    • Introduced an unstable JWT API with key generation, claim validation, and typed error handling, built on the JOSE functionality.
    • Added support for multiple standard alg/enc combinations with hardened handling of malformed inputs and unsupported header parameters.
  • Tests
    • Added Vitest coverage for JWS/JWE/JWT, including RFC vectors, round-trip behavior, tamper/wrong-key failures, and security regression cases.

Effect Schema definitions and WebCrypto-backed operations for the JOSE
family: RFC 7518 algorithm identifiers with separate import/signature
parameter sets, RFC 7517 JWK/JWK Set schemas, RFC 7515 JWS in all three
serializations with multi-signature sign/verify and extensible critical
headers validated at the type level, and a high-level RFC 7519 JWT layer
with JWK Set verification and registered-claim validation.

Keys embedded in tokens (jwk/jku header parameters) are ignored during
verification unless explicitly opted into.
@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog Jul 24, 2026
@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 35830ea

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 28 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/docgen Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the 4.0 label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d87176f-ebc7-44db-a86f-1c91060e8686

📥 Commits

Reviewing files that changed from the base of the PR and between 0d0afbb and 35830ea.

📒 Files selected for processing (2)
  • packages/effect/src/unstable/jose/Jwe.ts
  • packages/effect/test/unstable/jose/Security.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/effect/test/unstable/jose/Security.test.ts
  • packages/effect/src/unstable/jose/Jwe.ts

📝 Walkthrough

Walkthrough

Adds an unstable JOSE API with JWA/JWK schemas, JWS signing and verification, JWE encryption and decryption, JWT signing and claims validation, namespace exports, interoperability tests, security tests, and patch-release metadata.

Changes

JOSE API

Layer / File(s) Summary
Algorithm and key contracts
packages/effect/src/unstable/jose/Jwa.ts, packages/effect/src/unstable/jose/Jwk.ts, packages/effect/src/unstable/jose/index.ts, packages/effect/test/unstable/jose/Security.test.ts, .changeset/tame-pianos-sing.md
Defines algorithm schemas, WebCrypto mappings, JWK variants, compatibility predicates, namespace exports, RSA decoding tests, and patch-release metadata.
JWS signing and verification
packages/effect/src/unstable/jose/Jws.ts, packages/effect/test/unstable/jose/Jws.test.ts, packages/effect/test/unstable/jose/Security.test.ts
Adds JWS serialization schemas, critical-header handling, signing, verification, schema combinators, typed errors, and signature security tests.
JWE encryption and decryption
packages/effect/src/unstable/jose/Jwe.ts, packages/effect/test/unstable/jose/Jwe.test.ts, packages/effect/test/unstable/jose/Security.test.ts
Adds compact JWE parsing, AES content encryption, key-management algorithms, typed failures, encryption/decryption APIs, interoperability tests, and hardening checks.
JWT signing and verification
packages/effect/src/unstable/jose/Jwt.ts, packages/effect/test/unstable/jose/Jwt.test.ts, packages/effect/test/unstable/jose/Security.test.ts
Adds registered claims, ES256 key generation, compact JWT signing, trusted-key verification, claim validation, typed errors, and JWT security tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

JWT verification flow

sequenceDiagram
  participant Jwt
  participant Jws
  participant JwkSet
  participant WebCrypto
  Jwt->>Jws: Decode and verify compact JWT
  Jws->>JwkSet: Select compatible trusted keys
  Jws->>WebCrypto: Verify signature
  WebCrypto-->>Jws: Verification result
  Jws-->>Jwt: Verified claims payload
  Jwt-->>Jwt: Validate registered claims
Loading

JWE encryption flow

sequenceDiagram
  participant Jwe
  participant KeyManagement
  participant WebCrypto
  Jwe->>KeyManagement: Derive or wrap CEK
  KeyManagement->>WebCrypto: Execute key-management operation
  WebCrypto-->>KeyManagement: CEK or encrypted key
  Jwe->>WebCrypto: Encrypt plaintext with AAD
  WebCrypto-->>Jwe: Ciphertext and authentication tag
Loading
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

JWE Compact Serialization with WebCrypto-backed authenticated encryption
and decryption. Content encryption covers the AES-GCM and
AES-CBC-HMAC-SHA2 families; key management covers dir, RSA-OAEP,
AES key wrap, AES-GCM key wrap, ECDH-ES (direct and key-wrap), and PBES2.
RSA1_5 is omitted as WebCrypto does not implement it and RFC 8725
discourages it.

Also expands the test suite: an exhaustive JWE encrypt/decrypt matrix over
every key-management x content-encryption pairing, tamper and wrong-key
negative tests, and the RFC 7516 Appendix A.3 decryption vector; plus JWS
multi-signature, critical-header, and RFC 7515 Appendix A.1 tests. Fixes
JWS signing to populate the crit header from the configured critical
header names.
Addresses the findings from the adversarial audit. No behaviour change for
correctly-configured callers; adds fail-closed guards and opt-in hardening.

Jwe:
- Bound the attacker-controlled PBES2 iteration count on decrypt
  (maxPBES2Count, default 10000) to prevent a CPU-exhaustion DoS.
- Decode all attacker-supplied base64url segments through a typed decoder so
  malformed input yields JweError("Malformed") instead of an unhandled defect.
- Reject an unrecognized 'crit' header (RFC 7516 4.1.13).
- Add keyManagementAlgorithms / contentEncryptionAlgorithms allowlists.
- Convert key-management crypto rejections to typed errors (tryPromise).
- Validate a 'dir' key length equals the content-encryption CEK size.
- Bind apu/apv into the ECDH-ES Concat KDF (RFC 7518 4.6.2) and document the
  AES-GCM random-IV reuse bound.

Jws:
- Convert importKey/verify rejections to typed failures so an alg/key
  mismatch or malformed key material is skipped rather than crashing the
  verifying fiber.
- On the jku/embedded-jwk paths, use a key only if it is an asymmetric public
  key compatible with the header algorithm (never symmetric or private).
- Add an 'algorithms' allowlist and a 'maxSignatures' bound.

Jwt / Jwk:
- Add 'algorithms' and 'types' (typ) allowlists to Jwt.verify.
- Extract the alg<->key-type compatibility gate to Jwk.isCompatibleWith and
  add Jwk.isSymmetric / Jwk.isPrivate guards, shared by Jws and Jwt.

Adds test/unstable/jose/Security.test.ts with regression tests for each fix.
Follow-up to the security hardening: Jwt.verify imported every compatible
candidate key and failed the whole verification if any single one rejected,
so one malformed key in an otherwise-valid JWK Set could deny service to
tokens signed by the good keys. Import each candidate independently and skip
the unusable ones, mirroring Jws.verify; still fail closed when none import.
@mikearnaldi mikearnaldi added the enhancement New feature or request label Jul 24, 2026
@leonitousconforti
leonitousconforti marked this pull request as ready for review July 24, 2026 14:12
@leonitousconforti leonitousconforti changed the title Add unstable JOSE modules (Jwa, Jwk, Jws, Jwt) Add unstable JOSE modules (Jwa, Jwk, Jws, Jwt, Jwe) Jul 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/effect/src/unstable/jose/Jwt.ts (1)

25-46: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

All registered claims are OPTIONAL per RFC 7519 §4.1.

sub, aud, and iat are declared required here, but RFC 7519 defines every registered claim as optional. Tokens that legitimately omit sub or aud (common for access tokens) will fail ClaimsFromJson decoding and surface to callers as Malformed rather than a specific reason. If requiring them is a deliberate policy this is fine; otherwise consider making them optional and enforcing presence only where mandated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/src/unstable/jose/Jwt.ts` around lines 25 - 46, Update the
RegisteredClaims schema so every RFC 7519 registered claim, including sub, aud,
and iat, is optional; preserve the existing optional handling for nbf and jti.
Ensure ClaimsFromJson accepts tokens that omit any registered claim, leaving
presence enforcement to policy-specific validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/effect/src/unstable/jose/Jwe.ts`:
- Around line 673-677: Validate the CEK returned by keyManagementDecrypt against
params.cekBytes before proceeding to content decryption, and convert any
mismatch into the existing DecryptionFailed error path. Preserve the current
key-management flow and ensure invalid lengths for all supported algorithms
cannot reach crypto.subtle.importKey as an unhandled error.

In `@packages/effect/src/unstable/jose/Jwk.ts`:
- Around line 237-275: The RsaPrivateKey union currently matches the d-only
branch before the full RSA CRT branch, causing CRT fields to be discarded during
decoding. Reorder the Schema.Union branches so the Schema.Struct containing p,
q, dp, dq, and qi appears before the d-only variant, preserving all CRT
parameters for full private JWKs.

---

Nitpick comments:
In `@packages/effect/src/unstable/jose/Jwt.ts`:
- Around line 25-46: Update the RegisteredClaims schema so every RFC 7519
registered claim, including sub, aud, and iat, is optional; preserve the
existing optional handling for nbf and jti. Ensure ClaimsFromJson accepts tokens
that omit any registered claim, leaving presence enforcement to policy-specific
validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3894e119-2173-4e02-a9b8-b9edbad095b0

📥 Commits

Reviewing files that changed from the base of the PR and between b492841 and 7a82a65.

📒 Files selected for processing (10)
  • packages/effect/src/unstable/jose/Jwa.ts
  • packages/effect/src/unstable/jose/Jwe.ts
  • packages/effect/src/unstable/jose/Jwk.ts
  • packages/effect/src/unstable/jose/Jws.ts
  • packages/effect/src/unstable/jose/Jwt.ts
  • packages/effect/src/unstable/jose/index.ts
  • packages/effect/test/unstable/jose/Jwe.test.ts
  • packages/effect/test/unstable/jose/Jws.test.ts
  • packages/effect/test/unstable/jose/Jwt.test.ts
  • packages/effect/test/unstable/jose/Security.test.ts

Comment thread packages/effect/src/unstable/jose/Jwe.ts
Comment thread packages/effect/src/unstable/jose/Jwk.ts
@github-project-automation github-project-automation Bot moved this from Discussion Ongoing to Waiting on Author in PR Backlog Jul 24, 2026
leonitousconforti and others added 3 commits July 24, 2026 09:29
- Jwe.decrypt: reject a CEK whose length does not match the content
  algorithm before it reaches AES importKey. A key-management algorithm can
  yield a wrong-length CEK (e.g. an attacker RSA-OAEP-encrypts an
  arbitrary-length key to the recipient's public key), which otherwise
  rejected inside contentDecrypt as an unhandled defect rather than a typed
  DecryptionFailed.
- Jwk.RsaPrivateKey: order the union so the full CRT struct precedes the
  d-only struct. A CRT key also satisfies the d-only member and Struct
  decoding drops unlisted fields, so the previous order silently discarded
  p/q/dp/dq/qi from complete private keys.
…pto calls

The `dir` key-management decrypt exported the recipient's key with a bare
Effect.promise, so a token selecting alg:"dir" against a non-extractable or
non-raw-exportable key (e.g. an RSA private key) surfaced the WebCrypto
rejection as an unhandled defect instead of a typed JweError. Wrap it in
Effect.tryPromise -> KeyManagementFailed, and do the same for every other
bare crypto.subtle call on the decrypt path (aesKwUnwrap exportKey, the
ECDH-ES+AKW and PBES2 KEK imports, and the AES-GCM/HMAC/AES-CBC operations
in contentDecrypt) so no attacker input can defect the verifying fiber.

Adds a regression test asserting a dir token against an RSA private key
fails closed as a typed KeyManagementFailed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@leonitousconforti

Copy link
Copy Markdown
Contributor Author

Does this have a place in effect? Or should it belong in it's own package?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 enhancement New feature or request

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

2 participants