Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/protect/templates/guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let _protection: Awaited<ReturnType<typeof createProtection>> | undefined;
async function getProtection() {
if (!_protection) {
// Always-on: block by default. An explicit PATCHSTACK_MODE=dry-run downgrades to log-only.
const mode = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
const mode: "block" | "dry-run" = process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
const token = process.env.PATCHSTACK_WAF_TOKEN;
const siteUuid = PS_SITE_UUID.startsWith("__") ? process.env.PATCHSTACK_SITE_UUID : PS_SITE_UUID;
// Egress SSRF screening: block the app's outbound calls to internal / metadata addresses,
Expand Down Expand Up @@ -80,11 +80,11 @@ export async function inspectServerFn(data: unknown): Promise<{ rule?: string; m
// src/start.ts (the browser→Supabase tunnel screens its own forwarded response). Only acts on a
// web Response (text/JSON/HTML) — anything else, or any error, passes through untouched (fail-open,
// never breaks a response).
export async function screenResponse(response: unknown): Promise<unknown> {
export async function screenResponse<T>(response: T): Promise<T> {
try {
if (!(response instanceof Response)) return response;
const protection = await getProtection();
return protection.screenResponse ? await protection.screenResponse(response) : response;
return (protection.screenResponse ? await protection.screenResponse(response) : response) as T;
} catch {
return response; // fail open
}
Expand Down
9 changes: 9 additions & 0 deletions tests/protect/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ describe('runProtect scaffolder', () => {
expect(start).toContain('functionMiddleware: [patchstackFunctionGuard, attachSupabaseAuth]');
});

it('scaffolds a type-safe guard.ts (compiles under a strict TanStack build)', () => {
runProtect(dir);
const guard = read(dir, 'src/integrations/patchstack/guard.ts');
// Regression: a strict `tsc` build in the target app rejected a `string` mode and a
// `Promise<unknown>` from screenResponse (the middleware return type). Keep both type-safe.
expect(guard).toContain('const mode: "block" | "dry-run" =');
expect(guard).toContain('export async function screenResponse<T>(response: T): Promise<T>');
});

it('is idempotent — re-running does not duplicate wiring', () => {
runProtect(dir);
runProtect(dir);
Expand Down
Loading