From 1651b97755eb8e759098fd2ab4dce3673e47a575 Mon Sep 17 00:00:00 2001 From: Dave Jong Date: Fri, 14 Aug 2026 10:33:07 +0200 Subject: [PATCH 1/2] map: upload the attack surface to Pulse behind an explicit --upload flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leg 2's client half. `map` described the app's surface and the server could receive one, but nothing sent it, so per-site parameter pinning only ever worked on hand-fed data. patchstack-connect map --upload -> POST monitor/pulse/input-map/ -> "uploaded the attack surface (revision 1)" / "unchanged since revision 1" / a reported failure Opt-in, never implied. This is the only path that transmits anything derived from source code, so it takes an explicit flag rather than happening because a site UUID exists. With the flag the document goes to Patchstack instead of stdout — printing a full structural document AND sending it is noise. Fail-open by construction: every path returns an outcome instead of throwing, because this runs inside someone's build. A 422 is reported as a schema mismatch by version ("update @patchstack/connect") rather than retried or coerced — it means one side implements a different map schema, and guessing at compatibility is exactly how a consumer misreads a document. `unchanged` is a first-class result: most builds do not change the surface, and the server keeps one revision per distinct surface, not per deploy. Disclosure, which is the part that must not lag the code: README and AGENT-INSTALL previously said `map` "transmits nothing", which would have become false the moment this shipped. Both now state exactly what `--upload` sends — route paths, parameter names, the dependency behind each sink, file/line locations — and what it does not: no source code, no file contents, no environment variable values. Same file and bullet as the pending tier-wording fix, so whichever lands second needs a trivial rebase. Verified end to end against a local Pulse server rather than only with mocks: a real project's map uploaded as revision 1, an unchanged rebuild reported unchanged, a new endpoint stored revision 2, rule generation pinned `post.sql` and `post.q` from it, and the public rules endpoint served both. That run also turned up a middleware gap on the server side (the ingest path was missing from the CSRF exemption list) which every server-side feature test missed, because `postJson` bypasses CSRF. 12 upload tests; 940 total. Co-Authored-By: Claude Opus 4.8 --- AGENT-INSTALL.md | 5 +- README.md | 5 +- src/cli.ts | 34 +++++++++- src/client.ts | 70 ++++++++++++++++++++ tests/input-map-upload.test.ts | 115 +++++++++++++++++++++++++++++++++ 5 files changed, 223 insertions(+), 6 deletions(-) create mode 100644 tests/input-map-upload.test.ts diff --git a/AGENT-INSTALL.md b/AGENT-INSTALL.md index 113ac27..5a41e38 100644 --- a/AGENT-INSTALL.md +++ b/AGENT-INSTALL.md @@ -6,12 +6,13 @@ This versioned reference ships inside `@patchstack/connect` and documents each s - 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. - **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. (`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.) -- **One command reads source files, locally:** `map` (see below) parses your server source to report your app's attack surface. It runs only when you invoke it, prints to stdout, and transmits nothing. No other command reads source (`protect` writes guard files but does not analyze your code). +- **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). - **`scan` makes one source edit, and only after a successful post:** it adds (or updates) the disclosure widget's `