Skip to content

fix(protect): type-safe scaffolded guard.ts (strict TanStack build)#75

Merged
ejntaylor merged 1 commit into
mainfrom
fix/protect-template-types
Jul 14, 2026
Merged

fix(protect): type-safe scaffolded guard.ts (strict TanStack build)#75
ejntaylor merged 1 commit into
mainfrom
fix/protect-template-types

Conversation

@patchstackdave

Copy link
Copy Markdown
Contributor

A real patchstack-connect protect install failed the target app's tsc build with two errors in the scaffolded guard.ts:

guard.ts: TS2345 … Type 'string' is not assignable to type '"block" | "dry-run" | undefined'.
start.ts: TS2322 … Type 'Promise<unknown>' is not assignable to type 'Promise<RequestServerResult | Response>'.

Cause & fix

  • mode was inferred as string in the published template → the createProtection(...) argument was rejected. Pin it: const mode: "block" | "dry-run" = ….
  • screenResponse(response: unknown): Promise<unknown> (added in feat(protect): scaffold response screening + first scaffolder tests #70) made the request-middleware server fn return Promise<unknown>, which TanStack's RequestMiddlewareServerFnResult rejects. Make it a generic passthrough screenResponse<T>(response: T): Promise<T>, so screenResponse(await next()) preserves next()'s return type. (Only guard.ts changes — the start.ts error is caused by this signature, so it clears with no start.ts edit.)

Both were reproduced under tsc --strict in isolation and confirmed fixed. Added a scaffolder regression test asserting the guard ships these type-safe signatures.

Note

connect's own typecheck doesn't compile the .ts templates against a real TanStack app, which is why this slipped through #70. The new assertion is a content guard; a full template-typecheck-against-TanStack pass is a possible follow-up.

🤖 Generated with Claude Code

A `patchstack-connect protect` install failed `tsc` in the target app with two
errors in the scaffolded guard:

- `mode` inferred as `string`, not `"block" | "dry-run"` → createProtection arg
  rejected. Pin the annotation: `const mode: "block" | "dry-run" = …`.
- `screenResponse(response: unknown): Promise<unknown>` made the request
  middleware's server fn return `Promise<unknown>`, which TanStack's
  RequestMiddlewareServerFnResult rejects. Make it a generic passthrough
  `screenResponse<T>(response: T): Promise<T>` so `screenResponse(await next())`
  preserves next()'s return type.

Both reproduced + fixed under `tsc --strict` in isolation. Added a scaffolder
regression test asserting the guard ships these type-safe signatures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderbuds

coderbuds Bot commented Jul 14, 2026

Copy link
Copy Markdown

Introduces explicit union types and generics for strict type safety in guard.ts.

🎯 Quality: 100% Elite · 📦 Size: Small

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

See how your team is trending →

@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

@ejntaylor ejntaylor merged commit dd833ed into main Jul 14, 2026
4 checks passed
@ejntaylor ejntaylor deleted the fix/protect-template-types branch July 14, 2026 16:38
patchstackdave added a commit that referenced this pull request Jul 15, 2026
… rules

Two scaffolder features (both build on the #75 guard.ts type-safety fix, included here):

Route-level WAF (env-gated, off by default):
- guard.ts exports guardRequest(request) — runs the request-phase rules via fetchGuard
  on a cloned request and returns a 403 on a match (fail-open).
- the scaffolded request middleware calls it only when PATCHSTACK_ROUTE_WAF=1, so a plain
  `GET /?file=../../etc/passwd` gets a 403. Opt-in because it runs broad request rules on
  all traffic (classic-WAF false-positive surface); the default only screens the data path.

`protect --demo`:
- seeds a broad, many-class sample rule set (src/protect/templates/demo-rules.json, generated
  from + kept in lockstep with examples/protect/demo-rules.json minus its _demo vectors) instead
  of the high-precision starter, and SKIPS baking a site UUID so the local sample rules stay
  active (a baked UUID makes the guard fetch live Pulse rules instead).
- default install now writes the starter rules only if absent (no longer clobbers a
  customized rules.json on re-run).
- disclosed in the CLI help + AGENT-INSTALL.md; PATCHSTACK_MODE / PATCHSTACK_ROUTE_WAF added
  to the env list.

Verified: real CLI `protect --demo` seeds the 11-class bundle; route-WAF blocks a path-traversal
URL (403) and allows benign; +8 scaffolder tests incl. a template/examples parity check. 416 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
patchstackdave added a commit that referenced this pull request Jul 15, 2026
… rules (#77)

* feat(protect): opt-in route-level WAF (env) + `protect --demo` sample rules

Two scaffolder features (both build on the #75 guard.ts type-safety fix, included here):

Route-level WAF (env-gated, off by default):
- guard.ts exports guardRequest(request) — runs the request-phase rules via fetchGuard
  on a cloned request and returns a 403 on a match (fail-open).
- the scaffolded request middleware calls it only when PATCHSTACK_ROUTE_WAF=1, so a plain
  `GET /?file=../../etc/passwd` gets a 403. Opt-in because it runs broad request rules on
  all traffic (classic-WAF false-positive surface); the default only screens the data path.

`protect --demo`:
- seeds a broad, many-class sample rule set (src/protect/templates/demo-rules.json, generated
  from + kept in lockstep with examples/protect/demo-rules.json minus its _demo vectors) instead
  of the high-precision starter, and SKIPS baking a site UUID so the local sample rules stay
  active (a baked UUID makes the guard fetch live Pulse rules instead).
- default install now writes the starter rules only if absent (no longer clobbers a
  customized rules.json on re-run).
- disclosed in the CLI help + AGENT-INSTALL.md; PATCHSTACK_MODE / PATCHSTACK_ROUTE_WAF added
  to the env list.

Verified: real CLI `protect --demo` seeds the 11-class bundle; route-WAF blocks a path-traversal
URL (403) and allows benign; +8 scaffolder tests incl. a template/examples parity check. 416 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(protect): reconcile start.ts guards in place (upgrade existing installs)

patchStart previously skipped an already-wired start.ts, so a re-run after a
connect upgrade never refreshed the middleware (e.g. an existing app couldn't
pick up the new route-WAF hook). Make the generated blocks MANAGED:

- wrap each middleware block in `// #region patchstack-guard` … `// #endregion`
  markers; a re-run replaces the marked region in place (upgrade), and the guard
  import line is refreshed wholesale.
- migrate a legacy (pre-markers) block: bound it from its `const …` line (plus the
  comment header above) to its terminating `});`, and replace with the marked block.
- fresh installs insert as before. All paths stay idempotent (a second run is a no-op).

Verified end-to-end: re-running the CLI on a legacy-wired app adds the route-WAF
hook + markers + refreshed import with no duplication. +1 upgrade test (417 total).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants