Skip to content

[docs] Document Postgres World auth/security limitations explicitly - #3908

Merged
pranaygp merged 6 commits into
mainfrom
document-pg-world-auth
Sep 23, 2026
Merged

pranaygp merged 6 commits into
mainfrom
document-pg-world-auth

Conversation

@pranaygp

@pranaygp pranaygp commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Why

Someone pointed out that the generated internal queue endpoints accept forged, unauthenticated requests when using the Postgres World. That's true, and it's the intended trust model for a reference implementation — but our docs didn't say so anywhere:

  • docs/content/worlds/{v4,v5}/postgres.mdx, worlds-manifest.json, and the building-a-world guide all called it "production-ready".
  • The package README mentioned "reference implementation" in passing, in the middle of the opening sentence, and said nothing about authentication.
  • The Local World pages listed "No authentication — suitable only for local development" as a limitation and then pointed readers at the Postgres World "for production deployments" — even though it inherits that same queue handler.

Per the thread: we can't prescribe how people do auth, so the outcome is to be explicit about the limitation, the way @workflow/web already is about bringing your own auth.

Docs only — no behavior change.

What is actually unauthenticated

packages/world-postgres/src/queue.ts takes createQueueHandler straight from @workflow/world-local, and that handler (packages/world-local/src/queue.ts:373) validates only the x-vqs-* header shape, the __wkf_workflow_ queue-name prefix, and the payload schema. No signature, no shared secret, no caller identity. So anything that can reach POST /.well-known/workflow/v1/flow can start/advance runs and invoke steps directly, including a replay of a previously captured body.

Changes

Package docs

  • packages/world-postgres/README.md — reference-implementation notice at the top (including the recommendation to clone and adapt for production), plus a ## Security section: protect the queue route, bring your own auth, and data at rest.
  • packages/world-postgres/HOW_IT_WORKS.md — one-line note on the trust assumption where HTTP delivery is described.

Docs site (v4 + v5)

  • worlds/*/postgres.mdx — dropped "production-ready" from the frontmatter and intro, added a warn callout, a ## Security section, a deployment-checklist item, and "reference implementation" / "no built-in authentication" / "no encryption" to Limitations.
  • worlds/*/local.mdx — reworded the "for production deployments" pointer.
  • worlds/*/building-a-world.mdx — the reference-implementation callout no longer claims production-ready.
  • worlds-manifest.json — description no longer claims production-ready.

The auth guidance is deliberately network-level (ingress / reverse proxy / firewall, mTLS or a shared-secret header when the routes cross hosts) rather than framework middleware, because:

  1. the Next.js setup guide tells people to exclude /.well-known/workflow/* from the proxy matcher, and a handler that consumes the internal body breaks execution; and
  2. the World does not sign or attach a credential to its own delivery requests, so an in-app check requiring one would reject genuine deliveries. Worth calling out, since it's the first thing someone reaches for.

Also documented, since they came up while tracing the trust boundary: the webhook/:token route is authorized by the token alone, manifest.json is 404 unless WORKFLOW_PUBLIC_MANIFEST=1, and the World does not currently implement getEncryptionKeyForRun() — with a pointer to Custom World implementations, since a derived World can opt into encryption with that one method.

Review round 2

All 16 comments from @pranaygp are addressed and replied to inline:

  • Reverted the additions to docs/*/deploying.mdx (v4 + v5) and docs/v5/configuration/worlds.mdx — those pages stay a short link section and general configuration.
  • Applied the suggested wording for building-a-world, local.mdx, the frontmatter description, the Security intro (now recommending encryption too), "does not currently implement", the README notice, and the manifest description.
  • Condensed "What is unauthenticated" into "Protect the queue route" — the flow route is publicly reachable by default and must be protected, plus the two exceptions — and dropped the redundant Queue Behavior bullet.
  • Reframed the encryption note: a derived World can opt in by implementing getEncryptionKeyForRun().

Two small deviations, both flagged in the threads: the building-a-world suggestion's "reference to full in those gaps" is applied as "fill in those gaps", and the encryption link uses the relative /docs/... form so the v5 copy links to v5 rather than to the v4 page.

Docs Preview

Base URL from the vercel[bot] comment (workflow-docs row): https://workflow-docs-git-document-pg-world-auth.vercel.sh — links require Vercel team access.

Page v4 v5
Postgres World — Security /worlds/postgres#security /v5/worlds/postgres#security
Postgres World — Limitations /worlds/postgres#limitations /v5/worlds/postgres#limitations
Local World — Limitations /worlds/local#limitations /v5/worlds/local#limitations
Building a World /worlds/building-a-world /v5/worlds/building-a-world
Worlds index (manifest description) /worlds /v5/worlds

🤖 Generated with Claude Code

The Postgres World was described as "production-ready" in the docs, the
worlds manifest, and the building-a-world guide, while its README mentioned
"reference implementation" only in passing and neither said anything about
authentication. It inherits its queue HTTP handler from world-local, which
validates the x-vqs-* header shape, the queue-name prefix, and the payload
schema — never the caller — so any client that can reach
POST /.well-known/workflow/v1/flow can forge or replay workflow and step
invocations.

Make the reference-implementation framing and the bring-your-own-auth
expectation explicit instead:

- Add a Security section to the package README, HOW_IT_WORKS, and both the
  v4 and v5 Postgres World docs pages: what is unauthenticated, how to gate
  it at the network edge, why framework middleware is the wrong layer, and
  that the World never presents a credential of its own.
- Note that the World does not implement getEncryptionKeyForRun(), so data
  is stored unencrypted, and that self-hosted @workflow/web has no auth.
- Drop "production-ready" from the docs pages, the worlds manifest, and the
  building-a-world reference callout, and add "no built-in authentication"
  and "no encryption" to the Limitations list.
- Stop the Local World pages from pointing at the Postgres World for
  production without mentioning that it shares the same unauthenticated
  handler, and warn about self-hosted World security in the Deploying guide
  and the v5 Worlds configuration reference.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Co-Authored-By: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
@pranaygp
pranaygp requested a review from a team as a code owner August 31, 2026 21:35
@changeset-bot

changeset-bot Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 165c029

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

This PR includes changesets to release 1 package
Name Type
@workflow/world-postgres 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

@vercel

vercel Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated
example-nextjs-workflow-turbopack Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
example-nextjs-workflow-webpack Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
example-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-astro-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-express-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-fastify-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-hono-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-nestjs-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-nitro-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-nuxt-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-python-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-sveltekit-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-tanstack-start-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workbench-vite-workflow Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workflow-docs Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workflow-swc-playground Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workflow-tarballs Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC
workflow-web Ready Ready Preview, v0 Sep 10, 2026 11:03pm UTC

Comment thread docs/content/docs/v4/deploying.mdx Outdated
Comment on lines +76 to +77

### Using a third-party World
<Callout type="warn">

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not needed in this section. Let's leave it in the specific postgres world pages and leave this top level page to just be a short link section

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reverted — this page is back to just the link section.


## Postgres World

The Postgres World is a self-hosted durable backend for long-running server processes.
The Postgres World is a self-hosted durable backend for long-running server processes. It is a reference implementation: none of the options below configure authentication, because the World does not authenticate Workflow's internal HTTP routes at all. Restrict them yourself — see [Postgres World security](/worlds/postgres#security).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not needed in this section. This is just general configuration. leave it as is

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reverted — left as is.

Comment thread docs/content/docs/v5/deploying.mdx Outdated
@@ -68,7 +68,18 @@ For self-hosting or deploying to other cloud providers, you can use community-ma
</Card>
</Cards>

### Using a third-party World
<Callout type="warn">

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

like in v4, it' not needed in this section. Let's leave it in the specific postgres world pages and leave this top level page to just be a short link section

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reverted here too.

</Callout>

<Callout type="info">
**Reference implementation:** The [Postgres World source code](https://github.com/vercel/workflow/tree/main/packages/world-postgres) is a production-ready example of how to implement the World interface with a database backend and graphile-worker for queuing.
**Reference Implementation:** The [Postgres World source code](https://github.com/vercel/workflow/tree/main/packages/world-postgres) is a complete example of how to implement the World interface with a database backend and graphile-worker for queuing. Note that it deliberately leaves authentication to the deployment — see [Postgres World security](/worlds/postgres#security) for what a self-hosted World has to gate itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
**Reference Implementation:** The [Postgres World source code](https://github.com/vercel/workflow/tree/main/packages/world-postgres) is a complete example of how to implement the World interface with a database backend and graphile-worker for queuing. Note that it deliberately leaves authentication to the deployment — see [Postgres World security](/worlds/postgres#security) for what a self-hosted World has to gate itself.
**Reference Implementation:** The [Postgres World source code](https://github.com/vercel/workflow/tree/main/packages/world-postgres) is a complete example of how to implement the World interface with a database backend and graphile-worker for queuing. It is not optimized for scale, speed, or security and should be used as a reference to full in those gaps in a custom World. Please see the [Postgres World docs](/worlds/postgres) for what a self-hosted World has to gate itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied to both v4 and v5. One tweak: the suggestion had "reference to full in those gaps", which I took as "fill in those gaps".

Comment thread docs/content/worlds/v4/local.mdx Outdated
@@ -81,4 +81,4 @@ The local world is designed for development, not production:
- **Single instance** - Cannot handle distributed deployments
- **No authentication** - Suitable only for local development

For production deployments, use the [Vercel World](/worlds/vercel) or [Postgres World](/worlds/postgres).
For production deployments, use the [Vercel World](/worlds/vercel), which authenticates workflow traffic automatically, or the [Postgres World](/worlds/postgres) — which inherits this queue handler and its lack of authentication, so you must restrict its workflow routes yourself. See [Postgres World security](/worlds/postgres#security).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
For production deployments, use the [Vercel World](/worlds/vercel), which authenticates workflow traffic automatically, or the [Postgres World](/worlds/postgres) — which inherits this queue handler and its lack of authentication, so you must restrict its workflow routes yourself. See [Postgres World security](/worlds/postgres#security).
For production deployments, use the [Vercel World](/worlds/vercel), which handles execution, persistence, multi-tenancy, scale, observability, and security for you on Vercel, or check out the [Postgres World](/worlds/postgres) - a tested reference implementation that implements the complete World spec and can be used to deploy workflows anywhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied verbatim to both v4 and v5.

Comment thread docs/content/worlds/v4/postgres.mdx Outdated
@@ -1,6 +1,6 @@
---
title: Postgres World
description: Production-ready, self-hosted world using PostgreSQL for storage and graphile-worker for job processing.
description: Self-hosted reference world using PostgreSQL for storage and graphile-worker for job processing. Bring your own authentication.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
description: Self-hosted reference world using PostgreSQL for storage and graphile-worker for job processing. Bring your own authentication.
description: Self-hosted reference world using PostgreSQL for storage and graphile-worker for job processing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — also dropped the same trailing sentence from the worlds-manifest.json description.

Comment thread docs/content/worlds/v4/postgres.mdx Outdated

This architecture ensures workflows survive application restarts with all state reliably persisted. For implementation details, see the [source code](https://github.com/vercel/workflow/tree/main/packages/world-postgres).

## Security

Unlike the [Vercel World](/worlds/vercel), which authenticates workflow traffic with OIDC tokens automatically, the Postgres World does **not** authenticate the requests that drive workflow execution. Adding that is your responsibility, and it needs to be in place before an app using this World is reachable by untrusted clients.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
Unlike the [Vercel World](/worlds/vercel), which authenticates workflow traffic with OIDC tokens automatically, the Postgres World does **not** authenticate the requests that drive workflow execution. Adding that is your responsibility, and it needs to be in place before an app using this World is reachable by untrusted clients.
Unlike the [Vercel World](/worlds/vercel), which authenticates and [encrypts](/docs/how-it-works/encryption) workflow traffic automatically, the Postgres World does **not** automatically authenticate requests that drive workflow execution. Adding that is your responsibility and should be in place before an app using this World is used in production and reachable by untrusted clients. We also recommend [enabling encryption](https://workflow-sdk.dev/docs/how-it-works/encryption#custom-world-implementations).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied to both. One change: I used the relative /docs/how-it-works/encryption#custom-world-implementations instead of the absolute workflow-sdk.dev URL, so the v5 copy links to the v5 page rather than v4. Anchor verified against ## Custom World Implementations in both versions.

Comment thread docs/content/worlds/v4/postgres.mdx Outdated
Comment on lines +251 to +262
That route runs workflow orchestration and queued step invocations (on adapters that mount a separate step route, `POST /.well-known/workflow/v1/step` behaves the same way). The handler validates only:

- The presence and shape of the `x-vqs-queue-name`, `x-vqs-message-id`, and `x-vqs-message-attempt` headers
- That the queue name carries the expected `__wkf_workflow_` prefix
- That the body matches the queue payload schema

There is no request signature, no shared secret, and no caller-identity check. Any client that can reach the route can submit a well-formed message of its own, or re-send a body it captured earlier, and the runtime will execute it — starting or advancing runs and invoking steps directly, including steps your application would normally only reach after its own gating (an approval, a payment check, a permission test). Because nothing in a message is bound to a caller, a replayed delivery is indistinguishable from a genuine one.

Two related routes have their own limits worth knowing:

- `POST /.well-known/workflow/v1/webhook/:token`, created by [`createWebhook()`](/docs/api-reference/workflow/create-webhook), is authorized by the token in the URL and nothing else. When you need more than that, use [`createHook()`](/docs/api-reference/workflow/create-hook) behind your own authenticated route and call [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) once you have authorized the request.
- `GET /.well-known/workflow/v1/manifest.json` responds with `404` unless the `WORKFLOW_PUBLIC_MANIFEST` environment variable is set to `1`. Leave it unset outside of testing — the manifest lists your workflow and step names.

@pranaygp pranaygp Aug 31, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Too verbose. It's enough to say that this route is publicly accessible on the internet by default, and should be protected. And then just mention the 2 other routes (webhook and manifest and the exceptions there)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Cut. The section is now "Protect the queue route": one paragraph saying the flow route is publicly reachable by default and has no signature/secret/caller check, then the two exceptions (webhook token, manifest 404). The header-validation bullet list and the replay paragraph are gone. Applied the same trim to the README.

Comment thread docs/content/worlds/v5/building-a-world.mdx
…lout

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VaguelySerious and others added 2 commits September 10, 2026 15:46
The `configure-proxy-handler` id lives on a JSX heading inside a collapsed
accordion, so next-validate-link cannot see it and the anchor would not reveal
the content anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking all of `/.well-known/workflow/` breaks `createWebhook()`, whose
`webhook/:token` route is a sibling under the same prefix and is meant to be
reachable by the caller. Also note that WORKFLOW_PUBLIC_MANIFEST is read at
build time, and attribute payload validation to the runtime rather than the
queue handler, which only checks the header shape and queue-name prefix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@VaguelySerious VaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI review: no blocking issues

Conflicts with main are resolved and pushed. The overlap was the docs style pass (#3948, #3704): I kept this PR's security content and adopted main's colon separators and bulleted deployment checklist, so the merge is additive to both. Three follow-up commits fix things I found while checking the claims: a </Callout> I dropped in the v5 building-a-world resolution, the Docs Links failure on /docs/getting-started/next#configure-proxy-handler (that id is on a JSX heading inside a collapsed accordion, so next-validate-link cannot see it and the anchor would not reveal the content anyway), and the three items in the inline comments.

I verified the central claim rather than taking it from the description. Driving the queue handler that @workflow/world-postgres actually exposes (world.createQueueHandler, which is world-local's, packages/world-postgres/src/queue.ts:133) with forged requests against a live Postgres:

  • a request carrying no credential of any kind is accepted (200) and a forged step invocation (stepName: chargeCustomerCard) is dispatched to the runtime handler
  • the handler is told attempt, queueName, messageId and nothing about the caller
  • an identical replay of the same captured message, same x-vqs-message-id, is accepted and dispatched a second time
  • a deliberately wrong authorization / x-vercel-signature header changes nothing
  • controls behave as the page implies: wrong queue prefix and missing x-vqs-* headers both 400

So the page is accurate about the trust model, and the body is plain JSON (TypedJsonTransport), which makes forging easier than the wording suggests. The loopback default is as described (packages/world-postgres/src/queue.ts:209). Test code was local only, nothing committed.


Workflow does not prescribe an auth mechanism for self-hosted Worlds, so restrict the flow route at the network edge rather than inside the application:

- **Keep the flow route unreachable from outside.** By default the worker delivers to a loopback address (`http://localhost:{PORT}`, or `WORKFLOW_LOCAL_BASE_URL` when set), so in the common single-process topology nothing outside the container needs to reach it. Blocking external requests to `/.well-known/workflow/v1/flow` at your ingress, reverse proxy, or firewall costs you nothing, because loopback delivery never traverses that layer. Do not block the whole `/.well-known/workflow/` prefix if you use [`createWebhook()`](/docs/api-reference/workflow/create-webhook): its `webhook/:token` route sits under the same prefix and has to stay reachable by whoever calls it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI Review: Note

The ingress guidance originally said to block all of /.well-known/workflow/, which would have broken createWebhook(): its webhook/:token route is a sibling under the same prefix and has to stay reachable by whoever calls it (packages/builders/src/vercel-build-output-api.ts:118, packages/nitro/src/index.ts:326). The same section two paragraphs up tells readers that route is authorized by its token, so the page was telling them to block a route it had just described as intentionally public. I pushed the scoping to /.well-known/workflow/v1/flow plus the explicit exception, and the same change in the README. Flagging it here so the reasoning is on the record rather than buried in a commit.


## Security

Unlike the [Vercel World](/worlds/vercel), which authenticates and [encrypts](/docs/how-it-works/encryption) workflow traffic automatically, the Postgres World does **not** automatically authenticate requests that drive workflow execution. Adding that is your responsibility and should be in place before an app using this World is used in production and reachable by untrusted clients. We also recommend [enabling encryption](/docs/how-it-works/encryption#custom-world-implementations).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI Review: Note

I could not verify the first half of this sentence, and I would rather it be checked than published on my read of it.

getEncryptionKeyForRun is implemented for the Vercel World (packages/world-vercel/src/encryption.ts:182), so the encryption half holds. For authentication I found no inbound verification anywhere in the SDK: createQueueHandler in packages/world-vercel/src/queue.ts:511 hands the request to @vercel/queue's handleCallback, and that package's callback parsing (parseRawCallback/parseCallback) only reads CloudEvent headers and the body. Its OIDC use is outbound, for authenticating the SDK's own calls to the queue API, not for checking callers. So whatever protects the deployed flow route is platform-side, not something this adapter does.

I tried to settle it against a preview deployment of this branch and got a confounded answer: POST /.well-known/workflow/v1/flow on workbench-hono-workflow-git-document-pg-world-auth.labs.vercel.dev returns 401, but the body is {"protection":{"vercel_auth_enabled":true}}, so that is deployment protection answering, not workflow-level auth. GET / gives the same redirect, so it tells us nothing about the route.

You know the backend better than I do, so either confirm and leave it, or attribute it to the platform rather than the World. Worth getting exact: it is a security guarantee in a public security section, and it is the sentence a reader will use to decide the Vercel World needs nothing from them.


- `webhook/:token`, created by [`createWebhook()`](/docs/api-reference/workflow/create-webhook), is authorized by the token in the URL and nothing else. Use [`createHook()`](/docs/api-reference/workflow/create-hook) behind your own authenticated route and [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) when you need more than that.
- `manifest.json` responds with `404` unless the app was built with `WORKFLOW_PUBLIC_MANIFEST=1`. That variable is read at build time, so unsetting it in the runtime environment of an already-built deployment does not withdraw the manifest. Leave it unset outside of testing, because the manifest lists your workflow and step names.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI Review: Nit

WORKFLOW_PUBLIC_MANIFEST is read at build time, not per request: shouldExposePublicManifest (packages/builders/src/base-builder.ts:2217) is consulted while emitting output, Next writes the file into public/ during the build (packages/next/src/builder-eager.ts:139), and Nitro only registers the route when the variable is set at config time (packages/nitro/src/index.ts:329). The security-relevant direction is the one a reader would get wrong: unsetting it in the runtime environment of an already-built deployment does not withdraw the manifest. I pushed that clause here and in the README.


The worker sends workflow orchestration and queued step messages to the combined `.well-known/workflow/v1/flow` endpoint.

That endpoint is not authenticated: the queue HTTP handler is inherited from `@workflow/world-local` and checks the `x-vqs-*` header shape and the queue-name prefix, but never the caller, so restricting the route is left to the deployment. The payload is validated later, by the runtime that consumes the message. See [Security](./README.md#security).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI Review: Nit

The queue handler does not check the payload schema. packages/world-local/src/queue.ts:406 validates the x-vqs-* header shape and the queue-name prefix, then JSON-deserializes the body and calls the handler; the schema check happens downstream in the runtime (WorkflowInvokePayloadSchema.parse, packages/core/src/runtime.ts:745). I confirmed it by driving the real handler with {"total":"garbage","not":"an invoke payload"}: status 200 and the payload was dispatched to the consumer untouched. Since this line exists to describe the trust boundary precisely, I moved payload validation to the runtime in both this file and the README.


The Postgres World does not currently implement `getEncryptionKeyForRun()`, so it does not participate in Workflow's [end-to-end encryption](/docs/how-it-works/encryption): workflow and step inputs and return values, hook payloads and metadata, and stream chunks are all stored in your database in readable form. A World derived from this one can opt in by implementing that one method; see [Custom World implementations](/docs/how-it-works/encryption#custom-world-implementations). Until then, protect the database, its credentials, and its backups accordingly.

The [observability UI](/docs/observability) has no authentication of its own either. If you self-host `@workflow/web` against your Postgres database, put it behind your own auth layer, because everyone who can reach it can read every run.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI Review: Nit

Good addition, and it matches the notice @workflow/web's README already carries. One gap: the paragraph covers self-hosting @workflow/web, but npx workflow web and the CLI's inspect commands read the same database with no auth either, and the Observability section above tells readers to use exactly those against WORKFLOW_POSTGRES_URL. Not worth another paragraph; if you touch this again, widening it to "the observability UI and CLI read every run" would close it.

@VaguelySerious VaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI review: no blocking issues

Approving. Conflicts with main are resolved, all 180 checks pass, and the docs claims hold up against the code.

Unit Tests (windows-latest) went red once on packages/world-local/src/streamer.test.ts > scopes chunk listing to the stream, not the whole world, which ran 70729ms and timed out after one retry. That was the only input to the E2E Required Check failure; every E2E lane was green. This branch has a zero-file delta from the main commit it merges (git diff 74058c141c HEAD is the 11 docs, README, manifest and changeset files and nothing else), so no code in that test's path changed here. It passed on re-run.

Unrelated to this PR, worth knowing: the in-flight-before-decision-counted sim scenario fails on main as it stands. I ran node run.ts --report-only in workbench/sim-world at origin/main on macOS and got 40 passed, 1 failed, with check failed: the count guard fenced the write the watermark let through. It never gates CI because the test script passes --report-only and exits 0 regardless, so it has been failing quietly. Given the scenario exists to assert the count half of the fence fires, that is worth a look on its own.

@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #4318. Merge conflicts were resolved by AI — please review carefully. (backport job run)

This branch was successfully deployed

18 active deployments
Preview – workflow-swc-playground — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-nuxt-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workflow-docs — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – example-nextjs-workflow-webpack — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – example-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-sveltekit-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workflow-tarballs — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-vite-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-astro-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-tanstack-start-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-fastify-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-nitro-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-nestjs-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-express-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-hono-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workflow-web — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – example-nextjs-workflow-turbopack — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
Preview – workbench-python-workflow — 165c0297 Deployed Sep 10, 2026 by vercel[bot]
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.

3 participants