Reference

Secure frontend, AI-safe backend: Webflow collections, fallback publishing and grants

A static site built from Webflow has no login, no session and no server deciding anything at request time. That makes it fast and hard to break, and it moves every security decision to two other places: the build, which decides what becomes public, and the runtime behind it, which decides what each caller may do. This article describes how the two pair up in the development patterns used across CRM Sync, PIM Sync and the client sites built on them, and why the pattern holds when AI writes much of the code and makes many of the calls.

This knowledge base is itself published both ways, from one set of Markdown files in GitHub:

It extends Same frontend, AI-secure backend, which covers the runtime half (the worker as the gate, Xano as the record). This article covers the publishing half, and the vocabulary for both.

1. Vocabulary

Memory safety is the guarantee that a program reads and writes only memory it owns, only within the bounds it was given, and only while that memory is still valid. A memory-safe language enforces this for every line, whoever wrote it: Rust at compile time, Python, Java and JavaScript at run time through a garbage collector and bounds checks. It is a property of the running software, not of its data, its network or its permissions.

Runtime and memory

TermDefinition
Memory safetySee above. Removes use-after-free, buffer overflows and data races as a class.
Use-after-freeUsing memory after it has been released, so the program reads whatever now occupies it.
Buffer overflow (over-read)Reading or writing past the end of a block of memory. Heartbleed was an over-read.
Data raceTwo threads changing the same value at the same moment without coordination.
ImmutabilityA value that can't change after it's set. Rust's default; it limits accidental change. It is not undo or rollback.
Safe codeRust code without the unsafe keyword, where the compiler enforces every memory rule.

Identity and permission

TermDefinition
TLSEncryption of data in transit between two machines. It ends where the connection terminates.
Encryption at restEncryption of stored data on disk and in backups. It doesn't decide who the database answers.
AuthenticationEstablishing who the caller is: a person, an app or an agent.
AuthorizationDeciding what that caller may do, to which record, now. Always the application's job.
ClaimA statement inside a signed token about the caller, such as their ID or entitlements (Xano custom claims).
Capability (cap)Permission for one action on one kind of resource, written <plane>:<resource>:<verb>.
GrantThe act, and the record, of giving a capability. Grant = insert, revoke = delete, audit = read.
MandateA signed, scoped, expiring, revocable grant that lets an agent act for someone. Agents bring mandates, never keys.
Least privilegeGiving a caller only what the current task needs, for only as long as it needs it.
Row scopingQuerying by record ID and owner, so a valid caller still reaches only their own rows.
Fail closedWhen a check can't be completed, the answer is no.

AI and publishing safety

TermDefinition
Publish layerEverything a build emits. It is public to every reader, crawler and answer engine, permanently.
Publish grantThe decision that a piece of content may enter the publish layer, or be indexed from it.
Review switchA field (reviewed, claims_checked) whose state is the publish-to-engines grant for one item.
Fallback publishingWhat the pipeline emits when a template, locale or field is missing. Safe only if each fallback emits less.
Field allowlistA template that names its public fields. Fails closed when a field is added. Its opposite, a denylist, fails open.
Mass assignmentA request that sets fields it shouldn't, such as "role": "admin", because the input wasn't restricted to named fields.
Over-fetchingReturning or rendering more of a record than the reader needs; the output-side twin of mass assignment.
Build-time refusalA pipeline stopping on disagreement instead of guessing, the content equivalent of code that doesn't compile.
Answer engineA search or AI system that quotes content directly in its answer. It quotes what was indexed, not what was meant.
noindexA page instruction that keeps it out of search and answer-engine indexes while it stays readable.
Hide vs enforceThe interface hides what a caller can't do; the server refuses it. Only the second is a control.

2. Five layers, five different questions

Security controls are often compared as if they compete. They don't: each answers a different question, and a system can pass four and still leak through the fifth.

LayerThe question it answersWhat provides itWhat it never covers
In transitCan anyone read or change the data on the wire?TLS, provided by Xano, Cloudflare and GitHub PagesAnything after the data is decrypted
At restCan anyone read the stored data from the disk or a backup?Database and storage encryption, usually managedWho the database answers
In the running programCan the software be made to read or write memory it doesn't own?Memory safety: Rust, or a garbage-collected runtimeWhether the program's logic is right
In the logicIs this caller allowed to do this, to this record, now?Authentication and authorization: Xano claims, capability caps, row checksWhat the build already made public
At publishWhat does the build put where everyone, including crawlers and answer engines, can read it?The publishing rules in this articleAnything decided at runtime

TLS and its limits are covered in depth in What TLS actually buys, and where it stops, and the difference from row-level encryption in Transport encryption and row-level encryption. The stack, and which layer may refuse gives each layer of the CRM Sync stack its single question.

The first three are largely handled by platforms. The logic layer is always yours, on any platform, and it is where most real breaches happen: an endpoint that returns another customer's order because it never checked ownership is perfectly encrypted and perfectly memory-safe. The publish layer is the one a static architecture adds. Anything the build emits is a grant to every reader, forever, so it needs the same discipline as an API response.

For how permission itself is modeled here (a scoped claim for one action, not a role for the whole building), see Permissions for AI, in plain terms.

3. Memory safety, in one section

Memory safety (defined in section 1) does not mean code can't be undone or rewound; Rust programs roll back transactions like any other. What it removes is three classes of bug:

Heartbleed (2014) is why this matters for TLS. OpenSSL, written in C, had a buffer over-read in its heartbeat feature: a request claiming a larger payload than it sent got back up to 64 KB of whatever sat in adjacent memory, including passwords, session cookies and private TLS keys. The encryption was sound; the code implementing it wasn't. TLS libraries written in Rust, such as rustls, rule that class of bug out in safe code.

What makes memory safety relevant to AI is that the compiler doesn't care who wrote the code. AI-generated Rust faces the same ownership checks as human Rust, and code that breaks them never ships. In C or C++ a plausible-looking AI-written overflow compiles and runs. Python gets its memory safety from its interpreter instead, as Render and Runtime: a working dictionary explains.

Where Rust actually runs in this estate: Shopify Functions accept Rust compiled to WebAssembly (see the October 1 brief), and the game11ty demo ships a no_std Rust GLB parser. The book above is laid out like the Rust book but built with Eleventy on Node.js, so it inherits no memory-safety guarantee from the resemblance.

4. Webflow as MVC

The sync that turns a Webflow site into static files is a model-view-controller split, and each seam is where a permission belongs.

PartWhat it is hereWho writes it
ModelEach Webflow CMS collection, pulled through the Data API into _data/wf/<collection>.json, one record per item and localeEditors, in Webflow
ViewNunjucks templates: a hand template in _includes/templates/<collection>.njk if one exists, otherwise one generated from the Webflow template pageDevelopers, in the repo
ControllerThe sync script plus the Eleventy build, triggered by Webflow's site_publish and collection_item_published webhooksThe pipeline, in CI

Two rules keep the parts from leaking into each other. Markup travels; behavior never does: a Webflow page arrives as clean HTML, and everything interactive (login, consent, forms) comes from the repo's own components, as described in Fragments on Any Frontend. And each item has one writer: the ingest only manages items it created, so a Webflow-authored article and a Git-authored one share a collection without overwriting each other (Git to Every Surface).

Components follow the same split. A designer marks an element in Webflow (data-component="account", or the class component-account), and the sync swaps it for the repo's components/account.njk, passing only the element's data-* attributes, other classes and visible text as props. Webflow decides where; the repo decides what runs.

5. Fallback publishing: every fallback fails toward less exposure

A static pipeline needs fallbacks, because Webflow will always be partly unfinished: a template page not yet published, a locale not yet translated, a field not yet bound. The rule that makes fallbacks safe is that each one must publish less, never more.

When this is missingThe pipeline falls back toWhy that direction
A hand templateA template generated from the Webflow template pageThe designer's bound fields are the public ones
A usable Webflow template pageA schema template: the name, rich-text fields and images onlyPlain-text fields often hold internal notes, so they stay out
Any template pageThe data only, with no detail pagesNo page is safer than a guessed page
A component file in the repoThe element as Webflow drew it, plus a reportNothing runs that the repo didn't write
A published Korean localeEnglish chrome for Korean pages, and no hreflang pairDon't link pages that don't exist
A review sign-offThe page publishes, but noindex, out of the sitemap, with no JSON-LDUnaudited text is never what an answer engine quotes

The fallback that wasn't safe enough. The schema template rendered every rich-text field, on the reasoning that rich text is content and plain text is notes. One collection had a rich-text field named us_claim_note: an internal note about a regulatory claim. It was empty, so nothing had leaked, but the next note typed into it would have been published. The fix was a hand template that lists its public fields instead of excluding private ones. An allowlist fails closed when someone adds a field; a denylist fails open.

Review switches are publish grants. Legal pages carry a reviewed switch and product pages a claims_checked switch. Until a human (working with AI-assisted regulatory audits) turns it on, the page is published so reviewers can read it in place, but it is marked noindex, left out of the sitemap and given no structured data. Turning the switch on is the grant that lets search and answer engines quote it.

6. Pairing each seam with a grant

Every point where data crosses from one owner to another gets a named grant, enforced in a named place.

SeamThe grantEnforced inWhat happens without it
Collection field → pagePublic-field allowlist in the hand templateThe buildAn internal note is published
Draft → search and answer enginesThe review switchThe build: noindex, sitemap, JSON-LDUnaudited legal or product claims get quoted
Webflow element → running codeComponent placement; props from data-* onlyThe buildDesigner-controlled script runs on the page
Page → per-user dataCapability caps and row scopeThe worker and Xano, per callOne customer sees another's records
Page → analytics and ad tagsConsent, denied by default until grantedThe consent ladder, before any tag loadsPersonal data leaves before consent
Agent → actionA scoped, expiring mandateThe function tool, per callA perfectly signed record of an unauthorized purchase

Three rules from elsewhere in the book make the runtime rows hold:

Consent follows the same shape: deny everything synchronously before any tag exists, then replay the stored decision, then load tags into an already-correct state (Consent Resolution on Higher-Order Load).

7. The same seams in a Python backend

Xano gives you these controls through its interface. In a Python backend such as FastAPI, you bind them yourself, and the binding points are the same seams.

SeamWebflow pipelinePython backend
What may come inThe sync reads only fields the collection schema definesA Pydantic input model with extra="forbid", so a request can't slip in "role": "admin" (mass assignment)
What may go outThe hand template's public-field allowlistresponse_model=UserPublic, which drops any field the model doesn't name, even if the code returns the whole row
Who is callingThe worker's /auth/me, which returns the caller's capsA dependency that verifies the token and loads the user before the endpoint runs
What they may doCapability caps, <plane>:<resource>:<verb>Role or scope dependencies; Casbin for policy files; Django's permissions and django-guardian per object
Which rowsThe worker scopes every query to the callerQuery by ID and owner, never ID alone; or Postgres row-level security
Values into codeProps escaped as JSON; Webflow's {{ escaped before templatingBound SQL parameters instead of string formatting
SecretsNever in Webflow or the static outputEnvironment variables or a secrets manager; SecretStr keeps them out of logs

The row rule is the one most often missed:

@app.get("/orders/{order_id}", response_model=OrderPublic)
def get_order(order_id: int, user: User = Depends(get_current_user)):
    order = session.exec(
        select(Order).where(Order.id == order_id, Order.user_id == user.id)
    ).first()
    if not order:
        raise HTTPException(404)   # 404, not 403: don't confirm the record exists
    return order

Why the enforcement point sits in the backend rather than in database rules for this kind of runtime is covered in Why Xano + AI + e-commerce.

8. Why this is safe for AI-written code

AI changes two things at once: it writes more of the code, and it makes more of the calls. The pattern constrains both.

Build-time refusals constrain what AI builds. The content pipeline borrows the compiler's stance: when something disagrees, the build stops instead of guessing.

The rule that keeps these useful: never relax a refusal to make a build pass. A refusal an AI assistant can switch off is a suggestion, the same way a client-side check is.

Runtime identity constrains what AI does. An agent calls the same endpoints a person's app does, with a token. That token should carry only what the task needs, expire, and be checked on every call, as described in Permissions for AI Agents.

Neither replaces the other. Memory-safe code still serves the wrong customer's data if the ownership check is missing, and a perfect ownership check can still be bypassed by a memory bug like Heartbleed.

9. Checklist

Publishing

Runtime

Pipeline

Sources