@patchstack/connect 0.5.1 → 0.5.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
@@ -18,6 +18,7 @@ Every command at a glance — what it does, whether it reads your source, what i
18
18
  | `status` | Re-print the site UUID + dashboard URL and check whether the site still exists (active / removed / could not verify). | No | Nothing | Site-existence check |
19
19
  | `init <site-uuid>` | Optional: pre-seed `.patchstackrc.json` with an existing UUID. | No | `.patchstackrc.json` only | Nothing |
20
20
  | `mark-build` | Stamp built HTML with a production flag + build fingerprint and ensure the widget tag in built pages. Run as a `postbuild` step. | No | Build output only (`dist/ build/ out/ .output/public`) — never source | Nothing |
21
+ | `claim` | Attach the site to a Patchstack account from the terminal: print a link the user opens to sign in (or sign up) and poll (10 min). Whoever approves becomes the owner. Does **not** rotate the credential. Same result as opening the dashboard link `scan` prints. Not usable in CI. | No | Nothing, unless the server issues a credential for a checkout that had none — then `.patchstackrc.local.json` | Device-code request + approval poll |
21
22
  | `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
23
  | `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
24
 
@@ -142,6 +143,46 @@ It is server-only. Never put it in the widget tag, client bundles, or public env
142
143
 
143
144
  `setup` performs both steps automatically. The explicit commands are for manual setup or repair. If verification reports a generic or existing framework seam, complete the printed source edit and re-run `--check`; do not report protection as active until it exits successfully.
144
145
 
146
+ `--check` reads the app's source. It can establish that the guard is imported and called on a request
147
+ path; it cannot establish that a request ever reaches it — an app can wire the guard onto one server
148
+ and serve traffic from another, and that passes. To settle the difference there is an opt-in check
149
+ that **starts the application**:
150
+
151
+ ```
152
+ npx @patchstack/connect protect --check --runtime
153
+ ```
154
+
155
+ It launches the project's entry with `node`, moves the HTTP listeners **that process** opens to an
156
+ ephemeral loopback port, sends one request per listener carrying a per-run challenge, and reports
157
+ whether the scaffolded guard seam answered it. Exit `0` runtime traversal reached the seam, `1` a
158
+ listener answered and the seam did not, `2` it could not be established — neither a pass nor a
159
+ failure, with the structural checks still standing on their own.
160
+
161
+ Exit `2` is the answer for everything this cannot speak for, and the reason is always printed. The
162
+ common one is an entry that needs the project's own toolchain (a TypeScript entry, a framework
163
+ launcher, a watcher, another runtime, anything reached through a package manager), which this never
164
+ installs, builds or invents. The others are about scope: **the run answers for one process, one
165
+ thread, and one discovery window.** If the app attempts to start another process, the launch is
166
+ refused and the answer is `2`. A child can daemonize after it starts without declaring that in its
167
+ launch options, so allowing it would make the end-of-run process-group cleanup a claim the verifier
168
+ cannot establish. The app sees `EPERM`. A worker thread is also `2`: it inherits the listener
169
+ handling, but it cannot report back, so its listeners can be neither counted nor asked. So is a
170
+ listener that bound an address other than loopback, one that cannot be probed, and anything the app
171
+ opens after the discovery window has closed — the app is asked to stop and its acknowledgement is
172
+ what closes that window, so a run that never gets one is `2` as well. An inherited `NODE_OPTIONS`
173
+ that would run code before the listener handling is in place — a `--require` or `--import` in your
174
+ environment — is `2` too, and is refused before the app is launched rather than after.
175
+
176
+ A worker handed a replacement environment that does not preserve the propagated `NODE_OPTIONS` is
177
+ refused outright, because it would not load the listener handling. The app sees `EPERM`, and the run
178
+ reports `2`.
179
+
180
+ What a pass says is exactly: **runtime traversal reached the scaffolded guard seam.** It does not say
181
+ rules were delivered, that the deployed app is wired, or that ordinary traffic is blocked.
182
+
183
+ Nothing else runs the application. `protect`, `protect --check`, `setup`, `guide`, `scan`, `status`
184
+ and `mark-build` only read and write files.
185
+
145
186
  5. **Commit** `.patchstackrc.json`, the updated `package.json`, the guard/framework source changes, and the layout/HTML file carrying the widget tag (and the production marker, when `scan` wrote one into a JSX root), so every developer and CI run reports to the same site.
146
187
 
147
188
  **Do not commit `.patchstackrc.local.json`.** That file holds the API key issued at provision; the scan writes it and adds it to `.gitignore`, and tells you if it could not. `.patchstackrc.json` holds only the site UUID and settings, and the UUID is public by design — it ships in the widget tag in served HTML.
@@ -391,7 +432,8 @@ Two more endpoints the package can call, for completeness:
391
432
  ## Verifying the install
392
433
 
393
434
  - `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`).
394
- - `npx @patchstack/connect protect --check` verifies the runtime guard is connected to the request path.
435
+ - `npx @patchstack/connect protect --check` verifies from the source that the runtime guard is connected to the request path. It does not run the app.
436
+ - `npx @patchstack/connect protect --check --runtime` additionally **starts the app** on a loopback port and sends it one request, to establish that a request reaches the guard seam. Opt-in, and the only command that runs the application; exit `0`/`1`/`2` as described in step 4.
395
437
  - 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.
396
438
  - On the deployed site, the button appears only after a deploy that includes these source changes.
397
439
 
@@ -404,6 +446,54 @@ These are **two independent states** — never conflate them:
404
446
 
405
447
  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`, `.patchstackrc.local.json`, the dependency) is still in the project; want me to remove it?"* — not "Patchstack is still installed."
406
448
 
449
+ ## Attaching the site to an account — `claim`
450
+
451
+ A scan provisions the site without an owner. It is monitored from that moment, but its reports are
452
+ only visible once someone attaches it to a Patchstack account. Opening the dashboard link that `scan`
453
+ and `status` print does that. `claim` does the same thing from the terminal, for when the link is
454
+ output nobody is looking at.
455
+
456
+ ```
457
+ npx @patchstack/connect claim
458
+ ```
459
+
460
+ ```
461
+ Your code: BQDX-7ZKM
462
+ Claim at: https://api.patchstack.com/monitor/pulse/device?code=BQDX-7ZKM
463
+
464
+ Open that link and sign in to Patchstack — or create an account — to attach
465
+ this site to it. Whoever approves becomes the site's owner.
466
+ ```
467
+
468
+ **If you are an assistant running this, the sequence is three steps:**
469
+
470
+ ```
471
+ 1. npx @patchstack/connect claim → prints the link, exits straight away
472
+ 2. Give the user the link. Wait for them to say they have done it.
473
+ 3. npx @patchstack/connect claim → the SAME command again, after they confirm.
474
+ ```
475
+
476
+ - **Step 1 exits immediately** when the output is piped or captured, rather than blocking for ten
477
+ minutes on a link you cannot see yet.
478
+ - **Step 3 is the same command.** While a request is still valid it resumes rather than restarting, so
479
+ running `claim` again never invalidates the link the user is looking at. If they have not finished
480
+ yet it says so, with the time remaining, and exits 0.
481
+ - **An already-claimed site exits 0, not 1.** It is the goal state. Re-running after the user claimed
482
+ in the browser reports that and stops; it is not a setup failure.
483
+
484
+ `claim --wait` is the blocking variant. Prefer the plain re-run — it keeps each command short, which
485
+ is what fits a conversation.
486
+
487
+ ### What it does not do
488
+
489
+ - **It does not rotate the credential.** The project already holds one from provisioning, and CI,
490
+ deploys and other checkouts keep working. (`login` is the command that rotates; use it only to
491
+ recover a lost credential.) A credential is written only when the server issues one for a checkout
492
+ that had none.
493
+ - **It does not open a browser**, and it cannot claim on the user's behalf: the approval is a person
494
+ signing in to Patchstack.
495
+ - **It does not work in CI** — there is no browser and no one to sign in. It refuses and exits 1.
496
+
407
497
  ## Recovering a lost credential — `login`
408
498
 
409
499
  Use this when the project **already has a site** but its credential is gone or rejected: `.patchstackrc.local.json` was deleted, the repo was cloned without it (it is git-ignored, so a fresh clone never has it), a container was recycled, or ingest started failing with 401. The site UUID `login` needs comes from the committed `.patchstackrc.json`.
package/README.md CHANGED
@@ -73,6 +73,13 @@ patchstack-connect protect Install/reconcile the always-
73
73
  guard. Auto-wires supported server stacks;
74
74
  use --check to verify or --demo for local rules.
75
75
  Also run by setup; never run by scan/guide/mark-build.
76
+ --check reads your source and never runs the app.
77
+ --check --runtime STARTS THE APP on a loopback
78
+ port and sends it one request, to establish that
79
+ a request reaches the guard seam. Opt-in, and the
80
+ only mode that runs the app. Exit 0 traversed,
81
+ 1 a listener answered instead of the guard,
82
+ 2 could not be established (see below).
76
83
  patchstack-connect map [--dir p] [--out f] [--upload]
77
84
  Print a JSON map of this project's attack
78
85
  surface: server entry points, the inputs each
@@ -90,6 +97,24 @@ patchstack-connect demo node-serialize Production-backed walkthrough
90
97
  patchstack-connect demo-guide node-serialize Read-only, state-aware instructions for the
91
98
  local demo, including the next exact command,
92
99
  expected proof, and cleanup.
100
+ patchstack-connect claim [--wait] Attach this site to a Patchstack account from
101
+ the terminal. Prints a link for the user to open
102
+ and sign in (or sign up); whoever approves becomes
103
+ the site's owner. Piped or captured, it prints the
104
+ link and exits — run it again once the user
105
+ confirms. Does not rotate the credential. Same
106
+ result as opening the dashboard link scan prints.
107
+ Not usable in CI
108
+ patchstack-connect login [--wait] Recover this site's credential when
109
+ .patchstackrc.local.json has been lost. Prints a
110
+ link for the site's OWNER to approve. Approving
111
+ ROTATES the credential, so CI, deploys and other
112
+ machines using the old one must be updated.
113
+ Not usable in CI
114
+ patchstack-connect uninstall [options] Signal Patchstack that this package is being
115
+ removed. An unclaimed site record is deleted; a
116
+ claimed one is flagged for its owner. Does NOT
117
+ touch local files
93
118
  patchstack-connect help Print help
94
119
  patchstack-connect --version Print the installed version
95
120
 
@@ -105,6 +130,69 @@ Options (for demo and demo-guide):
105
130
  (default: http://localhost:3000/api/tasks)
106
131
  ```
107
132
 
133
+ ### Verifying the guard at runtime (opt-in)
134
+
135
+ `protect --check` reads the app's source. That establishes the guard is imported and called on a
136
+ request path — not that a request ever reaches it. An app can wire the guard onto one server and serve
137
+ its traffic from another, and the structural check passes.
138
+
139
+ `protect --check --runtime` settles that one question by **starting the application**:
140
+
141
+ ```
142
+ npx @patchstack/connect protect --check --runtime
143
+ ```
144
+
145
+ It runs the project's entry with `node`, moves the HTTP listeners **that process** opens to an ephemeral
146
+ loopback port (so a port already in use is not a failure), sends one request per listener carrying a
147
+ challenge generated for that run, and reports whether the scaffolded guard seam answered it. The child
148
+ is started in its own process group and killed with it — including if you interrupt the command. A
149
+ listener it cannot probe — a Unix socket, a file descriptor, a handed-over handle, an HTTP/2 server — is
150
+ reported and prevented from binding at all, rather than opened on the verifier's behalf.
151
+
152
+ **One process is the scope — one thread of it, and one discovery window.** If the app attempts to start
153
+ another process, the launch is refused and the answer is `2`, whatever that process is. A child can
154
+ daemonize after it starts without declaring that in its launch options, so allowing it would make the
155
+ end-of-run process-group cleanup a claim the verifier cannot establish. The app sees the launch fail
156
+ with `EPERM`. A **worker thread** is also `2`: it inherits the listener handling, but a worker has no
157
+ channel back, so its listeners can be neither counted nor asked. A worker handed a replacement
158
+ environment that does not preserve the propagated `NODE_OPTIONS` is refused, because it would not load
159
+ that handling at all.
160
+
161
+ Everything else the run finds also ends it this way: a listener that bound an address other than
162
+ loopback, a listener it cannot probe, and anything the app opens **after the discovery window closes** —
163
+ a second listener appearing while the first is still being asked cannot join a set that is already being
164
+ answered from. Closing that window is a handshake: the app is asked to stop opening listeners and its
165
+ acknowledgement is what proves nothing is still in flight, so a run that never gets one reports `2`
166
+ rather than passing. An inherited `NODE_OPTIONS` is checked before anything is launched, too: Node reads
167
+ that variable ahead of the command line, so a `--require` or `--import` sitting in your environment would
168
+ run before the listener handling was in place, and the run reports `2` rather than starting the app with
169
+ less containment than it claims. Recognised flags are passed through, with the reporter first.
170
+
171
+ A pass says exactly this: **runtime traversal reached the scaffolded guard seam.** It does not say
172
+ rules were delivered, that the deployed app is wired, or that ordinary traffic is blocked. The
173
+ challenge is generated per run, so a fixed response or a reflected header cannot answer it — but the
174
+ challenge does reach the whole app process, so this establishes traversal in a cooperating app rather
175
+ than against an app written to answer for itself.
176
+
177
+ | Exit | Meaning |
178
+ |---|---|
179
+ | `0` | A request reached the scaffolded guard seam. |
180
+ | `1` | A listener answered and the seam did not — or the structural checks failed, in which case the app is not started at all. |
181
+ | `2` | It could not be established. Neither a pass nor a failure; the structural checks still stand. |
182
+
183
+ Exit `2` is the common answer for entries this deliberately will not start. It runs `node <file>` on a
184
+ file the project already has, and nothing else — no package-manager scripts, no `node_modules/.bin`, no
185
+ build, no install. So a TypeScript entry, a framework launcher (`next start`), a watcher (`nodemon`),
186
+ another runtime (`bun`), or a script wrapped in an environment shim all report unavailable with the
187
+ reason printed. To make such a project verifiable, point a `start` script at a built, directly loadable
188
+ file — the check reports which entry it used and where it came from.
189
+
190
+ Windows reports exit `2` without starting anything: the cleanup this relies on is a POSIX process
191
+ group, and a verification that can leave a server running is worse than an unanswered question.
192
+
193
+ No other command runs your application. `protect`, `protect --check`, `setup`, `guide`, `scan`,
194
+ `status` and `mark-build` only read and write files.
195
+
108
196
  ## Configuration
109
197
 
110
198
  Precedence (highest wins):