Files
narl-workspace/compose.yml
T
nvrl 082f2c8602 security: close the holes a session or a stranger could walk through
DELETE /api/media/{id} joined the raw, percent-decoded id onto uploads/,
so ..%2Fdiary.db deleted the database. Ids are now parsed as uuids before
they reach the filesystem, and a file is only removed when its row was.

Import panicked on a link like %aé: percent_decode sliced the string
between the bytes of a character. Hex digits are now read as bytes. The
SFTP write_at offset is checked before it is cast, so an offset near
u64::MAX no longer wraps past the size limit and panics the session.

The global body limit was the upload ceiling, about a gigabyte, and
applied to the login too. Only the upload routes take that much now;
other bodies stop at 16 MiB and the login at 16 KiB.

Writes a browser says came from another origin are refused, which
SameSite=Lax alone does not cover for sibling subdomains or for
multipart forms. Logout only locks the vault for a caller who is
logged in, so a cross-site form can no longer shut it.

Login and vault attempts go through one at a time, so a burst of
parallel guesses cannot all pass the throttle before the first failure
is recorded, and parallel Argon2 runs cannot pile up.

Sealed files are served with no-store instead of a year of cache, so
their plaintext does not outlive :lock in the browser. The SFTP host key
is created 0600 rather than narrowed after being written.

SIGTERM is handled: as PID 1 in the container it was ignored, and every
docker stop ended in SIGKILL. Requests get eight seconds to finish, the
pool is closed so the WAL is checkpointed, and compose runs tini.
2026-10-20 01:13:03 +02:00

43 lines
1.4 KiB
YAML

services:
workspace:
build: .
image: narl-workspace:latest
restart: unless-stopped
# tini as PID 1: forwards SIGTERM and reaps anything the binary leaves behind.
init: true
env_file:
- .env
environment:
# These win over anything in .env: inside the container the app must
# listen on all interfaces and keep its state on the volume.
WORKSPACE_BIND: 0.0.0.0:4242
WORKSPACE_DATA_DIR: /data
volumes:
# Named for what it held when it was made. Renaming it would mean moving
# the database into a new volume by hand, which is no kind of upgrade.
- diary-data:/data
networks:
- internal_net
ports:
# Localhost only. Drop this in the override when a reverse proxy
# reaches the container over its own network instead.
- "127.0.0.1:4242:4242"
# Only listens if WORKSPACE_SFTP_BIND is set in .env (see there);
# otherwise this maps to a port nothing is bound on. Traefik is
# HTTP-only, so a deployment behind it must publish this port itself
# rather than relying on the reverse proxy — see compose.override.yml.example.
# - "127.0.0.1:2222:2222"
healthcheck:
# No extra tooling in the image, so probe the port with bash directly.
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/4242"]
interval: 30s
timeout: 3s
start_period: 5s
retries: 3
volumes:
diary-data:
networks:
internal_net: