Commands were joined into a string and passed to "su -c", so a login shell
parsed every interpolated value. A library name is chosen on the server, and
"sync -d /dsc/seafile/<name>" put it straight into that string: a library
named 'Docs; touch /tmp/pwned; #' ran as an arbitrary command. Confirmed
against the built image before and after this change.
Every command is now an argument list executed by "runuser --", which execs
directly with no shell. runuser -u sets the same HOME and a PATH that still
finds the seaf-cli wrapper, so the daemon environment is unchanged.
The password no longer needs its surrounding quotes, which also fixes
passwords containing quotes, dollar signs, and backticks.
Library names are untrusted, so dsc/paths.py reduces each one to a single
path component and proves the result stays under the libraries directory
with symlinks resolved. Empty, ".", "..", and names containing a separator
are skipped with a warning; names that would collide are suffixed with their
library ID rather than sharing a directory.
Tests cover quotes, command substitutions, separators, newlines, absolute
paths, parent traversal, Unicode, and colliding names. They run in a separate
CI job so application failures stay distinguishable from packaging failures.
The push job called build-push-action twice, once for the ref tag and once
for latest. Two builds meant the two tags could resolve to different manifest
digests. Build once and let docker/metadata-action supply both tags and the
OCI labels for that single build.
Replace the deprecated actions/checkout@v2 and docker/build-push-action@v1,
default workflow permissions to contents: read so pull requests cannot reach
publication credentials, and authenticate with a scoped DOCKER_TOKEN through
docker/login-action instead of DOCKER_PASSWORD.
The test job's docker-compose.test.yml branch was dead; that file does not
exist in the repository.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The list-form environment syntax in the README passed quote characters
through as part of the value, so LIBRARY_ID was read as '"<id>"' and no
matching library was found. Switch to mapping syntax, where YAML strips
the quotes.
- Pin the example to a versioned image instead of implicit latest
- Add a checked-in compose.example.yaml
- Validate it with `docker compose config` in CI
- Link the Docker Hub image from the README