feat(services): read-only file browsing inside a deployed service - #545
Open
DiogoDuart3 wants to merge 1 commit into
Open
feat(services): read-only file browsing inside a deployed service#545DiogoDuart3 wants to merge 1 commit into
DiogoDuart3 wants to merge 1 commit into
Conversation
Adds a Files tab next to Terminal — breadcrumb, directory listing with
sizes, text preview and download — plus the endpoints behind it:
GET /api/services/files/:serviceId/{list,read,download}.
Built on runtime.inContainerExecutor(), which docker and cloud both
implement as sh -c inside the container, so one command string serves
both and there is no docker-only path (getArchive) for cloud to lack.
Gated at the service terminal's admin tier with the same 404-shaped
denial: a container's filesystem holds its .env, so browsing it is
exactly as sensitive as opening a shell in it. Gated on the serviceShell
capability, which excludes bare — bare's inContainerExecutor ignores its
containerId and returns the HOST executor, so a weaker gate would make
this a host filesystem browser.
The probe protocol holds four invariants, each with tests:
1. Commands always exit 0. exec rejects on a non-zero exit with stdout as
the message, so leaning on exit codes would turn every 'file not
found' into a 500. Failures are markers on stdout.
2. Markers are nonce-scoped. Newlines are legal in filenames and the
probe prints names verbatim, so without a nonce a file could be NAMED
to forge 'ERR denied' (whole directory reads as forbidden) or 'END'
(listing truncated while still reporting success). Each probe mints an
unpredictable nonce a filename cannot contain.
3. Every probe is terminated, and reads also verify the decoded length
against the size the container reported. Node's base64 decoder is
lenient about truncated input, so without both a half-read .env would
be served looking complete.
4. Only stdout carries signal, and payloads are base64 — an alphabet with
no tab and no newline — so a stray stderr line can neither forge a
marker nor corrupt a payload on either runtime.
Reads refuse anything that is not a regular file: without that a FIFO or
character device passes every other guard and 'wc -c < /dev/zero' never
returns, bypassing both size caps.
Bounds: 2MB preview, 10MB download, 500 entries per directory (which also
bounds the per-file fork count), and a per-user concurrency cap mirroring
the terminal's. Over-cap results refuse rather than truncate, and a
capped listing is surfaced in the UI.
Verified against real containers on busybox, dash and bash: hostile
filenames, symlinked and broken symlinks, empty directories, devices and
FIFOs, and a 138KB binary round-tripped byte-exact.
Container-read failures answer 500 rather than 502: Cloudflare treats a
502 from the origin as a gateway failure and replaces the body with its
own error page, so the message would be invisible behind a CDN.
Strings translated across all 9 locales so the i18n parity ratchet
tightens rather than growing.
Member
|
It's clean, but we were planning to add our own file explorer. It was originally from Oblien.com. If you're open to implementing it, that would be great. I can open-source it and you can integrate it. It needs a few steps since it's a first-class feature in Oblien, but it supports some advanced functionality. |
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
Adds a read-only Files tab next to Terminal, so a service's filesystem can be browsed from the dashboard instead of only through a shell. Breadcrumb navigation, directory listing with sizes, text preview, and download.
Why
There is currently no way to look at a deployed container's files from the UI.
filesystem.controller.tslooks like it might do this but is a host directory picker for "choose a folder to deploy" — it lists directories only, never file contents, and the dashboard doesn't call it.Design
Substrate. Built on
runtime.inContainerExecutor(), which docker and cloud both implement assh -c <command>inside the container. One command string serves both; no docker-only path such asgetArchive, which cloud has no equivalent for.Authorization. Same admin tier as the service terminal, with the same 404-shaped denial. A container's filesystem holds its
.env, so browsing it is exactly as sensitive as opening a shell in it — gating it lower would let someone denied a shell read everything the shell would have shown them.Runtime gate:
serviceShell. Docker and cloud have it; bare does not, and that exclusion is load-bearing rather than incidental — bare'sinContainerExecutor()ignores itscontainerIdand returns the host executor, so a weaker gate would turn this into a host filesystem browser.Probe protocol. Four invariants, each with tests:
execrejects on a non-zero exit with stdout as the message, so leaning on exit codes would turn every "file not found" into a 500. Failures are markers on stdout.ERR denied(whole directory reads as forbidden) orEND(listing silently truncated while still reporting success). Each probe mints an unpredictable nonce; a filename cannot contain it.ExitCodeis non-zero —nullis falsy — so an early close does not throw, and Node's base64 decoder is lenient about truncated input. Without a terminator plus a length check, a half-read.envwould be served looking complete.Bounds. 2 MB preview, 10 MB download, 500 entries per directory (which also bounds the per-file
wc -cfork count), and a per-user concurrency cap mirroring the terminal's. Over-cap results refuse rather than truncate, and a capped listing is surfaced in the UI — a truncated.envthat looks complete is worse than a refusal.Portability is POSIX sh only — no
find -printf, nostat -c, nols --full-time, none of which exist on busybox, dash and bash alike.Testing
shfor every hostile input), path normalization, marker forgery, truncation detection, size caps and binary sniffing.main.*,-rf, spaces, quotes, embedded newlines), symlinked and broken symlinks, empty directories,/dev/zeroand FIFOs (refused instantly rather than hanging), a 138 KB binary round-tripped byte-exact against the container's own md5, and the entry cap.Notes