@patchstack/connect 0.3.30 → 0.3.32
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 +180 -20
- package/README.md +17 -3
- package/dist/{chunk-LLKP5EJS.js → chunk-3G2I6QL6.js} +1 -1
- package/dist/chunk-3G2I6QL6.js.map +1 -0
- package/dist/cli.js +309 -130
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +48 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -3
- package/dist/index.d.ts +58 -3
- package/dist/index.js +48 -18
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +2749 -1373
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +316 -0
- package/dist/protect.d.ts +134 -18
- package/dist/protect.edge.js +2776 -1412
- package/dist/protect.edge.js.map +3 -4
- package/dist/protect.js +2697 -1355
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-ZSP76JWQ.js → refresh-manifest-SWCPQ52Z.js} +49 -19
- package/dist/refresh-manifest-SWCPQ52Z.js.map +1 -0
- package/package.json +44 -11
- package/dist/chunk-LLKP5EJS.js.map +0 -1
- package/dist/refresh-manifest-ZSP76JWQ.js.map +0 -1
package/AGENT-INSTALL.md
CHANGED
|
@@ -21,12 +21,15 @@ 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` transmits nothing but package names + versions — never source code, env var values, file paths, or git history.
|
|
24
|
+
Only `map` reads your source, and only `map --upload` sends anything derived from it. `scan` transmits nothing but package names + versions — never source code, env var values, file paths, or git history. `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 only** — 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 env var values, no file paths, no git history is ever transmitted.
|
|
29
|
+
- **What is sent to Patchstack is the dependency list only** — 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 env var values, no file paths, no git history is ever transmitted.
|
|
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
|
+
- 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
|
+
- 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.)
|
|
30
33
|
- **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).
|
|
31
34
|
- **`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.
|
|
32
35
|
- 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.
|
|
@@ -141,18 +144,112 @@ would have stopped while it is still in dry-run. Two separate paths, with differ
|
|
|
141
144
|
token; the `apiKey` itself is not sent to the log endpoint. Disable with `PATCHSTACK_TELEMETRY=off`, or
|
|
142
145
|
`reportFirewallLog: false` in `createProtection`.
|
|
143
146
|
- **Every rule that matched** goes to `monitor/pulse/detections/<your site uuid>` — including matches that
|
|
144
|
-
blocked, which are reported on both paths.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
147
|
+
blocked, which are reported on both paths. It exists because a rule carrying `dry-run` blocks nothing, so
|
|
148
|
+
without it nothing distinguishes a rule that is protecting from one that is quietly wrong.
|
|
149
|
+
|
|
150
|
+
**This is on by default for a site enrolled with Patchstack that is running Patchstack-delivered rules**,
|
|
151
|
+
and off otherwise. Specifically, it requires all of: a provisioned site UUID, rules that came from
|
|
152
|
+
Patchstack rather than from a local bundle, and a resolvable credential. A local install, or a guard
|
|
153
|
+
running its own `rules`, sends nothing.
|
|
154
|
+
|
|
155
|
+
Switch it off with **`PATCHSTACK_REPORT_DETECTIONS=0`**, or `reportDetections: false` in
|
|
156
|
+
`createProtection`, or `PATCHSTACK_TELEMETRY=off` which covers all telemetry. `reportDetections` is an
|
|
157
|
+
opt-out only — passing `true` cannot switch reporting on for a site that is not enrolled.
|
|
158
|
+
|
|
159
|
+
`protection.detectionReporting` names the current state locally, so a guard that is not reporting says
|
|
160
|
+
which reason applies: `on`, `disabled-by-config`, `disabled-by-telemetry-opt-out`, `not-enrolled`,
|
|
161
|
+
`no-managed-rules`, or `unavailable-no-credential`.
|
|
162
|
+
|
|
163
|
+
**How the state reaches Patchstack, and what that costs on the network.** The state travels as a header
|
|
164
|
+
on the rules request the guard already makes — no extra request for it. Two of the six never travel:
|
|
165
|
+
`not-enrolled` makes no site-addressed request at all, and `unavailable-no-credential` cannot produce an
|
|
166
|
+
authenticated one, and the header is withheld from unauthenticated requests. Those two are local
|
|
167
|
+
diagnostics only.
|
|
168
|
+
|
|
169
|
+
There is one case that does add a request. The header is set before the rules request finishes, so a
|
|
170
|
+
guard booting with no cached rules declares `no-managed-rules` and then receives managed rules on that
|
|
171
|
+
same response. When that happens it sends **one immediate POST to the detections endpoint containing the
|
|
172
|
+
corrected state and no detections at all** — an empty `detections` array plus `reporting_state`. It is
|
|
173
|
+
sent once per process, only when the state changed, and never when the guard already had cached rules.
|
|
174
|
+
Without it, a guard with rule refreshing switched off would leave Patchstack holding the pre-resolution
|
|
175
|
+
answer for the life of the process.
|
|
176
|
+
|
|
177
|
+
What a detection report contains, per matched rule — on every phase, whatever fired it: the rule id, the revision of the rule when the
|
|
178
|
+
bundle carried one, the identifier of the rule bundle in use, which phase matched,
|
|
179
|
+
whether it was enforced, the request path **with the query string's values removed**,
|
|
180
|
+
that query's parameter names, the method, and a timestamp. Each batch also carries a count of reports dropped when traffic outran the flush,
|
|
181
|
+
so a partial sample is not read as a complete one.
|
|
182
|
+
|
|
183
|
+
Two fields depend on the phase, because one kind of detection has a client and the other does not. A
|
|
184
|
+
**request or response** detection also carries the user agent,
|
|
185
|
+
and the client address together with where that address came from.
|
|
186
|
+
An **egress** detection — a rule that fired on a call your application made outbound — carries neither:
|
|
187
|
+
the call was your application's own, so there is no visitor to attribute it to, and those fields read
|
|
188
|
+
`null` and `unavailable` rather than being guessed at. "What values a report can contain" below says the
|
|
189
|
+
same thing about captured evidence.
|
|
190
|
+
|
|
191
|
+
Every field is bounded in size, and an event that had to be shortened says so.
|
|
192
|
+
`truncated` lists **which fields were shortened**.
|
|
193
|
+
`parameters_total` records **how many parameters the rule reads**, when a rule reads more than the event
|
|
194
|
+
names.
|
|
195
|
+
`query_keys_total` records **how many query parameters the request carried**, counted as DISTINCT names,
|
|
196
|
+
when it carried more than the event lists — a parameter repeated three times is one name to look up. The
|
|
197
|
+
names themselves are the ones the guard addresses a parameter by, so `?first+name=x` is reported as
|
|
198
|
+
`first name`. Both appear only when something really was shortened, so their absence is not a claim of its own —
|
|
199
|
+
and a shortened route or rule id is marked rather than passed off as complete, because a reader must not
|
|
200
|
+
use one as a key believing it names the whole thing.
|
|
201
|
+
|
|
202
|
+
Delivery is retried, up to four attempts per batch, with exponential backoff and jitter, honouring a
|
|
203
|
+
`Retry-After` header when the endpoint sets one. Only failures worth retrying are retried — unreachable,
|
|
204
|
+
rate-limited, or a server error; a batch that was refused on its merits is not sent again. Every attempt
|
|
205
|
+
of one batch carries the same `Idempotency-Key` header, and a different batch carries a different one, so
|
|
206
|
+
a redelivery is identifiable as the same batch rather than a new one — an acknowledgement can be lost
|
|
207
|
+
after the server has already taken a batch. One request is in flight at a time, so a slow endpoint slows
|
|
208
|
+
the queue rather than opening more sockets; each attempt is abandoned after 10 seconds, so a request that
|
|
209
|
+
never settles cannot hold that slot; and a batch that exhausts its attempts is dropped and counted rather
|
|
210
|
+
than retried forever. Stopping a guard makes one last attempt at whatever is outstanding and counts
|
|
211
|
+
anything it could not send. `stop()` returns a promise that settles once the reporters have finished or
|
|
212
|
+
been given up on, so a shutdown handler can `await protection.stop()` instead of racing the last batch
|
|
213
|
+
against process exit. Each reporter has its own budget, and when it runs out that reporter is ended: its
|
|
214
|
+
requests are aborted, it starts nothing further, and it discards what it was holding. An abort is a
|
|
215
|
+
request to stop, not a guarantee — a transport that ignores it is detached rather than completed, so
|
|
216
|
+
"resolved" means the reporter is finished with it, and a runtime that kills the process still wins
|
|
217
|
+
regardless. Every detection event ends up delivered, refused or dropped and is reported in the health
|
|
218
|
+
counts; block-log records have no counters, so one lost to a failed send or an expired shutdown is
|
|
219
|
+
reported nowhere.
|
|
220
|
+
|
|
221
|
+
The client address is reported with its **provenance**, because an address is only as trustworthy as
|
|
222
|
+
whatever supplied it. `client_ip_source` is one of `runtime` (the address the transport observed),
|
|
223
|
+
`trusted-proxy` (read from a forwarded header, through peers you declared via `trustedProxy`), or
|
|
224
|
+
`unavailable`. When it is `unavailable` the `client_ip` field is **omitted entirely** rather than sent
|
|
225
|
+
empty, so a missing address cannot read as a failed lookup of a real one. A forwarded header is never
|
|
226
|
+
trusted implicitly: with no `trustedProxy` policy the address is whatever the transport observed, and in a
|
|
227
|
+
runtime that exposes no transport peer there is no address to report at all.
|
|
228
|
+
|
|
229
|
+
### Behaviour change: how the client address is determined
|
|
230
|
+
|
|
231
|
+
The guard resolves the client address itself, once per request, and shares that one answer with rule
|
|
232
|
+
matching, block logging and detection reports — so those cannot disagree about who a request came from.
|
|
233
|
+
Two consequences if you are upgrading:
|
|
234
|
+
|
|
235
|
+
- **Express and Node: forwarded headers are no longer read implicitly.** Earlier versions took the
|
|
236
|
+
address from `X-Forwarded-For`, `CF-Connecting-IP` or `X-Real-IP` (the Node guard), or from `req.ip`
|
|
237
|
+
(the Express guard, where it reflects Express's own `trust proxy` setting). Neither source can be
|
|
238
|
+
verified by the guard, and any client can send those headers, so both guards now read the transport
|
|
239
|
+
peer. **If your app runs behind a proxy or load balancer, addresses will now show as the proxy's**
|
|
240
|
+
until you declare your proxies with `trustedProxy` (below) — which affects attribution in reports and
|
|
241
|
+
any rule matching on `server.ip` or `REMOTE_ADDR`.
|
|
242
|
+
- **Fetch runtimes report no address at all.** A WHATWG `Request` exposes no transport peer, so a Fetch
|
|
243
|
+
guard (Workers, Deno, Bun, edge) has nothing to observe, and no forwarded header is accepted in its
|
|
244
|
+
place under any `trustedProxy` policy: `client_ip_source` is `unavailable` and no address is sent.
|
|
245
|
+
Earlier versions reported the forwarded header here, so an address-scoped rule that appeared to work on
|
|
246
|
+
such a runtime was matching a client-supplied value.
|
|
247
|
+
|
|
248
|
+
`trustedProxy` is the only way to make a forwarded header count. It takes the proxies you actually run —
|
|
249
|
+
`{ peers: ['10.0.0.0/8'] }`, or `{ hops: 1 }` to trust that many hops in from the peer, plus optional
|
|
250
|
+
`header` and `isTrusted` — and the chain is then read from your application inward, stopping at the first
|
|
251
|
+
hop you have not declared. There are no built-in provider presets: a header a provider sets is
|
|
252
|
+
indistinguishable from one a client sent unless you say which peers may set it.
|
|
156
253
|
|
|
157
254
|
The parameter names are **identifiers, and they name the request region they refer to** — `post.title`,
|
|
158
255
|
`get.redirect_to`, `cookie.session`, `server.HTTP_AUTHORIZATION`. So a rule that inspects a cookie or an
|
|
@@ -160,16 +257,79 @@ The parameter names are **identifiers, and they name the request region they ref
|
|
|
160
257
|
definition, not from your traffic, so they describe what is being screened rather than what any request
|
|
161
258
|
contained.
|
|
162
259
|
|
|
163
|
-
What
|
|
164
|
-
|
|
165
|
-
|
|
260
|
+
### What values a report can contain
|
|
261
|
+
|
|
262
|
+
**A request or response detection** carries the request's method and path,
|
|
263
|
+
the query string's parameter **names**,
|
|
264
|
+
the user agent, the client address with its provenance, and a timestamp.
|
|
265
|
+
|
|
266
|
+
**An egress detection** — a rule that fired on a request your application made outbound — carries the
|
|
267
|
+
outbound method and path, the query's parameter names, and a timestamp. It carries **no user agent and no
|
|
268
|
+
client address**: the call was your application's own, so there is no visitor to attribute it to, and
|
|
269
|
+
those fields read `null` and `unavailable` rather than being guessed at.
|
|
270
|
+
|
|
271
|
+
Beyond that baseline, either can include the **values of the parameters a rule names** — and nothing
|
|
272
|
+
else. Counting that a
|
|
273
|
+
rule fired is not enough to act on it: whoever triages a detection still has to decide whether the request
|
|
274
|
+
was really an attack, and for that they need to see what the rule saw.
|
|
275
|
+
|
|
276
|
+
**A rule earns each permission by naming what it reads.** What may be captured is derived from the rule
|
|
277
|
+
itself, never configured per site:
|
|
278
|
+
|
|
279
|
+
- a rule naming a parameter (`post.title`, `cookie.session`, `server.HTTP_AUTHORIZATION`) permits **that
|
|
280
|
+
parameter's value**, because the rule was written to inspect it;
|
|
281
|
+
- a prefix (`post.field_*`) permits the values of keys that match, and no others;
|
|
282
|
+
- a rule reading `raw` or `all` — the whole request — permits **nothing at all**, so the broadest rules
|
|
283
|
+
grant the narrowest capture;
|
|
284
|
+
- **response** values are never captured: the phase that reads them exists to redact secrets, and
|
|
285
|
+
capturing them would collect the very values that redaction stops leaving;
|
|
286
|
+
- **raw request bytes** need an explicit, reviewed opt-in on the individual rule, and are then limited to
|
|
287
|
+
a short prefix of the body.
|
|
288
|
+
|
|
289
|
+
**Everything is bounded, and the bounds report themselves.**
|
|
290
|
+
At most 10 values per detection, at most 512 characters each, and at most 5 values from any one prefix. A value shortened to fit is marked; values a bound
|
|
291
|
+
left out are counted; a value refused because it was not a plain string, number or boolean is counted
|
|
292
|
+
separately; and a read that failed is counted as a failure rather than as absence — so a short list is
|
|
293
|
+
never mistaken for a complete one.
|
|
294
|
+
|
|
295
|
+
**A `capture.plan` identifies the permissions, not the rule.** Each report carries a `capture.plan` reference derived
|
|
296
|
+
from the permissions themselves — which parameters, which prefixes, which bounds — so what a given report
|
|
297
|
+
was permitted to include can be established after the fact, without the rule in front of you. It
|
|
298
|
+
identifies the PERMISSIONS, not the rule: two different rules that read the same parameters share one
|
|
299
|
+
reference, and the rule document is identified by `rule_id` with `rule_revision`.
|
|
300
|
+
|
|
301
|
+
**Capture is the union of everything the rule reads, not only the condition that matched.** The engine
|
|
302
|
+
reports which rule fired, not which of its conditions did, so a rule reading `post.title` and
|
|
303
|
+
`cookie.session` permits both values whichever one triggered the detection. A rule scoped to one parameter
|
|
304
|
+
captures one; a broad rule captures what it is broad about.
|
|
305
|
+
|
|
306
|
+
**One header value always travels, whatever the rule names: the User-Agent** — on a request or response
|
|
307
|
+
detection, where there is a client to attribute. It is part of the baseline above, because attribution is
|
|
308
|
+
what this channel is for and a detection without it cannot be told from another client's. It is the only
|
|
309
|
+
exception to the rule-scoped policy, and the only header value sent without a rule naming it.
|
|
310
|
+
|
|
311
|
+
**What a report never contains:** the value of any parameter the matched rule does not name — the
|
|
312
|
+
User-Agent above excepted; any response body, header or status value;
|
|
313
|
+
the request body, other than the reviewed raw prefix above;
|
|
314
|
+
and anything at all from a rule that reads the whole request without that opt-in.
|
|
315
|
+
|
|
316
|
+
**One qualification on the query string.** The exclusion above is about baseline URL metadata: `route` and
|
|
317
|
+
`query_keys` describe a URL without disclosing what was in it. It is not a promise about captured
|
|
318
|
+
evidence. A rule that names `egress.url` reads the outbound URL, so its capture carries that URL as the
|
|
319
|
+
rule read it — query values included. That is the rule-scoped policy working as described, not an
|
|
320
|
+
exception to it: the rule named the parameter, so the parameter's value travels. The value recorded is the request as the
|
|
321
|
+
engine resolved it — URL- and entity-decoded — and not the result of a rule's own further mutations.
|
|
322
|
+
|
|
323
|
+
Reports are batched, capped in memory, retried a bounded number of times, and dropped if Patchstack cannot
|
|
166
324
|
be reached — a reporting failure never delays or fails a request.
|
|
167
325
|
|
|
168
|
-
The endpoint needs a credential, so
|
|
169
|
-
|
|
326
|
+
The endpoint needs a credential, so an enrolled site with none resolved starts nothing: the guard warns
|
|
327
|
+
once at boot and `protection.detectionReporting` reads `unavailable-no-credential` instead of `on`.
|
|
170
328
|
When reporting is on, `protection.detectionHealth()` returns local counts — detections attempted,
|
|
171
329
|
acknowledged, refused or unreachable, dropped for queue pressure — and the time of the last
|
|
172
|
-
acknowledgement.
|
|
330
|
+
acknowledgement. Counts for the state POST described above are kept separately under `capability`, since it
|
|
331
|
+
carries no detections and would otherwise read as one. Those counts stay in your process; nothing extra is
|
|
332
|
+
sent to report them.
|
|
173
333
|
|
|
174
334
|
`protection.stop()` stops everything the guard has running in the background — the rule-refresh loop, the
|
|
175
335
|
block-log reporter, the detection reporter — and flushes what is buffered. `protection.stopRefresh()` is
|
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.com) for continuous vulnerability monitoring. Scans your `package-lock.json` and reports installed packages so Patchstack can match them against its vulnerability database and notify you when something needs patching.
|
|
4
4
|
|
|
5
|
-
For how this repo fits with the wider Patchstack ecosystem (`saas`, `hub`, `patchstack-website`, `patchstack-connect`), see [`patchstack/saas` → `docs/ecosystem.md`](https://github.com/patchstack/saas/blob/main/docs/ecosystem.md).
|
|
6
|
-
|
|
7
5
|
## Agent-assisted setup
|
|
8
6
|
|
|
9
7
|
Copy this request into a coding assistant, or run the same command yourself:
|
|
@@ -89,6 +87,7 @@ patchstack-connect demo-guide node-serialize Read-only, state-aware instru
|
|
|
89
87
|
local demo, including the next exact command,
|
|
90
88
|
expected proof, and cleanup.
|
|
91
89
|
patchstack-connect help Print help
|
|
90
|
+
patchstack-connect --version Print the installed version
|
|
92
91
|
|
|
93
92
|
Options (for scan, setup, and status):
|
|
94
93
|
--site-uuid <uuid> Override the configured site UUID
|
|
@@ -216,6 +215,7 @@ Lower-level pieces are also exported: `scanLockfile`, `buildWirePayload`, `postM
|
|
|
216
215
|
```json
|
|
217
216
|
{
|
|
218
217
|
"ecosystem": "npm",
|
|
218
|
+
"installPathsComplete": false,
|
|
219
219
|
"packages": [
|
|
220
220
|
{ "name": "axios", "version": "1.6.0" },
|
|
221
221
|
{ "name": "lodash", "version": "4.17.15" },
|
|
@@ -224,7 +224,21 @@ Lower-level pieces are also exported: `scanLockfile`, `buildWirePayload`, `postM
|
|
|
224
224
|
}
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
-
That's the entire payload. No source code, no environment variable values, no file paths — just the package names and versions from your lockfile.
|
|
227
|
+
That's the entire payload. No source code, no environment variable values, no file paths — just the package names and versions from your lockfile.
|
|
228
|
+
|
|
229
|
+
### `scan --install-paths` (opt-in)
|
|
230
|
+
|
|
231
|
+
Pass it and each entry also carries where that version is installed:
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{ "name": "lodash", "version": "4.17.15", "paths": ["apps/api/node_modules/lodash"] }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Why you might want it: the two `lodash` entries above are not a contrived example — the same package is routinely installed twice at different versions. An advisory affecting only `4.17.15` cannot otherwise 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 separates "you are running the vulnerable copy" from "the vulnerable copy is installed but nothing reaches it". Without it 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.
|
|
238
|
+
|
|
239
|
+
These are repo-relative locations built from `node_modules` segments, plus a workspace directory name when a workspace pins its own copy. They come 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`.
|
|
240
|
+
|
|
241
|
+
`installPathsComplete` says whether the set is total. It is `false` without the flag, and `false` with it whenever the source cannot supply locations — a `yarn.lock` is flat because hoisting is decided at install time, and a v1 `package-lock.json` records the dependency graph rather than the installed tree. Whenever it is `false`, a missing `paths` means **"not recorded"**, never "not installed there". (The `map` command reads source files locally to report your attack surface; it transmits nothing unless you pass `--upload`, which sends that structural description — route paths, parameter names, the dependency behind each sink, and file/line locations, never file contents — to your own site's endpoint so rules can be pinned to your real parameter names.) Duplicate names with different versions are preserved so transitive vulnerabilities aren't missed. (`mark-build` separately stamps built HTML with a stack descriptor that may include hosting-related env variable *names* — e.g. `VERCEL` — never their values.)
|
|
228
242
|
|
|
229
243
|
## Supported lockfiles
|
|
230
244
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pulse-token.ts"],"mappings":"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","names":[]}
|