@patchstack/connect 0.5.12 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT-INSTALL.md +60 -32
- package/README.md +19 -19
- package/dist/cli.js +1275 -642
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +16 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +16 -9
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/demo-rules.json +2 -2
- package/dist/protect.cjs +2132 -915
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +135 -17
- package/dist/protect.d.ts +135 -17
- package/dist/protect.edge.js +2195 -985
- package/dist/protect.edge.js.map +3 -3
- package/dist/protect.js +2198 -988
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-47HJRCDX.js → refresh-manifest-DZQOEO3Q.js} +17 -10
- package/dist/refresh-manifest-DZQOEO3Q.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-47HJRCDX.js.map +0 -1
package/AGENT-INSTALL.md
CHANGED
|
@@ -8,7 +8,7 @@ Use the package setup flow below for an existing JS/Node application. Work in it
|
|
|
8
8
|
|
|
9
9
|
### Plain HTML sites
|
|
10
10
|
|
|
11
|
-
For a standalone site made of HTML, CSS, and browser JavaScript, with no package-managed application or server request handler, use the
|
|
11
|
+
For a standalone site made of HTML, CSS, and browser JavaScript, with no package-managed application or server request handler, use the Patchstack Connector directly. Do not create `package.json`, install a framework, invent build hooks, or add a server just to run Connect. `setup` requires an existing `package.json`; it is not a standalone HTML installer.
|
|
12
12
|
|
|
13
13
|
1. Use the public site UUID or widget snippet for the correct site in the Patchstack dashboard. An existing `.patchstackrc.json` can also supply `siteUuid`. If neither is available, ask the user for the site's public UUID or dashboard-provided snippet before editing the page. Never invent a UUID or use a claim token or API key as the widget identifier.
|
|
14
14
|
2. Add one widget tag before `</body>` in the page or shared layout. Preserve an existing correct tag. For a page published directly without a build step, disable the widget's build-mode onboarding with `data-build-mode="false"`:
|
|
@@ -18,16 +18,16 @@ For a standalone site made of HTML, CSS, and browser JavaScript, with no package
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Replace `YOUR_SITE_UUID` with the real public site UUID before saving. Keep credentials out of the page. The [public widget reference](https://cdn.patchstack.com/llm.html) documents this embed and its options.
|
|
21
|
-
3. Verify the saved tag uses the correct UUID. If a browser preview is available, reload it and check
|
|
21
|
+
3. Verify the saved tag uses the correct UUID. If a browser preview is available, reload it and check that the Patchstack Connector appears (a "Connect this website" panel until the site is claimed); otherwise tell the user that the browser check is pending. Do not submit a vulnerability report as an installation test. Save the HTML change and remind the user to publish it when ready; do not deploy it yourself.
|
|
22
22
|
|
|
23
|
-
Report this as **
|
|
23
|
+
Report this as **Patchstack Connector installed**, with any remaining preview or publishing step. This path does not inventory local JavaScript files or scripts loaded from a CDN, scan npm dependencies, or install runtime exploit protection. External APIs used by the page require their own server-side integration.
|
|
24
24
|
|
|
25
25
|
### JS/Node applications — the usual path
|
|
26
26
|
|
|
27
27
|
1. Check what is already done with `npx @patchstack/connect guide` (read-only). If the project is already provisioned, reuse it — see "Before you start — never install twice".
|
|
28
28
|
2. Install `@patchstack/connect` as a runtime dependency with the project's package manager.
|
|
29
29
|
3. Run `npx @patchstack/connect setup`. In a hosted builder, scope `PATCHSTACK_ENVIRONMENT=sandbox` to that command — see "Automated setup".
|
|
30
|
-
4. Finish any
|
|
30
|
+
4. Finish any `✘` line under `Missing` in the report at the end of `setup`. "Automated setup" names each one.
|
|
31
31
|
5. Tell the person the dashboard link, which parts are active and which are not, to refresh their preview, and to deploy when they are ready.
|
|
32
32
|
|
|
33
33
|
If your tool will not run the command, see "When your tool will not run this CLI". The sections below describe what each command reads, writes, and sends.
|
|
@@ -65,7 +65,7 @@ Only `map` analyses your source, and only `map --upload` sends anything derived
|
|
|
65
65
|
- **The address is the one your visitors use, and, apart from the tool's own `PATCHSTACK_*` settings, it is the only env var value read.** A site provisioned by a scan from a developer machine has no address, so the dashboard shows a placeholder and Patchstack cannot check that the published page still carries what was scanned. `scan` therefore sends `url` when — and only when — it can know it: `url` in `.patchstackrc.json` or `PATCHSTACK_SITE_URL` if you set one, otherwise the single variable a host publishes to name its own **production** URL (`VERCEL_PROJECT_PRODUCTION_URL` on a Vercel production deployment, Netlify's `URL` in the production context, `RENDER_EXTERNAL_URL`, `RAILWAY_PUBLIC_DOMAIN` in a production environment). Preview and branch deployments are excluded, as are hosts that publish no production signal. An address that is not how the public reaches a website is dropped: any IP address (in either family, however it is written), any single-label host such as `localhost` or `production`, and the reserved suffixes (`.local`, `.internal`, `.test`, `.invalid`, `.home.arpa`, …). A `url` you set explicitly that fails those checks is refused with an error rather than replaced by a guess. When nothing qualifies, `url` is omitted from the payload rather than guessed. Patchstack only ever applies it to a site that still has no address; it never re-points a site whose address is already real.
|
|
66
66
|
- **The name is read from your project, never from the host environment.** `name` in `.patchstackrc.json` (or `PATCHSTACK_SITE_NAME`) if you set one; otherwise the `<title>` of the project's root `index.html` (`public/index.html` if there is no root one), read from the file as text — a title your app sets from script is not seen; otherwise the `name` in `package.json`, unless it is a template placeholder such as `vite_react_shadcn_ts`. It is omitted when nothing qualifies, and it only ever fills in a site that has no name yet — a name set in the dashboard is never replaced.
|
|
67
67
|
- **Only `map` analyses source files.** It parses your server source to report your app's attack surface. It runs only when you invoke it and prints to stdout. It transmits nothing unless you explicitly pass `--upload`, which sends that description of your app's structure to your own site's Patchstack endpoint — never source code, and never without that flag. A `prebuild` scan reads the scaffolded guard and rules JSON only to identify and clear the reserved map stamp; it does not analyse them or transmit their contents.
|
|
68
|
-
- **`scan` makes up to three source edits:** the
|
|
68
|
+
- **`scan` makes up to three source edits:** the Patchstack Connector's `<script>` tag, the production marker, and — during `prebuild` only — removal of a previous `_patchstack.build_id` from the existing guard rules file. None runs on `--dry-run`; all are idempotent. `"widget": false` disables the first two, while stale-stamp removal is independent because it prevents old coordinates being attributed to a new build.
|
|
69
69
|
- The **widget tag** goes in the root HTML shell — the first of `index.html`, `public/index.html`, or `src/app.html` that exists — and only after a successful post, because it carries the site UUID.
|
|
70
70
|
- The **production marker** goes in a root shell that is JSX rather than HTML (e.g. `src/routes/__root.tsx`, `app/layout.tsx`), inside a `{/* #region patchstack */}` block placed above the widget tag. It is written *before* the post: it carries no site UUID and needs no network, and build scripts commonly chain `patchstack-connect scan || true`, where waiting on the server would mean an offline build silently ships without the flag. The marker is guarded by the framework's own production expression (`import.meta.env.PROD`, or `process.env.NODE_ENV === 'production'`), so it is inert in dev and preview builds. Without it a server-rendered site has no built HTML for `mark-build` to stamp, and the widget treats the published site as build mode. `mark-build` writes to build output only (`dist/`, `build/`, `out/`, `.output/public`), never to source. `guide`, `status`, and `init` write nothing except `init`'s own `.patchstackrc.json`.
|
|
71
71
|
- **`setup` runs `scan`, then `protect`, then edits `package.json` scripts:** provisioning happens first so the runtime guard can bake the real site UUID. It verifies the resulting framework seam, preserves existing commands, adds `scan` after dependency installs and before builds, adds `mark-build` after builds, and uses a direct build chain for Bun. It never runs the project build. If the widget or runtime guard needs a framework-specific manual merge, it prints the exact remaining step instead of overwriting user code.
|
|
@@ -74,18 +74,18 @@ Only `map` analyses your source, and only `map --upload` sends anything derived
|
|
|
74
74
|
- **`map` is local unless you pass `--upload`.** It walks the project's server source (skipping `node_modules`, build output and dot-directories; it does not follow symlinks out of the project unless you pass `--follow-symlinks`), parses it with the project's **own** `typescript`, and prints JSON describing the attack surface: entry points, the inputs each reads, the sinks they can reach (database / file system / process / outbound HTTP) with the npm package behind each, and evidence-backed input→sink flows, each labelled with how the link was established — from an exact read at the sink's own call site, through a transformed or cross-module link, down to the two being present together with no proven link. Static analysis is best-effort, so the output reports the *detected* surface with coverage counters — not a completeness guarantee. Without `--upload` it writes nothing except the file named by `--out`, and it is never invoked by `scan`, `setup`, `guide`, `protect`, or `mark-build`.
|
|
75
75
|
- **`map --upload` is the only command that sends a description of your source.** (The runtime guard can also report rule detections, which carry route paths and parameter names — see "Runtime guard reporting" below.) It POSTs the same JSON document to `monitor/pulse/input-map/<your site uuid>` so Patchstack can pin protection rules to your app's own parameter names instead of guessing them. During a pre-bundle build hook it hashes the policy-relevant document (all fields except analyser timing and memory observations), writes the SHA-256 value as `_patchstack.build_id` in the existing rules file imported by the scaffolded guard, and sends the same value as `build_id`. Outside that lifecycle it sends no identity and changes no file, so any generated scoped rule remains detect-only. **No source code, no file contents, no environment variable values.** A map with no recognised entry points is still uploaded because its import inventory and coverage limits are evidence; a failure to reach Patchstack is reported and ignored rather than failing your build. Omit the flag and the command stays entirely local.
|
|
76
76
|
- **`demo-guide node-serialize` is the read-only companion.** It checks the Host-created site configuration and vulnerable lockfile entry, explains the complete local prepare/run/restart/prove/cleanup sequence, and prints the next exact command. It does not require a deployment and does not change files or contact Patchstack.
|
|
77
|
-
- Patchstack is not WordPress-only.
|
|
77
|
+
- Patchstack is not WordPress-only. Connect monitors any JS/Node project — Vite, Next.js, plain vanilla JS, anything with a lockfile.
|
|
78
78
|
|
|
79
79
|
## Before you start — never install twice
|
|
80
80
|
|
|
81
|
-
- `npx @patchstack/connect guide` prints a read-only live checklist
|
|
81
|
+
- `npx @patchstack/connect guide` prints a read-only live checklist of the four steps (install, connect, sync, deploy) and the one next step, with any missing build hook, widget tag or runtime protection wiring listed under `Missing`. Add `--verbose` to `guide`, `scan` or `setup` for the technical detail (site UUID, endpoint, environment and what decided it, checksum, files written, the exact package.json lines and the runtime protection checks).
|
|
82
82
|
- If `.patchstackrc.json` contains a `siteUuid` key, the project is already provisioned. Reuse that UUID; run `npx @patchstack/connect status` to re-print it and the dashboard URL. **Do not delete the file and provision a second site.** (A `.patchstackrc.json` with other keys — e.g. an `endpoint` override — but no `siteUuid` is *not* provisioned yet; scan normally.)
|
|
83
83
|
- If `@patchstack/connect` is already in `dependencies`, skip the install command. If it is only in `devDependencies`, move it with the matching package manager so production runtimes that prune dev dependencies can load the generated guard.
|
|
84
84
|
- If the widget script tag (`cdn.patchstack.com/patchstack-widget.js`) is already in the layout, don't add a second one — `scan` also respects an existing tag: it updates its own managed tag in place and leaves a manual one untouched.
|
|
85
85
|
|
|
86
86
|
## Talk to the person while you work
|
|
87
87
|
|
|
88
|
-
The person who asked for this usually did not write the project and has never installed a package. Installs and builds here can run for several minutes, and a silent wall of build output is the worst part of their first experience of Patchstack. Narrate it.
|
|
88
|
+
The person who asked for this usually did not write the project and has never installed a package. Installs and builds here can run for several minutes, and a silent wall of build output is the worst part of their first experience of Patchstack. Narrate it, one or two sentences at a time.
|
|
89
89
|
|
|
90
90
|
- **Say what you are about to do, before the slow command.** One sentence, no command names: "I'm adding Patchstack to your site — this takes a few minutes because it has to reinstall everything the site is built from."
|
|
91
91
|
- **Say what the wait is.** "It's downloading the pieces your site is built out of. Nothing is broken; this is just slow."
|
|
@@ -97,6 +97,17 @@ The person who asked for this usually did not write the project and has never in
|
|
|
97
97
|
|
|
98
98
|
This is a request, not a mechanism: nothing in the install depends on it. Do it anyway — the wait is the only moment where the person is paying full attention.
|
|
99
99
|
|
|
100
|
+
### The message you end on
|
|
101
|
+
|
|
102
|
+
When setup is finished, the person reads one short message, not a transcript of the install. Keep it to about ten lines, in this order:
|
|
103
|
+
|
|
104
|
+
1. **One sentence on the result.** "Patchstack is now watching your site." If something did not finish, say what, in one line.
|
|
105
|
+
2. **The four steps**, copied from the end of the `setup` output with their `✔` and `✘`.
|
|
106
|
+
3. **The next step**, from the `Next:` line, in plain words, with its link or command.
|
|
107
|
+
4. **Refresh and deploy.** Refresh the preview to see the Patchstack Connector, and deploy when ready.
|
|
108
|
+
|
|
109
|
+
Leave out the files you changed, the commands you ran, settings, and anything that worked as expected. If the person asks for the detail, give it then. `--verbose` prints it.
|
|
110
|
+
|
|
100
111
|
## Automated setup
|
|
101
112
|
|
|
102
113
|
1. **Install** (skip if already present), matching the project's package manager:
|
|
@@ -133,11 +144,13 @@ This is a request, not a mechanism: nothing in the install depends on it. Do it
|
|
|
133
144
|
|
|
134
145
|
**What runtime protection can report:** a positively identified static build reports runtime protection as not applicable. A bundler-only project, including plain Vite, can remain runtime unknown and receive a generic scaffold with incomplete wiring. Report that limitation; do not add an artificial server merely to make the check pass, and never describe a widget or an unwired scaffold as runtime protection.
|
|
135
146
|
|
|
136
|
-
**A
|
|
147
|
+
**A `✘` line under `Missing` is yours to finish, not a result to report.** `setup` applies what it can apply safely and prints the exact edit (or the command that prints it) for anything it would have had to overwrite user code to do. They are: moving `@patchstack/connect` out of `devDependencies`, the widget tag in a root layout `setup` could not edit, the production marker on a server-rendered root, and wiring a generic guard into the server entry. The last three are steps 3 and 4 of "Manual setup" below; after the guard one, `npx @patchstack/connect protect --check` must exit 0.
|
|
148
|
+
|
|
149
|
+
**Read the report.** `setup` and `scan` start with `Done` (what this run did) and `Missing` (what is still missing, each with the one thing to do); `guide` shows `Missing` only. All three end on the same four steps — `Install the Patchstack connector`, `Connect project to Patchstack account`, `Sync and monitor in local environment`, `Deploy project to protect live app` — each marked `✔` (done) or `✘` (not yet), followed by the one next step. A part the project cannot carry (no build script, no request path for runtime protection) is simply not listed. The CLI does not store whether the site has an owner, so `Connect` stays `✘` until a scan hears otherwise from Patchstack; do not report the site as connected from that line alone, and do not report it as unconnected once the person has signed in. In the message you end on, name only the parts that are not active — dependency scans, the Patchstack Connector, the build hooks, runtime protection — and why, one line each; the four steps already say what is done.
|
|
137
150
|
|
|
138
|
-
**
|
|
151
|
+
**The widget is part of the install.** It is on by default; add it without asking the person whether to. Honour `"widget": false` in `.patchstackrc.json` only when the person set it themselves. If it is there and they did not ask for it, remove it, run `setup` again, and tell them the widget is back on.
|
|
139
152
|
|
|
140
|
-
**Finish by telling the user to refresh their preview.** The widget loads with the page, so a preview that was already open still shows the HTML from before setup — the widget is missing there until it reloads. Tell them what to expect after the refresh: a site that is not yet connected to an account shows the "Connect this website" panel
|
|
153
|
+
**Finish by telling the user to refresh their preview.** The widget loads with the page, so a preview that was already open still shows the HTML from before setup — the widget is missing there until it reloads. Tell them what to expect after the refresh: a site that is not yet connected to an account shows the "Connect this website" panel. A freshly set up site is unclaimed unless setup ran with a claim token. Nothing in the CLI can reach the user's browser, so relaying this is your job. Phrase it as a check rather than a required step: a builder that hot reloads, or a preview server you restarted, may have refreshed it already.
|
|
141
154
|
|
|
142
155
|
**Then tell them to deploy.** Setup changes source files, and the deployed site keeps serving its previous build until the next deploy — so visitors get no widget, and on a server-rendered root no production marker, until the user deploys (or hits Publish) again. Say it as a reminder; do not deploy anything yourself.
|
|
143
156
|
|
|
@@ -155,8 +168,8 @@ Handle it in this order:
|
|
|
155
168
|
and the source tree as they were.
|
|
156
169
|
|
|
157
170
|
2. **Hand the person the ways forward, with the exact text.** Say what the command does in plain words —
|
|
158
|
-
it registers the site with Patchstack, writes two small config files, adds the
|
|
159
|
-
|
|
171
|
+
it registers the site with Patchstack, writes two small config files, adds the Patchstack widget to the
|
|
172
|
+
page, and adds the protection files and build steps described above — then give them:
|
|
160
173
|
|
|
161
174
|
- **Run it themselves, in this session.** In Claude Code a line that starts with `!` runs in their shell
|
|
162
175
|
and its output lands in the conversation: `! npx @patchstack/connect setup`. Other tools have a
|
|
@@ -187,9 +200,9 @@ Handle it in this order:
|
|
|
187
200
|
`npx --yes patchstack-connect setup` are different texts, and the rules above do not cover them. On a
|
|
188
201
|
developer's machine the sandbox label is not needed anyway: a scan there reports `local` on its own.
|
|
189
202
|
|
|
190
|
-
4. **Resume from the output.** `setup` prints the same checklist,
|
|
203
|
+
4. **Resume from the output.** `setup` prints the same checklist, next step and dashboard link whoever
|
|
191
204
|
ran it, and re-running it changes nothing that is already done. If the person ran it, relay the
|
|
192
|
-
|
|
205
|
+
checklist and the next step from their output as they are. If your tool still will not run
|
|
193
206
|
`guide` or `status` for you, verify from the files instead of guessing: `siteUuid` in
|
|
194
207
|
`.patchstackrc.json` means the site is provisioned; `patchstack-connect scan` and
|
|
195
208
|
`patchstack-connect mark-build` in the `package.json` scripts mean the hooks are wired;
|
|
@@ -203,13 +216,13 @@ Handle it in this order:
|
|
|
203
216
|
|
|
204
217
|
## Manual setup
|
|
205
218
|
|
|
206
|
-
1. **First scan** — provisions a Patchstack site automatically, writes the UUID to `.patchstackrc.json`, and installs the
|
|
219
|
+
1. **First scan** — provisions a Patchstack site automatically, writes the UUID to `.patchstackrc.json`, and installs the Patchstack Connector's `<script>` tag into the root HTML shell (`index.html`, `public/index.html`, or `src/app.html`) when one exists — or, when the root shell is JSX, the production marker instead. No signup, dashboard step, or UUID is needed up front:
|
|
207
220
|
|
|
208
221
|
```
|
|
209
222
|
npx @patchstack/connect scan
|
|
210
223
|
```
|
|
211
224
|
|
|
212
|
-
It prints a dashboard link but never opens it. Open that link in a browser to view reports. It also prints what it did about the widget — if it added the tag, reload the preview and confirm the widget appears
|
|
225
|
+
It prints a dashboard link but never opens it. Open that link in a browser to view reports. It also prints what it did about the widget — if it added the tag, reload the preview and confirm the widget appears (the "Connect this website" panel while the site is unclaimed).
|
|
213
226
|
|
|
214
227
|
2. **Wire builds** in `package.json`:
|
|
215
228
|
|
|
@@ -227,7 +240,7 @@ Handle it in this order:
|
|
|
227
240
|
|
|
228
241
|
**Bun-managed projects:** `bun run` does not execute npm-style `pre`/`post` scripts, so wire the build script directly instead: `"build": "patchstack-connect scan && <existing build command> && patchstack-connect mark-build"`.
|
|
229
242
|
|
|
230
|
-
3. **Verify the
|
|
243
|
+
3. **Verify the Patchstack Connector** — a floating control whose form follows the site's claim state: while the site is unclaimed it is a one-time "Connect this website" panel. It is part of the install and on by default; do not ask whether to add it. `scan` installs it automatically into a plain HTML shell **or a JSX root** (Next, Remix, React Router, TanStack Start, Gatsby), and `mark-build` carries it into built HTML. Only when `scan` reported that it found no editable shell at all — a root whose head mechanism is not a plain script tag, e.g. Nuxt's `useHead` or an Astro layout — add the one-liner it printed to the root layout yourself, just before `</body>` (never a JS entry point), reading `siteUuid` from `.patchstackrc.json`. On those same roots the widget also needs the production marker above the tag — `scan` adds it automatically to a JSX root, and prints it to paste when it finds no anchor. A server-rendered site without the marker serves the build-mode claim flow to its visitors:
|
|
231
244
|
|
|
232
245
|
```html
|
|
233
246
|
<script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer></script>
|
|
@@ -297,7 +310,7 @@ It is server-only. Never put it in the widget tag, client bundles, or public env
|
|
|
297
310
|
|
|
298
311
|
6. **Connect the site to a Patchstack account.** The site is monitored either way, but its vulnerability reports are only visible once it is attached to an account, and an unattached site stays claimable by anyone who loads the page — the site UUID ships in the HTML and claiming is first-come. Three routes reach the same place; tell the user all three and lead with the first, which needs no terminal and no copied URL:
|
|
299
312
|
|
|
300
|
-
1. **The widget's "Connect this website" panel**, already on the preview. While the site is unclaimed the widget serves this panel
|
|
313
|
+
1. **The widget's "Connect this website" panel**, already on the preview. While the site is unclaimed the widget serves this panel, and signing in there attaches the site. On a published build it is hidden from visitors; the owner reveals it by appending `#patchstack` (or `?patchstack`) to the live URL.
|
|
301
314
|
2. **The dashboard link** the scan printed — open it in a browser and sign in.
|
|
302
315
|
3. **`npx @patchstack/connect claim`** from the terminal, which prints a link to sign in with and then attaches the site.
|
|
303
316
|
|
|
@@ -457,8 +470,9 @@ requests are aborted, it starts nothing further, and it discards what it was hol
|
|
|
457
470
|
request to stop, not a guarantee — a transport that ignores it is detached rather than completed, so
|
|
458
471
|
"resolved" means the reporter is finished with it, and a runtime that kills the process still wins
|
|
459
472
|
regardless. Every detection event ends up delivered, refused or dropped and is reported in the health
|
|
460
|
-
counts
|
|
461
|
-
|
|
473
|
+
counts. The block log keeps the same kind of local counts: `protection.blockLogHealth()` returns how many
|
|
474
|
+
block records were accepted, delivered, failed or dropped, and how many are still queued. Those counts
|
|
475
|
+
carry no request data and stay in your process.
|
|
462
476
|
|
|
463
477
|
The client address is reported with its **provenance**, because an address is only as trustworthy as
|
|
464
478
|
whatever supplied it. `client_ip_source` is one of `runtime` (the address the transport observed),
|
|
@@ -466,7 +480,8 @@ whatever supplied it. `client_ip_source` is one of `runtime` (the address the tr
|
|
|
466
480
|
`unavailable`. When it is `unavailable` the `client_ip` field is **omitted entirely** rather than sent
|
|
467
481
|
empty, so a missing address cannot read as a failed lookup of a real one. A forwarded header is never
|
|
468
482
|
trusted implicitly: with no `trustedProxy` policy the address is whatever the transport observed, and in a
|
|
469
|
-
runtime that exposes no transport peer there is no address to report at all
|
|
483
|
+
runtime that exposes no transport peer there is no address to report at all unless your code supplies one
|
|
484
|
+
with `peerAddress` (below).
|
|
470
485
|
|
|
471
486
|
### Behaviour change: how the client address is determined
|
|
472
487
|
|
|
@@ -481,11 +496,17 @@ Two consequences if you are upgrading:
|
|
|
481
496
|
peer. **If your app runs behind a proxy or load balancer, addresses will now show as the proxy's**
|
|
482
497
|
until you declare your proxies with `trustedProxy` (below) — which affects attribution in reports and
|
|
483
498
|
any rule matching on `server.ip` or `REMOTE_ADDR`.
|
|
484
|
-
- **Fetch runtimes report no address
|
|
485
|
-
guard (Workers, Deno, Bun, edge) has nothing to observe, and no
|
|
486
|
-
|
|
499
|
+
- **Fetch runtimes report no address unless you supply the peer.** A WHATWG `Request` exposes no
|
|
500
|
+
transport peer, so a Fetch guard (Workers, Deno, Bun, edge) has nothing to observe on its own, and no
|
|
501
|
+
forwarded header is accepted in its place: `client_ip_source` is `unavailable` and no address is sent.
|
|
487
502
|
Earlier versions reported the forwarded header here, so an address-scoped rule that appeared to work on
|
|
488
|
-
such a runtime was matching a client-supplied value.
|
|
503
|
+
such a runtime was matching a client-supplied value. Where your runtime does know the peer, pass
|
|
504
|
+
`peerAddress: (request, ...handlerArgs) => string` — for example `(req, info) => info.remoteAddr.hostname`
|
|
505
|
+
on Deno, or `(req, server) => server.requestIP(req)?.address` on Bun. It receives the request and the
|
|
506
|
+
arguments your handler was called with (`fetchGuard()(request, ...args)` and
|
|
507
|
+
`screenResponse(response, request, ...args)` pass them on), and that address then counts as the
|
|
508
|
+
transport peer, including for `trustedProxy`. With `trustedProxy` set and no peer supplied, the guard
|
|
509
|
+
warns once, because the policy can never apply.
|
|
489
510
|
|
|
490
511
|
`trustedProxy` is the only way to make a forwarded header count. It takes the proxies you actually run —
|
|
491
512
|
`{ peers: ['10.0.0.0/8'] }`, or `{ hops: 1 }` to trust that many hops in from the peer, plus optional
|
|
@@ -574,8 +595,15 @@ carries no detections and would otherwise read as one. Those counts stay in your
|
|
|
574
595
|
sent to report them.
|
|
575
596
|
|
|
576
597
|
`protection.stop()` stops everything the guard has running in the background — the rule-refresh loop, the
|
|
577
|
-
block-log reporter, the detection reporter — and flushes what is buffered. `
|
|
578
|
-
|
|
598
|
+
block-log reporter, the detection reporter — and flushes what is buffered. With `egress: true` it also
|
|
599
|
+
removes this guard's outbound-request screening; once no guard in the process is screening, `fetch` and
|
|
600
|
+
`node:http`/`node:https` are restored. Call it on shutdown; it is safe to call twice.
|
|
601
|
+
`protection.stopRefresh()` stops only the rule refresh: the reporters and outbound-request screening keep
|
|
602
|
+
running.
|
|
603
|
+
|
|
604
|
+
When more than one guard in a process has `egress: true`, an outbound call is checked by each of them and
|
|
605
|
+
refused if any one refuses it. A host listed in one guard's `allowHosts` is still refused when another guard
|
|
606
|
+
refuses it.
|
|
579
607
|
|
|
580
608
|
Two more endpoints the package can call, for completeness:
|
|
581
609
|
|
|
@@ -591,7 +619,7 @@ Two more endpoints the package can call, for completeness:
|
|
|
591
619
|
- `npx @patchstack/connect status` re-prints the site UUID and dashboard URL, and checks whether the site still exists on Patchstack (`Site status: active / removed / could not be verified`).
|
|
592
620
|
- `npx @patchstack/connect protect --check` verifies from the source that the runtime guard is connected to the request path. It does not run the app.
|
|
593
621
|
- `npx @patchstack/connect protect --check --runtime` additionally **starts the app** on a loopback port and sends it one request, to establish that a request reaches the guard seam. Opt-in, and the only command that runs the application; exit `0`/`1`/`2` as described in step 4.
|
|
594
|
-
- Load the site in a browser — the widget should appear
|
|
622
|
+
- Load the site in a browser — the widget should appear, as the "Connect this website" panel while the site is unclaimed. Refresh a page that was already open before the tag was added: the widget only loads with the page.
|
|
595
623
|
- On the deployed site, the button appears only after a deploy that includes these source changes.
|
|
596
624
|
|
|
597
625
|
## Answering "is Patchstack installed?" / "is Patchstack removed?"
|
|
@@ -709,11 +737,11 @@ You cannot complete this alone. It is deliberately a human-in-the-loop step: sta
|
|
|
709
737
|
|
|
710
738
|
## Uninstalling
|
|
711
739
|
|
|
712
|
-
Remove only the pieces that are actually present — check for each first. If none are present, Patchstack isn't installed; report that and stop. If the user asked to remove only one piece (e.g. "just the
|
|
740
|
+
Remove only the pieces that are actually present — check for each first. If none are present, Patchstack isn't installed; report that and stop. If the user asked to remove only one piece (e.g. "just the Patchstack Connector"), remove only that piece.
|
|
713
741
|
|
|
714
742
|
1. **Read the site UUID from `.patchstackrc.json` before deleting anything.** It is the only local record of the provisioned site — report it to the user at the end so they can identify the site in their dashboard.
|
|
715
|
-
2. **Remove the
|
|
716
|
-
3. **Remove runtime protection before uninstalling the package.** Delete the
|
|
743
|
+
2. **Remove the Patchstack Connector snippets** from the layout/template: the `<script src="https://cdn.patchstack.com/patchstack-widget.js">` tag and any `PatchstackWidget.init(...)` call (which may live in a separate client component/plugin/effect). Afterwards, grep the repo for `patchstack-widget` and `PatchstackWidget` to confirm nothing remains.
|
|
744
|
+
3. **Remove runtime protection before uninstalling the package.** Delete the Connect-managed guard/rules files and remove only their managed imports, middleware registrations, tunnel code, and `#region patchstack…` blocks from the framework/server files. Preserve unrelated middleware and application code. Run `rg "patchstack|x-ps-target"` (or the available equivalent) afterwards and inspect every remaining source hit.
|
|
717
745
|
4. **Remove the hooks from `package.json` scripts.** If a hook was chained (e.g. `"postbuild": "existing-command && patchstack-connect mark-build"`), remove only the `patchstack-connect …` part and keep the rest; if removal leaves a script empty, delete the key.
|
|
718
746
|
5. **Signal Patchstack that the package is being removed**: run `npx @patchstack/connect uninstall` (while the package is still installed and `.patchstackrc.json` still exists). If the site was never claimed, this deletes its anonymous record on Patchstack; if the site is claimed, it is only flagged — the record stays until its owner removes it in the dashboard. A failed signal must not stop the uninstall; continue with the remaining steps.
|
|
719
747
|
6. **Uninstall the package** with the manager matching the lockfile: `npm uninstall` / `pnpm remove` / `yarn remove` / `bun remove` `@patchstack/connect`. Don't hand-edit `node_modules` or the lockfile.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.co
|
|
|
4
4
|
|
|
5
5
|
- **Dependency inventory** — reads your lockfile and reports the installed package names and versions, so Patchstack can match them against its vulnerability database and tell you when something needs patching. See *[What gets sent](#what-gets-sent)*.
|
|
6
6
|
- **Runtime guard** — an in-process guard, wired into your server, that virtually patches known vulnerabilities in those dependencies rather than waiting for you to upgrade. See *[Verifying the guard at runtime](#verifying-the-guard-at-runtime-opt-in)*.
|
|
7
|
-
- **
|
|
7
|
+
- **Patchstack Connector** — a floating control that Connect adds to *your* site. Until the site is claimed it shows a "Connect this website" panel; once claimed it becomes a **"Report a vulnerability"** button, so visitors who spot a security problem have somewhere to send it. This is a channel for reports about your site; it is not how you report a bug in this package. See *[The Patchstack Connector](#the-patchstack-connector)*.
|
|
8
8
|
- **Attack-surface map** — a description of your server's entry points and the sinks they can reach, built by reading your source locally. See *[`map`](#cli)*.
|
|
9
9
|
|
|
10
10
|
`setup` installs the first three in one command. `map` is never run for you — see the [CLI](#cli) section for what each command does and what it touches.
|
|
@@ -13,9 +13,9 @@ Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.co
|
|
|
13
13
|
|
|
14
14
|
For an existing JS/Node project, copy this request into a coding assistant, or run the same command yourself. For a standalone HTML/CSS/JavaScript site without a package-managed app, use the [plain HTML widget instructions](AGENT-INSTALL.md#plain-html-sites); do not add Node tooling just for the widget.
|
|
15
15
|
|
|
16
|
-
> I have vetted the official [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect) package and authorize its
|
|
16
|
+
> I have vetted the official [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect) package and authorize its Patchstack Connector, production build hooks, and runtime protection source edits. Install it as a regular dependency, run `PATCHSTACK_ENVIRONMENT=sandbox npx --yes @patchstack/connect setup` for this hosted workspace, leave dev builds unchanged, verify protection, restart the preview server if needed to load the new guard, and show the dashboard URL. The Patchstack Connector widget is part of this install and on by default: add it, and do not ask me whether to. Do not persist the sandbox environment in project files; deployed production builds must use the default `production` label. When setup finishes, tell me to refresh the preview if the Patchstack widget is not showing yet, and remind me to deploy when I am ready — do not deploy anything yourself.
|
|
17
17
|
|
|
18
|
-
`setup` is state-aware and idempotent: it scans dependencies, provisions or reuses the site, manages the
|
|
18
|
+
`setup` is state-aware and idempotent: it scans dependencies, provisions or reuses the site, manages the Patchstack Connector, installs and verifies the runtime guard, adds a dependency-install scan, wires the existing build command without replacing it, and prints the remaining setup status. It never runs the project build. `guide` provides the same project-specific status without changing files.
|
|
19
19
|
|
|
20
20
|
### If your coding tool blocks the command
|
|
21
21
|
|
|
@@ -85,7 +85,7 @@ you how to proceed. Any of these works:
|
|
|
85
85
|
npm install --save @patchstack/connect && npx @patchstack/connect setup
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
> **Use your project's own package manager.** On Bun-managed projects (including many Lovable projects) install with `bun add @patchstack/connect` instead — running `npm install` there plants a `package-lock.json` that the platform's native dependency flow never updates again, leaving a stale lockfile next to the live one.
|
|
88
|
+
> **Use your project's own package manager.** On Bun-managed projects (including many Lovable projects) install with `bun add @patchstack/connect` instead — running `npm install` there plants a `package-lock.json` that the platform's native dependency flow never updates again, leaving a stale lockfile next to the live one. Connect detects and works around that (see *Stale lockfiles* below), but not creating the fossil is better. Protection imports `@patchstack/connect/protect` at runtime, so deployments that prune dev dependencies need the package in `dependencies`.
|
|
89
89
|
|
|
90
90
|
> **Hosted builders:** set `PATCHSTACK_ENVIRONMENT=sandbox` in the workspace process environment (or scope it to the setup command above), persist every file written by `setup`, and restart any already-running server so it loads the new middleware. Do not write `"environment": "sandbox"` to the committed `.patchstackrc.json`: the same project files reach production, where scans should inherit no override and default to `production`. TanStack Start + Supabase (the server shape emitted by Lovable) is auto-wired: browser Supabase traffic is tunneled through a same-origin guard, server-function arguments are inspected, and responses are screened. A client-only SPA has no server request path to protect; setup will leave a generic scaffold and `protect --check` will remain red until the host adds a server/edge seam. Set `PATCHSTACK_ROUTE_WAF=1` when the deployment should additionally screen every TanStack route request.
|
|
91
91
|
|
|
@@ -94,14 +94,14 @@ That's it. `setup`:
|
|
|
94
94
|
1. Reads your lockfile (see *Supported lockfiles*).
|
|
95
95
|
2. POSTs the package list to Patchstack with **no** UUID.
|
|
96
96
|
3. Patchstack provisions a fresh site and returns its UUID.
|
|
97
|
-
4.
|
|
98
|
-
5.
|
|
97
|
+
4. Connect writes the UUID to `.patchstackrc.json` so the next `scan` targets the same site.
|
|
98
|
+
5. Connect installs the Patchstack Connector's `<script>` tag into your root HTML shell (see *The Patchstack Connector* below) so the widget shows up on the next preview reload — as the "Connect this website" panel until the site is claimed, then as the "Report a vulnerability" button. On a server-rendered root it also adds the production marker, which is what tells the widget to switch from build mode to visitor report intake on the published site.
|
|
99
99
|
6. Installs the runtime guard after provisioning, bakes the site UUID into it, and verifies the framework seam. Known server stacks are auto-wired; unmatched or conflicting layouts get a generic scaffold and exact manual checks.
|
|
100
100
|
7. Adds `postinstall: patchstack-connect scan`, preserving any existing command, so dependencies added during a sandbox session and build-less production installs are reported immediately.
|
|
101
101
|
8. Wires `scan` before builds and `mark-build` after builds, preserving existing commands and using direct build chaining for Bun.
|
|
102
102
|
9. Prints a dashboard link — open it in a browser to attach the new site to your Patchstack account. You can re-display it any time with `npx @patchstack/connect status`.
|
|
103
103
|
|
|
104
|
-
Then **refresh your preview**. The widget loads with the page, so a preview that was already open still shows the HTML from before setup. Builders that hot reload will have refreshed it for you; if the widget is missing, refresh it once. Until the site is claimed it shows the "Connect this website" panel
|
|
104
|
+
Then **refresh your preview**. The widget loads with the page, so a preview that was already open still shows the HTML from before setup. Builders that hot reload will have refreshed it for you; if the widget is missing, refresh it once. Until the site is claimed it shows the "Connect this website" panel. `setup` prints the same reminder, and the CLI has no way to reload a browser itself.
|
|
105
105
|
|
|
106
106
|
Then **deploy**. These are source changes, so your live site keeps serving its previous build — visitors get the widget, and a server-rendered root gets the production marker, only after the next deploy.
|
|
107
107
|
|
|
@@ -120,9 +120,9 @@ npx @patchstack/connect setup
|
|
|
120
120
|
```
|
|
121
121
|
patchstack-connect scan [options] Scan the lockfile and POST to Patchstack.
|
|
122
122
|
If no UUID is configured the server provisions
|
|
123
|
-
one and
|
|
124
|
-
successful post, adds/updates the
|
|
125
|
-
|
|
123
|
+
one and Connect persists it. After a
|
|
124
|
+
successful post, adds/updates the Patchstack
|
|
125
|
+
Connector tag in the root HTML shell. Also adds the
|
|
126
126
|
production marker to a JSX root shell, before the
|
|
127
127
|
post (opt out of both with "widget": false in
|
|
128
128
|
.patchstackrc.json)
|
|
@@ -300,11 +300,11 @@ Two files, because one value is public and the other is not.
|
|
|
300
300
|
}
|
|
301
301
|
```
|
|
302
302
|
|
|
303
|
-
`"widget"` is optional and defaults to `true`; set it to `false` to stop
|
|
303
|
+
`"widget"` is optional and defaults to `true`; set it to `false` to stop Connect from managing the Patchstack Connector tag (see *The Patchstack Connector*).
|
|
304
304
|
|
|
305
|
-
**You do not write `apiKey` yourself.** The first `scan` provisions the site and
|
|
305
|
+
**You do not write `apiKey` yourself.** The first `scan` provisions the site and Connect saves it, so setup needs no manual step.
|
|
306
306
|
|
|
307
|
-
The site UUID identifies the site and is **not** a secret — the
|
|
307
|
+
The site UUID identifies the site and is **not** a secret — the Patchstack Connector ships the same UUID in client-side HTML.
|
|
308
308
|
|
|
309
309
|
`apiKey` **is** a secret. One credential authenticates both paths: Pulse ingest (manifest, attack-surface map, package removal), where it is exchanged for a short-lived token rather than sent directly, and block-log reporting. Keep it out of the widget tag, client bundles and public env vars (`NEXT_PUBLIC_*`), and out of the committed config — `.patchstackrc.local.json` is git-ignored for that reason. For deploys, prefer `PATCHSTACK_API_KEY` in the platform's secret store.
|
|
310
310
|
|
|
@@ -366,9 +366,9 @@ The guide inspects the Host-created site configuration and lockfile, explains th
|
|
|
366
366
|
|
|
367
367
|
Use `--url http://localhost:PORT/api/tasks` when the app does not use the default `http://localhost:3000/api/tasks`. Remove the deliberately vulnerable dependency after the walkthrough.
|
|
368
368
|
|
|
369
|
-
## The
|
|
369
|
+
## The Patchstack Connector
|
|
370
370
|
|
|
371
|
-
The
|
|
371
|
+
The Patchstack Connector is a floating control whose form follows the site's claim state: a "Connect this website" panel while the site is unclaimed, then a "Report a vulnerability" button — a disclosure channel for anyone who spots a bug on the site. Connect manages its install so the UUID never has to be copied by hand:
|
|
372
372
|
|
|
373
373
|
- **`scan`** (after a successful post) adds this managed tag to the first root HTML shell it finds — `index.html`, `public/index.html`, or `src/app.html` — immediately before `</body>`:
|
|
374
374
|
|
|
@@ -379,9 +379,9 @@ The widget is a floating "Report a vulnerability" button — a disclosure channe
|
|
|
379
379
|
- **`scan`** installs the widget tag into a plain HTML shell, and — where there is none — into a JSX root (`src/routes/__root.tsx`, `app/layout.tsx`, …), just before `</body>`. The same tag serves both: JSX reads `defer` as a boolean attribute and passes `data-*` through. A server-rendered app has no HTML shell at all, so without this its published site carries no widget and Patchstack never hears from the live page.
|
|
380
380
|
- **`scan`** also adds the production marker when the root shell is JSX rather than HTML (`src/routes/__root.tsx`, `app/layout.tsx`, …), above the widget tag and guarded by the framework's production expression. A server-rendered app emits no built HTML for `mark-build` to stamp, so without it the widget reads the published site as build mode and shows the claim flow to visitors instead of the report form.
|
|
381
381
|
|
|
382
|
-
Re-runs update the tag in place (the `data-patchstack-connect-widget` attribute marks it as
|
|
382
|
+
Re-runs update the tag in place (the `data-patchstack-connect-widget` attribute marks it as managed by Connect); a pre-existing manual widget tag is left untouched. `--dry-run` never edits anything; a failed post still skips the widget tag (it needs the site UUID) but the production marker may already have been written, since it runs before the post. Projects whose root layout is code rather than HTML (Next.js, Nuxt, Astro, …) get the exact snippet and target file printed instead — `guide` shows framework-specific placement.
|
|
383
383
|
|
|
384
|
-
- **`mark-build`** ensures the same tag in built HTML output, covering builds whose source shell
|
|
384
|
+
- **`mark-build`** ensures the same tag in built HTML output, covering builds whose source shell Connect couldn't edit, and stamps `window.__PATCHSTACK_PROD__` so the widget hides the claim/login UI on the published site (owners reach it by appending `#patchstack` to the live URL). It then reports what it did — `stamped`, `withheld`, `no-pages` for a server-rendered build, or `no-output` — alongside the same manifest `scan` sent before the bundler ran, so the dashboard can say why a published app is or is not reporting its build. That report is the second half of one build, not a second build: Patchstack keeps one copy of the manifest and reads the two together. It is sent only for a site that is already registered, and never carries the site's address or name, which `mark-build` does not resolve. The marker says the page is the live site, so **only a production build carries it**: the environment is read the same way `scan` reads it (the build platform's own tier or branch name, then the hosted builder the project belongs to), and a local or preview build gets the widget tag, no marker, and any marker an earlier build left behind removed. Publishing a static build by hand from your machine is the case that needs `--production` (or `PATCHSTACK_ENVIRONMENT=production`), because nothing in that environment can say the build is a deployment.
|
|
385
385
|
|
|
386
386
|
- **Opting out:** persist `"widget": false` in `.patchstackrc.json` to disable both the widget tag and the production marker (dependency scanning only). Without it, the next successful scan re-adds the managed tag, and the next scan re-adds the marker on a JSX root.
|
|
387
387
|
|
|
@@ -444,11 +444,11 @@ These are repo-relative locations built from `node_modules` segments, plus a wor
|
|
|
444
444
|
- ✅ `bun.lockb` (binary) — package list resolved by walking `node_modules/`
|
|
445
445
|
- ✅ `bun.lock` (text) — same fallback; direct parsing coming
|
|
446
446
|
|
|
447
|
-
If both a Bun lockfile and `node_modules/` are present,
|
|
447
|
+
If both a Bun lockfile and `node_modules/` are present, Connect walks `node_modules/` to enumerate the installed packages. Run `bun install` (or `npm install`) before scanning so the directory is populated.
|
|
448
448
|
|
|
449
449
|
### Stale lockfiles
|
|
450
450
|
|
|
451
|
-
Every scanned source is validated against `package.json`: if the chosen lockfile is missing dependencies that `package.json` declares, it is treated as a fossil (e.g. a `package-lock.json` created by a one-off `npm install` in a bun-managed project) and
|
|
451
|
+
Every scanned source is validated against `package.json`: if the chosen lockfile is missing dependencies that `package.json` declares, it is treated as a fossil (e.g. a `package-lock.json` created by a one-off `npm install` in a bun-managed project) and Connect falls through to the next source — ultimately walking `node_modules/`, the installed truth — and prints a warning naming the stale file. Delete the stale lockfile to silence the warning. Without this, the manifest and the build fingerprint would silently freeze while the real dependency set drifts.
|
|
452
452
|
|
|
453
453
|
## Development
|
|
454
454
|
|