Edge routing

A server holds far more than a website: source code, configuration, a database’s worth of raw content, logs. The question that earns trust isn’t “is the site secure?” but “what can an outsider actually reach?” On CTXR the answer is deliberately tiny. The edge — the first thing every request meets — is a narrow doorway that serves a short allow-list and refuses everything else, including the files that make the site run.

It behaves less like a web server pointed at a folder and more like a firewall: nothing is reachable unless it is explicitly let through.

Three kinds of request, nothing else

Before any application code runs, the edge sorts a request into one of three shapes — and anything that doesn’t fit is rejected at the door:

a page     →  the template is run, and HTML comes back   (never the template itself)
an asset   →  an allow-listed file from a gated folder    (css, js, an image, a feed)
the API    →  the single /api/ endpoint                   (bearer-checked, see Access & permissions)

That’s the whole surface. There is no “serve whatever file sits at this path” behaviour to misconfigure.

The same door for every host

Before it even looks at the path, the edge resolves which tenant a request belongs to — the landing page, a management module (platform, account, onboard), or a space — and the rules below apply identically to each. A management module is a directory with its own build/, data/ and config/, exactly like a space, so its source and configuration are just as unreachable. One doorway, one rulebook, for every domain on the platform.

What can be served

  • Rendered pages. When a request maps to a page, the edge executes the PHP template and returns the resulting HTML. The template runs; it is never handed over. You receive the output, never the program.
  • Allow-listed static assets, from a few gated roots only. Static files are served exclusively from /assets/, /cdn/ and /files/, plus the space’s /feed/ route — and only when the file’s extension is on a whitelist of safe static types (stylesheets, scripts, images, fonts, JSON data). A path outside those roots, or a file type not on the list, is simply not served.
  • The API, at the single /api/ endpoint, where every call is authenticated and permission-checked (see Access & permissions).

What can never be served

  • PHP source. .php is not on the static whitelist and no route maps to it. Templates are run, never downloaded — there is no URL that returns the code behind a page.
  • The content graph and the settings. The raw nodes (data/), the space configuration (config/), and the hand-written build/ source are not served roots. They sit on disk with no URL at all — an outsider cannot fetch a node’s raw JSON or read a config file, because nothing routes there.
  • Anything that tries to climb out. Path-traversal (../), dot-prefixed segments, and any request touching /config/ are blocked outright, before a single line of application code executes.

A second perimeter in front

The edge isn’t even the first thing a request meets: a CDN and web application firewall (Cloudflare) sits in front of it. Before traffic reaches the platform at all, that outer layer turns away the internet’s background noise — probes for dot-files and well-known sensitive paths (/.git, /.env, /config/, /data/, /build/, xmlrpc.php, stray config and YAML files, and the like), and a long roster of abusive or pointless crawlers by user-agent. None of those would get anywhere on CTXR — the edge refuses them anyway — but stopping them upstream means they never cost the platform a cycle. The one deliberate exception to the dot-path blocking is /.well-known/, which stays open because that’s where TLS certificates are validated and renewed.

So the perimeter is doubled: the CDN/WAF rejects the obvious bad traffic, and the edge enforces the narrow allow-list for everything that gets through. Two independent gates, neither relying on the other.

Why this builds trust

The reassuring part is structural, not a setting you switch on. Your content’s source of truth, your configuration, and your templates all live on the server — and none of them have an address. The site is reachable; the server behind it is not. This is the request-side mirror of the two foundations the rest of the platform rests on: that a space is a physical boundary and that everything is file-based. The files exist, but the only way in is the doorway — and the doorway only ever hands back a rendered page, an allow-listed asset, or an authenticated API call.