Skip to content

[ENG-3564] map: upload the attack surface to Pulse behind an explicit --upload flag - #134

Merged
patchstackdave merged 2 commits into
mainfrom
davejong/eng-3564-connect-post-the-attack-surface-map-to-pulse-map-leg-2
Aug 14, 2026
Merged

[ENG-3564] map: upload the attack surface to Pulse behind an explicit --upload flag#134
patchstackdave merged 2 commits into
mainfrom
davejong/eng-3564-connect-post-the-attack-surface-map-to-pulse-map-leg-2

Conversation

@patchstackdave

Copy link
Copy Markdown
Contributor

Closes the loop between two halves that already existed: map described the app's surface, the server can receive one, nothing sent it.

patchstack-connect map --upload
  → POST monitor/pulse/input-map/<site uuid>
  → "uploaded the attack surface (revision 1)" / "unchanged since revision 1" / a reported failure

Design calls

Opt-in, never implied. This is the only path that transmits anything derived from source code, so it takes an explicit flag rather than firing because a site UUID happens to exist. With the flag the document goes to Patchstack instead of stdout.

Fail-open by construction. Every path returns an outcome rather than throwing — 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 the two sides implement different map schemas, and guessing at compatibility is exactly how a consumer misreads a document. unchanged is a first-class result, not a failure.

Disclosure — the part that must not lag the code

README and AGENT-INSTALL both said map "transmits nothing". That becomes false the moment this ships, so 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 env values).

⚠️ Same file and bullet as #133 (the tier-wording fix), so whichever lands second needs a trivial rebase. Both are field-test gated and travel with the package, so the --persona hostile run belongs against the published tarball at release.

Verified end to end, not just with mocks

Against a local Pulse server, with a real project:

upload #1                    → uploaded the attack surface (revision 1)
upload #2 (same surface)     → unchanged since revision 1 — nothing to store
upload #3 (endpoint added)   → uploaded the attack surface (revision 2)
generate                     → pinned post.sql (/report) + post.q (/export), exact-local
GET /monitor/pulse/rules/…   → both served, dry-run enforcement

That run also found a bug the server-side tests could not: the ingest path was missing from the CSRF exemption list, so a real client got 419 while every feature test passed (postJson bypasses CSRF). Fixed on the saas branch.

12 upload tests, 940 total, typecheck clean.

@coderbuds

coderbuds Bot commented Aug 14, 2026

Copy link
Copy Markdown

Implements a clear opt-in --upload flag with comprehensive coverage tests.

🎯 Quality: 100% Elite · 📦 Size: Medium

📈 This month: Your 67th PR — above team average · Averaging Excellent

See how your team is trending →

@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

patchstackdave and others added 2 commits August 14, 2026 14:11
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/<site uuid>
      -> "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 <noreply@anthropic.com>
Review point on the upload client: `{ result: 'stored' }` with no revision, or revision 0, was reported as
a success with `revision: 0`. That is not a state the server can be in, and announcing it would claim an
upload that cannot be pointed at afterwards — worse than reporting a failure.

A revision must now be a positive integer for either success result to be accepted; anything else is a
failure with the generic unexpected-response message. Covered for a missing revision, zero, and a
non-numeric one.

941 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@patchstackdave
patchstackdave force-pushed the davejong/eng-3564-connect-post-the-attack-surface-map-to-pulse-map-leg-2 branch from 235c0ba to 9b2ee83 Compare August 14, 2026 12:11
@patchstackdave
patchstackdave merged commit f6b46c3 into main Aug 14, 2026
5 checks passed
@patchstackdave
patchstackdave deleted the davejong/eng-3564-connect-post-the-attack-surface-map-to-pulse-map-leg-2 branch August 14, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants