withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
45.81k stars 2.41k forks source link

Stale _astroActionPayload cookie breaks all SSR pages #12063

Open mlafeldt opened 4 days ago

mlafeldt commented 4 days ago

Astro Info

Astro                    v4.15.9
Node                     v22.8.0
System                   macOS (arm64)
Package Manager          bun
Output                   hybrid
Adapter                  none
Integrations             none

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

As the title says, returning NOT_FOUND from actions breaks SSR when you have a static 404 page.

This happens because the _astroActionPayload cookie never gets deleted in that case (I guess because there's no server). As a result, all SSR routes will return the 404 page until you manually clear that cookie.

See https://github.com/mlafeldt/extraterrestrial-equator for a small repo.

What's the expected result?

Reloading SSR routes should work even with a static 404 page.

Link to Minimal Reproducible Example

https://github.com/mlafeldt/extraterrestrial-equator

Participation

mlafeldt commented 4 days ago

Another interesting fact: With the Cloudflare adapter, server-rendering 404.astro via export const prerender = false will not fix the problem. However, doing the same with the default Node adapter will fix it (see repro). 🤔

mlafeldt commented 4 days ago

This can happen with other status codes too.

One of my apps is stuck/broken with a 500 error because the _astroActionPayload cookie contains this after one action returned a 500 (and Astro won't delete the cookie):

{"actionName":"user.deleteUser","actionResult":{"type":"error","status":500,"contentType":"application/json","body":"{\"type\":\"AstroActionError\",\"code\":\"INTERNAL_SERVER_ERROR\",\"status\":500,\"message\":\"User not found\"}"}}

Using a single cookie for all things SSR without scoping seems problematic.

mlafeldt commented 4 days ago

In fact, you can break all SSR routes of an Astro site by doing this:

document.cookie = "_astroActionPayload=boom"

This will even affect routes that don't use Astro Actions at all, which shows a possible unintended consequence of the current design.

michaelpayne02 commented 2 days ago

I encountered this when creating a repro for #11675 (and #11973). This error also occurs if the action call fails schema validation regardless if the handler returns an error code or not.

https://stackblitz.com/edit/github-g63nyt-zhjdjc?file=README.md

In 14.5.9 this is the stack trace

  Stack trace:
    at JSON.parse (<anonymous>)
    at eval (/home/projects/github-g63nyt-zhjdjc/node_modules/astro/dist/actions/runtime/middleware.js:27:103)
    at eval (/home/projects/github-g63nyt-zhjdjc/node_modules/astro/dist/core/middleware/sequence.js:21:12)
    at eval (file:///home/projects/github-g63nyt-zhjdjc/node_modules/astro/dist/core/middleware/sequence.js:58:18)
    at applyHandle (file:///home/projects/github-g63nyt-zhjdjc/node_modules/astro/dist/core/middleware/sequence.js:33:22)

After #12016 the result is base64-encoded for size savings and parsing still throws

01:07:00 [ERROR] Invalid character
  Stack trace:
    at decodeBase64_internal (file:///home/projects/github-g63nyt-zhjdjc/node_modules/@oslojs/encoding/dist/base64.js:90:23)
    at eval (/home/projects/github-g63nyt-zhjdjc/node_modules/astro/dist/actions/runtime/middleware.js:33:75)
    at eval (/home/projects/github-g63nyt-zhjdjc/node_modules/astro/dist/core/middleware/sequence.js:21:12)
    at eval (file:///home/projects/github-g63nyt-zhjdjc/node_modules/astro/dist/core/middleware/sequence.js:58:18)
    at applyHandle (file:///home/projects/github-g63nyt-zhjdjc/node_modules/astro/dist/core/middleware/sequence.js:33:22)

So two things:

  1. Somehow the cookie is not actually being removed on the server when set to deleted in the middleware chain and is leaking to the client.
  2. Because the parsing errors are server-side, we should be able to redirect to the correct page using the Referrer header with the payload cleared