From d819e08071dffacde493072a2de202fae7a194ea Mon Sep 17 00:00:00 2001 From: viettranx Date: Wed, 1 Apr 2026 18:11:42 +0700 Subject: [PATCH] fix(security): fix media upload permission denied + symlink protection - Fix workspace dir ownership in Docker entrypoint: chown dirs not owned by goclaw on startup (handles dirs created by root in previous lifecycle) - Add symlink check on .uploads/ via os.Lstat before file creation to prevent symlink-based attacks replacing .uploads with link to sensitive dir --- docker-entrypoint.sh | 9 +++++++++ internal/agent/media.go | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 6c30180e..1eaa8896 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -19,6 +19,15 @@ if [ "$(id -u)" = "0" ] && [ -d "$RUNTIME_DIR" ]; then chown -R goclaw:goclaw "$RUNTIME_DIR/pip" "$RUNTIME_DIR/npm-global" "$RUNTIME_DIR/pip-cache" 2>/dev/null || true fi +# Fix workspace directory ownership: handle dirs created by root in previous +# container lifecycle or via manual docker exec. +# Security: -type d = real directories only (not symlinks). +# find default -P mode = never follow symlinks. -maxdepth 5 limits traversal. +if [ "$(id -u)" = "0" ] && [ -d /app/workspace ]; then + find /app/workspace -maxdepth 5 -type d -not -user goclaw \ + -exec chown goclaw:goclaw {} + 2>/dev/null || true +fi + # Python: allow agent to pip install to writable target dir export PYTHONPATH="$RUNTIME_DIR/pip:${PYTHONPATH:-}" export PIP_TARGET="$RUNTIME_DIR/pip" diff --git a/internal/agent/media.go b/internal/agent/media.go index a0c68e71..76003e56 100644 --- a/internal/agent/media.go +++ b/internal/agent/media.go @@ -70,6 +70,12 @@ func (l *Loop) persistMedia(sessionKey string, files []bus.MediaFile, workspace slog.Warn("media: failed to create .uploads dir", "dir", uploadsDir, "error", err) return nil } + // Verify .uploads is a real directory (not symlink) to prevent symlink-based attacks. + // os.Lstat does NOT follow symlinks — rejects if attacker replaced .uploads with a symlink. + if fi, err := os.Lstat(uploadsDir); err == nil && fi.Mode()&os.ModeSymlink != 0 { + slog.Warn("media: .uploads is a symlink, refusing to use", "dir", uploadsDir) + return nil + } var refs []providers.MediaRef for _, f := range files {