Skip to content

[NO-TICKET] Protect Lovable's new template, and give any guard the read-only rules credential - #447

Merged
mariojgt merged 3 commits into
mainfrom
mariot/lovable-server-wrapper
Oct 26, 2026
Merged

mariojgt merged 3 commits into
mainfrom
mariot/lovable-server-wrapper

Conversation

@mariojgt

@mariojgt mariojgt commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

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.ts with tanstackStart: { server: { entry: "server" } }. The wrapper is a plain export default { fetch(request, env, ctx) { … } } object. protect refused both, so agents hand-wrote a guard.

  • Config check: accepts server: { entry: "server" } or "./server" as the only server option. Both name src/server.ts, the default entry. Anything else is still refused.
  • Entry rewriter: wraps the fetch of a plain export default { fetch } object with protectFetch, as it already does for createServerEntry({ fetch }).
  • Own protection: an entry that already builds its own protection is left untouched, and the message explains the options.
  • Types: siteUuid, apiKey, pulseAuth and supabaseUrl accept undefined, so the TanStack templates compile under exactOptionalPropertyTypes, 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.

  • Any rules file is maintained: setup and a keyed scan now also maintain the JSON file that a source importing @patchstack/connect/protect passes 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.
  • Copied cache files work: 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.
  • Clearer warning: the no-credential message now names the read-only credential path first, and the key as the alternative.
  • AGENT-INSTALL.md: a guard that reports no credential means run setup again, 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):

  • Hand-written guard, unchanged code:
    • setup's rules-file steps find its src/lib/…json copy of the cache, stamp it, and rewrite it as a plain rules file with the credential;
    • started with exactly its own options against a stubbed API, the guard exchanges the read-only credential, fetches live rules (origin: api), turns reporting on (on-unverified) and blocks a traversal (403);
    • before this change: 401, zero rules, reporting off, and the attack passes.
  • After removing the hand-written guard:
    • protect wires src/server.ts and protect --check passes;
    • the app type-checks and vite build produces the Cloudflare module;
    • running the built Worker module in Node, a traversal gets 403 and / 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, full npm test (4619 passed) and capabilities:check are green.

Rollout

  • AGENT-INSTALL.md changes, so the hostile field test is owed after the release that carries this, plus lovable for doc accuracy.
  • An affected app updates @patchstack/connect, runs setup again 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

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>
@coderbuds

coderbuds Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

🎯 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 report-ai-usage tool.

See how your team is trending →

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>
@mariojgt mariojgt changed the title [NO-TICKET] Wire protection into Lovable's TanStack server wrapper [NO-TICKET] Protect Lovable's new template, and give any guard the read-only rules credential Oct 26, 2026
@mariojgt

Copy link
Copy Markdown
Contributor Author

/review

1 similar comment
@mariojgt

Copy link
Copy Markdown
Contributor Author

/review

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>
@mariojgt
mariojgt merged commit a9958f0 into main Oct 26, 2026
23 checks passed
@mariojgt
mariojgt deleted the mariot/lovable-server-wrapper branch October 26, 2026 10:14
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