Skip to content

Unify runtime protection on the vendored node-waf lineage (+ TanStack server-function guard)#43

Merged
devlob merged 9 commits into
mainfrom
pulse-protect-node-waf
Jul 13, 2026
Merged

Unify runtime protection on the vendored node-waf lineage (+ TanStack server-function guard)#43
devlob merged 9 commits into
mainfrom
pulse-protect-node-waf

Conversation

@devlob

@devlob devlob commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What

Unify the connector's runtime-protection lineage on the vendored node-waf engine, and add the TanStack server-function guard so protect covers both ways a Lovable app talks to its database.

Draft — this reconciles two divergent 0.3.x protect lineages, so it needs @ejntaylor's eyes on the lineage decision before merge. Details below.

Why

Validating the runtime guard on a fresh Lovable app surfaced a real gap: modern Lovable scaffolds mutate data through TanStack server functions (createServerFn) using a server-side Supabase client, which bypasses the browser-client tunnel the guard patched. A stored-XSS marked payload sailed through and was saved. Fixed by also registering the guard as a function middleware.

Separately, two 0.3.x lineages diverged on npm:

Line Source protect behaviour
0.3.0 / 0.3.1 / 0.3.2 this branch vendored engine, @patchstack/connect/protect, server-function guard, per-piece idempotent
0.3.3 (current latest) main (#36#41) scaffolds engine.js/manifest.js, --manifest, request middleware only

0.3.3 became latest and regressed the server-function fix. This PR lands the vendored lineage on main so main is canonical and a v0.3.4 tag supersedes 0.3.3 with the fix included.

What this changes

  • Vendored engine: the node-waf RuleEngine ships inside the package (@patchstack/connect/protect); the guard scaffolds only guard.ts + rules.json (no engine.js/manifest.js in the user's app).
  • Two guards, one policy: protect wires src/start.ts as both a request middleware (browser→Supabase tunnel) and a function middleware (patchstackFunctionGuard → inspects server-function args). Same block/dry-run/fail-open policy on both paths.
  • Per-piece idempotent patchStart: re-running protect (incl. after an upgrade) reconciles only what's missing — no duplication, no silent skip.
  • Ships types for the ./protect subpath.
  • Drops the --manifest flag and the scaffolded engine.js/manifest.js templates.

Preserved from main (nothing reverted)

In-package install guide (AGENT-INSTALL.md, #38), mark-build build-stack descriptor (#35), tag-driven release workflow (#39/#40), the guide CLI command, and the verify-first install prompt (#41). package.json stays at the 0.2.11 base (release version comes from the tag).

Decision for review (@ejntaylor)

The main trade-off: this retires the scaffolded local manifest.js + package_cond mechanism you advanced in the 0.3.3 line. The vendored engine still supports package-conditional rules — but sourced from the Patchstack rules API (or the bundled fallback) rather than a manifest file generated into the user's app. If you want the local-manifest precision kept as well, we can fold it back in; flagging so it's a conscious call, not a silent drop.

Validation

  • Typecheck clean; 126 tests pass (includes the server-function guard cases).
  • Validated live end-to-end on a fresh Lovable app: the same UI task-add carrying <img src=x onerror="window.__ps_pwn_2026=1"> that got stored pre-fix is refused post-fix (server function throws the WAF receipt, task never saved, marker never set); a benign task still saves.

Release

Tag-driven: after merge, cut v0.3.4 to publish a latest that contains the server-function guard (superseding 0.3.3).

devlob added 8 commits July 13, 2026 13:03
patchstack-connect protect [--manifest] scaffolds the WAF guard into a
TanStack Start + Supabase app (engine, guard, rules, manifest), patches the
generated Supabase client + Start entry idempotently, and wires a manifest
refresh into prebuild. --manifest regenerates the package manifest only.

The guard tunnels browser data traffic through an in-app server guard that
runs the rule engine and blocks exploit payloads against known-vulnerable
packages (virtual patching) with zero changes to the user's own code.
…sses tsc

Lovable's build type-checks; guard.ts imported ./engine.js and ./manifest.js
(plain JS, no declarations) -> TS7016. Ship .d.ts for both, type the rules
cast as Rule[], and copy templates post-build (tsup's dts pass was clobbering
the .d.ts when copied via onSuccess). Verified: fresh scaffold -> tsc clean.
…untime protection (0.3.0)

Merges ps-node's protection into @patchstack/connect so a vibe app installs ONE package:

- Vendors the node-waf RuleEngine + adapters, createProtection, and the Supabase-tunnel
  guard into src/protect/, exported as @patchstack/connect/protect.
- `patchstack-connect protect` now scaffolds a thin guard that imports
  @patchstack/connect/protect (no vendored engine files in the app, no local manifest).
- Real engine (rule_v2, inline_xss) with rules from the Patchstack API (cached) + a bundled
  fallback. Always-on by default; PATCHSTACK_MODE=dry-run for log-only.

Replaces the earlier stopgap hand-rolled engine. 116 tests pass (3 new protect tests).
main is untouched; pulse-protect-command branch + its releases kept for rollback.
…pes (0.3.1)

Modern Lovable apps mutate data via TanStack server functions (createServerFn) using a
server-side Supabase client, which bypasses the browser-client tunnel the guard patched — so
exploit payloads reached the DB. Register the guard on functionMiddleware too:

- runtime: createServerFnGuard({protection}) — inspects decoded server-fn args via the same
  fetchGuard policy (args become the request body; engine resolves post.<field> identically).
- guard.ts template: shared protection + inspectServerFn(); handleGuardRequest unchanged.
- install.ts: patchStart registers patchstackFunctionGuard in functionMiddleware (adds the key
  if absent), alongside the existing requestMiddleware tunnel. Idempotent, no user code touched.
- ship hand-authored dist/protect.d.ts + add "types" to the ./protect export (removes the
  .d.ts shim strict-TS builds needed).
- tests: +2 server-fn guard cases (118 total).

Covers both Lovable data shapes (browser-direct AND server functions), fully installer-driven.
… 0.3.2

The single-marker idempotency check (skip if patchstackGuard present) meant re-running protect
after a connect upgrade silently did nothing — so an app wired by 0.3.0 never gained the new
server-function guard. Rework patchStart to reconcile each piece independently: extend the guard
import, add each middleware definition, and register each guard only when absent. Re-running now
adds exactly what's missing (verified: 0.3.0-wired app gains patchstackFunctionGuard with no
duplication; a second run is a no-op).
…lineage

Two 0.3.x protect lineages had diverged. main (0.3.3, #36-#41) advanced the
scaffold-engine + local-manifest/--manifest approach; this branch vendored the
node-waf engine into the package (@patchstack/connect/protect) and added the
TanStack server-function guard. This takes the vendored lineage as canonical:

- scaffolds only guard.ts + rules.json (the WAF engine ships in the package)
- protect wires BOTH request middleware (browser tunnel) AND function middleware
  (TanStack server functions); per-piece idempotent so upgrades reconcile cleanly
- drops the scaffolded engine.js/manifest.js templates and the --manifest flag
  (the vendored engine handles package-conditional rules via the API/rules)

Preserves all of main's non-protect advances: in-package install guide,
mark-build build-stack descriptor, tag-driven release workflow, guide command,
verify-first install prompt. Typecheck clean; 126 tests pass.
@coderbuds

coderbuds Bot commented Jul 13, 2026

Copy link
Copy Markdown

Comprehensive vendored runtime protection with clear modular adapters and rich features.

🎯 Quality: 60% Average · 📦 Size: Oversized — strongly consider breaking this down

🛡️ Standards: no pre-flight fit check ran for this change — wire assess-change-fit into your coding agents to catch size before opening.

📈 This month: Your 57th PR — above team average · Averaging Good

See how your team is trending →

@devlob devlob marked this pull request as ready for review July 13, 2026 16:54
@devlob

devlob commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

/review

@devlob devlob merged commit 538c9e9 into main Jul 13, 2026
4 checks passed
@devlob devlob deleted the pulse-protect-node-waf branch July 13, 2026 17:21
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