Skip to content

[ENG-3643] Report where the app is published, on the push that carries the credential - #177

Draft
mariojgt wants to merge 1 commit into
mainfrom
mariot/eng-3643-report-the-site-address
Draft

[ENG-3643] Report where the app is published, on the push that carries the credential#177
mariojgt wants to merge 1 commit into
mainfrom
mariot/eng-3643-report-the-site-address

Conversation

@mariojgt

Copy link
Copy Markdown
Contributor

Companion to patchstack/saas#1357, which stops the widget beacon from writing a site's identity.

Why this repo has to change

Patchstack provisions a site from the first manifest. A manifest posted from someone's laptop carries no address, so the site is created with a placeholder host. That address is not decoration — it is what Patchstack fetches to check the published page still carries what was scanned, and it is what the dashboard shows.

Until now the only thing that ever replaced it was the disclosure widget's beacon: a request with no credential, naming the site with a UUID printed in the page it runs in. So a site's address was writable by anyone who could load the site. Patchstack has stopped accepting it from there. This is what replaces it — the manifest push, which authenticates as the site.

What it does

scan sends url when it can know one:

source when
url in .patchstackrc.json, or PATCHSTACK_SITE_URL always wins — a person naming their own site outranks any inference
VERCEL_PROJECT_PRODUCTION_URL VERCEL_ENV=production
URL NETLIFY=true and CONTEXT=production
RENDER_EXTERNAL_URL RENDER=true, not a PR preview
RAILWAY_PUBLIC_DOMAIN RAILWAY_ENVIRONMENT_NAME=production

Preview and branch deployments are excluded on purpose. The address is adopted once and then belongs to the site, so a per-deployment URL would freeze the site at whatever preview got there first — which is also why VERCEL_URL is deliberately not read. Cloudflare Pages publishes no way to tell a production deployment from a branch one, so it configures url explicitly rather than being guessed at.

Hosts that could not be a published site — localhost, a private network, the placeholder suffix itself — are dropped, and the field is omitted rather than guessed when nothing qualifies. The server only ever applies it over a placeholder; it never re-points a site whose address is real.

The CLI prints the address it reported. It is the one value in the payload someone might disagree with, and a site pointing at the wrong place is otherwise a silent wrong answer in their dashboard.

The docs had to change with it

They said scan sends no environment variable value at all. That is no longer true, and an inaccuracy here is read by installing agents as misrepresentation. README.md and AGENT-INSTALL.md now name the four variables that are read, say the field is otherwise absent, and say what the server does with it.

AGENT-INSTALL.md is a gated artifact. node field-test/run.mjs --persona hostile --rounds 3 should run against a published build before this ships — the fixture installs the published package, so it cannot exercise this change end-to-end until then.

Tests

New tests/site-url.test.ts (16) covers normalisation and every detector, including the negatives that matter: a Vercel preview, a Netlify deploy-preview, a Render PR build, a bare URL with no Netlify marker beside it, and Cloudflare Pages. tests/config.test.ts covers precedence, and tests/client.test.ts asserts the field is sent when known and absent — not null — when not.

1595 pass. One suite fails to collect locally because fastify is not installed; it fails identically on main.

Ref ENG-3643

🤖 Generated with Claude Code

…ntial

A site provisioned by a scan from someone's laptop has no address, so Patchstack
stores a placeholder. That address is not decoration: it is what Patchstack
fetches to check the published page still carries what was scanned, and it is
what the dashboard shows.

Until now the only thing that ever filled it in was the disclosure widget's
beacon, which runs in a page any visitor can load and carries no credential — so
a site's address was writable by anyone who could read its UUID from the page
source. Patchstack has stopped accepting it from there, which leaves this: the
manifest push, which authenticates as the site.

So `scan` now sends `url` when it can know one. `url` in `.patchstackrc.json`
(or `PATCHSTACK_SITE_URL`) wins, since a person naming their own site outranks
any inference. Otherwise it reads the single variable a host publishes to name
its own PRODUCTION url — and only that one. Preview and branch deployments are
excluded on purpose: the address is adopted once and then belongs to the site,
so a per-deployment URL would freeze the site at whatever preview got there
first. Cloudflare Pages publishes no way to tell a production deployment from a
branch one, so it is left to configure `url` explicitly rather than guessed at.

Hosts that cannot be a published site — localhost, a private network, the
placeholder suffix itself — are dropped, and the field is omitted rather than
guessed when nothing qualifies. The CLI prints the address it reported, because
this is the one value in the payload someone might disagree with.

The shipped docs had to change with it: they said `scan` sends no environment
variable value at all, and that is no longer true. They now name the four
variables that are read and say the field is otherwise absent. AGENT-INSTALL.md
is a gated artifact — `node field-test/run.mjs --persona hostile --rounds 3`
should run against a published build before this ships.

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

coderbuds Bot commented Aug 26, 2026

Copy link
Copy Markdown

Introduces site URL reporting with clear logic and robust edge-case tests.

��� Quality: 94% Elite · 📦 Size: Large — consider splitting if possible

🛡️ Standards: no pre-flight fit check ran for this change — wire assess-change-fit into your coding agents to catch size before opening.

📈 This month: Your 81st PR — above team average · Averaging Excellent

See how your team is trending →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant