@patchstack/connect 0.3.25 → 0.3.26

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
@@ -76,12 +76,13 @@ This versioned reference ships inside `@patchstack/connect` and documents each s
76
76
  <script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer></script>
77
77
  ```
78
78
 
79
- Framework-specific placement patterns: https://cdn.patchstack.com/llm.html. The site UUID is public by design — it ships in client-side HTML and is not a secret. The credentials are the opposite, and `scan` writes both of them for you — **there is no manual step, and you should never invent or ask the user for these values**:
79
+ Framework-specific placement patterns: https://cdn.patchstack.com/llm.html. The site UUID is public by design — it ships in client-side HTML and is not a secret. The credential is the opposite, and `scan` writes it for you — **there is no manual step, and you should never invent or ask the user for this value**:
80
80
 
81
- - `apiKey` (also `PATCHSTACK_API_KEY`, WP format `{secret}-{oauth.id}`) — authenticates block-log reporting through the connector `POST /api/logs/log`, so "Threats blocked" fills in the dashboard.
82
- - `pulseAuth` (also `PATCHSTACK_PULSE_AUTH`) — authenticates Pulse ingest: the manifest, the attack-surface map and package removal. Exchanged for a short-lived token rather than sent directly. Falls back to `apiKey` when absent, so older projects keep working.
81
+ - `apiKey` (also `PATCHSTACK_API_KEY`, WP format `{secret}-{oauth.id}`) — one credential for both paths. It authenticates **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** through the connector `POST /api/logs/log`, so "Threats blocked" fills in the dashboard.
83
82
 
84
- Both are server-only. Never put either in the widget tag, client bundles, or public env vars (`NEXT_PUBLIC_*`, etc.). Prefer `PATCHSTACK_API_KEY` / `PATCHSTACK_PULSE_AUTH` in production; `.patchstackrc.json` is fine for local DX. If a credential is lost, `npx @patchstack/connect login` recovers it via dashboard approval — do not delete the file and re-provision, which would create a second site. Opt out of reporting with `PATCHSTACK_TELEMETRY=off`. If the project must not carry the widget, persist `"widget": false` in `.patchstackrc.json`; otherwise the next scan re-adds it.
83
+ It is server-only. Never put it in the widget tag, client bundles, or public env vars (`NEXT_PUBLIC_*`, etc.). Prefer `PATCHSTACK_API_KEY` in production; `.patchstackrc.json` is fine for local DX. If it is lost, `npx @patchstack/connect login` recovers it via dashboard approval — do not delete the file and re-provision, which would create a second site. Opt out of reporting with `PATCHSTACK_TELEMETRY=off`. If the project must not carry the widget, persist `"widget": false` in `.patchstackrc.json`; otherwise the next scan re-adds it.
84
+
85
+ A `pulseAuth` field is still honoured if a project has one, and `PATCHSTACK_PULSE_AUTH` still overrides it, for deployments that authenticate Pulse ingest with a different credential from block-logs. Do not add either yourself: they are unnecessary when the two share one credential, which is the default.
85
86
 
86
87
  4. **Install and verify runtime protection:**
87
88
 
@@ -102,7 +103,7 @@ Both are server-only. Never put either in the widget tag, client bundles, or pub
102
103
  - The CLI never opens the dashboard link and never asks for Patchstack credentials.
103
104
  - Label hosted workspace scans with `PATCHSTACK_ENVIRONMENT=sandbox` in that process only. Leave production builds unset (the default is `production`) and never commit a sandbox label into files shared with production.
104
105
  - If a step fails, stop and report it. Don't proceed with placeholders.
105
- - In CI where `.patchstackrc.json` can't be committed, set `PATCHSTACK_SITE_UUID` and `PATCHSTACK_PULSE_AUTH` as env vars instead. Precedence: CLI flag → env var → `.patchstackrc.json`. `login` is interactive and refuses to run in CI, so CI always takes its credential from the environment.
106
+ - In CI where `.patchstackrc.json` can't be committed, set `PATCHSTACK_SITE_UUID` and `PATCHSTACK_API_KEY` as env vars instead. Precedence: CLI flag → env var → `.patchstackrc.json`. `login` is interactive and refuses to run in CI, so CI always takes its credential from the environment.
106
107
 
107
108
  ## Verifying the install
108
109
 
@@ -119,6 +120,46 @@ These are **two independent states** — never conflate them:
119
120
 
120
121
  Local files alone cannot tell you whether the site was removed from Patchstack. Run `npx @patchstack/connect status` and read the `Site status` line, then answer with both states. For example, when the site was removed but the local files remain, say: *"The site itself was removed from Patchstack — reporting has stopped and the widget no longer renders. The local integration code (widget tag, `.patchstackrc.json`, the dependency) is still in the project; want me to remove it?"* — not "Patchstack is still installed."
121
122
 
123
+ ## Recovering a lost credential — `login`
124
+
125
+ Use this when the project **already has a site** but its credential is gone or rejected: `.patchstackrc.json` was deleted or never committed, the repo was cloned without it, a container was recycled, or ingest started failing with 401.
126
+
127
+ > **Do not "fix" a missing credential by deleting `.patchstackrc.json` and running `scan` again.** That provisions a **second site**, and the original — with all its history and its widget tag already live on the deployed page — is orphaned. `login` recovers the existing one.
128
+
129
+ ### What it does
130
+
131
+ ```
132
+ npx @patchstack/connect login
133
+
134
+ Your code: WDJB-MJHT
135
+ Approve at: https://api.patchstack.com/monitor/pulse/device?code=WDJB-MJHT
136
+
137
+ Waiting for approval… ✓ Credential restored
138
+ ```
139
+
140
+ The command asks Patchstack for a short code, prints a link, and polls until the site's **owner approves it in the dashboard**. On approval it writes the new credential into `.patchstackrc.json` and exits. The link opens the approval page with the code already filled in, so the person only has to confirm.
141
+
142
+ ### What you must do, as the agent
143
+
144
+ 1. **Run the command and surface the link and code to the user verbatim.** They must open it themselves — approval requires their signed-in Patchstack account, which you do not have and must not ask for.
145
+ 2. **Leave the command running.** It polls until approved or the code expires (10 minutes). Do not kill it and retry; each run issues a different code and invalidates the one already on screen.
146
+ 3. **Report the outcome.** On success, tell them the credential was restored *and* that the previous one no longer works — see the warning below.
147
+
148
+ You cannot complete this alone. It is deliberately a human-in-the-loop step: starting the flow proves nothing about who is running it, so the only authorisation is an owner approving in the browser.
149
+
150
+ ### Consequences to tell the user about
151
+
152
+ **Approving rotates the credential — the old one stops working immediately.** Anywhere it was configured needs the new value: CI secrets, hosting-platform env vars, preview environments, other developers' checkouts. Say this before they approve, not after.
153
+
154
+ ### When it will not work
155
+
156
+ | Situation | What happens | What to do |
157
+ |---|---|---|
158
+ | Site was never claimed | `409` — no owner exists to approve | Ask the user to claim the site in the dashboard first, or, if the site is disposable, delete `.patchstackrc.json` and `scan` to provision a fresh one |
159
+ | Running in CI | Refuses to start | CI takes its credential from `PATCHSTACK_PULSE_AUTH`; `login` is for a developer machine |
160
+ | No `siteUuid` configured | Refuses to start | There is no site to recover — run `scan` |
161
+ | Code expired | Poll ends after 10 minutes | Run the command again for a new code |
162
+
122
163
  ## Uninstalling
123
164
 
124
165
  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 widget"), remove only that piece.
package/README.md CHANGED
@@ -119,22 +119,23 @@ Environment variables:
119
119
  {
120
120
  "siteUuid": "550e8400-e29b-41d4-a716-446655440000",
121
121
  "apiKey": "…",
122
- "pulseAuth": "…",
123
122
  "widget": true
124
123
  }
125
124
  ```
126
125
 
127
126
  `"widget"` is optional and defaults to `true`; set it to `false` to stop the connector from managing the disclosure-widget tag (see *The disclosure widget*).
128
127
 
129
- **You do not write `apiKey` or `pulseAuth` yourself.** The first `scan` provisions the site and the connector saves both, so setup needs no manual step. They hold the same value today and exist as separate fields so Pulse ingest and block-log reporting can diverge later.
128
+ **You do not write `apiKey` yourself.** The first `scan` provisions the site and the connector saves it, so setup needs no manual step.
130
129
 
131
130
  The site UUID identifies the site and is **not** a secret — the disclosure widget ships the same UUID in client-side HTML.
132
131
 
133
- `apiKey` and `pulseAuth` **are** secrets. `apiKey` authenticates block-log reporting; `pulseAuth` authenticates Pulse ingest (manifest, attack-surface map, package removal) and is exchanged for a short-lived token rather than sent directly. Keep both out of the widget tag, client bundles and public env vars (`NEXT_PUBLIC_*`). For deploys, prefer `PATCHSTACK_API_KEY` and `PATCHSTACK_PULSE_AUTH` in the platform's secret store over the committed file.
132
+ `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_*`). For deploys, prefer `PATCHSTACK_API_KEY` in the platform's secret store over the committed file.
134
133
 
135
- If a credential is ever lost, `npx @patchstack/connect login` recovers it — approval happens in the dashboard and rotates the credential.
134
+ If it is ever lost, `npx @patchstack/connect login` recovers it — approval happens in the dashboard and rotates the credential.
136
135
 
137
- In CI setups where the file isn't committed, set `PATCHSTACK_SITE_UUID` and `PATCHSTACK_PULSE_AUTH`. Precedence is CLI flag → env var → `.patchstackrc.json`.
136
+ In CI setups where the file isn't committed, set `PATCHSTACK_SITE_UUID` and `PATCHSTACK_API_KEY`. Precedence is CLI flag → env var → `.patchstackrc.json`.
137
+
138
+ A `pulseAuth` field is still read if present, and `PATCHSTACK_PULSE_AUTH` still overrides, for deployments that authenticate Pulse ingest with a different credential from block-logs. Neither is written by default, and neither is needed when the two share one.
138
139
 
139
140
  ### Sandbox and production manifests
140
141
 
package/dist/cli.js CHANGED
@@ -1457,13 +1457,9 @@ async function persistSiteUuid(cwd, siteUuid) {
1457
1457
  return writeConfigFile(cwd, { ...existing, siteUuid });
1458
1458
  }
1459
1459
  async function persistApiKey(cwd, apiKey) {
1460
- const existing = await readConfigFile(cwd);
1460
+ const { pulseAuth: _dropped, ...existing } = await readConfigFile(cwd);
1461
1461
  return writeConfigFile(cwd, { ...existing, apiKey });
1462
1462
  }
1463
- async function persistPulseAuth(cwd, pulseAuth) {
1464
- const existing = await readConfigFile(cwd);
1465
- return writeConfigFile(cwd, { ...existing, pulseAuth });
1466
- }
1467
1463
  async function readConfigFile(cwd) {
1468
1464
  const target = path6.join(cwd, CONFIG_FILENAME);
1469
1465
  let raw;
@@ -3004,7 +3000,6 @@ async function login(config, onPrompt, deps = {}) {
3004
3000
  if (typeof apiKey !== "string" || apiKey.length === 0) {
3005
3001
  return { status: "failed", message: "Patchstack approved the request but returned no credential." };
3006
3002
  }
3007
- await persistPulseAuth(process.cwd(), apiKey);
3008
3003
  await persistApiKey(process.cwd(), apiKey);
3009
3004
  return { status: "approved", userCode, verificationUri };
3010
3005
  }
@@ -3741,7 +3736,7 @@ function classifyServerSurface(cwd, endpointCount, deploymentShapes, ts) {
3741
3736
  }
3742
3737
  function surfaceNote(surface) {
3743
3738
  if (surface.state === "server-runtime-detected") {
3744
- return "serverSurface: a server runtime was recognized in the analysed source (see its evidence). Request-path protection applies to this app.";
3739
+ return "serverSurface: a server runtime was recognized in the analysed source (see its evidence). Request-path shielding is APPLICABLE to these paths \u2014 this is not a statement that a guard is installed, fetching rules, or enforcing them.";
3745
3740
  }
3746
3741
  if (surface.state === "static-build-detected") {
3747
3742
  return "serverSurface: a static build was identified and no server runtime was recognized in the analysed source. This is NOT deployment attestation \u2014 it describes the source, not what is deployed, and a serverless function added at the platform level would not appear here. Advisories against this app are dependency and bundle concerns rather than request-path risk.";
@@ -5497,11 +5492,14 @@ Usage:
5497
5492
  what's missing, with tailored commands), then
5498
5493
  print the full setup guide. --full prints the
5499
5494
  guide even when setup is complete
5500
- patchstack-connect login [options] Recover this site's Patchstack credential when
5501
- .patchstackrc.json has been lost. Prints a short
5502
- code to approve in the dashboard; approving
5503
- rotates the credential, so the old one stops
5504
- working
5495
+ patchstack-connect login [options] Recover this site's credential when
5496
+ .patchstackrc.json has been lost. Prints a link
5497
+ for the site's OWNER to approve in the dashboard,
5498
+ then waits (10 min). Use this instead of deleting
5499
+ .patchstackrc.json and re-scanning, which would
5500
+ provision a second site. Approving ROTATES the
5501
+ credential: CI, deploys and other machines using
5502
+ the old one must be updated. Not usable in CI
5505
5503
  patchstack-connect help Print this message
5506
5504
 
5507
5505
  Options (for scan, setup, status, and uninstall):
@@ -5520,7 +5518,7 @@ Options (for demo and demo-guide):
5520
5518
  Environment:
5521
5519
  PATCHSTACK_SITE_UUID Site UUID
5522
5520
  PATCHSTACK_API_KEY WP-format site API key for block-log reporting (never put in the widget)
5523
- PATCHSTACK_PULSE_AUTH Credential for authenticated Pulse ingest (defaults to PATCHSTACK_API_KEY)
5521
+ PATCHSTACK_PULSE_AUTH Only if Pulse ingest uses a different credential from block-logs
5524
5522
  PATCHSTACK_TELEMETRY Set to off to disable block-log reporting
5525
5523
  PATCHSTACK_API_BASE API origin for /oauth/token and /api/logs/log (default: https://api.patchstack.com)
5526
5524
  PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest)
@@ -5607,10 +5605,15 @@ async function runLogin(args) {
5607
5605
  Your code: ${userCode}`);
5608
5606
  console.log(` Approve at: ${verificationUri}
5609
5607
  `);
5610
- console.log(" Waiting for approval\u2026");
5608
+ console.log(" Open that link and approve it as the site's owner. Approving issues a new");
5609
+ console.log(" credential and stops the current one working \u2014 CI, deploys and any other");
5610
+ console.log(" machine using it will need the new value.\n");
5611
+ console.log(" Waiting for approval (the code expires in 10 minutes)\u2026");
5611
5612
  });
5612
5613
  if (result.status === "approved") {
5613
- console.log("\n \u2713 Credential restored and saved to .patchstackrc.json.\n");
5614
+ console.log("\n \u2713 Credential restored and saved to .patchstackrc.json.");
5615
+ console.log(" The previous credential no longer works. Update it anywhere else it was set:");
5616
+ console.log(" CI secrets, hosting env vars, preview environments, other checkouts.\n");
5614
5617
  return 0;
5615
5618
  }
5616
5619
  console.error(`
@@ -5745,7 +5748,6 @@ async function runScan(args, options = {}) {
5745
5748
  }
5746
5749
  if (typeof response.api_key === "string" && response.api_key.length > 0) {
5747
5750
  const target = await persistApiKey(process.cwd(), response.api_key);
5748
- await persistPulseAuth(process.cwd(), response.api_key);
5749
5751
  console.log(`Saved API key to ${target} (authenticates Pulse ingest and block-log reporting; keep out of the public widget).`);
5750
5752
  }
5751
5753
  if (response.stored) {