@patchstack/connect 0.4.2 → 0.5.1
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 +24 -4
- package/README.md +27 -3
- package/dist/cli.js +319 -61
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +230 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +227 -17
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/astro-middleware.ts +22 -1
- package/dist/protect/templates/express-guard.cjs +54 -4
- package/dist/protect/templates/express-guard.js +54 -4
- package/dist/protect/templates/express-guard.ts +43 -5
- package/dist/protect/templates/fastify-plugin.cjs +41 -3
- package/dist/protect/templates/fastify-plugin.js +41 -3
- package/dist/protect/templates/fastify-plugin.ts +30 -2
- package/dist/protect/templates/generic-guard.cjs +58 -5
- package/dist/protect/templates/generic-guard.js +58 -5
- package/dist/protect/templates/generic-guard.ts +47 -4
- package/dist/protect/templates/guard.ts +42 -8
- package/dist/protect/templates/next-middleware.ts +22 -1
- package/dist/protect/templates/nuxt-middleware.ts +22 -1
- package/dist/protect/templates/sveltekit-hooks.ts +22 -1
- package/dist/protect.cjs +351 -97
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.edge.js +56 -24
- package/dist/protect.edge.js.map +1 -1
- package/dist/protect.js +57 -25
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-JNCTIAM5.js → refresh-manifest-2MMPQN2A.js} +243 -35
- package/dist/refresh-manifest-2MMPQN2A.js.map +1 -0
- package/package.json +3 -3
- package/dist/refresh-manifest-JNCTIAM5.js.map +0 -1
package/AGENT-INSTALL.md
CHANGED
|
@@ -8,8 +8,8 @@ Every command at a glance — what it does, whether it reads your source, what i
|
|
|
8
8
|
|
|
9
9
|
| Command | What it does | Reads your source? | Writes to your project | Sends over the network |
|
|
10
10
|
|---|---|---|---|---|
|
|
11
|
-
| `scan` | Provision (or reuse) the site and POST the dependency list for vulnerability matching. Also runs automatically via `setup` and the install/build hooks. | No — lockfile only; `node_modules/` is enumerated when no lockfile can be read (e.g. `bun.lockb`) or when the lockfiles present disagree | `.patchstackrc.json` (public: site UUID + settings); `.patchstackrc.local.json` (the API key, created owner-only) and a `.gitignore` entry for it — the CLI says so if it could not add one; the widget `<script>` tag in the root HTML shell — only after a successful post; the production marker in a code root shell — before the post, since it needs no site UUID | Package names + versions |
|
|
12
|
-
| `setup` | One bounded command: `scan` → manage the widget → install + verify `protect` → wire the install/build scans. Never runs the project build. | No | Config, widget tag, production marker, guard files, `package.json` scripts | Package names + versions (via `scan`) |
|
|
11
|
+
| `scan` | Provision (or reuse) the site and POST the dependency list for vulnerability matching. Also runs automatically via `setup` and the install/build hooks. | No — lockfile only; `node_modules/` is enumerated when no lockfile can be read (e.g. `bun.lockb`) or when the lockfiles present disagree. It also reads the `<title>` of the root `index.html` and the `name` in `package.json`, to report what the site is called | `.patchstackrc.json` (public: site UUID + settings); `.patchstackrc.local.json` (the API key, created owner-only) and a `.gitignore` entry for it — the CLI says so if it could not add one; the widget `<script>` tag in the root HTML shell — only after a successful post; the production marker in a code root shell — before the post, since it needs no site UUID | Package names + versions; this site's public address and name, where the project or build environment states them |
|
|
12
|
+
| `setup` | One bounded command: `scan` → manage the widget → install + verify `protect` → wire the install/build scans. Never runs the project build. | No | Config, widget tag, production marker, guard files, `package.json` scripts | Package names + versions and the site's public address and name (via `scan`); a claim token as a request header, only when you pass one |
|
|
13
13
|
| `map` | Local, read-only attack-surface analysis (entry points → inputs → sinks → evidence-backed flows). Never run by another command. | **Yes** — via the app's own TypeScript | Nothing (only the file named by `--out`) | Nothing — **unless `--upload`**: structure only (routes, parameter names, the package behind each sink, file:line). Never source code or env values |
|
|
14
14
|
| `protect` | Install the always-on runtime guard; auto-wire known stacks, or scaffold a generic guard + print a wiring plan. `--check` verifies the guard is wired (exit 1 if not); `--demo` seeds a broad sample rule set. Runs automatically **only** via `setup` — never by `scan`, `guide`, `status`, or `mark-build`. | No — writes guard files, does not analyze your code | Guard/framework files (e.g. `middleware.ts`, `src/patchstack/`) | Nothing |
|
|
15
15
|
| `demo node-serialize` | Production-backed walkthrough: confirm the vulnerable package is present, scan, wait for live rule `18843`, install + verify the guard, print test requests. Does not install the package or start/restart the app. | No | Same files as `scan` + `protect` | `scan` payload; polls the public Pulse rules endpoint (never the printed test requests) |
|
|
@@ -21,15 +21,18 @@ Every command at a glance — what it does, whether it reads your source, what i
|
|
|
21
21
|
| `login` | Recover a lost credential for an existing site: print an owner-approval link and poll (10 min). Approving **rotates** the credential. Not usable in CI. | No | New credential into `.patchstackrc.local.json` on approval | Device-code request + approval poll |
|
|
22
22
|
| `uninstall` | Signal Patchstack that the package is being removed: an unclaimed record is deleted, a claimed one is flagged. Does **not** touch local files. | No | Nothing local | Removal signal |
|
|
23
23
|
|
|
24
|
-
Only `map` reads your source, and only `map --upload` sends anything derived from it. `scan`
|
|
24
|
+
Only `map` reads your source, and only `map --upload` sends anything derived from it. `scan` additionally reads two declarations the project makes about itself — the `<title>` in the root `index.html` and the `name` in `package.json` — to report what the site is called; no other command reads either. `scan` transmits package names + versions, plus the site's own public address and name where the project states them — never source code, file paths, git history, or any environment variable value other than the published URL of this site. `scan --install-paths` additionally sends where each package sits in the dependency tree; it is off unless you pass it.
|
|
25
25
|
|
|
26
26
|
## Package and command behavior
|
|
27
27
|
|
|
28
28
|
- Package: [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect), MIT-licensed, source at https://github.com/patchstack/connect. `npm view @patchstack/connect` shows the live registry metadata.
|
|
29
|
-
- **What is sent to Patchstack is the dependency list
|
|
29
|
+
- **What is sent to Patchstack is the dependency list, plus this site's public address and name** — the dependencies are read from the lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`) or, on bun projects (`bun.lock`/`bun.lockb`), by enumerating the installed packages under `node_modules/` — package names + versions, for vulnerability matching. No source code, no file paths, no git history is ever transmitted.
|
|
30
30
|
- **`scan --install-paths` is the one exception, and it is opt-in.** It adds where each package sits in the dependency tree — repo-relative paths made of `node_modules` segments, plus a workspace directory name when a workspace pins its own copy. They are read from the lockfile's own keys or from the `node_modules` walk, **never from your source tree**: no path to a file you wrote is sent by either form of `scan`.
|
|
31
31
|
- Why it exists: the same package is routinely installed twice at different versions, and without the locations an advisory affecting only one of them cannot be matched to the copy your code actually loads. Node resolves an import by walking up from the importing file, so the location is what distinguishes "you are running the vulnerable copy" from "the vulnerable copy is installed but nothing reaches it". Absent them, every installed version has to be treated as if the app used it — warnings about code you never call, and protection rules pinned to routes that run the safe copy.
|
|
32
32
|
- Why it is off by default: it widens what leaves the machine, so it is your explicit choice and not a consequence of upgrading the package. (`mark-build` additionally stamps built HTML with a coarse stack descriptor that may include hosting-related env variable *names* — e.g. `VERCEL`, `CF_PAGES` — never their values.)
|
|
33
|
+
- **Only `scan` looks for the address and the name.** They are resolved in the one code path that reports them, so `guide`, `status`, `login`, `uninstall`, `mark-build`, `init`, `protect`, `demo-guide` and `map` neither read the host's URL variables nor open `index.html` or `package.json` for this. `setup` and `demo` do, because both run `scan`.
|
|
34
|
+
- **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.
|
|
35
|
+
- **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.
|
|
33
36
|
- **One command reads source files:** `map` (see below) 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. No other command reads source (`protect` writes guard files but does not analyze your code).
|
|
34
37
|
- **`scan` makes up to two source edits, both in the project's root shell:** the disclosure widget's `<script>` tag, and the production marker. Neither runs on `--dry-run`, both are idempotent, both leave a pre-existing manual install untouched, and `"widget": false` in `.patchstackrc.json` disables both.
|
|
35
38
|
- 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.
|
|
@@ -49,6 +52,20 @@ Only `map` reads your source, and only `map --upload` sends anything derived fro
|
|
|
49
52
|
- 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.
|
|
50
53
|
- 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.
|
|
51
54
|
|
|
55
|
+
## Talk to the person while you work
|
|
56
|
+
|
|
57
|
+
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.
|
|
58
|
+
|
|
59
|
+
- **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."
|
|
60
|
+
- **Say what the wait is.** "It's downloading the pieces your site is built out of. Nothing is broken; this is just slow."
|
|
61
|
+
- **Explain the product once, in plain words.** Your site is built out of hundreds of ready-made pieces of code. New security problems are found in them all the time. Patchstack keeps a list of the pieces this site uses and warns the owner when one of them turns out to be unsafe.
|
|
62
|
+
- **Say what changes and what does not.** Nothing visitors see changes. A couple of small config files are added, and a line that lets Patchstack recognise the site.
|
|
63
|
+
- **Hand over the link like a next step, not a log line.** When `setup` prints the dashboard link, say what it is for: "Open this link and sign in — that's what connects the site to your Patchstack account so you can see the reports."
|
|
64
|
+
- **Avoid these words with the user**: lockfile, dependency, manifest, npm, registry, UUID, environment variable, build artifact, package manager. Use them in commands, not in sentences addressed to the person.
|
|
65
|
+
- **Report a failure in terms of what they can do**, not what errored. If a step is genuinely unavailable on this platform, say which one and what it means for them, rather than reporting success.
|
|
66
|
+
|
|
67
|
+
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.
|
|
68
|
+
|
|
52
69
|
## Automated setup
|
|
53
70
|
|
|
54
71
|
1. **Install** (skip if already present), matching the project's package manager:
|
|
@@ -68,6 +85,8 @@ Only `map` reads your source, and only `map --upload` sends anything derived fro
|
|
|
68
85
|
|
|
69
86
|
This provisions or reuses the site, manages the widget, installs and verifies runtime protection, wires dependency-install and build scans, prints a dashboard link, and finishes with the same status shown by `guide`. Re-running it reuses existing configuration, widget tags, guards, and commands rather than duplicating them.
|
|
70
87
|
|
|
88
|
+
**If the request you were given includes a claim token**, pass it exactly as given: `npx @patchstack/connect setup --claim-token <token>`. The site is then created in the person's own Patchstack account and `setup` prints their dashboard link for it — there is no sign-in step to relay. The token comes only from the person's Patchstack dashboard; never invent one, never write it to a file, never print it back. If `setup` reports that the token had expired or was not recognised, the site is not connected: hand over the dashboard link it prints instead, and tell the person they can copy a fresh prompt from the dashboard.
|
|
89
|
+
|
|
71
90
|
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
91
|
|
|
73
92
|
**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.
|
|
@@ -132,6 +151,7 @@ It is server-only. Never put it in the widget tag, client bundles, or public env
|
|
|
132
151
|
## Rules
|
|
133
152
|
|
|
134
153
|
- Never invent or guess a UUID — the scan provisions it, the widget silently no-ops on a fake one.
|
|
154
|
+
- Never invent or guess a claim token either. One is only ever handed to you by the person, from their own Patchstack dashboard; pass it with `--claim-token` (or `PATCHSTACK_CLAIM_TOKEN`) and nowhere else — not into `.patchstackrc.json`, not into a committed file, not into your reply.
|
|
135
155
|
- The CLI never opens the dashboard link and never asks for Patchstack credentials.
|
|
136
156
|
- 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.
|
|
137
157
|
- If a step fails, stop and report it. Don't proceed with placeholders.
|
package/README.md
CHANGED
|
@@ -96,6 +96,8 @@ patchstack-connect --version Print the installed version
|
|
|
96
96
|
Options (for scan, setup, and status):
|
|
97
97
|
--site-uuid <uuid> Override the configured site UUID
|
|
98
98
|
--endpoint <url> Override the API endpoint
|
|
99
|
+
--claim-token <token> (scan, setup) Connect the site to the account that issued
|
|
100
|
+
the token (from the dashboard's "Connect website" prompt)
|
|
99
101
|
--dry-run (scan only) Print the payload without posting
|
|
100
102
|
|
|
101
103
|
Options (for demo and demo-guide):
|
|
@@ -107,7 +109,7 @@ Options (for demo and demo-guide):
|
|
|
107
109
|
|
|
108
110
|
Precedence (highest wins):
|
|
109
111
|
|
|
110
|
-
1. CLI flag (`--site-uuid`, `--endpoint`)
|
|
112
|
+
1. CLI flag (`--site-uuid`, `--endpoint`, `--claim-token`)
|
|
111
113
|
2. Environment variable
|
|
112
114
|
3. `.patchstackrc.local.json` in the current directory (the credential)
|
|
113
115
|
4. `.patchstackrc.json` in the current directory
|
|
@@ -118,6 +120,7 @@ Environment variables:
|
|
|
118
120
|
- `PATCHSTACK_ENDPOINT` — override the API endpoint (default `https://api.patchstack.com/monitor/pulse/manifest`)
|
|
119
121
|
- `PATCHSTACK_TIMEOUT_MS` — request timeout in milliseconds (default `30000`)
|
|
120
122
|
- `PATCHSTACK_ENVIRONMENT` — manifest label: `production` (default) or `sandbox`
|
|
123
|
+
- `PATCHSTACK_CLAIM_TOKEN` — connect the site straight to your account (see *Connecting straight to your account*)
|
|
121
124
|
|
|
122
125
|
Two files, because one value is public and the other is not.
|
|
123
126
|
|
|
@@ -152,6 +155,17 @@ The credential's file is never committed, so CI needs `PATCHSTACK_API_KEY` in th
|
|
|
152
155
|
|
|
153
156
|
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.
|
|
154
157
|
|
|
158
|
+
### Connecting straight to your account
|
|
159
|
+
|
|
160
|
+
The dashboard's "Connect website" prompt carries a **claim token**. Pass it to the first `setup` (or `scan`) and the site it provisions is created in your account, so there is no dashboard link to open afterwards:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npx @patchstack/connect setup --claim-token <token>
|
|
164
|
+
# or: PATCHSTACK_CLAIM_TOKEN=<token> npx @patchstack/connect setup
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The token names your account, not the project: it is never written to `.patchstackrc.json` or the credential file, it is sent to Patchstack as a request header rather than in the manifest body, and it stops working within a day. A token that has expired (or one Patchstack does not recognise) leaves the site exactly as a scan without one would — unconnected, with the dashboard link printed — and `scan` says so. Re-running `setup` with the same token against a site already in your account is a no-op that says the site is already connected; a site that belongs to a different account is left alone.
|
|
168
|
+
|
|
155
169
|
### Sandbox and production manifests
|
|
156
170
|
|
|
157
171
|
Every `scan` sends an environment label with its dependency manifest. The default is `production`; sandboxed builders should set `PATCHSTACK_ENVIRONMENT=sandbox` in the sandbox process only. Patchstack stores and deduplicates manifests per environment, so an iterative workspace scan does not replace the last production manifest.
|
|
@@ -230,11 +244,21 @@ Lower-level pieces are also exported: `scanLockfile`, `buildWirePayload`, `postM
|
|
|
230
244
|
{ "name": "axios", "version": "1.6.0" },
|
|
231
245
|
{ "name": "lodash", "version": "4.17.15" },
|
|
232
246
|
{ "name": "lodash", "version": "4.17.21" }
|
|
233
|
-
]
|
|
247
|
+
],
|
|
248
|
+
"url": "https://your-app.example.com",
|
|
249
|
+
"name": "ToDo Application"
|
|
234
250
|
}
|
|
235
251
|
```
|
|
236
252
|
|
|
237
|
-
That's the entire payload
|
|
253
|
+
That's the entire payload: the package names and versions from your lockfile, plus what your project says about the site itself — its public address and its name. No source code, no file paths, no secrets.
|
|
254
|
+
|
|
255
|
+
The address is included so the site in your dashboard shows where it lives instead of a placeholder, and so Patchstack can check the published page still carries what was scanned. It is taken from `url` in `.patchstackrc.json` (or `PATCHSTACK_SITE_URL`) if you set one; otherwise from the one 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. No other environment variable's value is read, and `url` is left out entirely when none of those says anything — a build on your own machine sends no address. An address that is not how the public reaches a website is refused: any IP address, any single-label host such as `localhost`, and the reserved suffixes (`.local`, `.internal`, `.test`, `.invalid`). If you set `url` yourself and it fails those checks, `scan` stops and says so rather than sending a different address.
|
|
256
|
+
|
|
257
|
+
The name is what the dashboard calls the site. It is taken from `name` in `.patchstackrc.json` (or `PATCHSTACK_SITE_NAME`) if you set one; otherwise from the `<title>` of the project's root `index.html`; otherwise from the `name` in `package.json`, unless that is a template placeholder such as `vite_react_shadcn_ts`. It is left out when nothing qualifies. Those two files are read only by `scan` — the commands that never post a manifest do not open them.
|
|
258
|
+
|
|
259
|
+
You can see exactly what would be sent, without sending it, by running `npx @patchstack/connect scan --dry-run`: the preview it prints is the request body itself. Patchstack only applies either field to a site that does not have one yet: it never re-points a site whose address is real, and never replaces a name set in the dashboard.
|
|
260
|
+
|
|
261
|
+
One thing travels outside that body: a claim token, when you pass one (`--claim-token` / `PATCHSTACK_CLAIM_TOKEN`), is sent as the `X-Patchstack-Claim-Token` request header so that the site is created in your account. Without one, no such header is sent.
|
|
238
262
|
|
|
239
263
|
### `scan --install-paths` (opt-in)
|
|
240
264
|
|