You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Warn about vite dev exposure and reflect the course in the page title
SPA:
- DefaultLayout renders a yellow/black hazard-striped v-system-bar at
the top of every page when import.meta.env.DEV is true, with the
text "Served by vite dev with HMR — do not expose this instance
publicly." The whole block is tree-shaken out of the production
vite build.
- App.vue sets document.title from the nav store's courseName
("REF - <course>"), falling back to plain "REF" until hydrate
finishes or if the API call fails.
Docs:
- README gains "Single-port web interface" and "Hot reloading (local
development only)" sections spelling out that --hot-reloading runs
vite dev behind Caddy and must never be used on a publicly
reachable host, and that SPA source changes require ./ctrl.sh build
in prod mode since the bundle is baked into the frontend-proxy
image.
- docs/ARCHITECTURE.md adds /admin and /spa redirect notes, a
Dev-mode security warning block covering the vite dev attack
surface, a note on ctrl.sh's COMPOSE_PROFILES=dev wiring, and a
warning box next to the --hot-reloading command reference.
- .claude/CLAUDE.md splits the web description into separate entries
for the Flask webapp (internal port only) and the Caddy
frontend-proxy, with the full routing table, profile gate, and
security warning.
Copy file name to clipboardExpand all lines: .claude/CLAUDE.md
+13-5Lines changed: 13 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -155,27 +155,35 @@ REF is a containerized platform for hosting programming exercises with isolated
155
155
156
156
### Components
157
157
158
-
1.**Web Application** (`webapp/`) - Flask app on port 8000
158
+
1.**Web Application** (`webapp/`) - Flask app served by uWSGI on internal port 8000 (not published; reached via `frontend-proxy`)
159
159
-`ref/view/` - HTML route handlers (exercises, grading, instances, file browser, visualization, admin student management, system settings, etc.)
160
160
-`ref/services_api/` - JSON endpoints called by services (SSH reverse proxy hooks in `ssh.py`, student container callbacks in `instance.py`)
161
161
-`ref/frontend_api/` - JSON endpoints consumed by the Vue SPA (registration/restore-key in `students.py`, public scoreboard in `scoreboard.py`; mounted under `/api/v2/*` + `/api/scoreboard/*`)
Student-facing pages (registration, restore-key, public scoreboard) are served by the Vue SPA under `/spa/*` and talk to `ref/frontend_api/`. Admin pages live under `ref/view/` as Jinja-rendered HTML. The Caddy `frontend-proxy` container fronts both on a single host port 8000 — it reverse-proxies `/spa/*` to `spa-frontend:5173` (dev, with HMR) or serves a baked SPA bundle (prod), serves Flask's `/static/*` directly, and proxies everything else to `web:8000`.
165
+
Student-facing pages (registration, restore-key, public scoreboard) are served by the Vue SPA under `/spa/*` and talk to `ref/frontend_api/`. Admin pages live under `ref/view/` as Jinja-rendered HTML.
166
166
167
-
2.**SSH Reverse Proxy** (`ssh-reverse-proxy/`) - Rust-based SSH proxy on port 2222
167
+
2.**Frontend Proxy** (`frontend-proxy/`) - Caddy 2 container that fronts the Flask `web` service and the Vue SPA on a **single host port** (`HTTP_HOST_PORT`, default 8000). Multi-stage Dockerfile: stage 1 builds the SPA with Node; stage 2 is `caddy:2-alpine` with `dist/` baked in at `/srv/spa-dist`. Routes:
168
+
-`/spa/*` → `spa-frontend:5173` (dev) or baked `/srv/spa-dist` via `file_server` (prod)
169
+
-`/static/*` → bind-mount of `webapp/ref/static` served directly
170
+
-`/admin`, `/admin/` → 302 to `/admin/exercise/view`
171
+
-`/spa` → 308 `/spa/`
172
+
- everything else → `reverse_proxy web:8000` with `header_up X-Tinyproxy {remote_host}` so Flask's rate limiter keys on the real client IP
173
+
Dev/prod is selected at container start by `entrypoint.sh` via `$HOT_RELOADING`. The `spa-frontend` service is gated behind the `dev` compose profile, and `ctrl.sh` exports `COMPOSE_PROFILES=dev` when `--hot-reloading` is active. **Never run `--hot-reloading` on a publicly reachable host** — `vite dev` is not a production server. The SPA renders a hazard-striped warning banner when `import.meta.env.DEV` is true.
174
+
175
+
3.**SSH Reverse Proxy** (`ssh-reverse-proxy/`) - Rust-based SSH proxy on port 2222
168
176
- Routes student SSH connections to exercise containers
169
177
- Uses web API with HMAC-signed requests for authentication and provisioning
170
178
- Supports shell, exec, SFTP, local/remote port forwarding, and X11 forwarding
171
179
172
-
3.**Instance Container** (`ref-docker-base/`) - Ubuntu 24.04 with dev tools
180
+
4.**Instance Container** (`ref-docker-base/`) - Ubuntu 24.04 with dev tools
173
181
- Isolated per student/exercise under `ref-instances.slice` cgroup
174
182
- SSH server on port 13370
175
183
- Contains `ref-utils` for submission testing
176
184
-`task`/`_task` scripts for submission testing, `reset-env` for container reset
Copy file name to clipboardExpand all lines: README.md
+32Lines changed: 32 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -44,6 +44,38 @@ After successfully building REF, the database has to be initialized:
44
44
./ctrl.sh down
45
45
```
46
46
47
+
### Single-port web interface
48
+
49
+
The Flask web app (`/`, `/admin/*`, `/api/*`), the Vue student SPA
50
+
(`/spa/*`), and Flask's static assets (`/static/*`) are all fronted by
51
+
a single Caddy reverse proxy (`frontend-proxy/`) on host port 8000.
52
+
Students go to `http://<host>:8000/spa/register` (or whichever landing
53
+
page is configured); admins go to `http://<host>:8000/admin/` (which
54
+
redirects to the exercise view). The SSH entry point for student
55
+
containers is unchanged on port 2222.
56
+
57
+
In production, `frontend-proxy` serves the Vue SPA as a static bundle
58
+
that is baked into the image at `docker build` time via a multi-stage
59
+
Dockerfile — rebuild with `./ctrl.sh build` after any change under
60
+
`spa-frontend/`.
61
+
62
+
### Hot reloading (local development only)
63
+
64
+
Use `./ctrl.sh up --hot-reloading` during local development to get
65
+
Flask auto-reload **and** Vite HMR for the SPA. This flag starts an
66
+
extra `spa-frontend` container (running `vite dev`) which is gated
67
+
behind the `dev` compose profile; Caddy then reverse-proxies `/spa/*`
68
+
(including the HMR websocket) to that container.
69
+
70
+
> ⚠️ **Do not run `--hot-reloading` on a publicly reachable host.**
71
+
> Vite's dev server is not designed for hostile clients — it serves
72
+
> raw source, exposes `/@fs/`, and has had several path-traversal CVEs
73
+
> in recent releases. While `--hot-reloading` is active, the SPA
74
+
> renders a yellow/black hazard-striped warning strip at the top of
75
+
> every page as a visible reminder; the whole warning is tree-shaken
76
+
> out of the production `vite build`, so nothing about it leaks into
77
+
> the prod bundle. See `docs/ARCHITECTURE.md` for the full model.
78
+
47
79
48
80
#### Build the custom Linux kernel
49
81
Building the custom Linux kernel is only required if you need the `no-randomize` attribute for some exercises. This attribute allows you to disable ASLR for a specific binary, even if it is a setuid binary. This is not allowed for unmodified kernels. The following assumes that your system is based on Debian and uses GRUB as a bootloader. For other systems or bootloaders, the instructions have to be adapted accordingly.
0 commit comments