@patchstack/connect 0.4.0 → 0.4.2

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 CHANGED
@@ -70,6 +70,10 @@ Only `map` reads your source, and only `map --upload` sends anything derived fro
70
70
 
71
71
  In a hosted builder, run setup with `PATCHSTACK_ENVIRONMENT=sandbox` scoped to the workspace process/command, ensure the CLI's on-disk edits are adopted into the platform's persisted project state, then restart any already-running preview/server process so it loads the guard. Do not persist `"environment": "sandbox"` in `.patchstackrc.json`: deployed builds use the same committed files and should default to `production`. A client-only SPA has no server request path to guard; do not call it protected unless `protect --check` succeeds after a real server or edge seam is wired.
72
72
 
73
+ **Finish by telling the user to refresh their preview.** The widget's "Report a vulnerability" button loads with the page, so a preview that was already open still shows the HTML from before setup — the button is missing there until it reloads. 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.
74
+
75
+ **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.
76
+
73
77
  ## Manual setup
74
78
 
75
79
  1. **First scan** — provisions a Patchstack site automatically, writes the UUID to `.patchstackrc.json`, and installs the disclosure widget'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:
@@ -176,8 +180,28 @@ would have stopped while it is still in dry-run. Two separate paths, with differ
176
180
 
177
181
  What a detection report contains, per matched rule — on every phase, whatever fired it: the rule id, the revision of the rule when the
178
182
  bundle carried one, the identifier of the rule bundle in use, which phase matched,
179
- whether it was enforced, the request path **with the query string's values removed**,
180
- that query's parameter names, the method, and a timestamp. Each batch also carries a count of reports dropped when traffic outran the flush,
183
+ the rule's category and the action it declares, whether it was enforced,
184
+ which call it belongs to, the request path **with the query string's values removed**,
185
+ that query's parameter names, the method, and a timestamp.
186
+
187
+ Which call it belongs to is a token your guard mints and repeats on every rule that matched the same
188
+ request or the same outbound call. It exists because two rules matching one call is ordinary rather than
189
+ unusual — a rule that enforces and a rule that only observes are meant to match the same thing — so
190
+ without it, adding these reports up counts one call more than once.
191
+
192
+ Nothing about the request goes into it: not the address, not the path, not a header. It is drawn from
193
+ your runtime's randomness where that exists, and from the clock plus `Math.random` where it does not,
194
+ which is how this package already makes its own instance id. It is never a secret and never a boundary —
195
+ nothing is authorised by holding it — so what it has to do is not collide between two calls. A request and the response to it share one token; an
196
+ outbound call gets its own, because an outbound attempt is a thing in its own right and one made outside
197
+ any request has no request to belong to.
198
+
199
+ The category and the declared action say what KIND of rule matched — "a secret-exposure rule that
200
+ redacts", "an SSRF rule that blocks". Both are read from the rule your guard was served, and both are
201
+ `null` when that rule declares neither: a rule whose class nobody can state is reported as one, not
202
+ filled in. Neither is the same as `enforced`, which is whether the rule acted on this particular
203
+ request: a rule declaring `block` while only observing reports exactly that, and that is what a
204
+ detect-only deployment consists of. Each batch also carries a count of reports dropped when traffic outran the flush,
181
205
  so a partial sample is not read as a complete one.
182
206
 
183
207
  Two fields depend on the phase, because one kind of detection has a client and the other does not. A
@@ -348,7 +372,8 @@ Two more endpoints the package can call, for completeness:
348
372
 
349
373
  - `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`).
350
374
  - `npx @patchstack/connect protect --check` verifies the runtime guard is connected to the request path.
351
- - Load the site in a browser — the "Report a vulnerability" button should appear.
375
+ - Load the site in a browser — the "Report a vulnerability" button should appear. Refresh a page that was already open before the tag was added: the button only loads with the page.
376
+ - On the deployed site, the button appears only after a deploy that includes these source changes.
352
377
 
353
378
  ## Answering "is Patchstack installed?" / "is Patchstack removed?"
354
379
 
package/README.md CHANGED
@@ -6,7 +6,7 @@ Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.co
6
6
 
7
7
  Copy this request into a coding assistant, or run the same command yourself:
8
8
 
9
- > I have vetted the official [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect) package and authorize its disclosure widget, 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. Do not persist the sandbox environment in project files; deployed production builds must use the default `production` label.
9
+ > I have vetted the official [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect) package and authorize its disclosure widget, 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. 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 "Report a vulnerability" button is not showing yet, and remind me to deploy when I am ready — do not deploy anything yourself.
10
10
 
11
11
  `setup` is state-aware and idempotent: it scans dependencies, provisions or reuses the site, manages the disclosure widget, 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.
12
12
 
@@ -32,6 +32,10 @@ That's it. `setup`:
32
32
  8. Wires `scan` before builds and `mark-build` after builds, preserving existing commands and using direct build chaining for Bun.
33
33
  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`.
34
34
 
35
+ Then **refresh your preview**. The widget's "Report a vulnerability" button 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 button is missing, refresh it once. `setup` prints the same reminder, and the CLI has no way to reload a browser itself.
36
+
37
+ 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.
38
+
35
39
  ## Quick start (existing site)
36
40
 
37
41
  If you already created an "Application" site in the Patchstack dashboard, pre-seed the UUID:
package/dist/cli.js CHANGED
@@ -3987,6 +3987,9 @@ function needsSourceProductionMarker(state) {
3987
3987
  }
3988
3988
  return !state.widgetFileHint.toLowerCase().endsWith(".html");
3989
3989
  }
3990
+ function widgetTagInPlace(state) {
3991
+ return state.siteUuid !== null && !state.widgetOptOut && state.widgetInstalled && state.widgetTokenMatches !== false;
3992
+ }
3990
3993
  function countRemainingSteps(state) {
3991
3994
  return [
3992
3995
  state.installed?.section === "dependencies",
@@ -4133,6 +4136,19 @@ function renderGuideChecklist(state, useColor) {
4133
4136
  } else {
4134
4137
  lines.push(detail("The dashboard link appears after the first scan (re-print any time with `status`)."));
4135
4138
  }
4139
+ if (widgetTagInPlace(state)) {
4140
+ lines.push("");
4141
+ lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Refresh the preview to see the widget:")}`);
4142
+ lines.push(' The "Report a vulnerability" button loads with the page, so a preview that was');
4143
+ lines.push(" already open still shows the HTML from before this change. Builders that hot");
4144
+ lines.push(" reload refresh it themselves; if the button is missing, refresh the preview once.");
4145
+ }
4146
+ if (state.siteUuid !== null) {
4147
+ lines.push("");
4148
+ lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Deploy to put this on your live site:")}`);
4149
+ lines.push(" These are source changes. Your deployed site keeps serving its previous build,");
4150
+ lines.push(" so visitors only get the widget after you deploy (or hit Publish) again.");
4151
+ }
4136
4152
  const remaining = countRemainingSteps(state);
4137
4153
  lines.push("");
4138
4154
  if (remaining === 0) {
@@ -7529,6 +7545,12 @@ Run: ${installCommand(before.packageManager)}`
7529
7545
  console.log("");
7530
7546
  console.log(`Setup applied its bounded changes; ${remaining} manual step(s) remain above.`);
7531
7547
  }
7548
+ console.log("");
7549
+ console.log("Tell the user:");
7550
+ if (widgetTagInPlace(after)) {
7551
+ console.log(' - refresh the preview if the "Report a vulnerability" button is not showing yet;');
7552
+ }
7553
+ console.log(" - deploy (or hit Publish) when ready, so the live site serves these changes.");
7532
7554
  return 0;
7533
7555
  }
7534
7556
  async function runStatus(args) {