Skip to content

build(deps): upgrade lestrrat-go/jwx v2 → v4 + Go 1.26#114

Merged
rsharath merged 1 commit intomainfrom
jwx-v4-upgrade
May 5, 2026
Merged

build(deps): upgrade lestrrat-go/jwx v2 → v4 + Go 1.26#114
rsharath merged 1 commit intomainfrom
jwx-v4-upgrade

Conversation

@rsharath
Copy link
Copy Markdown
Contributor

@rsharath rsharath commented May 4, 2026

Summary

Migrates the entire zeroid + pkg/authjwt jwx surface from lestrrat-go/jwx v2 → v4.0.1 and bumps the Go toolchain from 1.25.8 → 1.26 (with GOEXPERIMENT=jsonv2, which jwx v4 requires). Pure library-currency upgrade — does not change any wire behavior; published JWKS, issued tokens, and verification semantics are byte-identical to before.

Why now

  • v2 is in maintenance only; v4.0.1 (April 2026) is the current upstream stable.
  • Cleaner API surface — generic jwk.Import[T] / jwt.Get[T] accessors, structured error types (TokenExpiredError, InvalidIssuerError, InvalidAudienceError) usable with errors.Is, tuple-returning standard accessors that surface "claim absent" without sentinels.
  • Range iteratorsToken.Claims() iter.Seq2[string, any] lets us drop the Token.Keys() + jwt.Get[any] loop in two spots.
  • Go 1.26 alignment — moves the project off Go 1.25.8 onto the floor that v4 requires.
  • Removes deprecated jwx APIsjwk.FromRaw, jwk.Fetch, jwk.WithHTTPClient, Token.Get, jwt.ErrTokenExpired() etc. are all gone.

What this PR does NOT do

It is not a fix for #43 (use="JWT-SVID" on the published JWKS). The use filter that blocked us in v2 (jwa.AlgorithmsForKey) was relocated in v4 to jws/key_provider.go::keySetProvider.selectKey:115-121 — same hardcoded check that rejects keys whose use is anything outside {"", "sig"}. v4.0.1's release notes lean stricter on use enforcement, not looser (e.g. #2059 rejects use=enc in jkuProvider).

Closing #43 needs either:

  • an upstream jwx PR relaxing the allowlist, or
  • our verifier swapping jws.WithKeySet for manual per-kid resolution + jws.WithKey(alg, key), or
  • the boundary-rewrite approach community PR fix: set JWK use=jwt-svid in JWKS (JWT-SVID §4) #101 took (currently blocked on SDK normalization).

The new comment in internal/signing/jwks.go::addToKeySet points at the v4 location of the filter so whichever approach is picked has a clean starting point.

Mechanical migration

v2 → v4
jwa.ES256 / RS256 / HS256 value → jwa.ES256() / RS256() / HS256() function call
jwk.FromRaw(raw)jwk.Import[jwk.Key](raw) (interface-typed import; dispatcher chooses concrete type from raw key's Go type)
jwk.Fetch + jwk.WithHTTPClient (removed) → manual http.Client.Do + jwk.Parse(body), body-bounded to 1 MiB so a malicious or compromised JWKS endpoint can't exhaust process memory
Token.Subject() / Issuer() / Audience() / IssuedAt() / Expiration() / JwtID() → all return (value, present); call sites destructure with , _ when the bool isn't needed
Token.Get(name) / Iterate(ctx) (removed) → jwt.Get[T](token, name) for typed access; Token.Claims() iter.Seq2[string, any] for full-claim iteration in internal/attestation/oidc.go and pkg/authjwt/claims.go
jws.Headers.Algorithm() / KeyID() / Type()(value, present) tuple
jwt.ErrTokenExpired() / ErrInvalidIssuer() / ErrInvalidAudience() (function sentinels) → jwt.TokenExpiredError{} / InvalidIssuerError{} / InvalidAudienceError{} typed structs, used with errors.Is
jwk.Set.Key(i) string(jwk.Key, bool); key.KeyID() / KeyUsage()(string, bool)

All 60+ call sites verified against pkg.go.dev v4.0.1 docs.

Build infra

File Change
go.mod, pkg/authjwt/go.mod go 1.25.8go 1.26; lestrrat-go/jwx/v2 v2.1.6v4 v4.0.1
Dockerfile golang:1.25.8-alpine3.22golang:1.26.0-alpine3.22; ENV GOEXPERIMENT=jsonv2 on the build stage
Makefile export GOEXPERIMENT = jsonv2 at the top so every recipe inherits it
.github/workflows/pr-check.yml GO_VER: "1.25.8""1.26.0"; new workflow-level GOEXPERIMENT: "jsonv2"; golangci-lint bumped from v2.7.2v2.11.4 (older was built with Go 1.25 and couldn't load Go 1.26 modules)
.github/workflows/release.yml Same GO_VER + GOEXPERIMENT updates
.github/workflows/sdk-integration.yml Same GO_VER + GOEXPERIMENT updates

Files changed (26)

Group Files Notes
Build infra Dockerfile, Makefile, .github/workflows/{pr-check,release,sdk-integration}.yml, go.mod, go.sum, pkg/authjwt/go.{mod,sum} Go 1.26 + jsonv2 + lint version + module pin
Token issuance internal/service/{credential,proof}.go, internal/signing/jwks.go jwa.ES256() / RS256(), jwk.Import[jwk.Key], kid+typ JOSE headers preserved on both issuance paths (community #104 work in v4 syntax)
Token verification internal/middleware/agent_auth.go, internal/service/{authcode,oauth,proof}.go, internal/handler/{proof,wellknown}.go Tuple destructuring on Subject/Issuer/JwtID/Expiration/IssuedAt/Audience; jwt.Get[T] replaces Token.Get
Attestation internal/attestation/oidc.go Manual JWKS fetch (http.Client.Do + jwk.Parse) replaces removed jwk.Fetch/WithHTTPClient; Token.Claims() iterator replaces Token.AsMap
pkg/authjwt claims.go, jwks.go, verifier.go, verifier_test.go Generic jwt.Get[T] accessors, structured error types, Token.Claims() iterator for Custom collection, manual JWKS fetch with body bound, alg-confusion test assertions updated to track community PR #103's validateAlg error path (ErrInvalidToken)
Tests tests/integration/{attestation_oidc_test.go,authorization_code_test.go,helpers_test.go,jwt_svid_aud_test.go} jwk.Import[jwk.Key], jwa.X() calls, tuple destructuring on accessors

Test plan

  • go vet ./... — clean (root + pkg/authjwt)
  • golangci-lint run (v2.11.4, matching CI) — 0 issues on both modules
  • pkg/authjwt test suite — green, ~1.2s
  • tests/integration (testcontainers Postgres) — green, ~9.7s
  • Race-enabled make test — green, 60.2s, no race detector flags
  • Python SDK smoke tests — green on CI
  • TypeScript SDK smoke tests — green on CI
  • highflame-regression-test — green on CI
  • highflame-docker-check (Docker build with Go 1.26 + jsonv2) — green on CI
  • highflame-sast-check, highflame-trivy-check, Socket Security — green on CI

10/10 CI checks green.

Risk / compatibility

  • Wire-format unchanged. Published JWKS keys still carry use="sig"; issued tokens have the same claims, header shape, and signature schemes; verification accepts the same input set as before. Anything reading our tokens or JWKS today continues to work identically.
  • No public-API breakage in pkg/authjwt. Verifier, VerifierConfig, NewVerifier, Claims struct, error sentinels (ErrNoToken, ErrInvalidToken, ErrExpiredToken, …) all unchanged.
  • GOEXPERIMENT=jsonv2 requirement is the one operational change. Wired into Makefile, Dockerfile, and all CI workflows. Local devs will need it set on any direct go test/go build invocation; documenting in the next iteration of CONTRIBUTING.md is a small follow-up.
  • This PR is independent — it does not stack on any other open PR. It branches directly off main as a single commit. An earlier iteration stacked on #113 (which carried preliminary typ:JWT + algorithm-allowlist work), but #113 was closed in favor of community PRs #103 and #104 which shipped equivalent fixes with broader file coverage. After those merged to main on 2026-05-04, this branch was rebased directly onto main to drop the now-redundant earlier commit.

Review notes

Six Gemini review comments are on this PR — five claiming jwk.Import[jwk.Key] won't compile (it does; verified against pkg/mod/.../jwk/jwk.go:82 and pkg.go.dev v4.0.1 docs) and one claiming jwt.AsMap is a v4 package-level function (it isn't; grep -rn AsMap of the v4.0.1 source returns zero matches; pkg.go.dev v4.0.1 jwt page contains no such symbol). Each is rebutted in-thread with the official docs reference.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 4, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedgolang/​github.com/​lestrrat-go/​jwx/​v4@​v4.0.190100100100100

View full report

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request upgrades the project to Go 1.26.0 and migrates the jwx library from version 2 to version 4. The migration involves updating numerous API calls to accommodate new return signatures for standard claims (e.g., Issuer, Subject, Audience) and replacing the removed jwk.Fetch utility with manual HTTP implementations. Feedback focuses on improving the idiomatic quality of the code by using type inference for jwk.Import calls and utilizing the package-level jwt.AsMap function for more efficient claim extraction.

Comment thread internal/signing/jwks.go
Comment thread pkg/authjwt/verifier_test.go
Comment thread pkg/authjwt/verifier_test.go
Comment thread pkg/authjwt/verifier_test.go
Comment thread tests/integration/attestation_oidc_test.go
Comment thread internal/attestation/oidc.go Outdated
@rsharath rsharath changed the base branch from jwt-svid-compliance to main May 4, 2026 23:53
@rsharath rsharath force-pushed the jwx-v4-upgrade branch 3 times, most recently from 746e399 to 689f3a8 Compare May 5, 2026 16:44
Migrates the entire zeroid + pkg/authjwt jwx surface (211 call sites
across 19 files) to jwx v4.0.1 and bumps the language target to Go 1.26.

- jwx v2 is in maintenance only; v4 is the current upstream release.
- v4's API (generic Import/Get accessors, struct error types, Token
  getters returning (value, present)) is a forcing function for the
  same idioms we already prefer in newer code.
- Future work like #43 (use="JWT-SVID" on JWKS) needs a more flexible
  verifier path; v4 narrows the migration cost when that lands.

- jwa.ES256 / RS256 / HS256 are now functions; every call site adds ().
- jwk.FromRaw → jwk.Import[jwk.Key] (interface-typed import returns the
  matching concrete jwk.Key based on the raw key shape).
- jwk.Fetch + jwk.WithHTTPClient removed (companion module). Replaced
  with manual http.Client GET + jwk.Parse, body-bounded to 1 MiB so a
  malicious or compromised JWKS endpoint can't exhaust process memory.
- Token accessors (Subject, Issuer, Audience, JwtID, Expiration,
  IssuedAt) now return (value, present); call sites destructure.
- Token.Get / Token.Iterate removed → jwt.Get[T] generic accessor +
  Token.Keys() iteration.
- jws.Headers.Algorithm() / KeyID() / Type() return (value, present).
- jwt.ErrTokenExpired() etc. removed → typed structs
  (jwt.TokenExpiredError{}, jwt.InvalidIssuerError{},
  jwt.InvalidAudienceError{}) used with errors.Is.

- Go directive: 1.25.8 → 1.26 in both go.mod files.
- GO_VER bumped to 1.26.0 in pr-check, release, and sdk-integration
  workflows; GOEXPERIMENT=jsonv2 added at the workflow env level so
  every step inherits it (jwx v4 requires the encoding/json/v2 stack).
- Dockerfile: golang:1.25.8-alpine3.22 → golang:1.26.0-alpine3.22 +
  ENV GOEXPERIMENT=jsonv2 on the build stage.
- Makefile: export GOEXPERIMENT=jsonv2 so every target inherits it.

The jwx v4 use="JWT-SVID" path was hoped to unblock issue #43, but the
filter that v2 had in jwa.AlgorithmsForKey was relocated rather than
removed — v4's jws/key_provider.go::keySetProvider.selectKey
(lines 115–121) hardcodes the same check that rejects keys whose use
is anything outside {"", "sig"}. Comment in
internal/signing/jwks.go::addToKeySet now points at the new location.
Unblocking #43 still needs either an upstream jwx PR or our verifier
to swap WithKeySet for manual per-kid lookup; both are out of scope
for this PR.

- pkg/authjwt: full suite green (race, count=1).
- tests/integration: full suite green against the testcontainers
  Postgres fixture (60+ integration tests, count=1).
- go vet ./... clean on both modules.

Builds on top of #113 (PR-Z0). Once #113 merges, rebase to drop the
duplicate commit and this PR rebases cleanly onto main.
@rsharath rsharath merged commit e6cdc15 into main May 5, 2026
10 checks passed
@rsharath rsharath deleted the jwx-v4-upgrade branch May 5, 2026 17:32
rsharath added a commit that referenced this pull request May 5, 2026
…efresh

Closes the Go-ecosystem Dependabot alerts that were addressable via go get,
plus a currency refresh on the directly-used libraries that drift the most.

## Security-flagged

- uptrace/bun + dialect/pgdialect + driver/pgdriver: v1.2.11 -> v1.2.18
  Closes pgdriver SQL injection (GHSA tracked by Dependabot, MEDIUM).
- go-viper/mapstructure/v2: v2.2.1 -> v2.5.0
  Closes "May Leak Sensitive Information in Logs When Processing Malformed
  Data" (MEDIUM, indirect via koanf).
- testcontainers-go + modules/postgres: v0.41.0 -> v0.42.0
  Brings in moby/sys/* + sirupsen/logrus + gopsutil patch versions.

## Currency

- danielgtaylor/huma/v2: v2.37.2 -> v2.37.3
- go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc: v1.42.0 -> v1.43.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace + .../otlptracegrpc: v1.35.0 -> v1.43.0
- go.opentelemetry.io/proto/otlp: v1.9.0 -> v1.10.0
- google.golang.org/grpc: v1.79.3 -> v1.80.0
- google.golang.org/genproto/googleapis/{api,rpc}: 0209 -> 0401 snapshot

## Not addressed (known limitation)

The HIGH-severity docker/docker AuthZ-plugin-bypass advisory (>=v29.3.1)
is unactionable via go get. Moby's v29.x line uses docker-v29.x.x prefix
tags which the Go module proxy does not expose under
github.com/docker/docker; v28.5.2+incompatible is the maximum installable
version on the module path. The CVE concerns docker daemon AuthZ plugins
which we don't run -- testcontainers-go uses only the docker client to
manage test fixtures. Dependabot will continue to flag this until either
Moby republishes the v29.x line with plain v29.x.x tags or testcontainers
moves to a different docker client surface.

## Scope

- Root go.mod / go.sum only. pkg/authjwt/go.mod unchanged.
- Rebased onto current main (which now includes #114, jwx v4 + Go 1.26).
- No code changes -- pure dependency manifest update.

## Test plan

- [x] go vet ./... (root + pkg/authjwt) -- clean
- [x] Full integration suite (testcontainers Postgres) -- green ~10s
rsharath added a commit that referenced this pull request May 5, 2026
…efresh (#115)

* build(deps): bump pgdriver, mapstructure, testcontainers + currency refresh

Closes the Go-ecosystem Dependabot alerts that were addressable via go get,
plus a currency refresh on the directly-used libraries that drift the most.

## Security-flagged

- uptrace/bun + dialect/pgdialect + driver/pgdriver: v1.2.11 -> v1.2.18
  Closes pgdriver SQL injection (GHSA tracked by Dependabot, MEDIUM).
- go-viper/mapstructure/v2: v2.2.1 -> v2.5.0
  Closes "May Leak Sensitive Information in Logs When Processing Malformed
  Data" (MEDIUM, indirect via koanf).
- testcontainers-go + modules/postgres: v0.41.0 -> v0.42.0
  Brings in moby/sys/* + sirupsen/logrus + gopsutil patch versions.

## Currency

- danielgtaylor/huma/v2: v2.37.2 -> v2.37.3
- go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc: v1.42.0 -> v1.43.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace + .../otlptracegrpc: v1.35.0 -> v1.43.0
- go.opentelemetry.io/proto/otlp: v1.9.0 -> v1.10.0
- google.golang.org/grpc: v1.79.3 -> v1.80.0
- google.golang.org/genproto/googleapis/{api,rpc}: 0209 -> 0401 snapshot

## Not addressed (known limitation)

The HIGH-severity docker/docker AuthZ-plugin-bypass advisory (>=v29.3.1)
is unactionable via go get. Moby's v29.x line uses docker-v29.x.x prefix
tags which the Go module proxy does not expose under
github.com/docker/docker; v28.5.2+incompatible is the maximum installable
version on the module path. The CVE concerns docker daemon AuthZ plugins
which we don't run -- testcontainers-go uses only the docker client to
manage test fixtures. Dependabot will continue to flag this until either
Moby republishes the v29.x line with plain v29.x.x tags or testcontainers
moves to a different docker client surface.

## Scope

- Root go.mod / go.sum only. pkg/authjwt/go.mod unchanged.
- Rebased onto current main (which now includes #114, jwx v4 + Go 1.26).
- No code changes -- pure dependency manifest update.

## Test plan

- [x] go vet ./... (root + pkg/authjwt) -- clean
- [x] Full integration suite (testcontainers Postgres) -- green ~10s

* fix: replace deprecated bun.In with bun.List for IN clause

bun v1.2.18 deprecated bun.In in favor of bun.List/bun.Tuple. Inside an
existing 'IN (?)' template, bun.List produces identical SQL to bun.In
(comma-separated values, no surrounding parens), so this is a 1:1
mechanical swap. Resolves staticcheck SA1019 in lint-check.
rsharath added a commit that referenced this pull request May 5, 2026
…npm advisories (#117)

Closes the three npm-side Dependabot alerts on cli/package-lock.json:

| Severity | Package  | Before -> After     | Advisory                                   |
| -------- | -------- | ------------------- | ------------------------------------------ |
| MEDIUM   | postcss  | 8.5.8  -> 8.5.14    | XSS via Unescaped </style> (GHSA-qx2v-qp2m-jg93) |
| MEDIUM   | vite     | 5.4.21 -> 6.4.2     | Dev-server response leak (GHSA-4w7w-66w2-5vf9)   |
| MEDIUM   | esbuild  | 0.21.5 -> 0.25.12   | Dev-server CORS bypass (GHSA-67mh-4wv8-2f99)     |

## Approach

- vitest direct: ^2 -> ^3. vitest@3 supports vite ^5/^6/^7; the override
  below pins it onto v6 transitively. Smaller migration than vitest@4
  (which would force a major-major jump from v2). The test surface uses
  only describe/it/expect/beforeEach/vi.fn -- no v3 breaking changes
  exercised by this CLI's 101 tests.
- npm `overrides`:
  - `vite: ^6.4.2` -- forces vitest@3's transitive vite up from v5 to v6
    (clears the vite advisory and pulls esbuild 0.25.x as a side effect).
  - `postcss: ^8.5.10` -- forces both tsup's and vite's transitive postcss
    onto a fixed line (8.5.14 ends up resolved). Without the override,
    tsup pins postcss@8.5.8 indirectly via postcss-load-config.

esbuild needs no explicit override -- vite@6.4.2 already pulls
esbuild@0.25.12, well past the 0.24.2 advisory ceiling.

## Scope

- `cli/package.json` + `cli/package-lock.json` only.
- No src changes -- pure build-tool dependency update.
- Independent of #114 (jwx v4) and #115 (Go-side deps); both are Go-only.

## Test plan

- [x] `npm install` -- clean, 0 vulnerabilities.
- [x] `npm test` -- 101/101 passing (~590ms).
- [x] `npm run typecheck` -- clean.
- [x] `npm run build` (tsup) -- ESM + d.ts artifacts produced.
- [x] `npm run lint` (eslint) -- clean.
- [x] `npm audit` -- 0 vulnerabilities found.
safayavatsal added a commit to safayavatsal/zeroid-highflame-ai that referenced this pull request May 7, 2026
Resolve conflicts and migrate the new federation code to jwx v4 (per main
PR highflame-ai#114, which upgraded the rest of the codebase).

- config.go: keep both validation additions — wimse_domain (from main)
  runs before the external_issuers loop (this PR).
- zeroid.yaml: keep both new top-level sections — attestation (from main)
  followed by the commented external_issuers example.
- internal/service/oauth_external_idp.go: migrate from jwx/v2 to jwx/v4.
  Token.Issuer/IssuedAt are now (T, bool) tuples; AsMap dropped, use
  Claims() iter.Seq2. Replace jws.Parse-based alg gate with
  internal/jwtalg.Validate (also covers JWT-SVID alg allow-list) plus a
  small base64 header decoder for kid/alg lookup.
- internal/service/external_issuer_registry_test.go: jwk.FromRaw →
  jwk.Import[jwk.Key]; jwa.ES256 → jwa.ES256().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants