9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
152 lines
7.5 KiB
YAML
152 lines
7.5 KiB
YAML
name: openclaw filesystem write gate
|
|
|
|
# Detect when the upstream openclaw image starts writing to NEW paths under
|
|
# /home/node/.openclaw/ that the compose volume layout does not account for.
|
|
#
|
|
# Background: issue #498 originally proposed using `docker diff` to detect
|
|
# openclaw writes outside known paths. `docker diff` is structurally unable
|
|
# to see writes into volumes (named or bind), so it cannot observe writes to
|
|
# /home/node/.openclaw/ regardless of volume type.
|
|
#
|
|
# Mechanism instead: bind-mount a host directory over /home/node/.openclaw/
|
|
# inside the container, run openclaw briefly so it writes its initial files,
|
|
# then walk the host directory and fail if any file other than expected
|
|
# ephemeral startup files shows up. State directories such as agents/,
|
|
# canvas/, cron/, and workspace/ are mounted separately to mirror the
|
|
# production layout, so they do not pollute the inspection.
|
|
#
|
|
# Companion to the OpenClaw home-volume simplification: keep generated config
|
|
# and caches ephemeral, but fail when the image writes a state path that is not
|
|
# explicitly backed by the compose layout.
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- 'ods/config/openclaw/**'
|
|
- 'ods/extensions/services/openclaw/compose.yaml'
|
|
- 'ods/extensions/services/openclaw/manifest.yaml'
|
|
- '.github/workflows/openclaw-image-diff.yml'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
filesystem-write-gate:
|
|
name: Detect openclaw writes outside known paths
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
env:
|
|
OPENCLAW_IMAGE: ghcr.io/openclaw/openclaw:2026.3.8
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Pull openclaw image
|
|
run: docker pull "$OPENCLAW_IMAGE"
|
|
|
|
- name: Prepare bind-mount layout
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p ./openclaw-test-home
|
|
mkdir -p ./openclaw-test-agents
|
|
mkdir -p ./openclaw-test-canvas
|
|
mkdir -p ./openclaw-test-cron
|
|
mkdir -p ./openclaw-test-data
|
|
mkdir -p ./openclaw-test-workspace
|
|
|
|
- name: Start openclaw with bind-mounted home
|
|
run: |
|
|
set -euo pipefail
|
|
# Mirror the production compose user + entrypoint (root chown, then
|
|
# drop to uid 1000 + docker-entrypoint.sh) so the same startup code
|
|
# path runs. We do NOT use --rm: we want to capture logs after the
|
|
# container exits if it crashed.
|
|
docker run -d \
|
|
--name openclaw-test \
|
|
--user 0:0 \
|
|
-e OPENCLAW_CONFIG=/config/openclaw.json \
|
|
-e OPENCLAW_DATA=/data \
|
|
-e OPENCLAW_GATEWAY_TOKEN=ci-test-token \
|
|
-e OPENCLAW_TOKEN=ci-test-token \
|
|
-e LLM_MODEL=qwen3:30b-a3b \
|
|
-e OLLAMA_URL=http://llama-server:8080 \
|
|
-e HOME=/home/node \
|
|
-e OPENCLAW_EXTERNAL_PORT=7860 \
|
|
-e BIND_ADDRESS=127.0.0.1 \
|
|
-v "$(pwd)/ods/config/openclaw:/config:ro" \
|
|
-v "$(pwd)/openclaw-test-data:/data" \
|
|
-v "$(pwd)/openclaw-test-home:/home/node/.openclaw" \
|
|
-v "$(pwd)/openclaw-test-agents:/home/node/.openclaw/agents" \
|
|
-v "$(pwd)/openclaw-test-canvas:/home/node/.openclaw/canvas" \
|
|
-v "$(pwd)/openclaw-test-cron:/home/node/.openclaw/cron" \
|
|
-v "$(pwd)/openclaw-test-workspace:/home/node/.openclaw/workspace" \
|
|
--entrypoint /bin/sh \
|
|
"$OPENCLAW_IMAGE" \
|
|
-c "chown -R 1000:1000 /home/node/.openclaw; exec setpriv --reuid=1000 --regid=1000 --clear-groups /bin/sh -c 'node /config/inject-token.js; export OPENCLAW_CONFIG_PATH=/tmp/openclaw-config.json; exec docker-entrypoint.sh node openclaw.mjs gateway --allow-unconfigured --bind lan'"
|
|
# Give the container time to write its initial files. The writes we
|
|
# care about happen at startup, well within 30s.
|
|
sleep 30
|
|
# Sanity-check that the container is still running. If it crashed,
|
|
# the bind-mount may only contain writes from the wrapper script
|
|
# and the audit can false-green without observing OpenClaw itself.
|
|
if ! docker ps --filter name=openclaw-test --filter status=running --format '{{.Names}}' | grep -q '^openclaw-test$'; then
|
|
echo "::error::openclaw-test is not running after 30s; refusing to audit an incomplete startup."
|
|
echo "--- container logs ---"
|
|
docker logs openclaw-test 2>&1 | tail -100 || true
|
|
echo "--- end logs ---"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Inspect bind-mount for unexpected writes
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
# Stop and remove the container so the bind-mount is quiescent.
|
|
docker rm -f openclaw-test >/dev/null 2>&1 || true
|
|
# OpenClaw creates some directories with the container user's
|
|
# permissions. Normalize ownership so the host-side find can inspect
|
|
# the bind mount without failing before the allowlist check runs.
|
|
sudo chown -R "$(id -u):$(id -g)" \
|
|
./openclaw-test-home \
|
|
./openclaw-test-agents \
|
|
./openclaw-test-canvas \
|
|
./openclaw-test-cron \
|
|
./openclaw-test-data \
|
|
./openclaw-test-workspace
|
|
|
|
# Allowlist:
|
|
# - openclaw.json regenerated by openclaw on each container start
|
|
# - update-check.json disposable upstream update-check cache
|
|
# - agents/ bind-mounted separately to data/openclaw/home
|
|
# - canvas/ bind-mounted separately to data/openclaw/home
|
|
# - cron/ bind-mounted separately to data/openclaw/home
|
|
# - workspace/ bind-mounted separately to config/openclaw
|
|
# Anything else is a new write path that the compose volume layout
|
|
# does not account for.
|
|
UNEXPECTED=$(find ./openclaw-test-home -mindepth 1 -maxdepth 5 \
|
|
\( -path './openclaw-test-home/agents' \
|
|
-o -path './openclaw-test-home/canvas' \
|
|
-o -path './openclaw-test-home/cron' \
|
|
-o -path './openclaw-test-home/workspace' \) -prune -o \
|
|
-type f ! -name 'openclaw.json' ! -name 'update-check.json' -print)
|
|
|
|
if [ -n "$UNEXPECTED" ]; then
|
|
echo "::error::openclaw is writing to new paths under /home/node/.openclaw/. Update compose.yaml volume layout to include these paths or update this test's allowlist."
|
|
echo "Unexpected files:"
|
|
echo "$UNEXPECTED"
|
|
exit 1
|
|
fi
|
|
|
|
# Positive assertion: a successful audit must have observed openclaw.json being
|
|
# written. If it's missing, the container exited before the entrypoint got
|
|
# far enough to regenerate it, and the empty bind-mount tells us nothing
|
|
# about openclaw's actual write paths. This is a write-observation gate, so
|
|
# a container that produces no observable writes is a hard failure, not a
|
|
# silent pass. Treating this as warning-only would let a future image bump
|
|
# that crashes on startup ship undetected.
|
|
if [ ! -f ./openclaw-test-home/openclaw.json ]; then
|
|
echo "::error::openclaw.json was never written; container exited before producing the expected baseline write. The empty bind-mount makes this audit meaningless (false-green risk). Check the container logs above for the startup failure."
|
|
exit 1
|
|
fi
|
|
|
|
echo "openclaw filesystem writes match expected pattern (openclaw.json only)."
|