Skip to content

Keep older clients and plain-HTTP deployments working - #2121

Merged
ppXD merged 1 commit into
mainfrom
fix/keep-older-clients-and-plain-http-deployments-working
Oct 18, 2026
Merged

ppXD merged 1 commit into
mainfrom
fix/keep-older-clients-and-plain-http-deployments-working

Conversation

@ppXD

@ppXD ppXD commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Issue the HttpOnly session cookie from sign-in, change-password and invitation accept only when the request carries X-CodeSpace-Csrf, which the SPA sends on every request. A client that predates the cookie (an older SPA bundle, an API client, the CLI) cannot clear one, so it never gets one. It keeps the session in the response body, as before. POST /api/auth/session still sets the cookie unconditionally.
  • Bring back accountExists on the invitation preview as an [Obsolete] alias of forSignedInAccount for one release. The API is always replaced before the SPA, so the previous invite page reads the old name throughout a rollout. The alias is derived from the new field, so it describes the caller and is not the old account-existence oracle.
  • Allow http: in the SPA's img-src, so a self-managed provider on a plain-HTTP deployment can show avatars and README images. img-src does not protect the session: https: already admits every host, and script-src, connect-src and form-action are unchanged.
  • Write the nginx include init.sh generates under /tmp/codespace instead of /etc/nginx/codespace, so generating it needs nothing under /etc to be writable. This is the only write moved. The image does not yet start with a read-only root filesystem: dist/env.js is still substituted in place, npx keeps a cache, and nginx writes its pid file and cache directories.
  • A page past MaxPage on the pull request list, the issue list and git.list_prs is now an empty page and the provider is not asked, instead of page 1000 again for a client that keeps paging. The MaxPage documentation on both queries says so.
  • git.list_prs reads a number by its value. A number too large for an Int32 (3000000000) was taken for a missing page and read as page 1. It now counts as past the last page, or as the ceiling for a page size. A number written as 1001.0 or 1.001e3 is the whole number it says. A page size of 3000000000 is now 100, where it used to be the default of 30, and a fractional value is read at its whole part.

Test plan

  • Unit: GitListPullRequestsNodeTests (36), covering overflow, fraction and exponent spellings, and the ceilings; session cookie and invitation preview wire tests; both list handlers. Full unit suite 13862 passed, 1 skipped.
  • Integration: AgentToolReadBoundsFlowTests (9) through the real MCP handler, the pull request service and the GitLab provider against a loopback GitLab, including pages 2147483648 and 3000000000.
  • E2E: sign-in, change-password and invitation accept with and without the CSRF header; invitation preview wire shape; the Pulls and Issues lists past the last page, and a page that does not fit an int, which binding refuses with a 400 (51 tests).
  • Frontend (Node 22): static-hosting.test.ts and request.test.ts (58 tests), pnpm lint (0 errors), tsc --noEmit -p tsconfig.app.json.
  • CI: frontend/docker/e2e/run.sh (spa-image build and run, checking the include at /tmp/codespace/api-origin.conf and nothing left under /etc/nginx/codespace). It was not run locally because Docker was unavailable.

Four places where the session-cookie, invitation, CSP and paging
hardening broke something that worked before.

Session cookie: sign-in, change-password and invitation accept now set
the HttpOnly cookie only when the request carries X-CodeSpace-Csrf,
which the SPA sends on every request. A client that predates the cookie
(an older SPA bundle, an API client, the CLI) cannot clear one: only the
API can, and the older bundle's sign-out empties its own storage. Handed
one anyway, it held a 24-hour session it could never end. It keeps the
session in the body, as before. POST /api/auth/session still sets the
cookie unconditionally, since asking for it is that endpoint's whole job.

Invitation preview: bring back accountExists as an [Obsolete] alias of
forSignedInAccount for one release. The API is always replaced before
the SPA, so the previous invite page reads the old name throughout every
rollout, and without it asked a signed-in invitee to create an account.
The alias is derived from the new field, so it states the same fact
about the caller and not the old existence oracle.

SPA image: allow http: in the CSP's img-src, because a self-managed
provider on a plain-HTTP deployment serves avatars and README images
over http, and img-src does not protect the session (https: already
admits every host). init.sh writes its nginx include under
/tmp/codespace instead of /etc/nginx/codespace, so that step needs
nothing under /etc to be writable. That is the one write moved, not a
read-only root filesystem: dist/env.js is still substituted in place,
npx keeps a cache, and nginx writes its pid file and cache directories.

Paging: a page past MaxPage was clamped to MaxPage, so a client that
kept paging on received page 1000 again for as long as it asked. The
pull request and issue list handlers and the git.list_prs node now
answer an empty page and ask the provider nothing, and the MaxPage
documentation on both queries says so. The node reads a number by its
value: one too large for an Int32 (3000000000) had been taken for a
missing page and read as page 1, which is the same wrong-rows answer
for a page far on; it now counts as past the last page, or as the
ceiling for a page size. A number written as 1001.0 or 1.001e3 is the
whole number it says.
@ppXD
ppXD changed the base branch from fix/never-lose-a-rotated-oauth-pair to main October 18, 2026 11:21
@ppXD
ppXD force-pushed the fix/keep-older-clients-and-plain-http-deployments-working branch from 2d598a0 to 0f96f74 Compare October 18, 2026 11:21
@ppXD
ppXD merged commit bde1a69 into main Oct 18, 2026
8 checks passed
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.

1 participant