@patchstack/connect 0.3.24 → 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 +46 -5
- package/README.md +6 -5
- package/dist/cli.js +19 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.js +1 -1
- package/dist/{refresh-manifest-MUWVIPFR.js → refresh-manifest-KZSQD2X3.js} +1 -1
- package/dist/refresh-manifest-KZSQD2X3.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-MUWVIPFR.js.map +0 -1
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
|
|
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
|
-
|
|
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 `
|
|
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`
|
|
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`
|
|
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
|
|
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 `
|
|
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;
|
|
@@ -2987,7 +2983,7 @@ async function login(config, onPrompt, deps = {}) {
|
|
|
2987
2983
|
return { status: "failed", message: `Could not start the login (HTTP ${started.status}).` };
|
|
2988
2984
|
}
|
|
2989
2985
|
const { device_code: deviceCode, user_code: userCode, expires_in: expiresIn, interval } = await started.json();
|
|
2990
|
-
const verificationUri = `${
|
|
2986
|
+
const verificationUri = `${base}/device?code=${encodeURIComponent(userCode)}`;
|
|
2991
2987
|
onPrompt(userCode, verificationUri);
|
|
2992
2988
|
const deadline = now() + expiresIn * 1e3;
|
|
2993
2989
|
const intervalMs = Math.max(1, interval) * 1e3;
|
|
@@ -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
|
|
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
|
|
5501
|
-
.patchstackrc.json has been lost. Prints a
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
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
|
|
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("
|
|
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
|
|
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) {
|