Files
docker-seafile-client/.github/workflows/docker-publish.yml
T
Maxim Shulga c15763d9f8 Stop server-provided values from reaching a shell or escaping /dsc/seafile
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.
2026-08-02 16:53:19 -07:00

89 lines
1.9 KiB
YAML

name: Docker
on:
push:
branches:
- master
tags:
- '*'
pull_request:
permissions:
contents: read
env:
IMAGE: snegov/seafile-client
jobs:
# Application failures must be distinguishable from packaging failures, so
# the unit tests do not depend on the image build.
unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip
- name: Install dependencies
run: pip install -r requirements-dev.txt
- name: Run unit tests
run: pytest -q
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate compose example
run: docker compose --file compose.example.yaml config --quiet
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
push: false
load: true
tags: ${{ env.IMAGE }}:ci
push:
needs: [unit-test, test]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
# Every tag of a release must resolve to one manifest digest, so the
# image is built once and all tags are attached to that single build.
- name: Collect tags and labels
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE }}
tags: |
type=ref,event=tag
flavor: |
latest=true
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}