Skip to content

feat: supported session-state read for the embedding app's own chrome - #717

Merged
fforootd merged 4 commits into
mainfrom
claude/bold-liskov-1114cb
Aug 2, 2026
Merged

feat: supported session-state read for the embedding app's own chrome#717
fforootd merged 4 commits into
mainfrom
claude/bold-liskov-1114cb

Conversation

@fforootd

@fforootd fforootd commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

The 2026-08-02 Wicklore rerun (finding N3) showed that after embedding the scaffolded login/session widgets in a real app, the app's own chrome has no supported way to know session state — the header keeps rendering "Sign in"/"Join" beside a live session. The widgets read GET /sessions/me internally, but nothing documented how the host page gets the same answer.

Root cause is structural on Next: the scaffolded request boundary's matcher covers only /__nextgen/:path* and /profile/:path*, so on any other page the middleware never runs, no auth header is tunnelled, and server-side auth() reports signed-out. Nuxt doesn't share the gap (its scaffolded auth plugin seeds useAuth() on every render) — its gap was purely documentation.

This PR ships the smallest honest surface:

  • @zitadel/sdk-next/session — new dependency-light entry exporting getSession(): a client-side fetch of the same-origin {proxyPath}/sessions/me, the exact read <zitadel-session> performs. Works on any page (only the proxy path must be matched, which the scaffold always does), needs no configureZitadel(), and returns the client-safe ClientAuthResult — no token. 401/404 and anonymous sessions map to signed-out; other failures throw rather than silently rendering signed-out; calling it server-side throws with a pointer to auth(). It's a dedicated subpath because the root barrel pulls next/headers and ./client pulls Lit — both unsafe to import from an SSR'd header component.
  • ClientSession / ClientAuthState / ClientAuthResult lifted from sdk-nuxt into @zitadel/sdk-core as the single source; sdk-nuxt re-exports unchanged, so useAuth() (Nuxt) and getSession() (Next) return the identical shape.
  • Scaffold guidance: one framework-branched paragraph in the generated AGENTS.md managed section — Next names getSession() (plus auth() with its matcher precondition), Nuxt names useAuth(), SPA frameworks get the raw /__nextgen/sessions/me read (no claimed helper that doesn't exist there). Matching one-line comments emitted in the Next and Nuxt profile-page templates, plus a README section with a conditional-header example.

Deliberately not in scope: no state-management layer, no new widget events (zitadel-signout / zitadel-flow-complete already exist, and the scaffolded posture navigates on both transitions, so read-on-load suffices), no NextgenProvider-based pattern (see Notes).

Validation

  • sdk-next:test 103/103 (8 new getSession() tests: identity mapping, null-attribute handling, anonymous session, 401/404 vs throw semantics, proxy-path resolution order, server-side guard)
  • cli:test 942/942 (new guidance test pinning the per-framework branching, incl. that non-Next frameworks never reference @zitadel/sdk-next/session)
  • sdk-nuxt:test, plus build / typecheck / lint green across sdk-core, sdk-next, sdk-nuxt, cli; demo-nuxt:typecheck green (consumes the re-exported types through the built dist)
  • Built dist/session.js smoke-tested by direct import (function resolves; server-side guard fires)
  • changeset status verified
  • Known-good caveat: demo-next:typecheck fails in this fresh worktree, stash-verified identical without these changes (pre-existing JSX-augmentation issue)

Release notes / changeset

.changeset/session-state-for-app-chrome.md@zitadel/sdk-next minor (new ./session entry), @zitadel/sdk-core minor (new exported client-safe types), @zitadel/cli minor (guidance + emitted comments), @zitadel/sdk-nuxt patch (type re-export, no behavior change).

Notes

  • ADR 044/045 (docs: propose scaffold posture defaults and copy-as-branding-revision ADRs #714) checked before building: 044 (page/widget posture) is adjacent but doesn't cover session-state consumption — the new guidance paragraph and the profile-page comment are posture-neutral and survive 044's template branching; 045 is unrelated.
  • Found in passing, deliberately excluded, and being hardened in a separate session/PR: auth() on matcher-uncovered routes trusts a client-forgeable unverified x-nextgen-auth-token header, and the README's NextgenProvider pattern serializes the raw session token into the RSC payload (sdk-nuxt strips it for exactly this reason). Both are behavior changes to existing surface with a design decision in the auth() fix; this PR only narrows documented auth() usage to the safe (matcher-covered) region. The follow-up builds on the ClientAuthResult types this PR moves to sdk-core.

The widgets read GET /sessions/me internally, but an embedding app's own
header had no documented way to the same answer and kept rendering
signed-out CTAs beside a live session (2026-08-02 Wicklore rerun).

- @zitadel/sdk-next/session: new entry with getSession(), a client-side
  read of the same-origin {proxyPath}/sessions/me — works on any page,
  no middleware-matcher requirement, returns the client-safe
  ClientAuthResult (no token). 401/404/anonymous map to signed out;
  other failures throw.
- Client-safe auth shapes lifted to @zitadel/sdk-core as single source;
  sdk-nuxt re-exports unchanged, so useAuth() and getSession() return
  the identical shape.
- Scaffold guidance (AGENTS.md managed section) + generated profile-page
  comments now name each framework's session read path (Next getSession,
  Nuxt useAuth, SPA raw proxy read).
Copilot AI review requested due to automatic review settings August 2, 2026 17:32
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nextgen Ready Ready Preview Aug 2, 2026 5:56pm
nextgen-docs Ready Ready Preview Aug 2, 2026 5:56pm
nextgen-mock-zitadel Ready Ready Preview Aug 2, 2026 5:56pm

Request Review

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

🦋 Changeset detected

Latest commit: 2331d87

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

This PR includes changesets to release 20 packages
Name Type
@zitadel/sdk-next Minor
@zitadel/sdk-core Minor
@zitadel/sdk-nuxt Minor
@zitadel/cli Minor
@zitadel/sdk-angular Minor
@zitadel/sdk-qwik Minor
@zitadel/sdk-react Minor
@zitadel/sdk-solid Minor
@zitadel/sdk-svelte Minor
@zitadel/sdk-vue Minor
@zitadel/testing Minor
@zitadel/server Minor
@zitadel/server-linux-x64 Minor
@zitadel/server-linux-arm64 Minor
@zitadel/server-darwin-x64 Minor
@zitadel/server-darwin-arm64 Minor
@zitadel/server-win32-x64 Minor
@zitadel/api Minor
@zitadel/config Minor
@zitadel/components Minor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a supported, framework-appropriate way for embedding apps to read session state for their own UI chrome (headers/account menus), aligning Next and Nuxt on a shared client-safe auth shape while updating scaffold guidance and templates accordingly.

Changes:

  • Introduces @zitadel/sdk-next/session with getSession() (browser-only, same-origin {proxyPath}/sessions/me read) and exports client-safe ClientAuthResult types.
  • Moves client-safe auth result types (ClientSession / ClientAuthState / ClientAuthResult) into @zitadel/sdk-core and re-exports them from sdk-nuxt and sdk-next.
  • Updates CLI scaffold guidance/tests and framework templates to document the supported session-state read per framework (Next getSession(), Nuxt useAuth(), SPA raw proxy fetch).

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/sdk-nuxt/src/runtime/types.ts Re-exports client-safe auth types from sdk-core instead of defining Nuxt-local copies.
packages/sdk-next/src/types.ts Re-exports the shared client-safe auth types from sdk-core for Next usage.
packages/sdk-next/src/session.ts Adds getSession() client-side helper and re-exports client-safe auth types from the session entry.
packages/sdk-next/src/index.ts Exposes getSession() / GetSessionOptions from the main entry in addition to the dedicated subpath.
packages/sdk-next/src/tests/session.test.ts Adds unit tests covering getSession() status handling, mapping, and config precedence.
packages/sdk-next/README.md Documents “Session state for your own UI” using @zitadel/sdk-next/session.
packages/sdk-next/package.json Adds ./session export and includes src/session.ts in the build entry list.
packages/sdk-core/src/middleware.ts Defines shared client-safe auth result types in the single source of truth.
apps/cli/tests/unit/lib/orca/patchers/rule/guidance.test.ts Pins the per-framework guidance branching (Next vs Nuxt vs SPA frameworks).
apps/cli/src/lib/orca/patchers/rule/nuxt/templates.ts Adds a Nuxt template comment pointing app chrome to useAuth() for session state.
apps/cli/src/lib/orca/patchers/rule/next/renderers/react/index.ts Adds a Next template comment pointing app chrome to getSession() from the new subpath.
apps/cli/src/lib/orca/patchers/rule/guidance.ts Adds a framework-specific guidance paragraph describing the supported session-state read path.
.changeset/session-state-for-app-chrome.md Adds release notes and version bumps for sdk-next/sdk-core/cli (minor) and sdk-nuxt (patch).

Comment thread packages/sdk-next/src/session.ts Outdated
@fforootd
fforootd enabled auto-merge (squash) August 2, 2026 17:46
Review round (Copilot + codex): strip trailing slashes from proxyPath
(matches the typed client's base-URL normalization); treat 404 as
signed-out only when it is the backend's JSON error — a framework's
HTML 404 from a misrouted proxy now throws; README/JSDoc header example
keeps unknown/error states instead of catching failures into 'Sign in'.
@fforootd

fforootd commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Codex P2 (example swallows the failures the helper preserves) — agreed and fixed in 2fd9d18: the header example in both the README and the getSession() JSDoc now keeps a tri-state — undefined (not yet known → neutral chrome), rejection (→ "Session unavailable" alert, never the signed-out CTAs), and only the resolved ClientAuthResult renders sign-in/account links. The contract text no longer suggests .catch()-to-signed-out either; it now says to treat a rejection as unknown, distinct from signed-out.

Same commit also hardens the read itself beyond Copilot's trailing-slash point: a 404 maps to signed-out only when it is the backend's JSON error — a framework router's HTML 404 (e.g. from a matcher miss on a malformed proxy path) now throws instead of quietly rendering "Sign in", which was this PR's original sin to fix.

Branch updated with main via merge (8b75248, no force-push) and re-validated on the merged head: cli 957/957, sdk-next 108/108, lint/typecheck green (a stale-lockfile 127 cascade after the merge was the local worktree needing pnpm install, not a semantic conflict).

@fforootd
fforootd merged commit cb42772 into main Aug 2, 2026
14 checks passed
@fforootd
fforootd deleted the claude/bold-liskov-1114cb branch August 2, 2026 18:05
@github-project-automation github-project-automation Bot moved this from Inbox to Done in Engineering Kanban Aug 2, 2026
fforootd added a commit that referenced this pull request Aug 2, 2026
…g-lalande-218efe

Resolution notes: took main's #717-final session.ts/session.test.ts/changeset
(JSON-guarded 404, proxyPath normalization, unknown-vs-signed-out example),
re-applied the .js-extension policy to session.ts, kept the tsc per-file
build and /react wiring, preserved main's next>=15 peer floor.
fforootd added a commit that referenced this pull request Aug 3, 2026
…loads (#718)

Was stacked on #717 (merged — now targeting `main`). Closes the two
sdk-next hardening findings from the getSession session-state work, plus
a Codex review round (provider client-boundary leak,
`opaqueTokenTimeoutMs`).

## Summary

- **`auth()` verifies instead of trusts.** It previously `decodeJwt()`ed
the `x-nextgen-auth-token` header without signature verification and
treated *any* other non-empty value as a middleware-validated opaque
token — so on any route outside the middleware `matcher` (the scaffolded
matcher covers only the proxy path and protected routes), a
client-supplied header spoofed any identity, and even
`x-nextgen-auth-token: garbage` returned `isAuthenticated: true`.
`auth()` now re-verifies every value it reads: JWT-shaped tokens get
full `verifyJwt` (JWKS signature, `iss`, `exp`, alg/typ allow-lists —
same rules and defaults as the middleware, in-process key cache), opaque
tokens are validated against `GET /sessions/me` (React
`cache()`-deduplicated per render pass, fail-closed with a
`console.warn` that distinguishes forged headers from JWKS/backend
reachability problems). Opaque sessions now also return the real
identity from the backend response instead of `userId: "unknown"`. New
optional `AuthOptions` (a `Pick` of the middleware options) for apps
that customise verification; the docstring now states the matcher
precondition instead of claiming the middleware always validated the
header.
- **`NextgenProvider` strips the token server-side — and is
server-only.** The README's documented pattern (`await auth()` into the
provider) serialised the raw session token into the RSC flight payload,
readable by any client script. The provider now converts to the
client-safe `ClientAuthResult` (from #717, explicit field pick)
**before** the value crosses the server→client boundary. Per the Codex
P1 finding, the provider is additionally guarded with `server-only` and
exported from `@zitadel/sdk-next/server` (+ root), *not* `/react`:
wrapping it in a `"use client"` file (the common `providers.tsx`
pattern) would make the wrapper the boundary and serialise the
still-unstripped prop — that wrapper is now a build error instead of a
silent leak. Client-seeded trees (e.g. from `getSession()`) use the new
`AuthContextProvider` on `/react`, which only accepts the token-less
shape. `useAuth()` returns `ClientAuthResult`, matching sdk-nuxt and
`getSession()`.
- **Client entry + root-barrel guard.** New `@zitadel/sdk-next/react`
entry (`useAuth`, `AuthContextProvider`, client-safe types); `auth.ts`
imports `server-only` (already a dependency, previously unwired), so
pulling `auth()` into a `"use client"` graph fails the build with an
import trace. README gains an entry-point table; `sideEffects` declared
(`client.js` for element registration, `auth.js`/`provider.js` to keep
the guard imports unprunable).
- **`opaqueTokenTimeoutMs` honoured (Codex P2 — ran deeper than
reported):** the option already existed in sdk-core's
`NextgenMiddlewareOptions` and sdk-nuxt honours it, but *both* sdk-next
validation passes coupled the `GET /sessions/me` timeout to
`jwksTimeoutMs` — the middleware's opaque fallback (pre-existing bug)
and the new `auth()` (which copied it faithfully). Both now thread
`opaqueTokenTimeoutMs` (default 5000 ms), it's part of `AuthOptions`,
and the README options table documents it.
- **Build correctness (found while wiring the above):** tsup's chunk
splitting hoisted the compiled `createContext` module into a shared
chunk *without* its `"use client"` directive — the published
provider/hook entries could never have worked from a real consumer
(nothing in-repo exercised them, which is how it survived). The package
now builds with per-file `tsc` emit like sdk-core (directives preserved
per module, dep types resolved via built dist by neutralising the
`@zitadel/source` condition), relative imports carry `.js` extensions,
and demo-next now renders `NextgenProvider` + a `useAuth` badge so the
documented pattern compiles in CI forever.
- `isJwtShaped` moves to `@zitadel/sdk-core` (shared JWS/JWE structural
detection; the middleware imports it from there).

## Validation

- `moon run sdk-core:{lint,typecheck,build,test}
sdk-next:{lint,typecheck,build,test} demo-next:{lint,typecheck,build}
demo-next-e2e:lint` — all green (sdk-next: 132 tests at head; earlier
counts in review threads reflect older heads).
- New regression tests use real RSA-signed JWTs against a mocked JWKS:
forged/unsigned JWT rejected, signed-without-`sub` rejected (previously
*authenticated* via the opaque fallthrough), expired rejected, garbage
opaque header rejected via mocked 401, backend-confirmed opaque token
authenticates with real identity, anonymous/500/unreachable fail closed,
and `opaqueTokenTimeoutMs` proven with a signal-respecting fetch mock
(abort actually reaches the request). Provider tests assert the context
value contains no `token` key for every accepted input shape;
`AuthContextProvider` covered for client seeding.
- Compile-guard checks in demo-next (temporary routes, since removed):
importing `auth` from the root barrel in a `"use client"` page fails the
build with the full import trace into `dist/auth.js`; the Codex P1 repro
— a `"use client"` wrapper around `NextgenProvider` — fails with
`'server-only' cannot be imported from a Client Component module`; the
committed subpath-based provider/badge usage builds clean.
- `demo-next-e2e/src/auth.spec.ts` now asserts the raw session-cookie
value appears **nowhere** in the server's `/admin` response (HTML +
inlined RSC flight payload) — a durable leak detector for this class.
Lint/typecheck-verified; not executed locally (port 8080 is held by a
live local `nextgen` server and the e2e web-server config pins that
port; the suite is opt-in local). Worth a run wherever 8080 is free.
- `dist/` inspected: `"use client"` heads `context.js`/`useAuth.js`,
`provider.js` opens with `import "server-only"` (shared-render
deliberately removed), `auth.js` likewise.

## Release notes / changeset

`.changeset/verify-auth-header-strip-token.md` — `@zitadel/sdk-next`
minor, `@zitadel/sdk-core` minor. Security hardening plus surface
changes (`useAuth()` return shape, new `/react` entry, `NextgenProvider`
server-only, root barrel build-errors in client graphs, opaque timeout
no longer follows `jwksTimeoutMs`).

## Notes

- Defense-in-depth cost: on matcher-covered routes the middleware has
already validated the token, so `auth()`'s re-verification adds ~one
cached-JWKS signature check for JWTs, or one `/sessions/me` round-trip
per render pass for opaque tokens (per-render `cache()` dedupe). If that
round-trip ever matters, an HMAC attestation minted by the middleware
(e.g. keyed off `ZITADEL_PROJECT_SECRET`) could skip it — deliberately
out of scope here.
- A further evolution would be `auth()` reading the `__nextgen_session`
cookie directly and dropping the matcher precondition entirely — but
that contradicts the just-landed #717 guidance (which routes app chrome
to `getSession()`), so it's flagged rather than done.
- Root-barrel client imports of *client* exports (`useAuth`) happen to
compile under Turbopack's per-export tree shaking; that's
bundler-dependent, so the README documents `/react` and `/session` as
the supported client imports rather than blessing it.
fforootd added a commit that referenced this pull request Aug 3, 2026
## Summary

- normalize missing, expired, superseded, and rotated self-session
cookies to the canonical `401 auth.unauthorized` contract for both `GET
/sessions/me` and `DELETE /sessions/me`, with `Cache-Control: no-store`
on every response path
- keep the browser-facing Next.js `getSession` helper on the dedicated
`@zitadel/sdk-next/session` subpath and require canonical 401/404
envelopes before reporting a signed-out state
- attach project credentials only to the exact `POST /sessions/exchange`
operation in Next.js, Nuxt, and generated Vite/Angular proxies while
preserving caller-provided authorization
- surgically migrate only fingerprinted legacy managed proxies, preserve
target/rewrite/TLS options and surrounding comments, and make `zitadel
doctor` warn instead of certifying unknown or deceptively marked proxy
code
- merge #718's token verification and provider hardening, resolving its
export overlap without restoring `getSession` at the package root

## Validation

- `moon run server:test`
- `go test -c -tags postgres_integration
./internal/api/integration_test` (container-backed suite compiled
locally)
- focused CLI proxy/doctor tests: 73 passed
- `moon run cli:test`: 105 files / 963 tests passed
- `moon run cli:lint`, `moon run cli:typecheck`
- merged-tree SDK checks: `sdk-core` (6 tests) and `sdk-next` (7 files /
144 tests), plus lint, typecheck, and build
- merged-tree `demo-next` build/typecheck/lint and `demo-next-e2e` lint
- `moon run workspace:check -- --only pack`
- `moon run workspace:check -- --only release`
- `moon run workspace:check -- --only journey` (Next, Nuxt, React, Vue,
Angular, Solid, Svelte, and Qwik fresh-app browser journeys)
- OpenAPI validation, generated-code checks, Go format/vet/tests,
changeset status, and PR-title validation

The all-workspace Node aggregate hit one unrelated console test timeout
under concurrency; the exact console suite passed 8/8 when rerun in
isolation. Postgres and Spanner integration phases could not run locally
because Docker was not running, so GitHub Actions remains the source of
truth for those container-backed lanes.

## Release notes / changeset

Added `.changeset/harden-session-proxy-credentials.md` with patch
releases for `@zitadel/cli`, `@zitadel/server`, `@zitadel/sdk-next`, and
`@zitadel/sdk-nuxt`. The merged #718 changeset independently carries its
release intent; Changesets resolves the combined branch outcome.

## Notes

This is the security follow-up to #717. #718 is now merged and its
provider/token-verification hardening is retained. Existing apps with a
legacy managed Vite or Angular proxy should run `zitadel doctor --fix`
after upgrading the CLI. Custom or unrecognized proxy implementations
are deliberately left untouched and surfaced for manual review.

The implementation was checked against ADR 005 (public runtime/private
credentials), ADR 030 (canonical error envelopes), ADR 037
(server-authoritative sessions), and proposed ADR 036 (interim
credential boundary). Restricting the confidential project secret to the
exact handoff exchange is the immediate hardening step; the
publishable-key design remains the target state.

The first merged-tree `full-pr` run exposed that the integration test
server bypassed the production `WithSessionStateNoStore` wrapper, so its
expired-session assertion could not observe the header that production
emits. The harness now mirrors that production middleware; the restarted
Postgres lane is the runtime proof.
fforootd added a commit that referenced this pull request Aug 3, 2026
…ession-read plane (#722)

## Summary

Amends ADR 036 (API credential planes) in place — status stays Proposed,
with an `Amended: 2026-08-03` marker — with three decisions settled
while hardening the sdk-next auth surface (#717/#718):

- **Credential exposure contracts.** The browser-bundle litmus test
generalises to a per-class contract table (publishable key, project
secret, session token, handoff token): which surfaces each credential
may appear on, and which it must never appear on. The session-token row
makes the httpOnly contract explicit — *no server-side surface may
re-materialise the token into browser-readable content* (HTML/DOM,
serialised SSR/RSC payloads, client state, URLs, logs) — and cites the
wired enforcement: sdk-nuxt's SSR-payload strip, sdk-next's server-only
`NextgenProvider` strip, and the demo-next e2e leak guard (#718) as the
template new SDK integrations are expected to carry. OIDC access/refresh
tokens stay in ADR 037.
- **Server-originated me-ops.** SSR session validation (`auth()`, the
framework middlewares) is public-plane software acting for the human:
publishable key for attribution + the user's session cookie as principal
— never the project secret, preserving the zero-platform-secrets endgame
for SSR. Origin-allowlist enforcement is scoped to
credential-*establishing* operations (flow ops, public-plane handoff
exchange, where it also derives the WebAuthn RP ID); cookie-principal
reads authenticate by the cookie — a strictly stronger credential than
an attacker-settable `Origin` — so server-originated calls with no
browser-attested `Origin` pass.
- **SSR session validation happens at the render layer.** The
`x-nextgen-auth-token` tunnel is named for what it was — an unnamed
intra-app credential channel, spoofable wherever the middleware didn't
run. #718 closed the spoof by re-verifying the header; this amendment
removes the channel: `auth()` reads the session cookie (and
`Authorization` in Route Handlers) directly and validates once, works on
every route, and the `matcher` constrains only redirects — aligning
server reads with `getSession()`. Middleware keeps two jobs: the
credential-free proxy and full-validation redirect gating on
`protectedRoutes` only. The HMAC-attestation alternative is explicitly
foreclosed: it would need a durable shared secret in exactly the layer
this ADR empties of secrets.

The amendment also records specced-vs-wired: #717's `getSession()` is
the model's client half (shipped), #718's verification core and
client-safe boundary are the server half (shipped behind the old
transport); the remaining work is the transport swap, tunnel deletion,
publishable-key attachment once the key exists, and the matcher-prose
sweep. Neither PR is obsoleted — the delete is ~30 lines of transport
plus prose.

## Validation

Docs-only. PR title validated with `scripts/check-pr-title.mjs`. Table
style matches the ADR's existing tables.

## Release notes / changeset

No changeset required — no shipped behavior changed.

## Notes

- One judgment call to sanity-check in review: middleware **retains full
validation on `protectedRoutes`** (a structural-only check would render
signed-out content instead of redirecting), accepting bounded
double-validation there. The alternative — moving redirect gating to the
render layer too — changes only that bullet.
- The origin carve-out implies verifier work when publishable keys land:
origin checks become per-operation-class, not blanket per-credential.
- Follow-up code PR (cookie-reading `auth()` in sdk-next, tunnel
deletion, prose sweep) is gated on this review; sdk-nuxt already
validates every request and only needs publishable-key wiring.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants