Repository navigation
[NO-TICKET] Protect Lovable's new template, and give any guard the read-only rules credential - #447
Merged
Merged
Conversation
Lovable's current TanStack template points TanStack at its own wrapper
in src/server.ts (tanstackStart: { server: { entry: "server" } }), a
plain `export default { fetch }` object. protect refused both the
config option and that entry shape and asked for manual integration,
so agents hand-wrote a guard that had no rules credential and blocked
nothing.
- The config check accepts server.entry "server" or "./server", which
name src/server.ts, the default entry.
- The entry rewriter wraps the fetch of a plain `export default { fetch }`
object with protectFetch, as it does for createServerEntry({ fetch }).
- An entry that builds its own protection is left untouched, with a
message saying what to remove so the managed guard can replace it.
- siteUuid, apiKey, pulseAuth and supabaseUrl accept undefined, so the
TanStack templates compile under exactOptionalPropertyTypes, which
that template turns on. The template typecheck now checks them with it.
- AGENT-INSTALL.md describes the wrapper and says not to hand-write a
guard.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
🎯 Good quality (79%) — Bundles adapter wiring, rules-file discovery and runtime shape changes, with duplicated directory-walk and cache-unwrapping logic. 📦 Medium 🤖 Written by Claude Code — going by its own attribution. Unknown whether a person read it; agents can report that with the |
A guard written by hand passed its rules from a file setup did not know
about, so setup never wrote the read-only rules credential into it. The
guard then could not fetch rule updates without the site key, and its
warning sent agents to the key.
- Setup and a keyed scan now also maintain the JSON file a source that
imports @patchstack/connect/protect passes in (static import, dynamic
import, require or new URL), under any name and in any folder, with
the site's current rules, the read-only credential and the map stamp.
- A copy of the guard's runtime cache entry ({ bundle, etag, ... }) is
read as the bundle it holds: the runtime runs those rules, and setup
rewrites the file as a plain rules file.
- The no-credential warning names the read-only credential first.
- The TanStack message and AGENT-INSTALL.md say a hand-wired guard works
once setup has run, and to run setup rather than ask for the key.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
/review |
1 similar comment
Contributor
Author
|
/review |
daniloradovic
approved these changes
Oct 26, 2026
Keeps main's Patchstack for JavaScript wording in protect.d.ts alongside this branch's optional fields that accept undefined. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Two fixes so a Node app gets working protection, with live rules and no site key, however it ended up wired.
1. Lovable's TanStack server wrapper is wired automatically
Lovable's current TanStack template points TanStack at its own wrapper in
src/server.tswithtanstackStart: { server: { entry: "server" } }. The wrapper is a plainexport default { fetch(request, env, ctx) { … } }object.protectrefused both, so agents hand-wrote a guard.server: { entry: "server" }or"./server"as the only server option. Both namesrc/server.ts, the default entry. Anything else is still refused.fetchof a plainexport default { fetch }object withprotectFetch, as it already does forcreateServerEntry({ fetch }).siteUuid,apiKey,pulseAuthandsupabaseUrlacceptundefined, so the TanStack templates compile underexactOptionalPropertyTypes, which that template turns on. This is a types-only change, so existing guards are not churned. The template typecheck now checks the TanStack templates with it on.2. Any guard gets the read-only rules credential
The hand-written guard passed its rules from a file setup did not recognise: another folder, another name, a dynamic
import(). So setup never wrote the read-only rules credential into it. The guard could not fetch rule updates without the key (401), and its warning ("Set PATCHSTACK_API_KEY") sent the agent to the key.setupand a keyedscannow also maintain the JSON file that a source importing@patchstack/connect/protectpasses in, under any name and in any folder. They write the site's current rules, the read-only credential and the map stamp into it. Ownership is decided the same way as for a scaffolded guard: a source that imports the protection runtime and that exact file. A JSON file only non-Patchstack code imports is left alone.{ "bundle": …, "etag": … }) is read as the bundle it holds. The runtime runs those rules, and setup rewrites the file as a plain rules file.AGENT-INSTALL.md: a guard that reports no credential means runsetupagain, not ask the person for the key. The managed guard is still preferred.How it was verified
On a copy of an affected app (dependencies installed with scripts off, so nothing was sent to Patchstack):
src/lib/…jsoncopy of the cache, stamp it, and rewrite it as a plain rules file with the credential;origin: api), turns reporting on (on-unverified) and blocks a traversal (403);protectwiressrc/server.tsandprotect --checkpasses;vite buildproduces the Cloudflare module;/gets 200.Tests (each behaviour test fails on the previous code):
tests/protect/framework-entries.test.ts: the wrapper shape, refusals, and an entry with its own protection.tests/bundled-rules.test.ts: a hand-wired file under any name and folder; static, dynamic and require imports; cache-shape conversion; a JSON file only non-Patchstack code imports is left alone.tests/build-stamp.test.ts: stamping a cache-shaped, hand-wired file.tests/protect/cache-shaped-rules.test.ts: the runtime runs cache-shaped rules.npm run typecheck,npm run build, fullnpm test(4619 passed) andcapabilities:checkare green.Rollout
AGENT-INSTALL.mdchanges, so the hostile field test is owed after the release that carries this, pluslovablefor doc accuracy.@patchstack/connect, runssetupagain and republishes. A hand-written guard then works as it is, and removing it in favour of the managed guard is still preferred.🤖 Generated with Claude Code