Guarding the proxy
A base:/dir:
app proxies any
sub-path under its prefix — that's what lets a whole multi-file bundle
work from one registration. But it also means an unauthenticated client
can ask for paths that don't exist, or ones you never meant to expose.
Left unchecked, a flood of guaranteed-miss names would drive an
unbounded stream of outbound fetches. Two defenses keep it bounded:
manifest:
opt-in · exact
Declare the exact files an app ships. Anything else is a
404
decided locally, before
any upstream fetch. You choose precisely what's reachable.
negative cache
always on
Even with no manifest, a definitive upstream 404
is briefly remembered (negative_ttl), so a repeated miss
isn't re-fetched — and concurrent upstream fetches are capped.
Probe it
Each row fetches /apps/<name>/<path>
through the proxy and shows the status. Watch
secret.js: the guarded
app blocks it (it's not in the manifest) while the open
app serves the very same file. The non-existent
views/ghost.mjs
is a 404
either way — but the guarded app never fetches it, and the open app
negative-caches the miss (re-run and its latency drops).
The allowed files still make a working island
The manifest only blocks what isn't listed — the entry, its lazy view
chunks, stylesheet, icon and data file are all allowed, so
dashboard-guarded
mounts as a real code-split island (click the tabs to stream each
chunk in through the proxy):
How it's wired
Same origin, registered twice — the only difference is the
manifest: key:
config :keen_phoenix_svelte,
apps: %{
# only the files in keen-manifest.json may be served — else 404, no fetch
"dashboard-guarded" => %{
base: "…/dashboard/",
entry: "main.mjs",
manifest: "keen-manifest.json"
},
# no manifest — any sub-path is proxied (misses hit upstream, then cache)
"dashboard-open" => %{base: "…/dashboard/", entry: "main.mjs"}
}
The manifest is just the list of servable files — each path relative to
base:. Three shapes are auto-detected: a
flat JSON array
(below), a Vite manifest.json
(its output files are pulled out for you), or a
newline list. Generate it with the
keenManifest() Vite plugin, or hand-write it like this:
// dashboard/keen-manifest.json — the exact files this app ships
["main.mjs", "dashboard.css", "icon.svg", "data.json",
"views/overview.mjs", "views/charts.mjs", "views/table.mjs"]
How the proxy reads it, per request
-
The entry
(
main.mjs) and the manifest file itself are always allowed — even if you forget to list them. -
Any other sub-path must be an exact match
in the list (paths are normalized; nested paths like
views/charts.mjsmatch at any depth). -
A miss is a
404decided locally, before any upstream fetch — the origin is never touched. - The manifest is fetched and cached like any file, and re-parsed only when the bundle changes (keyed by ETag).
-
If the manifest itself can't be loaded, the app fails open
—
negative_ttlandmax_concurrent_fetchesstill bound abuse, so it's a tightening, not a single point of failure.