@patchstack/connect 0.3.21 → 0.3.23
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 +14 -3
- package/dist/cli.js +62 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +277 -12
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +27 -2
- package/dist/protect.js +275 -13
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-XBOGN446.js → refresh-manifest-ZIFRZF25.js} +4 -1
- package/dist/refresh-manifest-ZIFRZF25.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-XBOGN446.js.map +0 -1
package/AGENT-INSTALL.md
CHANGED
|
@@ -73,7 +73,7 @@ This versioned reference ships inside `@patchstack/connect` and documents each s
|
|
|
73
73
|
<script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer></script>
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
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. If the project must not carry the widget, persist `"widget": false` in `.patchstackrc.json`; otherwise the next scan re-adds it.
|
|
76
|
+
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 `apiKey` (also `PATCHSTACK_API_KEY`, WP format `{secret}-{oauth.id}`) is the opposite: server-only, used to authenticate block-log reporting through the existing connector `POST /api/logs/log` so "Threats blocked" fills in the dashboard. Never put `apiKey` 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. 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.
|
|
77
77
|
|
|
78
78
|
4. **Install and verify runtime protection:**
|
|
79
79
|
|
|
@@ -98,10 +98,19 @@ This versioned reference ships inside `@patchstack/connect` and documents each s
|
|
|
98
98
|
|
|
99
99
|
## Verifying the install
|
|
100
100
|
|
|
101
|
-
- `npx @patchstack/connect status` re-prints the site UUID and dashboard URL.
|
|
101
|
+
- `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`).
|
|
102
102
|
- `npx @patchstack/connect protect --check` verifies the runtime guard is connected to the request path.
|
|
103
103
|
- Load the site in a browser — the "Report a vulnerability" button should appear.
|
|
104
104
|
|
|
105
|
+
## Answering "is Patchstack installed?" / "is Patchstack removed?"
|
|
106
|
+
|
|
107
|
+
These are **two independent states** — never conflate them:
|
|
108
|
+
|
|
109
|
+
1. **The site record on Patchstack** (remote). Deleting the site in the dashboard or through the widget's uninstall flow removes it. Reporting stops and the widget stops rendering, but nothing in the project changes.
|
|
110
|
+
2. **The local integration** (this repo): the widget `<script>` tag, `.patchstackrc.json`, the `@patchstack/connect` dependency, the runtime guard files, and the build hooks.
|
|
111
|
+
|
|
112
|
+
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."
|
|
113
|
+
|
|
105
114
|
## Uninstalling
|
|
106
115
|
|
|
107
116
|
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.
|
|
@@ -111,7 +120,9 @@ Remove only the pieces that are actually present — check for each first. If no
|
|
|
111
120
|
3. **Remove runtime protection before uninstalling the package.** Delete the connector-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.
|
|
112
121
|
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.
|
|
113
122
|
5. **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.
|
|
114
|
-
6. **Delete `.patchstackrc.json`** and remove `PATCHSTACK_SITE_UUID` (and public-prefixed variants like `NEXT_PUBLIC_PATCHSTACK_SITE_UUID`) from env files and CI variables.
|
|
123
|
+
6. **Delete `.patchstackrc.json`** and remove `PATCHSTACK_SITE_UUID`, `PATCHSTACK_API_KEY` (and public-prefixed variants like `NEXT_PUBLIC_PATCHSTACK_SITE_UUID`) from env files and CI variables.
|
|
115
124
|
7. **Commit** the changes. Reporting stops immediately. The `window.__PATCHSTACK_PROD__` flag that `mark-build` injected lives only in build output, never in source — the next build simply won't contain it (rebuild if build output is committed).
|
|
116
125
|
|
|
117
126
|
Local removal does not delete the site record on Patchstack's side. An unclaimed site is an anonymous record that stops receiving reports; a claimed site is removed by the user in their dashboard at https://app.patchstack.com. There is no CLI command for account-side deletion — do not invent one, and never attempt to authenticate or remove the site on the user's behalf.
|
|
127
|
+
|
|
128
|
+
The reverse also holds: removing the site on Patchstack's side (dashboard delete or the widget's uninstall flow) does not touch these local files — they must still be removed with the steps above. `npx @patchstack/connect status` shows `Site status: removed from Patchstack` in that state.
|
package/dist/cli.js
CHANGED
|
@@ -946,6 +946,31 @@ function buildClaimUrl(endpoint, siteUuid) {
|
|
|
946
946
|
const origin = new URL(endpoint).origin;
|
|
947
947
|
return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;
|
|
948
948
|
}
|
|
949
|
+
function buildSettingsUrl(endpoint, siteUuid) {
|
|
950
|
+
const origin = new URL(endpoint).origin;
|
|
951
|
+
return `${origin}/monitor/widget/settings/${encodeURIComponent(siteUuid)}`;
|
|
952
|
+
}
|
|
953
|
+
async function fetchSiteStatus(config) {
|
|
954
|
+
if (config.siteUuid === null) return "unknown";
|
|
955
|
+
const url = new URL(buildSettingsUrl(config.endpoint, config.siteUuid));
|
|
956
|
+
url.searchParams.set("t", Date.now().toString());
|
|
957
|
+
try {
|
|
958
|
+
const response = await fetch(url.toString(), {
|
|
959
|
+
method: "GET",
|
|
960
|
+
headers: {
|
|
961
|
+
Accept: "application/json",
|
|
962
|
+
"Cache-Control": "no-cache",
|
|
963
|
+
"User-Agent": "@patchstack/connect"
|
|
964
|
+
},
|
|
965
|
+
signal: AbortSignal.timeout(config.timeoutMs)
|
|
966
|
+
});
|
|
967
|
+
if (response.status === 404) return "removed";
|
|
968
|
+
if (response.ok) return "active";
|
|
969
|
+
return "unknown";
|
|
970
|
+
} catch {
|
|
971
|
+
return "unknown";
|
|
972
|
+
}
|
|
973
|
+
}
|
|
949
974
|
async function postManifest(config, payload) {
|
|
950
975
|
const url = buildEndpointUrl(config.endpoint, config.siteUuid);
|
|
951
976
|
const timeoutMs = config.timeoutMs;
|
|
@@ -1247,8 +1272,10 @@ async function resolveConfig(options) {
|
|
|
1247
1272
|
"CONFIG_MISSING"
|
|
1248
1273
|
);
|
|
1249
1274
|
}
|
|
1275
|
+
const apiKeyRaw = fromEnv.apiKey ?? fromFile.apiKey ?? null;
|
|
1250
1276
|
return {
|
|
1251
1277
|
siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
|
|
1278
|
+
apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
|
|
1252
1279
|
endpoint,
|
|
1253
1280
|
timeoutMs,
|
|
1254
1281
|
environment,
|
|
@@ -1265,6 +1292,10 @@ async function persistSiteUuid(cwd, siteUuid) {
|
|
|
1265
1292
|
const existing = await readConfigFile(cwd);
|
|
1266
1293
|
return writeConfigFile(cwd, { ...existing, siteUuid });
|
|
1267
1294
|
}
|
|
1295
|
+
async function persistApiKey(cwd, apiKey) {
|
|
1296
|
+
const existing = await readConfigFile(cwd);
|
|
1297
|
+
return writeConfigFile(cwd, { ...existing, apiKey });
|
|
1298
|
+
}
|
|
1268
1299
|
async function readConfigFile(cwd) {
|
|
1269
1300
|
const target = path6.join(cwd, CONFIG_FILENAME);
|
|
1270
1301
|
let raw;
|
|
@@ -1306,6 +1337,7 @@ function readEnv() {
|
|
|
1306
1337
|
const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;
|
|
1307
1338
|
return {
|
|
1308
1339
|
siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0,
|
|
1340
|
+
apiKey: process.env.PATCHSTACK_API_KEY ?? void 0,
|
|
1309
1341
|
endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0,
|
|
1310
1342
|
timeoutMs,
|
|
1311
1343
|
environment: environmentRaw !== void 0 && environmentRaw.length > 0 ? environmentRaw : void 0
|
|
@@ -2858,7 +2890,9 @@ Usage:
|
|
|
2858
2890
|
Never runs the project build
|
|
2859
2891
|
patchstack-connect init <site-uuid> Optional: pre-seed .patchstackrc.json
|
|
2860
2892
|
with an existing site UUID
|
|
2861
|
-
patchstack-connect status [options] Show current configuration
|
|
2893
|
+
patchstack-connect status [options] Show current configuration and whether the
|
|
2894
|
+
site still exists on Patchstack (active /
|
|
2895
|
+
removed)
|
|
2862
2896
|
patchstack-connect mark-build [options] Stamp built HTML with a production flag +
|
|
2863
2897
|
build fingerprint, and ensure the widget
|
|
2864
2898
|
tag in built pages (run as a postbuild step)
|
|
@@ -2899,6 +2933,9 @@ Options (for demo and demo-guide):
|
|
|
2899
2933
|
|
|
2900
2934
|
Environment:
|
|
2901
2935
|
PATCHSTACK_SITE_UUID Site UUID
|
|
2936
|
+
PATCHSTACK_API_KEY WP-format site API key for block-log reporting (never put in the widget)
|
|
2937
|
+
PATCHSTACK_TELEMETRY Set to off to disable block-log reporting
|
|
2938
|
+
PATCHSTACK_API_BASE API origin for /oauth/token and /api/logs/log (default: https://api.patchstack.com)
|
|
2902
2939
|
PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest)
|
|
2903
2940
|
PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000)
|
|
2904
2941
|
PATCHSTACK_ENVIRONMENT Manifest environment: production | sandbox (default: production)
|
|
@@ -3022,6 +3059,10 @@ async function runScan(args, options = {}) {
|
|
|
3022
3059
|
const target = await persistSiteUuid(process.cwd(), response.uuid);
|
|
3023
3060
|
console.log(`Provisioned site ${response.uuid}. Saved UUID to ${target}.`);
|
|
3024
3061
|
}
|
|
3062
|
+
if (typeof response.api_key === "string" && response.api_key.length > 0) {
|
|
3063
|
+
const target = await persistApiKey(process.cwd(), response.api_key);
|
|
3064
|
+
console.log(`Saved API key to ${target} (for block-log reporting via /api/logs/log; keep out of the public widget).`);
|
|
3065
|
+
}
|
|
3025
3066
|
if (response.stored) {
|
|
3026
3067
|
console.log(`Stored manifest #${response.manifest_id} (checksum ${response.checksum}).`);
|
|
3027
3068
|
} else if (response.reason === "duplicate") {
|
|
@@ -3290,6 +3331,26 @@ async function runStatus(args) {
|
|
|
3290
3331
|
console.log(`Environment: ${config.environment}`);
|
|
3291
3332
|
if (config.siteUuid !== null) {
|
|
3292
3333
|
console.log(`Dashboard URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);
|
|
3334
|
+
switch (await fetchSiteStatus(config)) {
|
|
3335
|
+
case "active":
|
|
3336
|
+
console.log("Site status: active on Patchstack");
|
|
3337
|
+
break;
|
|
3338
|
+
case "removed":
|
|
3339
|
+
console.log("Site status: removed from Patchstack");
|
|
3340
|
+
console.log(
|
|
3341
|
+
" The site record no longer exists (deleted from the dashboard or via the"
|
|
3342
|
+
);
|
|
3343
|
+
console.log(
|
|
3344
|
+
" widget uninstall flow). The local integration files are still in this"
|
|
3345
|
+
);
|
|
3346
|
+
console.log(
|
|
3347
|
+
' project \u2014 see "Uninstalling" in AGENT-INSTALL.md to remove them.'
|
|
3348
|
+
);
|
|
3349
|
+
break;
|
|
3350
|
+
case "unknown":
|
|
3351
|
+
console.log("Site status: could not be verified (Patchstack unreachable)");
|
|
3352
|
+
break;
|
|
3353
|
+
}
|
|
3293
3354
|
}
|
|
3294
3355
|
return 0;
|
|
3295
3356
|
}
|