diff --git a/.env.example b/.env.example index 02e402d..b5c97c6 100644 --- a/.env.example +++ b/.env.example @@ -45,8 +45,8 @@ ANTHROPIC_API_KEY= # PAPERCLIP_DEPLOYMENT_MODE=authenticated # PAPERCLIP_BIND=lan # Docker/Podman-friendly bind preset # PAPERCLIP_ALLOWED_HOSTNAMES=192.168.1.50,my-host.local # Trusted LAN/private hosts only, no scheme or port -# ENABLE_HERMES=true # Start Hermes API server on HERMES_PORT -# HERMES_PORT=8642 +# Bundled Hermes is temporarily unavailable in v1.1.3. Existing +# /home/opencode/.hermes data remains preserved for rollback or a future fix. # CLIPROXYAPI_ENABLED=true # Add OpenCode provider for an externally managed CLIProxyAPI endpoint # CLIPROXYAPI_BASE_URL=http://your-cliproxy-host:8317/v1 # CLIPROXYAPI_API_KEY= diff --git a/.gitattributes b/.gitattributes index 33dc1ab..4264326 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ *.sh text eol=lf +*.py text eol=lf s6-overlay/**/run text eol=lf s6-overlay/**/type text eol=lf diff --git a/.github/SECURITY.md b/.github/SECURITY.md index fa7e886..143f8da 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -23,10 +23,12 @@ Release tags use exact `vX.Y.Z`; Docker image tags drop the `v` prefix. Each seg HolyCode ships many third-party CLIs inside one Docker image. Tagged releases refresh the pinned Dockerfile tools, but optional OpenCode plugins are live registry installs trusted at container startup when you enable them. Those boot-installed plugins are outside the image SBOM. Renovate configuration covers images, Actions, npm, PyPI, GitHub releases, and plugin pins; release audits still record scanner findings and compatibility holds before publication. -Trivy's release gate keeps its full critical/high report visible and blocks fixable critical findings. npm 12 lifecycle scripts are denied by default and checked against exact package versions, script bodies, and architecture rules. The dated versions, holds, removals, and scanner disposition are recorded in [`docs/dependency-audit-v1.1.2.md`](../docs/dependency-audit-v1.1.2.md). +Trivy's release gate keeps its full critical/high report visible and blocks every fixable critical or high finding. npm lifecycle scripts are disabled during installation, then checked against exact package versions, integrity values, script bodies, and architecture rules before approved scripts run. The dated versions, holds, removals, and scanner disposition are recorded in [`docs/dependency-audit-v1.1.3.md`](../docs/dependency-audit-v1.1.3.md). When `ENABLE_PAPERCLIP=true`, HolyCode exposes an authenticated local agent board on the configured Paperclip port. Keep that port on trusted LAN/private networks or behind a VPN, and do not publish it directly to the public internet. -When `ENABLE_HERMES=true`, set `API_SERVER_KEY` and keep port `8642` on a trusted network. If you enable CLIProxyAPI integration, keep the external endpoint private and protect its config, auth files, and API key outside HolyCode. +Bundled Hermes is temporarily unavailable in v1.1.3 because its current releases require vulnerable dependency pins. HolyCode preserves `/home/opencode/.hermes`, and `ENABLE_HERMES=true` stops startup with a migration message. If you enable CLIProxyAPI integration, keep the external endpoint private and protect its config, auth files, and API key outside HolyCode. + +Chromium runs as the `opencode` user with its sandbox enabled. Keep `config/chromium-seccomp.json` attached through Compose or the equivalent Podman `--security-opt` option. Do not work around browser failures with `--no-sandbox`, `SYS_ADMIN`, or `seccomp=unconfined`. Netlify CLI is installed without its platform-specific local functions binaries. HolyCode supports remote build/deploy commands only; `netlify dev` and local functions are intentionally unsupported until upstream rebuilds those binaries. diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 88a4aff..a42b85d 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -7,8 +7,8 @@ on: - 'v*' concurrency: - group: docker-${{ github.ref }} - cancel-in-progress: true + group: docker-release + cancel-in-progress: false env: DOCKERHUB_IMAGE: coderluii/holycode @@ -19,26 +19,42 @@ env: # DOCKERHUB_TOKEN - Docker Hub access token with push and description-update access. jobs: - protected-validation: - uses: ./.github/workflows/protected-validation.yml - secrets: inherit - with: - version: ${{ github.ref_name }} - previous_version: v1.1.1 - previous_image: coderluii/holycode:1.1.1@sha256:7d64d2cbfa55673b0b98529b38ec11df8e69354e9c36976139ea14132c98a8fd - release_title: ${{ github.ref_name }} - allow_legacy_bridge: false - - build-and-push: - needs: protected-validation + verify-protected-validation: runs-on: ubuntu-latest permissions: + actions: read + contents: read + outputs: + run_id: ${{ steps.protected.outputs.run_id }} + + steps: + - name: Require successful protected validation for this commit + id: protected + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + response="$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "/repos/$GITHUB_REPOSITORY/actions/workflows/protected-validation.yml/runs?head_sha=$GITHUB_SHA&status=success&event=workflow_dispatch&per_page=100")" + run_id="$(jq -er --arg sha "$GITHUB_SHA" \ + '[.workflow_runs[] | select(.head_sha == $sha and .conclusion == "success")] | sort_by(.created_at) | last | .id' \ + <<<"$response")" + echo "run_id=$run_id" >> "$GITHUB_OUTPUT" + echo "Protected validation run: $run_id" >> "$GITHUB_STEP_SUMMARY" + + promote-candidate: + needs: verify-protected-validation + runs-on: ubuntu-latest + permissions: + actions: read contents: read packages: write steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 @@ -46,31 +62,52 @@ jobs: run: | set -euo pipefail - current="${GITHUB_REF_NAME}" + current="$GITHUB_REF_NAME" previous="$(git tag --list 'v*' --sort=-v:refname | grep -Fxv "$current" | head -n 1 || true)" commit_title="$(git log -1 --format=%s "$GITHUB_SHA")" - legacy_args=() - - if [ "$previous" = "v1.0.13" ] && [ "$current" = "v1.1.0" ]; then - legacy_args=(--allow-legacy-v1-0-13-to-v1-1-0) - fi - python scripts/validate_version.py \ --version "$current" \ --previous-version "$previous" \ --tag-name "$current" \ - --commit-title "$commit_title" \ - "${legacy_args[@]}" + --commit-title "$commit_title" + + - name: Require matching published GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail - - name: Free up disk space + release='' + for _ in $(seq 1 18); do + if release="$(gh api \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + "/repos/$GITHUB_REPOSITORY/releases/tags/$GITHUB_REF_NAME" 2>/dev/null)"; then + break + fi + sleep 10 + done + [ -n "$release" ] + jq -e --arg tag "$GITHUB_REF_NAME" ' + .tag_name == $tag and + .name == $tag and + .draft == false and + (.body | test("^## [0-9]{2}/[0-9]{2}/[0-9]{4}")) + ' <<<"$release" >/dev/null + + - name: Download protected candidate digest + env: + GH_TOKEN: ${{ github.token }} + PROTECTED_RUN_ID: ${{ needs.verify-protected-validation.outputs.run_id }} run: | - sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache - sudo apt-get clean - docker system prune -af - df -h + set -euo pipefail - - name: Set up QEMU (multi-arch) - uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 + gh run download "$PROTECTED_RUN_ID" \ + --repo "$GITHUB_REPOSITORY" \ + --name "holycode-$GITHUB_REF_NAME-candidate" \ + --dir candidate + candidate_digest="$(tr -d '\r\n' < candidate/candidate-digest.txt)" + [[ "$candidate_digest" =~ ^sha256:[0-9a-f]{64}$ ]] + echo "CANDIDATE_DIGEST=$candidate_digest" >> "$GITHUB_ENV" - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 @@ -88,39 +125,80 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata - id: meta - uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 - with: - images: | - ${{ env.DOCKERHUB_IMAGE }} - ${{ env.GHCR_IMAGE }} - tags: | - type=raw,value=latest - type=semver,pattern={{version}} - - - name: Build and push - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - sbom: true - provenance: mode=max + - name: Verify and promote exact protected candidate + run: | + set -euo pipefail + + version="${GITHUB_REF_NAME#v}" + source="$GHCR_IMAGE@$CANDIDATE_DIGEST" + manifest="$(docker buildx imagetools inspect "$source" --format '{{json .Manifest}}')" + digest="$(jq -er '.digest' <<<"$manifest")" + platforms="$(jq '[.manifests[] | select(.platform.os == "linux" and (.platform.architecture == "amd64" or .platform.architecture == "arm64"))] | length' <<<"$manifest")" + attestations="$(jq '[.manifests[] | select(.annotations["vnd.docker.reference.type"] == "attestation-manifest")] | length' <<<"$manifest")" + [ "$digest" = "$CANDIDATE_DIGEST" ] + [ "$platforms" -eq 2 ] + [ "$attestations" -ge 2 ] + + docker buildx imagetools create \ + --tag "$DOCKERHUB_IMAGE:$version" \ + --tag "$DOCKERHUB_IMAGE:latest" \ + --tag "$GHCR_IMAGE:$version" \ + --tag "$GHCR_IMAGE:latest" \ + "$source" + + - name: Verify published manifests and attestations + run: | + set -euo pipefail + + version="${GITHUB_REF_NAME#v}" + refs=( + "$DOCKERHUB_IMAGE:$version" + "$DOCKERHUB_IMAGE:latest" + "$GHCR_IMAGE:$version" + "$GHCR_IMAGE:latest" + ) + + for attempt in $(seq 1 12); do + ready=true + for ref in "${refs[@]}"; do + if ! docker buildx imagetools inspect "$ref" >/dev/null 2>&1; then + ready=false + break + fi + done + [ "$ready" = true ] && break + sleep 10 + done + [ "$ready" = true ] + + expected_digest='' + for ref in "${refs[@]}"; do + manifest="$(docker buildx imagetools inspect "$ref" --format '{{json .Manifest}}')" + digest="$(jq -er '.digest' <<<"$manifest")" + platforms="$(jq '[.manifests[] | select(.platform.os == "linux" and (.platform.architecture == "amd64" or .platform.architecture == "arm64"))] | length' <<<"$manifest")" + attestations="$(jq '[.manifests[] | select(.annotations["vnd.docker.reference.type"] == "attestation-manifest")] | length' <<<"$manifest")" + [ "$platforms" -eq 2 ] + [ "$attestations" -ge 2 ] + if [ -z "$expected_digest" ]; then + expected_digest="$digest" + else + [ "$digest" = "$expected_digest" ] + fi + echo "$ref -> $digest ($platforms platforms, $attestations attestation manifests)" + done + + [ "$expected_digest" = "$CANDIDATE_DIGEST" ] + echo "Published multi-architecture digest: $expected_digest" >> "$GITHUB_STEP_SUMMARY" update-dockerhub-description: - needs: build-and-push + needs: promote-candidate runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') permissions: contents: read steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Update Docker Hub description uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0 diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 4982647..152fbd9 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Python version policy tests run: python -m unittest discover -s tests @@ -23,6 +23,9 @@ jobs: - name: Static action pin checks run: python scripts/validate_workflow_pins.py + - name: Chromium seccomp profile + run: python scripts/validate_chromium_seccomp.py + - name: JSON syntax run: python -m json.tool renovate.json >/tmp/renovate.json @@ -40,7 +43,7 @@ jobs: fi - name: Renovate configuration - run: npx --yes --package renovate@43.263.9 renovate-config-validator --strict renovate.json + run: npx --yes --package renovate@43.274.0 renovate-config-validator --strict renovate.json build-and-smoke-test: needs: static-validation @@ -50,7 +53,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Build image run: docker build -t holycode-pr-test . @@ -115,10 +118,10 @@ jobs: done [ "$ready" = true ] - docker exec holycode-paperclip-test sh -lc ' - pid="$(pgrep -u opencode -f "/usr/local/bin/paperclipai" | head -n 1)" + docker exec -u opencode holycode-paperclip-test sh -lc ' + pid="$(pgrep -u opencode -f "^node.*/paperclipai" | head -n 1)" test -n "$pid" - tr "\0" "\n" < "/proc/$pid/environ" > /tmp/paperclip-env + xargs -0 -n 1 < "/proc/$pid/environ" > /tmp/paperclip-env grep -Fx "HOME=/home/opencode" /tmp/paperclip-env grep -Fx "XDG_CONFIG_HOME=/home/opencode/.config" /tmp/paperclip-env grep -Fx "XDG_CACHE_HOME=/home/opencode/.cache" /tmp/paperclip-env @@ -137,46 +140,21 @@ jobs: exit 1 fi - - name: Smoke test Hermes runtime environment + - name: Validate Hermes migration guard run: | set -eu - docker run -d --name holycode-hermes-test \ - -e ENABLE_HERMES=true \ - -e API_SERVER_KEY=holycode-ci-smoke-key \ - holycode-pr-test - - cleanup() { - docker logs holycode-hermes-test || true - docker rm -f holycode-hermes-test || true - } - trap cleanup EXIT - - ready=false - for _ in $(seq 1 90); do - if docker exec holycode-hermes-test curl -fsS \ - -H 'Authorization: Bearer holycode-ci-smoke-key' \ - http://localhost:8642/v1/models >/dev/null 2>&1; then - ready=true - break - fi - sleep 2 - done - [ "$ready" = true ] - - docker exec -u opencode holycode-hermes-test sh -lc ' - pid="$(pgrep -u opencode -f "hermes gateway" | head -n 1)" - test -n "$pid" - tr "\0" "\n" < "/proc/$pid/environ" > /tmp/hermes-env - grep -Fx "HOME=/home/opencode" /tmp/hermes-env - grep -Fx "XDG_CONFIG_HOME=/home/opencode/.config" /tmp/hermes-env - grep -Fx "XDG_CACHE_HOME=/home/opencode/.cache" /tmp/hermes-env - grep -Fx "XDG_DATA_HOME=/home/opencode/.local/share" /tmp/hermes-env - grep -Fx "XDG_STATE_HOME=/home/opencode/.local/state" /tmp/hermes-env - test -w /home/opencode/.hermes - ' + docker volume create holycode-hermes-data >/dev/null + trap 'docker volume rm holycode-hermes-data >/dev/null 2>&1 || true' EXIT + docker run --rm --entrypoint sh -v holycode-hermes-data:/home/opencode holycode-pr-test \ + -lc 'mkdir -p /home/opencode/.hermes && touch /home/opencode/.hermes/preserved' - docker logs holycode-hermes-test > /tmp/holycode-hermes.log 2>&1 || true - if grep -E '/root/\.(config|cache|local)|EACCES.*(/root|/home/opencode)' /tmp/holycode-hermes.log; then + if docker run --rm -v holycode-hermes-data:/home/opencode \ + -e ENABLE_HERMES=true holycode-pr-test >/tmp/holycode-hermes.log 2>&1; then + echo "ENABLE_HERMES=true unexpectedly started the removed bundled service" >&2 exit 1 fi + grep -F "bundled Hermes is temporarily unavailable" /tmp/holycode-hermes.log + grep -F "/home/opencode/.hermes is preserved" /tmp/holycode-hermes.log + docker run --rm --entrypoint test -v holycode-hermes-data:/home/opencode \ + holycode-pr-test -f /home/opencode/.hermes/preserved diff --git a/.github/workflows/protected-validation.yml b/.github/workflows/protected-validation.yml index 698f1a6..d82601d 100644 --- a/.github/workflows/protected-validation.yml +++ b/.github/workflows/protected-validation.yml @@ -3,151 +3,196 @@ name: HolyCode - Protected Validation on: workflow_call: inputs: - version: + ref: required: true type: string - previous_version: - required: false - default: '' - type: string - previous_image: + version: required: true type: string release_title: required: true type: string - allow_legacy_bridge: - required: false - default: false - type: boolean + secrets: + DOCKERHUB_USERNAME: + required: true + DOCKERHUB_TOKEN: + required: true workflow_dispatch: inputs: - version: - description: Release tag, for example v1.1.1 - required: true - type: string - previous_version: - description: Previous release tag + ref: + description: Exact 40-character candidate commit SHA required: true type: string - previous_image: - description: Previous Docker image tag and immutable digest + version: + description: Planned release tag, for example v1.1.3 required: true type: string release_title: - description: GitHub release title + description: Planned GitHub release title required: true type: string - allow_legacy_bridge: - description: Allow the one-time v1.0.13 to v1.1.0 bridge - required: true - default: false - type: boolean concurrency: - group: protected-validation-${{ github.ref }} + group: protected-validation-${{ github.sha }} cancel-in-progress: true +env: + CANDIDATE_IMAGE: ghcr.io/coderluii/holycode + PREVIOUS_IMAGE: coderluii/holycode:1.1.2@sha256:65740c4d8aa416217391f53de4be984ea9f8dfd5f10553dead94db402645b537 + PREVIOUS_VERSION: v1.1.2 + RELEASE_VERSION: v1.1.3 + jobs: - protected-validation: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - platform: linux/amd64 - suffix: amd64 - - platform: linux/arm64 - suffix: arm64 + build-candidate: + runs-on: ubuntu-24.04 permissions: contents: read + packages: write + outputs: + digest: ${{ steps.build.outputs.digest }} steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: Checkout exact workflow commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - ref: ${{ inputs.version }} + ref: ${{ github.sha }} - - name: Validate release metadata + - name: Validate release metadata and commit binding env: - ALLOW_LEGACY_BRIDGE: ${{ inputs.allow_legacy_bridge }} - PREVIOUS_VERSION: ${{ inputs.previous_version }} - RELEASE_TITLE: ${{ inputs.release_title }} - RELEASE_VERSION: ${{ inputs.version }} + REQUESTED_REF: ${{ inputs.ref }} + REQUESTED_RELEASE_TITLE: ${{ inputs.release_title }} + REQUESTED_VERSION: ${{ inputs.version }} run: | set -euo pipefail - legacy_args=() - if [ -z "$PREVIOUS_VERSION" ]; then - PREVIOUS_VERSION="$(git tag --list 'v*' --sort=-v:refname | grep -Fxv "$RELEASE_VERSION" | head -n 1 || true)" - fi - if [ "$ALLOW_LEGACY_BRIDGE" = "true" ]; then - legacy_args=(--allow-legacy-v1-0-13-to-v1-1-0) - fi - - git rev-parse -q --verify "refs/tags/$RELEASE_VERSION" >/dev/null - commit_title="$(git log -1 --format=%s "$RELEASE_VERSION")" + actual_sha="$(git rev-parse HEAD)" + [ "$REQUESTED_REF" = "$GITHUB_SHA" ] + [ "$actual_sha" = "$GITHUB_SHA" ] + [ "$REQUESTED_VERSION" = "$RELEASE_VERSION" ] + [ "$REQUESTED_RELEASE_TITLE" = "$RELEASE_VERSION" ] + commit_title="$(git log -1 --format=%s HEAD)" python scripts/validate_version.py \ --version "$RELEASE_VERSION" \ --previous-version "$PREVIOUS_VERSION" \ --tag-name "$RELEASE_VERSION" \ --commit-title "$commit_title" \ - --release-title "$RELEASE_TITLE" \ - "${legacy_args[@]}" + --release-title "$REQUESTED_RELEASE_TITLE" - git merge-base --is-ancestor "$PREVIOUS_VERSION" "$RELEASE_VERSION" - commit_count="$(git rev-list --count "$PREVIOUS_VERSION..$RELEASE_VERSION")" + git merge-base --is-ancestor "$PREVIOUS_VERSION" HEAD + commit_count="$(git rev-list --count "$PREVIOUS_VERSION..HEAD")" if [ "$commit_count" -ne 1 ]; then echo "Release range $PREVIOUS_VERSION..$RELEASE_VERSION contains $commit_count commits; expected exactly 1" >&2 exit 1 fi + echo "Validated exact candidate $actual_sha for $RELEASE_VERSION" >> "$GITHUB_STEP_SUMMARY" + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache + sudo apt-get clean + docker system prune -af + df -h + - name: Set up QEMU uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - - name: Build local ${{ matrix.platform }} scan image + - name: Login to GHCR + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push attested candidate + id: build + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ env.CANDIDATE_IMAGE }}:validation-${{ github.sha }} + labels: | + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.version=${{ inputs.version }} + cache-from: type=gha + cache-to: type=gha,mode=max + sbom: true + provenance: mode=max + + - name: Record immutable candidate digest run: | - docker buildx build \ - --platform "${{ matrix.platform }}" \ - --load \ - --tag "holycode-protected-validation:${{ matrix.suffix }}" \ - . - - # The loaded image is retained by Docker. Remove BuildKit's duplicate - # cache before pulling the previous release for upgrade/rollback tests. - - name: Reclaim build cache before upgrade validation + set -euo pipefail + digest='${{ steps.build.outputs.digest }}' + [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] + printf '%s\n' "$digest" > candidate-digest.txt + echo "Candidate digest: $digest" >> "$GITHUB_STEP_SUMMARY" + + - name: Upload candidate digest + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: holycode-${{ inputs.version }}-candidate + if-no-files-found: error + path: candidate-digest.txt + + protected-validation: + needs: build-candidate + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + suffix: amd64 + runner: ubuntu-24.04 + scout_arch: amd64 + scout_sha256: 0f778f9d833f28bc6cccff95e33039849c0afcecafa38d9f46fe74bfd0915714 + - platform: linux/arm64 + suffix: arm64 + runner: ubuntu-24.04-arm + scout_arch: arm64 + scout_sha256: 88eecb7273f19bd18300d70e6f85b2e7d784e9e4f3cbb4a2b400db6b8355a52a + permissions: + contents: read + packages: read + + steps: + - name: Checkout exact workflow commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + ref: ${{ github.sha }} + + - name: Login to GHCR + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Pull exact candidate digest + env: + CANDIDATE_DIGEST: ${{ needs.build-candidate.outputs.digest }} run: | - docker buildx prune --all --force - docker container prune --force - docker image prune --force - docker system df - df -h / + set -euo pipefail + [[ "$CANDIDATE_DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] + docker pull --platform "${{ matrix.platform }}" "$CANDIDATE_IMAGE@$CANDIDATE_DIGEST" + docker tag "$CANDIDATE_IMAGE@$CANDIDATE_DIGEST" "holycode-protected-validation:${{ matrix.suffix }}" + + - name: Smoke test tools and Chromium sandbox + run: bash scripts/smoke_image.sh "holycode-protected-validation:${{ matrix.suffix }}" - name: Validate upgrade, persistence, and rollback env: CURRENT_IMAGE: holycode-protected-validation:${{ matrix.suffix }} PLATFORM: ${{ matrix.platform }} - PREVIOUS_IMAGE: ${{ inputs.previous_image }} run: bash scripts/test_upgrade_rollback.sh "$CURRENT_IMAGE" "$PREVIOUS_IMAGE" "$PLATFORM" - - name: Reclaim scanner workspace - env: - PREVIOUS_IMAGE: ${{ inputs.previous_image }} - run: | - docker image rm "$PREVIOUS_IMAGE" || true - docker container prune --force - docker buildx prune --all --force - docker image prune --force - docker system df - df -h / - - # Scout copies local images while indexing them. Feed it an SPDX SBOM so - # the large runtime image stays within GitHub-hosted runner disk limits. - name: Generate SPDX SBOM for Docker Scout uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: @@ -158,16 +203,16 @@ jobs: - name: Install Docker Scout CLI env: - SCOUT_SHA256: 0f778f9d833f28bc6cccff95e33039849c0afcecafa38d9f46fe74bfd0915714 + SCOUT_SHA256: ${{ matrix.scout_sha256 }} SCOUT_VERSION: 1.23.1 run: | set -euo pipefail - archive="$RUNNER_TEMP/docker-scout_${SCOUT_VERSION}_linux_amd64.tar.gz" + archive="$RUNNER_TEMP/docker-scout_${SCOUT_VERSION}_linux_${{ matrix.scout_arch }}.tar.gz" bin_dir="$RUNNER_TEMP/docker-scout-bin" curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \ --output "$archive" \ - "https://github.com/docker/scout-cli/releases/download/v${SCOUT_VERSION}/docker-scout_${SCOUT_VERSION}_linux_amd64.tar.gz" + "https://github.com/docker/scout-cli/releases/download/v${SCOUT_VERSION}/docker-scout_${SCOUT_VERSION}_linux_${{ matrix.scout_arch }}.tar.gz" printf '%s %s\n' "$SCOUT_SHA256" "$archive" | sha256sum --check --strict mkdir -p "$bin_dir" tar -xzf "$archive" -C "$bin_dir" docker-scout @@ -181,8 +226,9 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Docker Scout vulnerability report + - name: Docker Scout vulnerability reports env: + SCOUT_FIXABLE_REPORT: holycode-${{ matrix.suffix }}.scout-fixable.sarif SCOUT_REPORT: holycode-${{ matrix.suffix }}.scout.sarif SCOUT_SBOM: holycode-${{ matrix.suffix }}.spdx.json run: | @@ -192,6 +238,25 @@ jobs: --only-severity critical,high \ --format sarif \ --output "$SCOUT_REPORT" + docker-scout cves "sbom://$SCOUT_SBOM" \ + --only-severity critical,high \ + --only-fixed \ + --format sarif \ + --output "$SCOUT_FIXABLE_REPORT" + + - name: Docker Scout fixable critical and high gate + env: + SCOUT_FIXABLE_REPORT: holycode-${{ matrix.suffix }}.scout-fixable.sarif + run: | + set -euo pipefail + + jq -e '.runs | type == "array" and length > 0' "$SCOUT_FIXABLE_REPORT" >/dev/null + findings="$(jq '[.runs[].results[]?] | length' "$SCOUT_FIXABLE_REPORT")" + if [ "$findings" -ne 0 ]; then + jq -r '.runs[].results[]? | [.ruleId, .message.text] | @tsv' "$SCOUT_FIXABLE_REPORT" >&2 + echo "Docker Scout found $findings fixable critical/high findings" >&2 + exit 1 + fi - name: Summarize Docker Scout vulnerability report env: @@ -202,17 +267,14 @@ jobs: jq -e '.runs | type == "array" and length > 0' "$SCOUT_REPORT" >/dev/null findings="$(jq '[.runs[].results[]?] | length' "$SCOUT_REPORT")" rules="$(jq '[.runs[].tool.driver.rules[]?] | length' "$SCOUT_REPORT")" - { echo "### Docker Scout (${{ matrix.platform }})" echo echo "- SARIF results: $findings" echo "- Vulnerability rules: $rules" - echo "- Input: SPDX SBOM generated from the tested image" + echo "- Input: SPDX SBOM generated from the exact candidate digest" } >> "$GITHUB_STEP_SUMMARY" - echo "Docker Scout recorded $findings SARIF results across $rules vulnerability rules." - - name: Trivy vulnerability report uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: @@ -222,14 +284,48 @@ jobs: scanners: vuln,secret severity: CRITICAL,HIGH exit-code: 0 + output: holycode-${{ matrix.suffix }}.trivy.txt + + - name: Show Trivy vulnerability report + run: cat holycode-${{ matrix.suffix }}.trivy.txt - - name: Trivy fixable critical gate + - name: Trivy fixable critical and high gate uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: version: v0.72.0 image-ref: holycode-protected-validation:${{ matrix.suffix }} format: table scanners: vuln,secret - severity: CRITICAL + severity: CRITICAL,HIGH exit-code: 1 ignore-unfixed: true + + - name: Collect architecture evidence + env: + CANDIDATE_DIGEST: ${{ needs.build-candidate.outputs.digest }} + run: | + set -euo pipefail + + docker run --rm --entrypoint cat \ + "holycode-protected-validation:${{ matrix.suffix }}" \ + /usr/local/share/holycode/dpkg-inventory.txt \ + > "holycode-${{ matrix.suffix }}.dpkg-inventory.txt" + docker image inspect \ + --format '{{.Id}}' \ + "holycode-protected-validation:${{ matrix.suffix }}" \ + > "holycode-${{ matrix.suffix }}.image-id.txt" + printf '%s\n' "$CANDIDATE_DIGEST" > "holycode-${{ matrix.suffix }}.candidate-digest.txt" + + - name: Upload architecture evidence + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: holycode-${{ inputs.version }}-${{ matrix.suffix }}-evidence + if-no-files-found: error + path: | + holycode-${{ matrix.suffix }}.candidate-digest.txt + holycode-${{ matrix.suffix }}.dpkg-inventory.txt + holycode-${{ matrix.suffix }}.image-id.txt + holycode-${{ matrix.suffix }}.scout-fixable.sarif + holycode-${{ matrix.suffix }}.scout.sarif + holycode-${{ matrix.suffix }}.spdx.json + holycode-${{ matrix.suffix }}.trivy.txt diff --git a/Dockerfile b/Dockerfile index 96ba2a3..717e342 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,22 +3,43 @@ # https://github.com/coderluii/holycode # ============================================================================== +# renovate: datasource=github-releases depName=cli/cli +ARG GITHUB_CLI_VERSION=2.96.0 +ARG GITHUB_CLI_REF=b300f2ec7ec9dc9addc39b2ad88c54097ded7ca0 + +# GitHub CLI 2.96.0 was released before Go 1.26.5 fixed CVE-2026-39822. +# Rebuild the exact upstream tag with the fixed toolchain until GitHub ships it. +FROM golang:1.26.5-trixie@sha256:4ee9ffa999b4583ce281939cdff828763083610292f252279a0cee77473bd9a7 AS github-cli-builder +ARG GITHUB_CLI_VERSION +ARG GITHUB_CLI_REF +RUN git clone --branch "v${GITHUB_CLI_VERSION}" --depth 1 \ + https://github.com/cli/cli.git /src && \ + cd /src && \ + test "$(git rev-parse HEAD)" = "${GITHUB_CLI_REF}" && \ + test "$(git describe --tags --exact-match HEAD)" = "v${GITHUB_CLI_VERSION}" && \ + SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)" \ + GH_VERSION="${GITHUB_CLI_VERSION}" go run ./script/build.go bin/gh && \ + install -D -m 0755 bin/gh /out/gh && \ + go version -m /out/gh | grep -F "go1.26.5" && \ + /out/gh --version | grep -F "gh version ${GITHUB_CLI_VERSION}" + FROM node:24.18.0-trixie-slim@sha256:ae91dcc111a68c9d2d81ff2a17bda61be126426176fde6fe7d08ab13b7f50573 # ---------- Build args ---------- -ARG S6_OVERLAY_VERSION=3.2.3.1 -ARG FZF_VERSION=0.74.0 +ARG GITHUB_CLI_VERSION +ARG S6_OVERLAY_VERSION=3.2.3.2 +ARG FZF_VERSION=0.74.1 ARG LAZYGIT_VERSION=0.63.1 ARG DELTA_VERSION=0.19.2 ARG EZA_VERSION=0.23.5 -ARG HERMES_AGENT_VERSION=v2026.7.7.2 -ARG HERMES_AGENT_REF=b7751df34688835a108e0d630f3495fc11f3df79 # renovate: datasource=npm depName=opencode-ai -ARG OPENCODE_VERSION=1.18.2 +ARG OPENCODE_VERSION=1.18.4 # renovate: datasource=npm depName=@anthropic-ai/claude-code -ARG CLAUDE_CODE_VERSION=2.1.210 +ARG CLAUDE_CODE_VERSION=2.1.216 # renovate: datasource=npm depName=paperclipai ARG PAPERCLIP_VERSION=2026.707.0 +# renovate: datasource=npm depName=undici +ARG PAPERCLIP_UNDICI_VERSION=6.27.0 # renovate: datasource=npm depName=typescript ARG TYPESCRIPT_VERSION=6.0.3 # renovate: datasource=npm depName=npm @@ -26,18 +47,28 @@ ARG NPM_VERSION=12.0.1 # renovate: datasource=npm depName=tsx ARG TSX_VERSION=4.23.1 # renovate: datasource=npm depName=pnpm -ARG PNPM_VERSION=11.13.0 +ARG PNPM_VERSION=11.15.1 +# renovate: datasource=npm depName=vite +ARG VITE_VERSION=8.1.5 +# renovate: datasource=npm depName=prettier +ARG PRETTIER_VERSION=3.9.6 +# renovate: datasource=npm depName=prisma +ARG PRISMA_VERSION=7.9.0 +# renovate: datasource=npm depName=lighthouse +ARG LIGHTHOUSE_VERSION=13.4.1 # renovate: datasource=npm depName=wrangler -ARG WRANGLER_VERSION=4.111.0 -# renovate: datasource=npm depName=vercel -ARG VERCEL_VERSION=54.21.0 +ARG WRANGLER_VERSION=4.112.0 # renovate: datasource=npm depName=netlify-cli ARG NETLIFY_CLI_VERSION=26.2.0 # renovate: datasource=pypi depName=numpy ARG NUMPY_VERSION=2.5.1 +# renovate: datasource=pypi depName=pip +ARG PIP_VERSION=26.1.2 +ARG RELEASE_APT_REFRESH=2026-07-21 ARG TARGETARCH LABEL org.opencontainers.image.source=https://github.com/CoderLuii/HolyCode \ + io.holycode.version.github-cli=${GITHUB_CLI_VERSION} \ io.holycode.version.opencode=${OPENCODE_VERSION} \ io.holycode.version.claude-code=${CLAUDE_CODE_VERSION} \ io.holycode.version.paperclip=${PAPERCLIP_VERSION} \ @@ -45,8 +76,13 @@ LABEL org.opencontainers.image.source=https://github.com/CoderLuii/HolyCode \ io.holycode.version.typescript=${TYPESCRIPT_VERSION} \ io.holycode.version.tsx=${TSX_VERSION} \ io.holycode.version.pnpm=${PNPM_VERSION} \ + io.holycode.version.vite=${VITE_VERSION} \ + io.holycode.version.prettier=${PRETTIER_VERSION} \ + io.holycode.version.prisma=${PRISMA_VERSION} \ + io.holycode.version.lighthouse=${LIGHTHOUSE_VERSION} \ + io.holycode.version.s6-overlay=${S6_OVERLAY_VERSION} \ + io.holycode.version.fzf=${FZF_VERSION} \ io.holycode.version.wrangler=${WRANGLER_VERSION} \ - io.holycode.version.vercel=${VERCEL_VERSION} \ io.holycode.version.netlify-cli=${NETLIFY_CLI_VERSION} \ io.holycode.version.numpy=${NUMPY_VERSION} @@ -58,24 +94,24 @@ ENV DEBIAN_FRONTEND=noninteractive \ DBUS_SESSION_BUS_ADDRESS=disabled: \ CHROME_PATH=/usr/bin/chromium \ PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \ - CHROMIUM_FLAGS="--no-sandbox --disable-gpu --disable-dev-shm-usage" \ + CHROMIUM_FLAGS="--disable-gpu --disable-dev-shm-usage" \ OPENCODE_DISABLE_AUTOUPDATE=true \ OPENCODE_DISABLE_TERMINAL_TITLE=true # ---------- s6-overlay v3 (multi-arch) ---------- -RUN apt-get update && apt-get upgrade -y && \ +RUN test -n "${RELEASE_APT_REFRESH}" && apt-get update && apt-get upgrade -y && \ apt-get install -y --no-install-recommends xz-utils curl ca-certificates && \ rm -rf /var/lib/apt/lists/* RUN S6_ARCH=$(case "$TARGETARCH" in arm64) echo "aarch64";; *) echo "x86_64";; esac) && \ S6_ARCH_SHA256=$(case "$TARGETARCH" in \ - arm64) echo "c79b5cc7e5e405f6e1ae1466a8160ac84d29b86614e1e01ff0fb11dc832fee1b";; \ - *) echo "ed72fdb3abf196472d121b026bed63b46f3443507bd2ce67df6bd187f7d4dc0a";; \ + arm64) echo "b17f17a82e7a515c682a91edaf2ffdabb73f891981b6c1fd712115693a2f8b4c";; \ + *) echo "e6befcc96a437a3831386ecfc51808c5d3e939dc5fe3c02ae9284599e8aa2408";; \ esac) && \ curl -fsSL -o /tmp/s6-overlay-noarch.tar.xz \ "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" && \ curl -fsSL -o /tmp/s6-overlay-arch.tar.xz \ "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" && \ - echo "43d99d266fefe32cdc1510963aaadeb211cc8450b60af27817b64af450c934be /tmp/s6-overlay-noarch.tar.xz" | sha256sum -c - && \ + echo "5379750ed30a84bbd2e2dd74847ba6b5bd29cd0b2e3ea2ec58049b57eb2eda12 /tmp/s6-overlay-noarch.tar.xz" | sha256sum -c - && \ echo "${S6_ARCH_SHA256} /tmp/s6-overlay-arch.tar.xz" | sha256sum -c - && \ tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \ tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz && \ @@ -106,7 +142,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ htop procps iproute2 lsof strace \ # Build essentials (needed for native npm addons) build-essential pkg-config \ - postgresql-client redis-tools sqlite3 \ + postgresql-client-17 redis-tools sqlite3 \ # SSH client (NOT server) openssh-client \ imagemagick \ @@ -121,8 +157,8 @@ RUN ln -sf /usr/bin/batcat /usr/local/bin/bat 2>/dev/null || true # ---------- fzf ---------- RUN FZF_SHA256=$(case "$TARGETARCH" in \ - arm64) echo "bd9e6165ebdb702215d42368cbb95b8dd70a4e77ee97925adac8c31660e30ef7";; \ - *) echo "cf919f05b7581b4c744d764eaa704665d61dd6d3ca785f0df2351281dff60cda";; \ + arm64) echo "f22204dd1a091d43e102268d062fd53b47133c8d8581671ee5eb225b75e31183";; \ + *) echo "df53438be5f51e151bb4044d78fda72bdfe209e3ecd2baecae48e8dea370c81b";; \ esac) && \ curl -fsSL -o /tmp/fzf.tar.gz \ "https://github.com/junegunn/fzf/releases/download/v${FZF_VERSION}/fzf-${FZF_VERSION}-linux_${TARGETARCH}.tar.gz" && \ @@ -140,14 +176,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* # ---------- GitHub CLI ---------- -RUN curl -fsSL -o /tmp/githubcli-archive-keyring.gpg \ - https://cli.github.com/packages/githubcli-archive-keyring.gpg && \ - echo "6084d5d7bd8e288441e0e94fc6275570895da18e6751f70f057485dc2d1a811b /tmp/githubcli-archive-keyring.gpg" | sha256sum -c - && \ - install -m 0644 /tmp/githubcli-archive-keyring.gpg /usr/share/keyrings/githubcli-archive-keyring.gpg && \ - rm /tmp/githubcli-archive-keyring.gpg && \ - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ - > /etc/apt/sources.list.d/github-cli.list && \ - apt-get update && apt-get install -y gh && rm -rf /var/lib/apt/lists/* +COPY --from=github-cli-builder /out/gh /usr/local/bin/gh +RUN gh --version | grep -F "gh version ${GITHUB_CLI_VERSION}" # ---------- lazygit ---------- RUN LAZYGIT_ARCH=$(case "$TARGETARCH" in arm64) echo "arm64";; *) echo "x86_64";; esac) && \ @@ -188,21 +218,36 @@ RUN EZA_ARCH=$(case "$TARGETARCH" in arm64) echo "aarch64";; *) echo "x86_64";; # ---------- Headless browser (Chromium + Xvfb + fonts) ---------- RUN apt-get update && apt-get install -y --no-install-recommends \ - chromium \ + chromium chromium-sandbox \ xvfb \ fonts-liberation2 fonts-dejavu-core fonts-noto-core fonts-noto-color-emoji \ + && test -u /usr/lib/chromium/chrome-sandbox \ && rm -rf /var/lib/apt/lists/* # ---------- Playwright (Python, uses system Chromium via env vars) ---------- RUN pip install --no-cache-dir --break-system-packages playwright==1.61.0 +# Trixie's packaging and wheel modules are dpkg-owned and have no pip RECORD. +# Install the audited releases into /usr/local without removing Debian files. +RUN pip install --no-cache-dir --break-system-packages --ignore-installed \ + packaging==26.2 wheel==0.47.0 + RUN pip install --no-cache-dir --break-system-packages \ - requests==2.33.0 httpx==0.28.1 beautifulsoup4==4.15.0 lxml==6.1.1 \ - Pillow==12.2.0 openpyxl==3.1.5 python-docx==1.2.0 \ - pandas==3.0.3 numpy==${NUMPY_VERSION} matplotlib==3.11.0 seaborn==0.13.2 \ - rich==14.3.3 click==8.4.2 tqdm==4.68.4 apprise==1.12.0 \ + requests==2.34.2 httpx==0.28.1 beautifulsoup4==4.15.0 lxml==6.1.1 \ + Pillow==12.3.0 openpyxl==3.1.5 python-docx==1.2.0 \ + pandas==3.0.3 numpy==${NUMPY_VERSION} matplotlib==3.11.1 seaborn==0.13.2 \ + rich==15.0.0 click==8.4.2 tqdm==4.69.0 apprise==1.12.0 \ jinja2==3.1.6 pyyaml==6.0.3 python-dotenv==1.2.2 markdown==3.10.2 \ - fastapi==0.139.0 uvicorn==0.51.0 + fastapi==0.139.2 uvicorn==0.51.0 + +# Replace Debian's vulnerable wheel metadata after installing fixed copies in +# /usr/local. pip remains available from the exact PyPI package. +RUN python3 -m pip install --no-cache-dir --break-system-packages --ignore-installed \ + "pip==${PIP_VERSION}" && \ + apt-get purge -y python3-pip python3-wheel && \ + rm -rf /var/lib/apt/lists/* && \ + python3 -m pip --version | grep -F "pip ${PIP_VERSION}" && \ + python3 -m pip check RUN rm -f /usr/local/bin/dotenv @@ -217,7 +262,7 @@ RUN chmod +x /usr/local/bin/validate-npm-script-policy && \ # ---------- OpenCode (AI coding agent) ---------- # Installed via npm as root (global install needs write access to /usr/local/lib) -RUN npm i -g --allow-scripts=opencode-ai,@anthropic-ai/claude-code "opencode-ai@${OPENCODE_VERSION}" "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" && \ +RUN npm i -g --ignore-scripts "opencode-ai@${OPENCODE_VERSION}" "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" && \ rm -rf /root/.npm ENV PATH="/home/opencode/.local/bin:${PATH}" @@ -226,15 +271,15 @@ ENV PATH="/home/opencode/.local/bin:${PATH}" RUN npm i -g --ignore-scripts \ "typescript@${TYPESCRIPT_VERSION}" "tsx@${TSX_VERSION}" \ "pnpm@${PNPM_VERSION}" \ - vite@8.1.4 esbuild@0.28.1 \ - eslint@10.7.0 prettier@3.9.5 \ - serve@14.2.6 nodemon@3.1.14 concurrently@10.0.3 \ + "vite@${VITE_VERSION}" esbuild@0.28.1 \ + eslint@10.7.0 "prettier@${PRETTIER_VERSION}" \ + serve@14.2.6 nodemon@3.1.14 \ dotenv-cli@11.0.0 \ - "wrangler@${WRANGLER_VERSION}" "vercel@${VERCEL_VERSION}" \ + "wrangler@${WRANGLER_VERSION}" \ pm2@7.0.3 \ - prisma@7.8.0 drizzle-kit@0.31.10 \ - lighthouse@13.4.0 @lhci/cli@0.15.1 \ - sharp-cli@5.2.0 json-server@0.17.4 http-server@14.1.1 && \ + "prisma@${PRISMA_VERSION}" drizzle-kit@0.31.10 \ + "lighthouse@${LIGHTHOUSE_VERSION}" \ + json-server@0.17.4 http-server@14.1.1 && \ DRIZZLE_DIR=/usr/local/lib/node_modules/drizzle-kit && \ jq '.dependencies |= del(."@esbuild-kit/esm-loader") | .dependencies.esbuild = "0.28.1"' \ "$DRIZZLE_DIR/package.json" > "$DRIZZLE_DIR/package.json.tmp" && \ @@ -260,44 +305,50 @@ RUN npm i -g --ignore-scripts --omit=optional "netlify-cli@${NETLIFY_CLI_VERSION netlify deploy --help >/dev/null && \ rm -rf /root/.npm -# Trixie's python3-packaging is dpkg-owned and has no pip RECORD. Install the -# exact Hermes pin into /usr/local without attempting to remove the Debian copy. -RUN pip install --no-cache-dir --break-system-packages --ignore-installed packaging==26.0 - -RUN HERMES_AGENT_COMMIT=$(git ls-remote --tags https://github.com/NousResearch/hermes-agent.git "refs/tags/${HERMES_AGENT_VERSION}" | awk '{print $1}') && \ - if [ "$HERMES_AGENT_COMMIT" != "$HERMES_AGENT_REF" ]; then \ - echo "Hermes tag ${HERMES_AGENT_VERSION} resolved to ${HERMES_AGENT_COMMIT:-missing}, expected ${HERMES_AGENT_REF}" >&2; \ - exit 1; \ - fi && \ - pip install --no-cache-dir --break-system-packages \ - "hermes-agent[pty,mcp,messaging] @ git+https://github.com/NousResearch/hermes-agent.git@${HERMES_AGENT_REF}" && \ - python3 -m pip check - -RUN npm i -g --allow-scripts=@embedded-postgres/linux-x64,@embedded-postgres/linux-arm64 \ +RUN npm i -g --ignore-scripts \ "paperclipai@${PAPERCLIP_VERSION}" && \ rm -rf /root/.npm +# Paperclip's Cursor adapter currently resolves Undici 5 through Connect 1.x. +# Keep Paperclip stable while replacing that HTTP client with the first fixed +# 6.x release; remove this reviewed compatibility patch when Paperclip updates Connect. +RUN test "$(npm view "undici@${PAPERCLIP_UNDICI_VERSION}" dist.integrity)" = \ + "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==" && \ + UNDICI_TARBALL=$(npm pack --silent --pack-destination /tmp "undici@${PAPERCLIP_UNDICI_VERSION}") && \ + UNDICI_DIR=/usr/local/lib/node_modules/paperclipai/node_modules/undici && \ + CONNECT_NODE_PACKAGE=/usr/local/lib/node_modules/paperclipai/node_modules/@connectrpc/connect-node/package.json && \ + rm -rf "$UNDICI_DIR" && mkdir "$UNDICI_DIR" && \ + tar -xzf "/tmp/${UNDICI_TARBALL}" -C "$UNDICI_DIR" --strip-components=1 && \ + rm "/tmp/${UNDICI_TARBALL}" && \ + node -e 'const fs=require("fs"); const file=process.argv[1]; const version=process.argv[2]; const pkg=JSON.parse(fs.readFileSync(file,"utf8")); pkg.dependencies.undici=version; fs.writeFileSync(file,`${JSON.stringify(pkg,null,2)}\n`)' \ + "$CONNECT_NODE_PACKAGE" "^${PAPERCLIP_UNDICI_VERSION}" && \ + test "$(node -p 'require("/usr/local/lib/node_modules/paperclipai/node_modules/undici/package.json").version')" = \ + "${PAPERCLIP_UNDICI_VERSION}" && \ + (cd /usr/local/lib/node_modules/paperclipai && npm ls undici --all >/dev/null) && \ + node --input-type=module -e 'const {testEnvironment}=await import("file:///usr/local/lib/node_modules/paperclipai/node_modules/@paperclipai/adapter-cursor-cloud/dist/server/index.js"); const result=await testEnvironment({adapterType:"cursor_cloud",config:{}}); if(result.status!=="fail" || !result.checks.some((check)=>check.code==="cursor_cloud_api_key_missing")) process.exit(1)' RUN find /usr/local/lib/node_modules/paperclipai/node_modules/@embedded-postgres \ -path '*/native/lib' -type d -exec sh -c '\ for lib_dir do \ [ -f "$lib_dir/libcrypto.so.1.1" ] && ln -sf libcrypto.so.1.1 "$lib_dir/libcrypto.so.1"; \ [ -f "$lib_dir/libssl.so.1.1" ] && ln -sf libssl.so.1.1 "$lib_dir/libssl.so.1"; \ done' sh {} + -RUN POSTGRES_PACKAGE=$(find /usr/local/lib/node_modules/paperclipai/node_modules/@embedded-postgres \ - -mindepth 1 -maxdepth 1 -type d -name 'linux-*' -print -quit) && \ - test -n "$POSTGRES_PACKAGE" && \ - node -e 'const fs=require("fs"); const path=require("path"); const root=process.argv[1]; const links=JSON.parse(fs.readFileSync(path.join(root,"native/pg-symlinks.json"),"utf8")); for (const {source,target} of links) { const sourcePath=path.join(root,source); const targetPath=path.join(root,target); if (!fs.lstatSync(targetPath).isSymbolicLink() || fs.realpathSync(targetPath)!==fs.realpathSync(sourcePath)) throw new Error(`invalid PostgreSQL link: ${target}`); }' \ - "$POSTGRES_PACKAGE" - -RUN validate-npm-script-policy \ +RUN python3 /usr/local/bin/validate-npm-script-policy \ --policy /usr/local/share/holycode/npm-global-script-policy.json \ --root /usr/local/lib/node_modules \ --target-arch "${TARGETARCH}" && \ + (cd /usr/local/lib/node_modules/opencode-ai && node ./postinstall.mjs) && \ + (cd /usr/local/lib/node_modules/@anthropic-ai/claude-code && node install.cjs) && \ + POSTGRES_PACKAGE=$(find /usr/local/lib/node_modules/paperclipai/node_modules/@embedded-postgres \ + -mindepth 1 -maxdepth 1 -type d -name 'linux-*' -print -quit) && \ + test -n "$POSTGRES_PACKAGE" && \ + (cd "$POSTGRES_PACKAGE" && node scripts/hydrate-symlinks.js) && \ + node -e 'const fs=require("fs"); const path=require("path"); const root=process.argv[1]; const links=JSON.parse(fs.readFileSync(path.join(root,"native/pg-symlinks.json"),"utf8")); for (const {source,target} of links) { const sourcePath=path.join(root,source); const targetPath=path.join(root,target); if (!fs.lstatSync(targetPath).isSymbolicLink() || fs.realpathSync(targetPath)!==fs.realpathSync(sourcePath)) throw new Error(`invalid PostgreSQL link: ${target}`); }' \ + "$POSTGRES_PACKAGE" && \ opencode --version | grep -Fx "${OPENCODE_VERSION}" && \ claude --version | grep -F "${CLAUDE_CODE_VERSION}" && \ esbuild --version | grep -Fx "0.28.1" && \ prisma --version >/dev/null && \ wrangler --version | grep -F "${WRANGLER_VERSION}" && \ - node -e 'const sharp=require("/usr/local/lib/node_modules/sharp-cli/node_modules/sharp"); sharp({create:{width:1,height:1,channels:4,background:{r:0,g:0,b:0,alpha:1}}}).png().toBuffer().then(b=>{if(!b.length)process.exit(1)})' && \ + ! command -v vercel && ! command -v sharp && ! command -v concurrently && ! command -v lhci && \ WORKERD_BIN=$(find /usr/local/lib/node_modules/wrangler -path '*/workerd/bin/workerd' -type f -print -quit) && \ test -n "${WORKERD_BIN}" && "${WORKERD_BIN}" --version >/dev/null && \ node -e 'const ssh2=require("/usr/local/lib/node_modules/paperclipai/node_modules/ssh2"); if(typeof ssh2.Client!=="function") process.exit(1)' @@ -324,11 +375,9 @@ COPY s6-overlay/s6-rc.d/xvfb/run /etc/s6-overlay/s6-rc.d/xvfb/run RUN chmod +x /etc/s6-overlay/s6-rc.d/xvfb/run && \ touch /etc/s6-overlay/user-bundles.d/user/contents.d/xvfb -COPY s6-overlay/s6-rc.d/hermes/type /etc/s6-overlay/s6-rc.d/hermes/type -COPY s6-overlay/s6-rc.d/hermes/run /etc/s6-overlay/s6-rc.d/hermes/run COPY s6-overlay/s6-rc.d/paperclip/type /etc/s6-overlay/s6-rc.d/paperclip/type COPY s6-overlay/s6-rc.d/paperclip/run /etc/s6-overlay/s6-rc.d/paperclip/run -RUN chmod +x /etc/s6-overlay/s6-rc.d/hermes/run /etc/s6-overlay/s6-rc.d/paperclip/run +RUN chmod +x /etc/s6-overlay/s6-rc.d/paperclip/run # ---------- Working directory ---------- WORKDIR /workspace diff --git a/README.md b/README.md index 3cadfc5..aaf3f63 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,11 @@ ### One container. Every tool. Any provider. -OpenCode running in a container with everything already installed. 50+ dev tools, 10+ AI providers, headless browser, persistent state, and two serious upgrades on top: Hermes Agent and Paperclip. Drop it on any machine and pick up exactly where you left off. +OpenCode running in a container with everything already installed. 50+ dev tools, 10+ AI providers, a sandboxed headless browser, persistent state, and Paperclip on top. Drop it on any machine and pick up exactly where you left off. Release tags use exact `vX.Y.Z`; Docker image tags drop the `v` prefix. Each version segment is one digit, so `v1.0.9` rolls to `v1.1.0`, `v1.1.9` rolls to `v1.2.0`, and `v1.9.9` rolls to `v2.0.0`. The commit subject, Git tag, and GitHub release title must match. Published `v1.0.10` through `v1.0.13` stay immutable as historical releases. -**Hermes Agent turns HolyCode into a meta-agent runtime.** You get a smarter planning layer on top of OpenCode, an API surface on port `8642`, MCP support, messaging adapters, and a clean way to let a "brain" delegate code work into the local container instead of bolting that together yourself. +**Hermes is temporarily unbundled in v1.1.3.** Its current releases pin vulnerable dependencies. HolyCode leaves `/home/opencode/.hermes` untouched so you can restore the service when upstream publishes compatible fixed pins. **Paperclip turns HolyCode into an agent board.** You get a dashboard on port `3100` where you create a company, hire OpenCode-backed workers, wake them on heartbeat, and manage agent work from a real UI instead of hand-rolling scripts around `opencode run`. @@ -38,7 +38,7 @@ Release tags use exact `vX.Y.Z`; Docker image tags drop the `v` prefix. Each ver **Multi-agent orchestration built in.** Enable oh-my-openagent and turn OpenCode into a coordinated agent system with parallel execution. -**You were going to spend an hour getting your environment back. Or you could just `docker compose up` and get a coding workstation, a meta-agent, and an agent board in one shot.** +**You were going to spend an hour getting your environment back. Or you could just `docker compose up` and get a coding workstation and an agent board in one shot.** > **Don't want to self-host?** [HolyCode Cloud](https://holycode.coderluii.dev/cloud) is coming. Same tools, zero setup. Early access is free. --- @@ -55,7 +55,7 @@ It wraps [OpenCode](https://opencode.ai), an AI coding agent with a built-in web It's the same idea as [HolyClaude](https://github.com/coderluii/holyclaude) but wrapping OpenCode instead of Claude Code. And here's the thing: OpenCode isn't locked to one provider. Point it at Anthropic, OpenAI, Google Gemini, Groq, AWS Bedrock, or Azure OpenAI. Same container, your choice of model. -50+ dev tools, two language runtimes, a headless browser stack, process supervision, and two bundled orchestration layers. All wired up, all ready on first boot. I've been running this on my own server. Every bug has been hit, diagnosed, and fixed. +50+ dev tools, two language runtimes, a sandboxed headless browser stack, process supervision, and an optional Paperclip agent board. All wired up, all ready on first boot. I've been running this on my own server. Every bug has been hit, diagnosed, and fixed. You pull it. You run it. You open your browser. You build. @@ -99,6 +99,14 @@ docker pull coderluii/holycode:latest **Step 2.** Create a `docker-compose.yaml`. +The Compose file uses HolyCode's Chromium seccomp profile. If you are not running from a clone of this repository, download the release copy first: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -106,6 +114,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -339,7 +349,7 @@ services: # - ENABLE_OH_MY_OPENAGENT=true ``` -CLIProxyAPI remains supported as an external OpenAI-compatible endpoint. HolyCode no longer bundles its Docker sidecar because the `v7.2.77` image still contains fixable high-severity Go dependencies. Set `CLIPROXYAPI_BASE_URL` to an endpoint that the HolyCode container can reach; this does not replace Claude Auth. +CLIProxyAPI remains supported as an external OpenAI-compatible endpoint. HolyCode does not bundle its Docker sidecar until its release binaries have verifiable compiler provenance and pass `govulncheck`. Set `CLIPROXYAPI_BASE_URL` to an endpoint that the HolyCode container can reach; this does not replace Claude Auth.

back to top @@ -395,9 +405,7 @@ Prefer Podman? HolyCode uses the same container image there too. The Podman guid | `PAPERCLIP_DEPLOYMENT_MODE` | `authenticated` | Docker-safe Paperclip startup mode; HolyCode defaults this away from `local_trusted` | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset used on first boot; `lan` binds inside Docker on `0.0.0.0` | | `PAPERCLIP_ALLOWED_HOSTNAMES` | (none) | Comma-separated Paperclip remote hostnames/IPs to allow; use hostname/IP only, no scheme or port | -| `ENABLE_HERMES` | (none) | Set to `true` to start Hermes as a bundled meta-agent API | -| `HERMES_PORT` | `8642` | Override the container port used by Hermes | -| `API_SERVER_KEY` | (none) | Required when `ENABLE_HERMES=true`; use a real bearer token, such as `openssl rand -hex 32` | +| `ENABLE_HERMES` | (none) | Legacy flag; `true` stops v1.1.3 with a migration message while bundled Hermes is unavailable | | `CLIPROXYAPI_ENABLED` | (none) | Set to `true` to add the optional OpenCode `cliproxyapi` provider | | `CLIPROXYAPI_BASE_URL` | `http://cliproxyapi:8317/v1` | Externally managed CLIProxyAPI base URL reachable from the HolyCode container | | `CLIPROXYAPI_API_KEY` | (none) | Optional API key for CLIProxyAPI, stored only as an OpenCode env reference when set | @@ -421,9 +429,7 @@ Prefer Podman? HolyCode uses the same container image there too. The Podman guid > `PAPERCLIP_ALLOWED_HOSTNAMES` lets Paperclip accept listed LAN/private hostnames or IPs. Use comma-separated hostname/IP values only, without `http://`, `https://`, or ports. Restart the container after changing it. The hostname guard and Paperclip authentication stay enabled. -> `ENABLE_HERMES=true` starts Hermes on port `8642` inside the container. Hermes runs with `/home/opencode` as its home, persists under `/home/opencode/.hermes`, uses the already-installed `opencode` binary, and can expose an OpenAI-compatible API while delegating code work back into HolyCode. Set `API_SERVER_KEY` before enabling the API server. - -> Hermes is an API service, not a landing page. A `404` at `http://localhost:8642/` is expected. The important signal is that the port is listening and the process stays healthy. +> Bundled Hermes is temporarily unavailable in v1.1.3 because its current releases require vulnerable dependency pins. If an older deployment still sets `ENABLE_HERMES=true`, startup stops with a migration message instead of silently ignoring the flag. Your `/home/opencode/.hermes` data is not changed. > `CLIPROXYAPI_ENABLED=true` adds a separate OpenCode provider named `cliproxyapi`. It does not change `ENABLE_CLAUDE_AUTH`, does not touch `/home/opencode/.claude`, and does not set global `ANTHROPIC_*` proxy variables. Set `CLIPROXYAPI_BASE_URL` to your externally managed service and keep its credentials and network exposure outside the HolyCode container. @@ -473,38 +479,40 @@ Prefer Podman? HolyCode uses the same container image there too. The Podman guid

-v1.1.2 release pins +v1.1.3 release pins | Component | Version | |-----------|---------| -| OpenCode | 1.18.2 | +| OpenCode | 1.18.4 | | Paperclip | 2026.707.0 | -| Hermes | v2026.7.7.2 | +| Hermes | Bundled service temporarily removed; existing `.hermes` data is preserved | | CLIProxyAPI | Bundled sidecar removed; external endpoints remain supported | -| s6-overlay | 3.2.3.1 | +| s6-overlay | 3.2.3.2 | | eza | 0.23.5 | -| fzf | 0.74.0 | +| fzf | 0.74.1 | | lazygit | 0.63.1 | -| pnpm | 11.13.0 | -| Vite | 8.1.4 | +| pnpm | 11.15.1 | +| Vite | 8.1.5 | | ESLint | 10.7.0 | -| Prettier | 3.9.5 | -| Wrangler | 4.111.0; legacy service environments are not supported | +| Prettier | 3.9.6 | +| Wrangler | 4.112.0; legacy service environments are not supported | +| Prisma | 7.9.0 | +| Lighthouse | 13.4.1 | | Netlify CLI | 26.2.0, remote build/deploy only; local functions binaries removed | -| Vercel | 54.21.0, held because scope/team behavior is unproven | -| tqdm | 4.68.4 | +| Vercel, sharp-cli, concurrently, LHCI | Removed because their current dependency trees contain fixable high or critical findings | +| tqdm | 4.69.0 | | uvicorn | 0.51.0 | -| Claude stable | 2.1.210 | +| Claude stable | 2.1.216 | | tsx | 4.23.1 | | TypeScript | 6.0.3, held until TypeScript 7 exposes the stable toolchain APIs this image needs | | NumPy | 2.5.1 on Python 3.13 | | json-server | 0.17.4, held on the stable release instead of the 1.0 beta | | opencode-claude-auth default | 2.0.0 | -| oh-my-openagent default | 4.18.1 | +| oh-my-openagent default | 4.19.0 | -Release assets use digests, checksums, and action SHAs for hardening. npm lifecycle scripts are denied by default and checked against a reviewed version-and-script policy. HolyCode also publishes per-platform SBOM and provenance attestations and runs per-platform vulnerability scans, but it does not claim zero vulnerabilities or universal freshness. +Release assets use digests, checksums, and action SHAs for hardening. npm lifecycle scripts are installed disabled, then their exact package version, integrity, architecture, and script body are validated before the approved scripts run. HolyCode also publishes per-platform SBOM and provenance attestations and runs per-platform vulnerability scans, but it does not claim universal freshness or that future rebuilds will retain the same scanner result. -The dated adoption, hold, removal, and scanner decisions are in the [v1.1.2 dependency audit](docs/dependency-audit-v1.1.2.md). +The dated adoption, hold, removal, and scanner decisions are in the [v1.1.3 dependency audit](docs/dependency-audit-v1.1.3.md).
@@ -547,7 +555,6 @@ Includes Liberation, DejaVu, Noto, and Noto Color Emoji fonts for correct page r | Service | Purpose | |---------|---------| -| Hermes Agent | Self-improving meta-agent with MCP, messaging adapters, and OpenCode delegation | | Paperclip | Local agent board that hires OpenCode workers and wakes them on heartbeat | | CLIProxyAPI integration | External OpenAI-compatible account/model routing through `CLIPROXYAPI_*` | | Claude Code CLI | Installed for Claude subscription auth flows via `ENABLE_CLAUDE_AUTH` | @@ -574,37 +581,16 @@ s6-overlay supervises OpenCode and Xvfb. If a process crashes, it restarts autom ## 🧩 Bundled Services -HolyCode ships with two optional bundled layers on top of OpenCode, plus integration for an externally managed CLIProxyAPI endpoint. You do **not** need any of them to use the container. +HolyCode ships with optional Paperclip on top of OpenCode, plus integration for an externally managed CLIProxyAPI endpoint. You do **not** need either one to use the container. -- **Hermes Agent** is for when you want a smarter coordinator sitting above OpenCode. - **Paperclip** is for when you want a board, a workflow, and actual agent management instead of just one-off prompts. - **CLIProxyAPI integration** is for when you already manage an OpenAI-compatible endpoint and want OpenCode to use it as a separate provider. -Flip the env var, restart the container, and the service comes up alongside the normal web UI. - ### Hermes Agent -Hermes is the "smarter brain" option. It runs as a bundled meta-agent, exposes an API service on port `8642`, and delegates coding work by calling the local `opencode` binary that HolyCode already ships. +Hermes is temporarily not bundled in v1.1.3. The current Hermes release line pins vulnerable Pillow and adds unresolved findings through its optional dependency set. HolyCode removes the runtime and service instead of shipping those packages. -Why that matters: - -- **Planning above execution.** OpenCode does the hands-on coding. Hermes gives you a layer that can reason, coordinate, and delegate down into that local worker. -- **API-ready agent runtime.** You can point other tooling at Hermes instead of wiring your own service around OpenCode. -- **MCP and messaging in the same box.** HolyCode already solves the dev-environment side. Hermes adds the "agent platform" layer on top. -- **Persistent agent state.** Its data lives under `~/.hermes`, so rebuilds don't wipe the runtime you just configured. - -If you want HolyCode to feel less like "a container with a coding tool" and more like "an AI runtime you can build systems on top of," Hermes is the part that changes that. - -Turn it on with: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Hermes state lives under `/home/opencode/.hermes`, so it follows the same persistence story as the rest of HolyCode. Generate `API_SERVER_KEY` with a secret generator such as `openssl rand -hex 32`; do not reuse an AI provider key. +Existing state under `/home/opencode/.hermes` remains untouched. Remove `ENABLE_HERMES=true` from older Compose deployments before starting v1.1.3. The container stops with a clear migration message when that legacy flag remains set, so the missing service cannot be mistaken for a successful start. ### Paperclip @@ -617,7 +603,7 @@ Why that matters: - **Faster delegation experiments.** Create a company, assign work, and see how an agent workflow feels without building the orchestration stack yourself. - **Persistent board state.** Data, config, storage, and embedded Postgres all live under `~/.paperclip`. -If Hermes is the brain, Paperclip is the control room. It's the thing you turn on when you want to manage agent work, not just launch it. +Paperclip is the control room. Turn it on when you want to manage agent work, not just launch it. Turn it on with: @@ -642,7 +628,7 @@ Why that matters: - **One provider surface.** OpenCode can use `cliproxyapi/` while CLIProxyAPI handles the account/model routing behind it. - **Isolated from Claude Auth.** This does not replace `ENABLE_CLAUDE_AUTH`, does not touch `/home/opencode/.claude`, and does not set global Anthropic proxy variables. -- **No bundled vulnerable service.** The `v7.2.77` sidecar image is not included while its fixable high-severity Go findings remain unresolved. +- **No bundled unverified service.** CLIProxyAPI stays external-only until its published binaries have verifiable compiler provenance and pass `govulncheck`. - **Separate ownership.** You manage CLIProxyAPI config, auth, updates, and network exposure outside HolyCode. Turn it on with: @@ -676,7 +662,6 @@ graph TD F --> G G --> H[Xvfb :99] G --> I[opencode web :4096] - G --> Q[Hermes API :8642] G --> R[Paperclip UI :3100] V[External CLIProxyAPI endpoint] --> U[cliproxyapi provider] I --> J[Web UI] @@ -686,12 +671,11 @@ graph TD M --> N[opencode TUI] M --> O[opencode run 'message'] M --> P[opencode attach localhost:4096] - Q --> S[Meta-agent API clients] R --> T[Agent board and CEO invite] I --> U ``` -The entrypoint handles user remapping, plugin toggles, optional bundled-service toggles, CLIProxyAPI provider injection, and first-boot setup. s6-overlay supervises Xvfb, the OpenCode web server, and any optional bundled services you enabled inside the HolyCode container. CLIProxyAPI, when configured, is external. Access the web UI at port 4096, Hermes on 8642, or Paperclip on 3100 when those services are enabled. +The entrypoint handles user remapping, plugin toggles, Paperclip startup, CLIProxyAPI provider injection, and first-boot setup. s6-overlay supervises Xvfb, the OpenCode web server, and Paperclip when enabled. CLIProxyAPI, when configured, is external. Access the OpenCode web UI at port 4096 or Paperclip on 3100.

back to top @@ -811,7 +795,7 @@ Plugin cache is mounted separately at `./local-cache/opencode` by default so you Rebuild the container anytime. Run `docker compose pull && docker compose up -d` and your sessions, settings, and configs come back automatically. -The Dockerfile pins direct npm, PyPI, and GitHub-release versions. Binary release assets use checksums, container bases use digests, and GitHub Actions use commit SHAs. Claude Code is installed from `@anthropic-ai/claude-code@2.1.210`. npm 12 lifecycle scripts are denied by default and limited to the exact reviewed scripts required by OpenCode, Claude Code, and Paperclip's architecture-specific embedded PostgreSQL package. Debian packages still resolve from the current Trixie repositories at build time, so a later rebuild is not guaranteed to be byte-for-byte identical. Optional OpenCode plugins are live registry installs trusted at boot and sit outside the image SBOM. Netlify CLI supports remote build/deploy commands only; `netlify dev` and local functions are intentionally unavailable. Each release publishes per-platform SBOM and provenance attestations and runs per-platform vulnerability scans without promising zero vulnerabilities or universal freshness. +The Dockerfile pins direct npm, PyPI, and GitHub-release versions. Binary release assets use checksums, container bases use digests, and GitHub Actions use commit SHAs. Claude Code is installed from `@anthropic-ai/claude-code@2.1.216`. npm lifecycle scripts are disabled during installation. HolyCode validates each script package's version, integrity, architecture, and script body before running only the approved OpenCode, Claude Code, and Paperclip embedded PostgreSQL steps. Debian packages still resolve from the current Trixie repositories at build time, so a later rebuild is not guaranteed to be byte-for-byte identical. Optional OpenCode plugins are live registry installs trusted at boot and sit outside the image SBOM. Netlify CLI supports remote build/deploy commands only; `netlify dev` and local functions are intentionally unavailable. Each release publishes per-platform SBOM and provenance attestations and runs per-platform vulnerability scans without promising universal freshness. **SQLite WAL note.** The sessions database uses Write-Ahead Logging. Don't copy the `.db` file while the container is running. Stop the container first if you need to back up or migrate the database file. @@ -860,6 +844,19 @@ If you skip this, files in your workspace may be owned by root and you'll need s Stop the stack and copy `./data`, `./local-cache`, and `./workspace` before upgrading. Migrations can change persisted data, so keep those pre-upgrade copies until the new image has passed your normal workflows. +If you are upgrading from a release before `v1.1.3`, download the Chromium seccomp profile and add it to the `holycode` service before recreating the container: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + +```yaml +security_opt: + - seccomp=./config/chromium-seccomp.json +``` + ```bash docker compose stop # Copy ./data, ./local-cache, and ./workspace with your host backup tool. @@ -867,7 +864,7 @@ docker compose pull docker compose up -d ``` -If you need to roll back, stop the stack, change the Compose image to `coderluii/holycode:1.1.1`, restore the untouched pre-`v1.1.2` copies, and start the stack again. Do not point `v1.1.1` at data already migrated by `v1.1.2` unless the migration is known to be backward compatible. +If you need to roll back, stop the stack, change the Compose image to `coderluii/holycode:1.1.2`, restore the untouched pre-`v1.1.3` copies, and start the stack again. Rollback means restoring those snapshots. It does not reverse database migrations in place, and you should not point `v1.1.2` at data already changed by `v1.1.3`. After the checks pass, remove the backup on your own schedule. @@ -953,19 +950,19 @@ OpenCode takes a few seconds to initialize. Give it 10-15 seconds after `docker

-Why doesn't HolyCode need SYS_ADMIN or seccomp=unconfined? +How is Chromium sandboxed? -Chromium runs with `--no-sandbox` inside the container, which is standard for containerized browser setups. This eliminates the need for `SYS_ADMIN` capabilities or `seccomp=unconfined` that some other Docker browser setups require. The container itself provides the isolation boundary. +HolyCode runs Chromium as the `opencode` user with Chromium's sandbox enabled. The shipped Compose files apply a constrained seccomp profile that permits the namespace syscalls the sandbox needs without granting `SYS_ADMIN` or disabling seccomp. -If you prefer to use Chromium's built-in sandbox instead, add the following to your compose file and remove `--no-sandbox` from the `CHROMIUM_FLAGS` environment variable: +Keep this setting when writing your own Compose file: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +The profile path is relative to your Compose file. Do not add `--no-sandbox`, `SYS_ADMIN`, or `seccomp=unconfined` as a browser workaround. +
diff --git a/THIRD-PARTY-NOTICES b/THIRD-PARTY-NOTICES index 5b62588..244d79a 100644 --- a/THIRD-PARTY-NOTICES +++ b/THIRD-PARTY-NOTICES @@ -82,25 +82,20 @@ automation via Playwright and Puppeteer. Installed via pip. Uses the system Chromium binary, not Playwright's bundled browsers. +HolyCode also vendors Playwright's Docker seccomp profile at +`config/chromium-seccomp.json` under the same Apache-2.0 license. The profile +permits the namespace syscalls required by Chromium's sandbox. + ## Claude Code CLI - License: SEE LICENSE IN README.md - npm: https://www.npmjs.com/package/@anthropic-ai/claude-code -- Version: 2.1.210 +- Version: 2.1.216 -Installed from the pinned npm package `@anthropic-ai/claude-code@2.1.210`. +Installed from the pinned npm package `@anthropic-ai/claude-code@2.1.216`. Used for Claude subscription authentication flows when ENABLE_CLAUDE_AUTH=true. -## Hermes Agent - -- License: MIT -- Source: https://github.com/NousResearch/hermes-agent -- Pin: v2026.7.7.2@b7751df34688835a108e0d630f3495fc11f3df79 - -Installed from the pinned release `v2026.7.7.2` and commit -`b7751df34688835a108e0d630f3495fc11f3df79`. Used only when ENABLE_HERMES=true. - ## Paperclip - License: MIT @@ -110,15 +105,20 @@ Installed from the pinned release `v2026.7.7.2` and commit Installed via npm as the global package `paperclipai@2026.707.0`. Paperclip includes @paperclipai/skills-catalog through its published package set, which HolyCode -uses for the Skills page. HolyCode permits the exact architecture-specific embedded -PostgreSQL postinstall script needed to hydrate its packaged library symlinks before -Paperclip runs as an unprivileged user. Used only when ENABLE_PAPERCLIP=true. +uses for the Skills page. HolyCode validates the package version, integrity, +architecture, and lifecycle-script body before running the exact embedded PostgreSQL +postinstall needed to hydrate its packaged library symlinks. Paperclip then runs as +an unprivileged user. Used only when ENABLE_PAPERCLIP=true. ## GitHub CLI (gh) - License: MIT - Copyright: GitHub, Inc. - Source: https://github.com/cli/cli +- Version: 2.96.0 (`b300f2ec7ec9dc9addc39b2ad88c54097ded7ca0`) + +Built from the exact upstream tag with Go 1.26.5 because the official 2.96.0 +package was compiled before the Go standard-library fix for CVE-2026-39822. ## lazygit @@ -137,13 +137,13 @@ Paperclip runs as an unprivileged user. Used only when ENABLE_PAPERCLIP=true. - License: Apache-2.0 OR BSD-2-Clause - Source: https://github.com/pypa/packaging -- Version: 26.0 +- Version: 26.2 ## Wrangler - License: Apache-2.0 OR MIT - Source: https://github.com/cloudflare/workers-sdk -- Version: 4.111.0 +- Version: 4.112.0 ## delta diff --git a/config/chromium-seccomp.json b/config/chromium-seccomp.json new file mode 100644 index 0000000..fddc05f --- /dev/null +++ b/config/chromium-seccomp.json @@ -0,0 +1,831 @@ +{ + "defaultAction": "SCMP_ACT_ERRNO", + "archMap": [ + { + "architecture": "SCMP_ARCH_X86_64", + "subArchitectures": [ + "SCMP_ARCH_X86", + "SCMP_ARCH_X32" + ] + }, + { + "architecture": "SCMP_ARCH_AARCH64", + "subArchitectures": [ + "SCMP_ARCH_ARM" + ] + }, + { + "architecture": "SCMP_ARCH_MIPS64", + "subArchitectures": [ + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64N32" + ] + }, + { + "architecture": "SCMP_ARCH_MIPS64N32", + "subArchitectures": [ + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64" + ] + }, + { + "architecture": "SCMP_ARCH_MIPSEL64", + "subArchitectures": [ + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64N32" + ] + }, + { + "architecture": "SCMP_ARCH_MIPSEL64N32", + "subArchitectures": [ + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64" + ] + }, + { + "architecture": "SCMP_ARCH_S390X", + "subArchitectures": [ + "SCMP_ARCH_S390" + ] + } + ], + "syscalls": [ + { + "comment": "Allow create user namespaces", + "names": [ + "clone", + "setns", + "unshare" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "includes": {}, + "excludes": {} + }, + { + "names": [ + "accept", + "accept4", + "access", + "adjtimex", + "alarm", + "bind", + "brk", + "capget", + "capset", + "chdir", + "chmod", + "chown", + "chown32", + "clock_adjtime", + "clock_adjtime64", + "clock_getres", + "clock_getres_time64", + "clock_gettime", + "clock_gettime64", + "clock_nanosleep", + "clock_nanosleep_time64", + "close", + "connect", + "copy_file_range", + "creat", + "dup", + "dup2", + "dup3", + "epoll_create", + "epoll_create1", + "epoll_ctl", + "epoll_ctl_old", + "epoll_pwait", + "epoll_wait", + "epoll_wait_old", + "eventfd", + "eventfd2", + "execve", + "execveat", + "exit", + "exit_group", + "faccessat", + "fadvise64", + "fadvise64_64", + "fallocate", + "fanotify_mark", + "fchdir", + "fchmod", + "fchmodat", + "fchown", + "fchown32", + "fchownat", + "fcntl", + "fcntl64", + "fdatasync", + "fgetxattr", + "flistxattr", + "flock", + "fork", + "fremovexattr", + "fsetxattr", + "fstat", + "fstat64", + "fstatat64", + "fstatfs", + "fstatfs64", + "fsync", + "ftruncate", + "ftruncate64", + "futex", + "futex_time64", + "futimesat", + "getcpu", + "getcwd", + "getdents", + "getdents64", + "getegid", + "getegid32", + "geteuid", + "geteuid32", + "getgid", + "getgid32", + "getgroups", + "getgroups32", + "getitimer", + "getpeername", + "getpgid", + "getpgrp", + "getpid", + "getppid", + "getpriority", + "getrandom", + "getresgid", + "getresgid32", + "getresuid", + "getresuid32", + "getrlimit", + "get_robust_list", + "getrusage", + "getsid", + "getsockname", + "getsockopt", + "get_thread_area", + "gettid", + "gettimeofday", + "getuid", + "getuid32", + "getxattr", + "inotify_add_watch", + "inotify_init", + "inotify_init1", + "inotify_rm_watch", + "io_cancel", + "ioctl", + "io_destroy", + "io_getevents", + "io_pgetevents", + "io_pgetevents_time64", + "ioprio_get", + "ioprio_set", + "io_setup", + "io_submit", + "io_uring_enter", + "io_uring_register", + "io_uring_setup", + "ipc", + "kill", + "lchown", + "lchown32", + "lgetxattr", + "link", + "linkat", + "listen", + "listxattr", + "llistxattr", + "_llseek", + "lremovexattr", + "lseek", + "lsetxattr", + "lstat", + "lstat64", + "madvise", + "membarrier", + "memfd_create", + "mincore", + "mkdir", + "mkdirat", + "mknod", + "mknodat", + "mlock", + "mlock2", + "mlockall", + "mmap", + "mmap2", + "mprotect", + "mq_getsetattr", + "mq_notify", + "mq_open", + "mq_timedreceive", + "mq_timedreceive_time64", + "mq_timedsend", + "mq_timedsend_time64", + "mq_unlink", + "mremap", + "msgctl", + "msgget", + "msgrcv", + "msgsnd", + "msync", + "munlock", + "munlockall", + "munmap", + "nanosleep", + "newfstatat", + "_newselect", + "open", + "openat", + "pause", + "pipe", + "pipe2", + "poll", + "ppoll", + "ppoll_time64", + "prctl", + "pread64", + "preadv", + "preadv2", + "prlimit64", + "pselect6", + "pselect6_time64", + "pwrite64", + "pwritev", + "pwritev2", + "read", + "readahead", + "readlink", + "readlinkat", + "readv", + "recv", + "recvfrom", + "recvmmsg", + "recvmmsg_time64", + "recvmsg", + "remap_file_pages", + "removexattr", + "rename", + "renameat", + "renameat2", + "restart_syscall", + "rmdir", + "rseq", + "rt_sigaction", + "rt_sigpending", + "rt_sigprocmask", + "rt_sigqueueinfo", + "rt_sigreturn", + "rt_sigsuspend", + "rt_sigtimedwait", + "rt_sigtimedwait_time64", + "rt_tgsigqueueinfo", + "sched_getaffinity", + "sched_getattr", + "sched_getparam", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_getscheduler", + "sched_rr_get_interval", + "sched_rr_get_interval_time64", + "sched_setaffinity", + "sched_setattr", + "sched_setparam", + "sched_setscheduler", + "sched_yield", + "seccomp", + "select", + "semctl", + "semget", + "semop", + "semtimedop", + "semtimedop_time64", + "send", + "sendfile", + "sendfile64", + "sendmmsg", + "sendmsg", + "sendto", + "setfsgid", + "setfsgid32", + "setfsuid", + "setfsuid32", + "setgid", + "setgid32", + "setgroups", + "setgroups32", + "setitimer", + "setpgid", + "setpriority", + "setregid", + "setregid32", + "setresgid", + "setresgid32", + "setresuid", + "setresuid32", + "setreuid", + "setreuid32", + "setrlimit", + "set_robust_list", + "setsid", + "setsockopt", + "set_thread_area", + "set_tid_address", + "setuid", + "setuid32", + "setxattr", + "shmat", + "shmctl", + "shmdt", + "shmget", + "shutdown", + "sigaltstack", + "signalfd", + "signalfd4", + "sigprocmask", + "sigreturn", + "socket", + "socketcall", + "socketpair", + "splice", + "stat", + "stat64", + "statfs", + "statfs64", + "statx", + "symlink", + "symlinkat", + "sync", + "sync_file_range", + "syncfs", + "sysinfo", + "tee", + "tgkill", + "time", + "timer_create", + "timer_delete", + "timer_getoverrun", + "timer_gettime", + "timer_gettime64", + "timer_settime", + "timer_settime64", + "timerfd_create", + "timerfd_gettime", + "timerfd_gettime64", + "timerfd_settime", + "timerfd_settime64", + "times", + "tkill", + "truncate", + "truncate64", + "ugetrlimit", + "umask", + "uname", + "unlink", + "unlinkat", + "utime", + "utimensat", + "utimensat_time64", + "utimes", + "vfork", + "vmsplice", + "wait4", + "waitid", + "waitpid", + "write", + "writev" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": {}, + "excludes": {} + }, + { + "names": [ + "ptrace" + ], + "action": "SCMP_ACT_ALLOW", + "args": null, + "comment": "", + "includes": { + "minKernel": "4.8" + }, + "excludes": {} + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 0, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": {}, + "excludes": {} + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 8, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": {}, + "excludes": {} + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 131072, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": {}, + "excludes": {} + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 131080, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": {}, + "excludes": {} + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 4294967295, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": {}, + "excludes": {} + }, + { + "names": [ + "sync_file_range2" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "arches": [ + "ppc64le" + ] + }, + "excludes": {} + }, + { + "names": [ + "arm_fadvise64_64", + "arm_sync_file_range", + "sync_file_range2", + "breakpoint", + "cacheflush", + "set_tls" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "arches": [ + "arm", + "arm64" + ] + }, + "excludes": {} + }, + { + "names": [ + "arch_prctl" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "arches": [ + "amd64", + "x32" + ] + }, + "excludes": {} + }, + { + "names": [ + "modify_ldt" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "arches": [ + "amd64", + "x32", + "x86" + ] + }, + "excludes": {} + }, + { + "names": [ + "s390_pci_mmio_read", + "s390_pci_mmio_write", + "s390_runtime_instr" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "arches": [ + "s390", + "s390x" + ] + }, + "excludes": {} + }, + { + "names": [ + "open_by_handle_at" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_DAC_READ_SEARCH" + ] + }, + "excludes": {} + }, + { + "names": [ + "bpf", + "clone", + "fanotify_init", + "lookup_dcookie", + "mount", + "name_to_handle_at", + "perf_event_open", + "quotactl", + "setdomainname", + "sethostname", + "setns", + "syslog", + "umount", + "umount2", + "unshare" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + }, + "excludes": {} + }, + { + "names": [ + "clone" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 2114060288, + "valueTwo": 0, + "op": "SCMP_CMP_MASKED_EQ" + } + ], + "comment": "", + "includes": {}, + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ], + "arches": [ + "s390", + "s390x" + ] + } + }, + { + "names": [ + "clone" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 1, + "value": 2114060288, + "valueTwo": 0, + "op": "SCMP_CMP_MASKED_EQ" + } + ], + "comment": "s390 parameter ordering for clone is different", + "includes": { + "arches": [ + "s390", + "s390x" + ] + }, + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + } + }, + { + "names": [ + "reboot" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_BOOT" + ] + }, + "excludes": {} + }, + { + "names": [ + "chroot" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_CHROOT" + ] + }, + "excludes": {} + }, + { + "names": [ + "delete_module", + "init_module", + "finit_module" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_MODULE" + ] + }, + "excludes": {} + }, + { + "names": [ + "acct" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_PACCT" + ] + }, + "excludes": {} + }, + { + "names": [ + "kcmp", + "process_vm_readv", + "process_vm_writev", + "ptrace" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_PTRACE" + ] + }, + "excludes": {} + }, + { + "names": [ + "iopl", + "ioperm" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_RAWIO" + ] + }, + "excludes": {} + }, + { + "names": [ + "settimeofday", + "stime", + "clock_settime" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_TIME" + ] + }, + "excludes": {} + }, + { + "names": [ + "vhangup" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_TTY_CONFIG" + ] + }, + "excludes": {} + }, + { + "names": [ + "get_mempolicy", + "mbind", + "set_mempolicy" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_NICE" + ] + }, + "excludes": {} + }, + { + "names": [ + "syslog" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYSLOG" + ] + }, + "excludes": {} + } + ] +} diff --git a/config/npm-global-script-policy.json b/config/npm-global-script-policy.json index a585221..0d86cc7 100644 --- a/config/npm-global-script-policy.json +++ b/config/npm-global-script-policy.json @@ -2,71 +2,83 @@ "npmVersion": "12.0.1", "mode": "deny-by-default", "allowScripts": { - "@anthropic-ai/claude-code@2.1.210": { + "@anthropic-ai/claude-code@2.1.216": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-UH4XncIO6ceNZMq2HxphEn9YR7uu5NGVJMcogGSPvu0nXRw8ubEjbu5cD3Dhvx3vXxizwFA82tmk6mVXjhr/dw==", "scripts": { "postinstall": "node install.cjs" }, "reason": "Claude Code uses this script to install its pinned platform-native binary." }, - "opencode-ai@1.18.2": { + "opencode-ai@1.18.4": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-B8pFAs1g158ZU+C6eSiJz/5ZhG7Vr2mH7Z7ruYEHLElAlX9tehrRTnzTTgjSxHY2vwZ/5VplwR3odFU+Dai8jg==", "scripts": { "postinstall": "node ./postinstall.mjs" }, "reason": "OpenCode uses this script to hydrate its pinned platform CLI and refuses to start without it." }, "@embedded-postgres/linux-x64@18.1.0-beta.16": { "architectures": ["amd64"], + "integrity": "sha512-+GIabpHh7QV2AcYBuzyQC41AYczSFphxfHy4ccTPIPrp6OSthZXH+A9fymjQzOiDHg9+1UeYET00Aj7sScjXrg==", "scripts": { "postinstall": "node scripts/hydrate-symlinks.js" }, "reason": "Paperclip requires the packaged PostgreSQL library symlinks before it drops to the unprivileged runtime user." }, "@embedded-postgres/linux-arm64@18.1.0-beta.16": { "architectures": ["arm64"], + "integrity": "sha512-G0f/reVFc7svqncDQL7blwKulzYIYsz+o/3TEtAJOaGMXYkD8Swzv9RFKfEJOr9+IVRwCmoFFppMeBnUwMoGZg==", "scripts": { "postinstall": "node scripts/hydrate-symlinks.js" }, "reason": "Paperclip requires the packaged PostgreSQL library symlinks before it drops to the unprivileged runtime user." } }, "blockedScripts": { "esbuild@0.28.1": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "scripts": { "postinstall": "node install.js" }, "reason": "The platform binary is packaged and verified directly." }, "esbuild@0.28.0": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "scripts": { "postinstall": "node install.js" }, "reason": "The transitive platform binary is packaged; Netlify local functions remain unsupported." }, - "esbuild@0.27.0": { - "scripts": { "postinstall": "node install.js" }, - "reason": "The transitive platform binary is packaged and does not require an installer." - }, - "prisma@7.8.0": { + "prisma@7.9.0": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-isQTJEK4pyOlAVzm6kBUDjzgdsgs0A/snpB38ycTHeOHW34qfepP+ClQltgDXqjZBnXALhEtE4duh9L3tN5fHw==", "scripts": { "preinstall": "node scripts/preinstall-entry.js" }, "reason": "The pinned CLI and packaged engine are verified directly." }, - "@prisma/engines@7.8.0": { + "@prisma/engines@7.9.0": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-lDWJp/pgSWCLfYsupmmNo96jfsbQnH1yjia8XVM2Kh8nRZhD0bQU2jCHuy3ZTPMLR3apRD3k145ybENalAYjYw==", "scripts": { "postinstall": "node scripts/postinstall.js" }, "reason": "The packaged Prisma engine is verified by the CLI smoke test." }, - "sharp@0.34.2": { - "scripts": { "install": "node install/check" }, - "reason": "The packaged native module is not used as HolyCode's supported Sharp CLI." - }, "sharp@0.34.5": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "scripts": { "install": "node install/check.js || npm run build" }, "reason": "The packaged native module is verified with an in-memory image operation." }, - "workerd@1.20260710.1": { + "workerd@1.20260714.1": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-oIbQzfdyl9UQUnG6XLegcSq0Mgt/7WKDbFOoqGgOWCS+/fhyGB460uKEgdAQQ9RHCO/ttcNCX/KiMIQzdoeu3Q==", "scripts": { "postinstall": "node install.js" }, "reason": "Wrangler ships the platform binary, which is executed directly during validation." }, "netlify-cli@26.2.0": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-3jQg9WQoa1H74478fHZisj3T8dLM67x4F4Sgi7kROBHzJD9NNCYYw99dKRYWJOtEa1dUNyZu2W4VTdPzA1kjiw==", "scripts": { "postinstall": "node ./scripts/postinstall.js" }, "reason": "Only remote build and deploy commands are supported; local function binaries are removed." }, - "@parcel/watcher@2.5.6": { - "scripts": { "install": "node scripts/build-from-source.js" }, - "reason": "The package includes a platform binary and is not needed by supported remote Netlify commands." - }, "ssh2@1.17.0": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==", "scripts": { "install": "node install.js" }, "reason": "The JavaScript implementation is functional without optional CPU feature compilation." }, "cpu-features@0.0.10": { + "architectures": ["amd64", "arm64"], + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", "scripts": { "install": "node buildcheck.js > buildcheck.gypi && node-gyp rebuild" }, "reason": "Optional SSH acceleration is not required for Paperclip." } diff --git a/docker-compose.full.yaml b/docker-compose.full.yaml index 9255b14..0507eba 100644 --- a/docker-compose.full.yaml +++ b/docker-compose.full.yaml @@ -8,11 +8,12 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" # OpenCode web UI # - "3100:3100" # Paperclip dashboard - # - "8642:8642" # Hermes API server volumes: # --- Persistent state (all OpenCode data under home dir) --- @@ -89,10 +90,6 @@ services: # - PAPERCLIP_BIND=lan # - PAPERCLIP_ALLOWED_HOSTNAMES=192.168.1.50,my-host.local - # Hermes starts an OpenAI-compatible API + messaging-capable meta-agent - # - ENABLE_HERMES=true - # - HERMES_PORT=8642 - # --- CLIProxyAPI (externally managed endpoint) --- # Adds a separate OpenCode provider named "cliproxyapi". Does not replace Claude Auth. # - CLIPROXYAPI_ENABLED=true diff --git a/docker-compose.yaml b/docker-compose.yaml index 0de0753..dd83084 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -8,10 +8,11 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" # - "3100:3100" # Paperclip dashboard - # - "8642:8642" # Hermes API server volumes: - ./data/opencode:/home/opencode - ./local-cache/opencode:/home/opencode/.cache/opencode @@ -25,5 +26,3 @@ services: # - PAPERCLIP_DEPLOYMENT_MODE=authenticated # - PAPERCLIP_BIND=lan # - PAPERCLIP_ALLOWED_HOSTNAMES=192.168.1.50,my-host.local - # - ENABLE_HERMES=true - # - HERMES_PORT=8642 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9b1f22d..51d7e80 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,38 @@ All notable changes to HolyCode will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). +## [1.1.3] - 07/21/2026 + +### Changed + +- Refresh OpenCode to 1.18.4, Claude Code to 2.1.216, s6-overlay to 3.2.3.2, fzf to 0.74.1, pnpm to 11.15.1, Vite to 8.1.5, Prettier to 3.9.6, Wrangler to 4.112.0, Prisma to 7.9.0, Lighthouse to 13.4.1, and the default `oh-my-openagent` pin to 4.19.0 +- Refresh pip to 26.1.2, Requests to 2.34.2, Pillow to 12.3.0, matplotlib to 3.11.1, tqdm to 4.69.0, FastAPI to 0.139.2, Packaging to 26.2, wheel to 0.47.0, and Rich to 15.0.0 +- Keep Paperclip at 2026.707.0 while the 2026.720.0 migration chain receives separate persistence testing +- Run Chromium as `opencode` with its sandbox enabled through the shipped constrained seccomp profile +- Install the versioned PostgreSQL 17 client directly so vulnerability scanners do not attribute obsolete APT findings to its empty compatibility metapackage +- Rebuild GitHub CLI 2.96.0 from its exact upstream tag with digest-pinned Go 1.26.5 while the official package still embeds the vulnerable Go 1.26.4 standard library + +### Removed + +- Temporarily remove bundled Hermes while its release line requires vulnerable dependency pins; preserve existing `/home/opencode/.hermes` data and stop with a migration message when the legacy flag remains enabled +- Remove Vercel CLI, sharp-cli, concurrently, and LHCI because their current dependency trees contain fixable critical or high findings + +### Fixed + +- Replace Paperclip's vulnerable nested Undici 5.29.0 with Undici 6.27.0, align the installed Connect dependency declaration, and validate the Cursor adapter until Paperclip updates Connect upstream +- Install npm packages with lifecycle scripts disabled, then validate exact version, integrity, architecture, and script bodies before approved scripts run +- Define rollback as restoring untouched pre-upgrade volumes with image `1.1.2` instead of attempting an in-place database downgrade +- Restore the missing historical v1.0.3 changelog entry +- Promote the exact multi-architecture image digest built and scanned by protected validation instead of rebuilding mutable APT layers during publication + +### Security + +- Block protected releases on every fixable critical or high scanner finding +- Remove duplicate Debian pip/wheel package metadata after installing the fixed Python packages under `/usr/local` +- Remove the fixable `CVE-2026-39822` path from GitHub CLI and verify its source commit, embedded Go toolchain, and runtime version during the image build +- Update GitHub Actions pins and the Renovate validation pin, and add Chromium sandbox and nonblank Playwright screenshot gates +- Read Docker Scout's fixable-finding SARIF directly so scanner terminal-renderer failures cannot mask or manufacture a release-gate result + ## [1.1.2] - 07/15/2026 ### Added @@ -212,6 +244,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this - Add an explicit rerun + doctor + model-capability refresh path for stale visible default-model behavior after provider changes +## [1.0.3] - 04/04/2026 + +### Added + +- Ship a built-in `/oh-my-openagent-setup` skill for first-time setup and reruns after provider changes +- Copy HolyCode-managed OpenCode skills into `~/.config/opencode/skills` on boot without overwriting existing user skill folders +- Ensure enabled plugin packages are installed on boot if they are missing from the OpenCode cache + +### Changed + +- Document `/oh-my-openagent-setup` as the supported path for writing `oh-my-openagent.jsonc` +- Document the default picker policy so only Sisyphus, Hephaestus, Prometheus, and Atlas are visible by default + +### Fixed + +- Add an explicit rerun + doctor + model-capability refresh path for stale visible default-model behavior after provider changes + ## [1.0.2] - 04/03/2026 ### Changed diff --git a/docs/dependency-audit-v1.1.3.md b/docs/dependency-audit-v1.1.3.md new file mode 100644 index 0000000..abf0283 --- /dev/null +++ b/docs/dependency-audit-v1.1.3.md @@ -0,0 +1,97 @@ +# HolyCode v1.1.3 Dependency Audit + +**Audit date:** 07/21/2026 + +This record covers the security and compatible dependency refresh in `v1.1.3`. Registry versions, image manifests, release assets, checksums, licenses, and advisories were rechecked on the audit date. Every reviewed dependency ended as adopted, held, or removed. + +## Adopted + +| Component | Version | Validation | +|-----------|---------|------------| +| OpenCode | `1.18.4` | Exact CLI version, web service smoke, and plugin-mode tests | +| Claude Code | `2.1.216` | Exact CLI version plus authentication-volume persistence gate | +| s6-overlay | `3.2.3.2` | Official AMD64, ARM64, and noarch release assets with SHA-256 verification | +| fzf | `0.74.1` | Official AMD64 and ARM64 release assets with SHA-256 verification | +| pnpm | `11.15.1` | Exact CLI version and install smoke | +| Vite / Prettier | `8.1.5` / `3.9.6` | Exact CLI versions | +| Wrangler | `4.112.0` | Exact CLI version with its matching blocked Workerd lifecycle entry | +| Prisma | `7.9.0` | Exact CLI and engines versions with lifecycle policy checks | +| Lighthouse | `13.4.1` | Exact CLI version; LHCI is not installed | +| OpenCode plugins | `opencode-claude-auth@2.0.0`; `oh-my-openagent@4.19.0` | Fresh, manual, automatic, and disabled startup modes | +| Renovate validator | `43.274.0` | Exact CI version and strict config validation | +| Python security refresh | pip `26.1.2`; Requests `2.34.2`; Pillow `12.3.0`; wheel `0.47.0` | Python imports, exact version checks, and `pip check` | +| Python compatible refresh | matplotlib `3.11.1`; tqdm `4.69.0`; FastAPI `0.139.2`; Packaging `26.2`; Rich `15.0.0` | Python imports, exact version checks, and `pip check` | +| GitHub CLI | `2.96.0` at `b300f2ec7ec9dc9addc39b2ad88c54097ded7ca0` | Exact upstream tag rebuilt with digest-pinned Go `1.26.5`; source commit, embedded toolchain, and CLI version checks | + +## Held + +| Component | Version | Reason | +|-----------|---------|--------| +| Node base | `node:24.18.0-trixie-slim@sha256:ae91dcc111a68c9d2d81ff2a17bda61be126426176fde6fe7d08ab13b7f50573` | Remains on the digest-pinned LTS base and rebuilds against current Trixie repositories | +| npm | `12.0.1` | Current image pin; covered by the lifecycle policy | +| Paperclip | `2026.707.0` | `2026.720.0` contains a destructive automatic migration chain that requires separate persistence testing | +| Netlify CLI | `26.2.0` | Remote build/deploy commands only; optional local-functions binaries remain absent | +| TypeScript | `6.0.3` | TypeScript 7 is not a compatible default programmatic API for every bundled toolchain | +| json-server | `0.17.4` | The 1.0 release remains a beta | + +Paperclip's published Connect 1.x dependency still declares Undici 5 and installs vulnerable Undici 5.29.0. HolyCode replaces that nested package with Undici 6.27.0 and updates Connect's installed dependency declaration to match. The image build and smoke test require a valid `npm ls undici --all` tree and exercise the Cursor adapter's environment check. Remove this reviewed compatibility patch when Paperclip updates Connect upstream. + +## Removed + +| Component | Decision | +|-----------|----------| +| Hermes Agent | Bundled runtime and service removed while current releases pin vulnerable Pillow and add unresolved cryptography, MCP, and Starlette findings; `/home/opencode/.hermes` remains untouched | +| Vercel CLI | Removed because its current dependency tree still contains a fixable critical finding | +| sharp-cli | Removed because its current dependency tree contains fixable high findings | +| concurrently | Removed because its current dependency tree contains vulnerable `shell-quote` | +| LHCI | Removed because its current dependency tree contains vulnerable `tmp`; regular Lighthouse remains installed | +| Bundled CLIProxyAPI | Remains external-only until published binaries have verifiable compiler provenance and pass `govulncheck`; `CLIPROXYAPI_*` provider configuration remains supported | + +Hermes restoration is tracked against [PR #63942](https://github.com/NousResearch/hermes-agent/pull/63942), [issue #60841](https://github.com/NousResearch/hermes-agent/issues/60841), and [issue #60685](https://github.com/NousResearch/hermes-agent/issues/60685). + +## npm Lifecycle Policy + +All global npm packages are installed with lifecycle scripts disabled. The build then validates each script-bearing package's exact version, npm integrity, architecture rule, and script body against `config/npm-global-script-policy.json`. + +Only OpenCode 1.18.4, Claude Code 2.1.216, and the active architecture's Paperclip embedded PostgreSQL package may run reviewed scripts. Installed but unneeded scripts remain blocked, including esbuild, Prisma engines, Sharp, Workerd, Netlify, ssh2, and cpu-features. A new package, changed version, changed integrity, changed script body, or unmatched architecture stops the image build. + +## Chromium Sandbox + +Chromium runs as `opencode` with the Debian sandbox helper installed. Docker Compose applies `config/chromium-seccomp.json`, the constrained namespace profile vendored from Playwright. The release gate launches system Chromium through Playwright and requires a nonblank screenshot on AMD64 and ARM64. `--no-sandbox`, `SYS_ADMIN`, and `seccomp=unconfined` are not accepted fallbacks. + +## Image Evidence + +Protected validation builds one attested AMD64/ARM64 candidate from the exact release commit and records its immutable digest, Debian inventories, SBOMs, and scanner reports as workflow artifacts. Native architecture jobs pull that exact digest for smoke, upgrade, rollback, Chromium, Docker Scout, and Trivy gates. Publication promotes the same validated digest to Docker Hub and GHCR instead of rebuilding it. The release workflow verifies that `1.1.3` and `latest` resolve to the candidate digest with both platforms and provenance attestations. + +The candidate digest and final package counts are intentionally recorded in protected workflow evidence and the release summary. They cannot be known before the release commit is finalized, and Debian packages resolve from current Trixie repositories during that candidate build. + +## Upgrade And Rollback + +The release gate upgrades copied `v1.1.2` volumes to the candidate image. It verifies OpenCode state, Paperclip state, persisted Claude credential files, plugin behavior, writable paths, and an untouched `.hermes` marker. A maintainer separately verified the real Claude authentication flow without exposing credentials to CI. Bundled Hermes is not started in v1.1.3. + +Rollback means stopping the candidate, restoring untouched pre-upgrade volumes, and starting image `1.1.2`. An in-place Paperclip or other database downgrade is not supported. + +## Residual Findings + +The pre-release Docker Scout database reported the following unfixable critical/high findings on both architectures on 07/21/2026. Protected validation records the final count against the exact candidate digest: + +| Package and layer | Findings | Reachability and upstream status | +|-------------------|----------|----------------------------------| +| Debian `perl` 5.40.1-6 | `CVE-2026-12087`, `CVE-2026-48959`, `CVE-2026-48962` | No bundled service calls the affected Socket or IO::Compress APIs with untrusted input. They remain reachable to an interactive shell user. Debian Trixie has no fixed package; Debian marks the module updates as minor/no-DSA work and unstable carries partial fixes. Review after the next Trixie security update. | +| Debian `vim` 2:9.1.1230-2 | `CVE-2026-34982`, `CVE-2026-52860`, `CVE-2026-52858`, `CVE-2026-47162` | Interactive only. Exploitation requires opening crafted content and using modelines, Python omni-completion, or netrw. No HolyCode service invokes Vim. Upstream fixes exist, but Debian Trixie has no fixed package. Do not open untrusted files in Vim until Trixie publishes the update. | +| Debian `libheif` 1.19.8-1 | `CVE-2026-32740`, `CVE-2026-32882`, `CVE-2026-32741` | ImageMagick is the installed consumer, and its system policy denies every coder except GIF, JPEG, PNG, and WEBP. The smoke test enforces that policy, so HEIF/AVIF decoding is blocked. Debian Trixie has no fixed package; sid carries 1.23.1. | +| Debian `libde265` 1.0.15-1 | `CVE-2026-33164` | Pulled by `libheif`; the same ImageMagick policy blocks its HEIF/H.265 path. Debian Trixie has no fixed package; newer Debian suites carry a fix. | +| `github.com/docker/cli` in `/usr/local/bin/gh` | `CVE-2025-15558` | The advisory affects Windows Docker CLI plugin lookup. HolyCode runs the GitHub CLI inside Linux and does not expose Docker CLI plugin discovery, so this path is not reachable. Scout does not recognize the OS-specific condition in the embedded Go module. | + +The first local candidate exposed `CVE-2026-39822` in the Go 1.26.4 standard library embedded in GitHub CLI 2.96.0. GitHub had not published a newer CLI release, so the final candidate rebuilds that exact upstream tag with Go 1.26.5, which contains the standard-library fix. The release is blocked unless both Docker Scout and Trivy report zero **fixable** critical and zero **fixable** high findings. This audit does not claim universal freshness, byte-for-byte rebuilds, zero total findings, or that future rebuilds will retain the same scanner result. + +## Translation Change Map + +The English source changed in these translated sections: + +1. Runtime and release pins: OpenCode 1.18.4, Claude Code 2.1.216, updated compatible tools, and plugin 4.19.0. +2. Holds and removals: Paperclip 2026.707.0 retained; bundled Hermes and vulnerable global CLIs removed. +3. Browser guidance: Chromium uses the shipped constrained seccomp profile with its sandbox enabled. +4. Migration behavior: old `ENABLE_HERMES=true` deployments stop with a clear message while `.hermes` data remains untouched. + +All ten translated READMEs are checked against this map before release. diff --git a/docs/dockerhub-description.md b/docs/dockerhub-description.md index a563df7..45ba9c5 100644 --- a/docs/dockerhub-description.md +++ b/docs/dockerhub-description.md @@ -2,9 +2,9 @@ **One container. Every tool. Any provider.** -OpenCode AI coding agent with built-in web UI, Claude subscription support, 50+ dev tools, headless browser, bundled Hermes + Paperclip integrations, and external CLIProxyAPI endpoint support. Use your existing Claude Max/Pro plan. No separate API key needed. +OpenCode AI coding agent with built-in web UI, Claude subscription support, 50+ dev tools, a sandboxed headless browser, optional Paperclip, and external CLIProxyAPI endpoint support. Use your existing Claude Max/Pro plan. No separate API key needed. -v1.1.2 migrates the image to Debian Trixie with Python 3.13, npm 12.0.1, and NumPy 2.5.1. It also refreshes OpenCode to 1.18.2, Wrangler to 4.111.0, and lazygit to 0.63.1. Release tags use exact `vX.Y.Z`; Docker image tags drop the `v` prefix. Every version segment is one digit: `v1.0.9` rolls to `v1.1.0`, `v1.1.9` to `v1.2.0`, and `v1.9.9` to `v2.0.0`. +v1.1.3 refreshes OpenCode to 1.18.4, Claude Code to 2.1.216, and compatible tool pins. It temporarily removes bundled Hermes and the Vercel, sharp-cli, concurrently, and LHCI CLIs to eliminate vulnerable dependency paths. Release tags use exact `vX.Y.Z`; Docker image tags drop the `v` prefix. Every version segment is one digit: `v1.0.9` rolls to `v1.1.0`, `v1.1.9` to `v1.2.0`, and `v1.9.9` to `v2.0.0`. [![Docker Pulls](https://img.shields.io/docker/pulls/coderluii/holycode?style=flat-square&logo=docker)](https://hub.docker.com/r/coderluii/holycode) [![GitHub Stars](https://img.shields.io/github/stars/coderluii/holycode?style=flat-square&logo=github)](https://github.com/CoderLuii/HolyCode) @@ -12,6 +12,14 @@ v1.1.2 migrates the image to Debian Trixie with Python 3.13, npm 12.0.1, and Num ## Quick Start +Download the Chromium seccomp profile next to your Compose file: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -19,10 +27,11 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" # - "3100:3100" # Paperclip dashboard - # - "8642:8642" # Hermes API server volumes: - ./data/opencode:/home/opencode - ./local-cache/opencode:/home/opencode/.cache/opencode @@ -32,8 +41,6 @@ services: # - ENABLE_PAPERCLIP=true # - PAPERCLIP_BIND=lan # - PAPERCLIP_ALLOWED_HOSTNAMES=192.168.1.50,my-host.local - # - ENABLE_HERMES=true - # - API_SERVER_KEY=replace-with-a-real-secret ``` ```bash @@ -53,11 +60,11 @@ That's it. Open your browser and start building. 🌐 **Headless Browser** — Chromium + Xvfb + Playwright, pre-configured for screenshots, scraping, and browser automation. -🛠️ **50+ Dev Tools:** Node.js 24.18.0 LTS with npm 12.0.1, Python 3.13 on Trixie, OpenCode 1.18.2, Paperclip 2026.707.0, Hermes v2026.7.7.2, eza 0.23.5, fzf 0.74.0, lazygit 0.63.1, pnpm 11.13.0, tsx 4.23.1, Vite 8.1.4, ESLint 10.7.0, Prettier 3.9.5, Wrangler 4.111.0, Netlify CLI 26.2.0, tqdm 4.68.4, uvicorn 0.51.0, Claude stable 2.1.210, TypeScript 6.0.3, NumPy 2.5.1, json-server 0.17.4, git, ripgrep, bat, delta, gh CLI, Prisma, and more. +🛠️ **50+ Dev Tools:** Node.js 24.18.0 LTS with npm 12.0.1, Python 3.13 on Trixie, OpenCode 1.18.4, Paperclip 2026.707.0, eza 0.23.5, fzf 0.74.1, lazygit 0.63.1, pnpm 11.15.1, tsx 4.23.1, Vite 8.1.5, ESLint 10.7.0, Prettier 3.9.6, Wrangler 4.112.0, Prisma 7.9.0, Lighthouse 13.4.1, Netlify CLI 26.2.0, tqdm 4.69.0, uvicorn 0.51.0, Claude stable 2.1.216, TypeScript 6.0.3, NumPy 2.5.1, json-server 0.17.4, git, ripgrep, bat, delta, gh CLI, and more. -TypeScript stays on 6.0.3 until the 7.x programmatic API is ready for the bundled toolchains. json-server stays on its stable 0.17.4 release, and Vercel stays on 54.21.0 until authenticated personal and team/scope behavior is proven. Wrangler's removed `legacy_env` mode is not supported. +TypeScript stays on 6.0.3 until the 7.x programmatic API is ready for the bundled toolchains. json-server stays on its stable 0.17.4 release. Vercel, sharp-cli, concurrently, and LHCI are removed because their current dependency trees contain fixable high or critical findings. Wrangler's removed `legacy_env` mode is not supported. -🧩 **Bundled Services** — Optional Hermes Agent on port 8642 and Paperclip on port 3100. CLIProxyAPI integration remains available for an externally managed endpoint. +🧩 **Bundled Services** — Optional Paperclip on port 3100. Hermes is temporarily unbundled while upstream dependency fixes land; existing `.hermes` data is preserved. CLIProxyAPI integration remains available for an externally managed endpoint. 🤝 **10+ AI Providers** — Anthropic, OpenAI, Gemini, Groq, AWS Bedrock, Azure OpenAI, Vertex AI, GitHub Models, Ollama, and any OpenAI-compatible endpoint. @@ -82,8 +89,7 @@ TypeScript stays on 6.0.3 until the 7.x programmatic API is ready for the bundle | `PAPERCLIP_DEPLOYMENT_MODE` | Keep Paperclip in Docker-safe authenticated mode | | `PAPERCLIP_BIND` | Paperclip reachability preset; defaults to `lan` for Docker port publishing | | `PAPERCLIP_ALLOWED_HOSTNAMES` | Allow comma-separated Paperclip remote hostnames/IPs, without scheme or port | -| `ENABLE_HERMES` | Start Hermes API + messaging bridge | -| `API_SERVER_KEY` | Required when Hermes API server is enabled | +| `ENABLE_HERMES` | Legacy flag; `true` stops v1.1.3 with a migration message while Hermes is unbundled | | `CLIPROXYAPI_ENABLED` | Add an OpenCode `cliproxyapi` provider for an external CLIProxyAPI endpoint | | `CLIPROXYAPI_BASE_URL` | Externally managed CLIProxyAPI base URL reachable from the container | | `CLIPROXYAPI_API_KEY` | Optional CLIProxyAPI API key env reference | @@ -98,20 +104,33 @@ Paperclip now ships its Skills catalog through the package set HolyCode installs Set `PAPERCLIP_ALLOWED_HOSTNAMES` only for trusted LAN/private hostnames or IPs. Restart after changing it; hostname guard and authentication remain enabled. -Hermes exposes an API service. Set `API_SERVER_KEY` before enabling it. A `404` from `/` is normal as long as the process is healthy and port `8642` is listening. +Hermes is temporarily not bundled. Remove `ENABLE_HERMES=true` from older deployments before starting v1.1.3. HolyCode leaves `/home/opencode/.hermes` untouched for a future fixed release or an externally managed Hermes instance. -CLIProxyAPI support is disabled by default and targets an externally managed endpoint. HolyCode no longer bundles the sidecar while the `v7.2.77` image contains fixable high-severity Go findings. The integration still adds a separate `cliproxyapi` provider without changing `ENABLE_CLAUDE_AUTH`, `opencode-claude-auth`, or `/home/opencode/.claude`. +CLIProxyAPI support is disabled by default and targets an externally managed endpoint. HolyCode does not bundle the sidecar until its release binaries have verifiable compiler provenance and pass `govulncheck`. The integration still adds a separate `cliproxyapi` provider without changing `ENABLE_CLAUDE_AUTH`, `opencode-claude-auth`, or `/home/opencode/.claude`. ## Updates and Audit Notes -Update with: +When upgrading from a release before `v1.1.3`, download the Chromium seccomp profile and add it to the `holycode` service before recreating the container: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + +```yaml +security_opt: + - seccomp=./config/chromium-seccomp.json +``` + +Then update with: ```bash docker compose pull docker compose up -d ``` -Tagged images pin direct npm, PyPI, and GitHub-release versions. Binary assets use checksums, container bases use digests, and GitHub Actions use commit SHAs. Claude Code is pinned to `@anthropic-ai/claude-code@2.1.210`. npm lifecycle scripts are denied by default and checked against a reviewed version-and-script policy. Debian packages resolve from current Trixie repositories at build time, and boot-installed OpenCode plugins are live registry installs outside the image SBOM. Netlify CLI is limited to remote build/deploy commands; its local functions binaries are removed. HolyCode publishes per-platform SBOM and provenance attestations and runs per-platform vulnerability scans without claiming byte-for-byte reproducibility, zero vulnerabilities, or universal freshness. +Tagged images pin direct npm, PyPI, and GitHub-release versions. Binary assets use checksums, container bases use digests, and GitHub Actions use commit SHAs. Claude Code is pinned to `@anthropic-ai/claude-code@2.1.216`. npm lifecycle scripts are disabled during installation and validated by exact package version, integrity, architecture, and script body before approved scripts run. Debian packages resolve from current Trixie repositories at build time, and boot-installed OpenCode plugins are live registry installs outside the image SBOM. Netlify CLI is limited to remote build/deploy commands; its local functions binaries are removed. HolyCode publishes per-platform SBOM and provenance attestations and runs per-platform vulnerability scans without claiming byte-for-byte reproducibility or universal freshness. ## Links diff --git a/docs/podman.md b/docs/podman.md index 9f604b2..ee5593f 100644 --- a/docs/podman.md +++ b/docs/podman.md @@ -11,7 +11,7 @@ This guide mirrors the minimal HolyCode web UI setup. For the full Docker Compos - Loading provider keys from `.env` with `--env-file .env` - SELinux labels for Fedora/RHEL/CoreOS hosts - Rootless Podman permission and user namespace notes -- Optional service boundaries for Paperclip, Hermes, and external CLIProxyAPI endpoints +- Optional service boundaries for Paperclip and external CLIProxyAPI endpoints - Safe update and recreate behavior ## Prerequisites @@ -26,6 +26,14 @@ cp .env.example .env Edit `.env` and set the provider you plan to use, for example `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, or another supported provider variable. +Keep HolyCode's Chromium seccomp profile in the project folder. A repository clone already has it. Otherwise, download the release copy: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + Podman treats relative bind mount sources as paths relative to the directory where you run `podman`. Missing bind mount sources fail, so create them first. ## Minimal web UI setup @@ -43,6 +51,7 @@ podman run -d \ --name holycode \ --restart unless-stopped \ --shm-size=2g \ + --security-opt seccomp=./config/chromium-seccomp.json \ -p 4096:4096 \ -v ./data/opencode:/home/opencode \ -v ./local-cache/opencode:/home/opencode/.cache/opencode \ @@ -58,6 +67,7 @@ Open http://localhost:4096. What the important options do: - `--shm-size=2g` gives Chromium and browser automation enough shared memory. +- `--security-opt seccomp=./config/chromium-seccomp.json` keeps Chromium's sandbox enabled with the namespace syscalls it needs. - `-p 4096:4096` publishes the OpenCode web UI. - `./data/opencode:/home/opencode` persists OpenCode config, sessions, plugins, and service state. - `./local-cache/opencode:/home/opencode/.cache/opencode` keeps plugin and package cache on local disk. @@ -77,6 +87,7 @@ podman run -d \ --name holycode \ --restart unless-stopped \ --shm-size=2g \ + --security-opt seccomp=./config/chromium-seccomp.json \ -p 4096:4096 \ -v ./data/opencode:/home/opencode:Z \ -v ./local-cache/opencode:/home/opencode/.cache/opencode:Z \ @@ -106,7 +117,7 @@ Some rootless setups use custom user namespace modes such as `--userns=keep-id`. The command above runs the minimal HolyCode web UI on port `4096`. -Paperclip and Hermes can be enabled with the same environment variables used by the Docker Compose examples: +Paperclip can be enabled with the same environment variables used by the Docker Compose examples: ```env ENABLE_PAPERCLIP=true @@ -114,27 +125,27 @@ PAPERCLIP_PORT=3100 PAPERCLIP_DEPLOYMENT_MODE=authenticated PAPERCLIP_BIND=lan PAPERCLIP_ALLOWED_HOSTNAMES=192.168.1.50,my-host.local -ENABLE_HERMES=true -HERMES_PORT=8642 -API_SERVER_KEY=replace-with-a-real-secret ``` If you enable them, publish the ports you need: ```bash -p 4096:4096 \ --p 3100:3100 \ --p 8642:8642 +-p 3100:3100 ``` -Paperclip listens on `3100` when enabled. `PAPERCLIP_BIND=lan` lets the service bind inside the container so the Podman port publish can reach it. Paperclip runs with `/home/opencode` as its home and keeps OpenCode config/cache/state paths under that same directory, so keep the `/home/opencode` bind mount in place when enabling it. Paperclip now ships its Skills catalog through the package set HolyCode installs, so the Skills page can load the bundled catalog without a HolyCode compatibility shim. Hermes exposes an API service on `8642`; set `API_SERVER_KEY` before enabling it. A `404` at `/` is normal as long as the process stays healthy. +Paperclip listens on `3100` when enabled. `PAPERCLIP_BIND=lan` lets the service bind inside the container so the Podman port publish can reach it. Paperclip runs with `/home/opencode` as its home and keeps OpenCode config/cache/state paths under that same directory, so keep the `/home/opencode` bind mount in place when enabling it. Paperclip ships its Skills catalog through the package set HolyCode installs, so the Skills page can load the bundled catalog without a compatibility shim. + +Bundled Hermes is temporarily unavailable in v1.1.3 because its current releases require vulnerable dependency pins. Remove `ENABLE_HERMES=true` from older Podman deployments before recreating the container. HolyCode leaves `/home/opencode/.hermes` untouched. -CLIProxyAPI is different. HolyCode keeps its provider integration, but does not bundle the `v7.2.77` sidecar while that image contains fixable high-severity Go dependencies. Point `CLIPROXYAPI_BASE_URL` at an externally managed endpoint that the Podman container can reach. +CLIProxyAPI is different. HolyCode keeps its provider integration, but does not bundle the sidecar until its release binaries have verifiable compiler provenance and pass `govulncheck`. Point `CLIPROXYAPI_BASE_URL` at an externally managed endpoint that the Podman container can reach. ## Updating HolyCode Stop the container and copy `./data`, `./local-cache`, and `./workspace` with your host backup tool before pulling the new image. Keep those copies until the updated container passes your normal workflows. +When upgrading from a release before `v1.1.3`, download `config/chromium-seccomp.json` with the command near the top of this guide. Keep `--security-opt seccomp=./config/chromium-seccomp.json` in the recreated `podman run` command. The new image does not support disabling Chromium's sandbox as a fallback. + Pull the latest image: ```bash @@ -150,9 +161,9 @@ podman rm holycode Run the `podman run` command again. Your data stays in `./data/opencode`, `./local-cache/opencode`, and `./workspace`. -`v1.1.2` moves the image from Debian Bookworm/Python 3.11 to Debian Trixie/Python 3.13. Keep the untouched pre-upgrade copies until Paperclip, Hermes, OpenCode, and your normal provider workflow have all passed. +`v1.1.3` removes bundled Hermes and vulnerable global CLIs while preserving existing state. Keep the untouched pre-upgrade copies until Paperclip, OpenCode, Chromium, and your normal provider workflow have all passed. -If you need to roll back, stop and remove the container, restore the pre-`v1.1.2` copies, then recreate it with `docker.io/coderluii/holycode:1.1.1`. Do not reuse data already migrated by `v1.1.2` unless the migration is known to be backward compatible. +If you need to roll back, stop and remove the container, restore the untouched pre-`v1.1.3` copies, then recreate it with `docker.io/coderluii/holycode:1.1.2`. Rollback means restoring those snapshots, not reversing a database migration in place. Do not use `podman start holycode` as an update path. It restarts the existing container with the old image, environment variables, ports, and mount settings. @@ -199,9 +210,10 @@ Make sure the command includes: ```bash --shm-size=2g +--security-opt seccomp=./config/chromium-seccomp.json ``` -Without enough `/dev/shm`, Chromium can crash or produce broken automation results. +Without enough `/dev/shm`, Chromium can crash or produce broken automation results. Without the shipped seccomp profile, Chromium's sandbox may be unable to create its user namespace. Do not replace the profile with `--no-sandbox`, `SYS_ADMIN`, or `seccomp=unconfined`. ### Environment variables did not change diff --git a/docs/translations/README.de.md b/docs/translations/README.de.md index 09db8de..52853dd 100644 --- a/docs/translations/README.de.md +++ b/docs/translations/README.de.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **Schritt 2.** Erstelle eine `docker-compose.yaml`. +Das Compose-Setup verwendet das Chromium-seccomp-Profil von HolyCode. Wenn du nicht aus einem Repository-Klon arbeitest, lade zuerst die Release-Datei herunter: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Überschreibt den Container-Port für Paperclip | | `PAPERCLIP_INSTANCE_ID` | `default` | Lokaler Paperclip-Instanzname für isolierten Zustand | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (keiner) | Auf `true` setzen, um Hermes als integrierten Meta-Agenten-API zu starten | -| `HERMES_PORT` | `8642` | Überschreibt den Container-Port für Hermes | -| `API_SERVER_KEY` | (keiner) | Vor dem Aktivieren von Hermes setzen; verwende ein echtes Bearer-Token | +| `ENABLE_HERMES` | (keiner) | Veraltete Einstellung; v1.1.3 beendet den Start mit einem Migrationshinweis, solange Hermes nicht gebündelt ist | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | Plugin-Aktualisierungsmodus: `manual` (installiert nur fehlende Plugins und lässt Benutzer-Versionen unverändert) oder `auto` (synchronisiert deklarierte Pins beim Start) | > Plugin-Umschalter (`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`) werden beim Neustart des Containers wirksam. Umgebungsvariable setzen und `docker compose down && up -d` ausführen. @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` startet Paperclip auf Port `3100` im Container. Öffne das Dashboard, erstelle ein Unternehmen und stelle dort OpenCode-gestützte Agenten ein. Paperclip persistiert automatisch unter `~/.paperclip`. -> `ENABLE_HERMES=true` startet Hermes auf Port `8642` im Container. Hermes persistiert unter `~/.hermes`, verwendet das bereits installierte `opencode`-Binary und kann eine OpenAI-kompatible API bereitstellen, während es Codearbeit zurück an HolyCode delegiert. +> Hermes ist in v1.1.3 vorübergehend nicht gebündelt. Wenn `ENABLE_HERMES=true` aus einer älteren Installation bestehen bleibt, beendet HolyCode den Start mit einem kurzen Migrationshinweis. `/home/opencode/.hermes` bleibt unverändert erhalten, damit die Daten nach einer späteren Wiederaufnahme der Integration oder bei einem Rollback verfügbar sind. > `GIT_USER_NAME` und `GIT_USER_EMAIL` werden nur beim ersten Start angewendet. Zum erneuten Anwenden die Sentinel-Datei löschen und neu starten: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` dann `docker compose restart`. @@ -427,7 +435,7 @@ services: > Release-Tags verwenden exakt `vX.Y.Z`. Docker-Image-Tags lassen das `v` weg. Nach `v1.0.9` folgt `v1.1.0`, nach `v1.1.9` folgt `v1.2.0` und nach `v1.9.9` folgt `v2.0.0`. `v1.0.10` bis `v1.0.13` bleiben unveränderlich. -> v1.1.2 wechselt zu Debian Trixie mit Python 3.13, npm 12.0.1 und NumPy 2.5.1. OpenCode wird auf 1.18.2, Wrangler auf 4.111.0 und lazygit auf 0.63.1 aktualisiert. TypeScript bleibt bei 6.0.3 und Vercel bei 54.21.0. Netlify unterstützt nur Remote-Builds und -Deployments. Der gebündelte CLIProxyAPI-Sidecar bleibt entfernt; extern verwaltete Endpunkte werden weiterhin unterstützt. +> v1.1.3 aktualisiert OpenCode auf 1.18.4, Claude Code auf 2.1.216, oh-my-openagent auf 4.19.0, s6-overlay auf 3.2.3.2, fzf auf 0.74.1, pnpm auf 11.15.1, Vite auf 8.1.5, Prettier auf 3.9.6, Wrangler auf 4.112.0, Prisma auf 7.9.0 und Lighthouse auf 13.4.1. Paperclip bleibt bei 2026.707.0. Hermes ist vorübergehend nicht gebündelt; Vercel, LHCI, sharp-cli und concurrently wurden aus dem Image entfernt. Netlify unterstützt nur Remote-Builds und -Deployments. Extern verwaltete CLIProxyAPI-Endpunkte werden weiterhin unterstützt.
@@ -470,7 +478,6 @@ Enthält Liberation-, DejaVu-, Noto- und Noto Color Emoji-Schriftarten für korr | Dienst | Zweck | |---------|---------| -| Hermes Agent | Selbstverbessernder Meta-Agent mit MCP, Messaging-Adaptern und OpenCode-Delegation | | Paperclip | Lokales Agenten-Board, das OpenCode-Arbeiter einstellt und per Heartbeat weckt | | Claude Code CLI | Installiert für Claude-Abonnement-Authentifizierungsabläufe via `ENABLE_CLAUDE_AUTH` | @@ -496,24 +503,13 @@ s6-overlay überwacht OpenCode und Xvfb. Wenn ein Prozess abstürzt, startet er ## 🧩 Integrierte Dienste -HolyCode wird jetzt mit zwei optionalen Schichten über OpenCode geliefert. Du **brauchst sie nicht**, um den Container zu verwenden. Aktiviere die Umgebungsvariable, starte den Container neu und der Dienst startet neben der normalen Web-UI. +Paperclip bleibt als optionaler Dienst über OpenCode gebündelt. Die CLIProxyAPI-Integration verwendet weiterhin einen extern verwalteten Endpunkt. Hermes ist vorübergehend nicht im Image enthalten. -### Hermes Agent +### Hermes Agent (vorübergehend nicht gebündelt) -Hermes ist die Option "klügeres Gehirn". Er läuft als integrierter Meta-Agent, stellt eine OpenAI-kompatible API auf Port `8642` bereit und delegiert Codearbeit durch Aufruf des lokalen `opencode`-Binaries, das HolyCode bereits mitliefert. +v1.1.3 entfernt den Hermes-Laufzeitdienst vorübergehend, weil die derzeitigen Abhängigkeiten noch nicht mit den erforderlichen Sicherheitskorrekturen kompatibel sind. HolyCode startet keinen Hermes-Prozess und veröffentlicht keinen Hermes-Port. -Aktivieren mit: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Setze `API_SERVER_KEY`, bevor du Hermes aktivierst. - -Der Hermes-Zustand liegt unter `/home/opencode/.hermes` und folgt derselben Persistenzgeschichte wie der Rest von HolyCode. +Deine vorhandenen Daten unter `/home/opencode/.hermes` werden weder gelöscht noch migriert. Entferne `ENABLE_HERMES=true` aus älteren Konfigurationen, damit v1.1.3 normal startet. Bewahre das Verzeichnis für eine spätere Wiederaufnahme der Integration oder für die Wiederherstellung eines unveränderten Vorab-Snapshots auf. ### Paperclip @@ -701,6 +697,8 @@ Wenn du das weglässt, können Dateien in deinem Workspace root gehören und du Das neueste Image herunterladen und den Container neu erstellen. Deine Daten bleiben unberührt. +Wenn du von einer Version vor `v1.1.3` aktualisierst, lade das oben gezeigte seccomp-Profil herunter und füge `security_opt` zum Dienst `holycode` hinzu, bevor du den Container neu erstellst. + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode braucht ein paar Sekunden zur Initialisierung. 10-15 Sekunden nach `doc
-Warum braucht HolyCode kein SYS_ADMIN oder seccomp=unconfined? +Wie verwendet HolyCode die Chromium-Sandbox? -Chromium läuft mit `--no-sandbox` im Container, was für containerisierte Browser-Setups Standard ist. Das eliminiert den Bedarf an `SYS_ADMIN`-Fähigkeiten oder `seccomp=unconfined`, die einige andere Docker-Browser-Setups benötigen. Der Container selbst stellt die Isolierungsgrenze bereit. +Chromium läuft als Benutzer `opencode` mit aktivierter integrierter Sandbox. Die Compose-Dateien laden das eingeschränkte Profil `config/chromium-seccomp.json` automatisch. -Wenn der eingebaute Sandbox von Chromium bevorzugt wird, folgendes zur Compose-Datei hinzufügen und `--no-sandbox` aus der `CHROMIUM_FLAGS`-Umgebungsvariable entfernen: +Bei einem eigenen Container-Aufruf muss dasselbe Profil geladen werden: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +Deaktiviere die Sandbox nicht und gewähre dem Container keine zusätzlichen Berechtigungen. +
diff --git a/docs/translations/README.es.md b/docs/translations/README.es.md index 0b0a056..05a401f 100644 --- a/docs/translations/README.es.md +++ b/docs/translations/README.es.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **Paso 2.** Crea un `docker-compose.yaml`. +La configuración de Compose usa el perfil seccomp de Chromium de HolyCode. Si no ejecutas desde un clon del repositorio, descarga primero la copia de la versión: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Sobreescribe el puerto del contenedor usado por Paperclip | | `PAPERCLIP_INSTANCE_ID` | `default` | Nombre de instancia local de Paperclip para estado aislado | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (ninguno) | Establece `true` para iniciar Hermes como API de meta-agente incluida | -| `HERMES_PORT` | `8642` | Sobreescribe el puerto del contenedor usado por Hermes | -| `API_SERVER_KEY` | (ninguno) | Configúralo antes de activar Hermes; usa un token Bearer real | +| `ENABLE_HERMES` | (ninguno) | Ajuste heredado; v1.1.3 detiene el arranque con un aviso de migración mientras Hermes no está incluido | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | Modo de actualización de plugins: `manual` (instala solo lo que falta y conserva las versiones del usuario) o `auto` (sincroniza los pins declarados al arrancar) | > Los toggles de plugins (`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`) tienen efecto al reiniciar el contenedor. Establece la variable de entorno y ejecuta `docker compose down && up -d`. @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` inicia Paperclip en el puerto `3100` dentro del contenedor. Abre el panel de control, crea una empresa y contrata agentes respaldados por OpenCode desde allí. Paperclip persiste en `~/.paperclip` automáticamente. -> `ENABLE_HERMES=true` inicia Hermes en el puerto `8642` dentro del contenedor. Hermes persiste en `~/.hermes`, usa el binario `opencode` ya instalado y puede exponer una API compatible con OpenAI mientras delega el trabajo de código de vuelta a HolyCode. +> Hermes no está incluido temporalmente en v1.1.3. Si una instalación anterior conserva `ENABLE_HERMES=true`, HolyCode detiene el arranque con un aviso breve de migración. `/home/opencode/.hermes` se conserva sin cambios para una futura restauración de la integración o para volver a una instantánea anterior. > `GIT_USER_NAME` y `GIT_USER_EMAIL` solo se aplican en el primer arranque. Para volver a aplicarlos, elimina el archivo centinela y reinicia: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` y luego `docker compose restart`. @@ -427,7 +435,7 @@ services: > Las etiquetas de release usan exactamente `vX.Y.Z`. Las etiquetas de Docker omiten la `v`. Después de `v1.0.9` viene `v1.1.0`, después de `v1.1.9` viene `v1.2.0` y después de `v1.9.9` viene `v2.0.0`. `v1.0.10` a `v1.0.13` siguen siendo inmutables. -> v1.1.2 migra a Debian Trixie con Python 3.13, npm 12.0.1 y NumPy 2.5.1. OpenCode se actualiza a 1.18.2, Wrangler a 4.111.0 y lazygit a 0.63.1. TypeScript se mantiene en 6.0.3 y Vercel en 54.21.0. Netlify admite solo compilaciones y despliegues remotos. El sidecar CLIProxyAPI incluido sigue eliminado; los endpoints administrados externamente siguen siendo compatibles. +> v1.1.3 actualiza OpenCode a 1.18.4, Claude Code a 2.1.216, oh-my-openagent a 4.19.0, s6-overlay a 3.2.3.2, fzf a 0.74.1, pnpm a 11.15.1, Vite a 8.1.5, Prettier a 3.9.6, Wrangler a 4.112.0, Prisma a 7.9.0 y Lighthouse a 13.4.1. Paperclip se mantiene en 2026.707.0. Hermes no está incluido temporalmente; Vercel, LHCI, sharp-cli y concurrently se eliminaron de la imagen. Netlify admite solo compilaciones y despliegues remotos. Los endpoints CLIProxyAPI administrados externamente siguen siendo compatibles.
@@ -470,7 +478,6 @@ Incluye fuentes Liberation, DejaVu, Noto y Noto Color Emoji para renderizado cor | Servicio | Propósito | |---------|---------| -| Hermes Agent | Meta-agente automejorable con MCP, adaptadores de mensajería y delegación a OpenCode | | Paperclip | Tablero de agentes local que contrata trabajadores OpenCode y los activa por latido | | Claude Code CLI | Instalado para flujos de autenticación de suscripción Claude mediante `ENABLE_CLAUDE_AUTH` | @@ -496,24 +503,13 @@ s6-overlay supervisa OpenCode y Xvfb. Si un proceso falla, se reinicia automáti ## 🧩 Servicios incluidos -HolyCode ahora incluye dos capas opcionales sobre OpenCode. **No las necesitas** para usar el contenedor. Activa la variable de entorno, reinicia el contenedor y el servicio se levanta junto a la interfaz web normal. +Paperclip sigue incluido como servicio opcional sobre OpenCode. La integración CLIProxyAPI continúa usando un endpoint administrado externamente. Hermes no está temporalmente en la imagen. -### Hermes Agent +### Hermes Agent (no incluido temporalmente) -Hermes es la opción de "cerebro más inteligente". Se ejecuta como un meta-agente incluido, expone una API compatible con OpenAI en el puerto `8642` y delega el trabajo de código llamando al binario `opencode` local que HolyCode ya incluye. +v1.1.3 retira temporalmente el servicio de ejecución de Hermes porque sus dependencias actuales todavía no son compatibles con las correcciones de seguridad requeridas. HolyCode no inicia ningún proceso de Hermes ni publica su puerto. -Actívalo con: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Configura `API_SERVER_KEY` antes de activar Hermes. - -El estado de Hermes vive en `/home/opencode/.hermes`, siguiendo la misma historia de persistencia que el resto de HolyCode. +Los datos existentes en `/home/opencode/.hermes` no se eliminan ni se migran. Quita `ENABLE_HERMES=true` de configuraciones anteriores para que v1.1.3 arranque con normalidad. Conserva ese directorio para una futura restauración de la integración o para restaurar una instantánea anterior sin modificar. ### Paperclip @@ -701,6 +697,8 @@ Si omites esto, los archivos de tu espacio de trabajo pueden ser propiedad de ro Descarga la última imagen y recrea el contenedor. Tus datos permanecen intactos. +Si actualizas desde una versión anterior a `v1.1.3`, descarga el perfil seccomp indicado arriba y agrega `security_opt` al servicio `holycode` antes de recrear el contenedor. + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode tarda unos segundos en inicializarse. Espera 10-15 segundos después de
-¿Por qué HolyCode no necesita SYS_ADMIN ni seccomp=unconfined? +¿Cómo utiliza HolyCode el sandbox de Chromium? -Chromium se ejecuta con `--no-sandbox` dentro del contenedor, lo cual es estándar para configuraciones de navegador en contenedores. Esto elimina la necesidad de capacidades `SYS_ADMIN` o `seccomp=unconfined` que requieren algunas otras configuraciones de navegador en Docker. El contenedor en sí proporciona el límite de aislamiento. +Chromium se ejecuta como el usuario `opencode` con su sandbox integrado activado. Los archivos Compose cargan automáticamente el perfil restringido `config/chromium-seccomp.json`. -Si prefieres usar el sandbox integrado de Chromium, añade lo siguiente a tu archivo compose y elimina `--no-sandbox` de la variable de entorno `CHROMIUM_FLAGS`: +Si ejecutas el contenedor por tu cuenta, carga el mismo perfil: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +No desactives el sandbox ni concedas privilegios adicionales al contenedor. +
diff --git a/docs/translations/README.fr.md b/docs/translations/README.fr.md index 5b78721..44654e9 100644 --- a/docs/translations/README.fr.md +++ b/docs/translations/README.fr.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **Étape 2.** Créez un `docker-compose.yaml`. +La configuration Compose utilise le profil seccomp Chromium de HolyCode. Si vous ne travaillez pas depuis un clone du dépôt, téléchargez d'abord la copie de la version : + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Remplace le port du conteneur utilisé par Paperclip | | `PAPERCLIP_INSTANCE_ID` | `default` | Nom d'instance Paperclip locale pour un état isolé | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (aucune) | Définissez sur `true` pour démarrer Hermes comme API de méta-agent intégrée | -| `HERMES_PORT` | `8642` | Remplace le port du conteneur utilisé par Hermes | -| `API_SERVER_KEY` | (aucune) | Définissez-le avant d'activer Hermes ; utilisez un vrai jeton Bearer | +| `ENABLE_HERMES` | (aucune) | Réglage hérité ; v1.1.3 arrête le démarrage avec un message de migration tant que Hermes n'est pas intégré | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | Mode de mise à jour des plugins : `manual` (installe seulement ce qui manque et conserve les versions de l'utilisateur) ou `auto` (synchronise les pins déclarés au démarrage) | > Les bascules de plugins (`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`) prennent effet au redémarrage du conteneur. Définissez la variable d'environnement et exécutez `docker compose down && up -d`. @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` démarre Paperclip sur le port `3100` dans le conteneur. Ouvrez le tableau de bord, créez une entreprise, puis engagez des agents OpenCode depuis là. Paperclip persiste sous `~/.paperclip` automatiquement. -> `ENABLE_HERMES=true` démarre Hermes sur le port `8642` dans le conteneur. Hermes persiste sous `~/.hermes`, utilise le binaire `opencode` déjà installé et peut exposer une API compatible OpenAI tout en déléguant le travail de code à HolyCode. +> Hermes n'est temporairement pas intégré à v1.1.3. Si une ancienne installation conserve `ENABLE_HERMES=true`, HolyCode arrête le démarrage avec un court message de migration. `/home/opencode/.hermes` reste intact pour une future restauration de l'intégration ou pour revenir à un instantané antérieur. > `GIT_USER_NAME` et `GIT_USER_EMAIL` ne sont appliqués qu'au premier démarrage. Pour les réappliquer, supprimez le fichier sentinelle et redémarrez : `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` puis `docker compose restart`. @@ -427,7 +435,7 @@ services: > Les balises de release utilisent exactement `vX.Y.Z`. Les balises Docker omettent le `v`. Après `v1.0.9`, utilisez `v1.1.0`; après `v1.1.9`, utilisez `v1.2.0`; après `v1.9.9`, utilisez `v2.0.0`. `v1.0.10` à `v1.0.13` restent immuables. -> v1.1.2 migre vers Debian Trixie avec Python 3.13, npm 12.0.1 et NumPy 2.5.1. OpenCode passe à 1.18.2, Wrangler à 4.111.0 et lazygit à 0.63.1. TypeScript reste en 6.0.3 et Vercel en 54.21.0. Netlify prend uniquement en charge les builds et déploiements distants. Le sidecar CLIProxyAPI intégré reste retiré; les endpoints gérés séparément restent pris en charge. +> v1.1.3 met à jour OpenCode vers 1.18.4, Claude Code vers 2.1.216, oh-my-openagent vers 4.19.0, s6-overlay vers 3.2.3.2, fzf vers 0.74.1, pnpm vers 11.15.1, Vite vers 8.1.5, Prettier vers 3.9.6, Wrangler vers 4.112.0, Prisma vers 7.9.0 et Lighthouse vers 13.4.1. Paperclip reste en 2026.707.0. Hermes n'est temporairement pas intégré ; Vercel, LHCI, sharp-cli et concurrently ont été retirés de l'image. Netlify prend uniquement en charge les builds et déploiements distants. Les endpoints CLIProxyAPI gérés séparément restent pris en charge.
@@ -470,7 +478,6 @@ Inclut les polices Liberation, DejaVu, Noto et Noto Color Emoji pour un rendu co | Service | Rôle | |---------|---------| -| Hermes Agent | Méta-agent auto-améliorant avec MCP, adaptateurs de messagerie et délégation OpenCode | | Paperclip | Tableau d'agents local qui engage des travailleurs OpenCode et les réveille sur battement | | Claude Code CLI | Installé pour les flux d'authentification par abonnement Claude via `ENABLE_CLAUDE_AUTH` | @@ -496,24 +503,13 @@ s6-overlay supervise OpenCode et Xvfb. Si un processus plante, il redémarre aut ## 🧩 Services intégrés -HolyCode est maintenant livré avec deux couches optionnelles au-dessus d'OpenCode. Vous **n'en avez pas besoin** pour utiliser le conteneur. Activez la variable d'environnement, redémarrez le conteneur et le service démarre aux côtés de l'interface web normale. +Paperclip reste intégré comme service optionnel au-dessus d'OpenCode. L'intégration CLIProxyAPI continue d'utiliser un endpoint géré séparément. Hermes n'est temporairement pas présent dans l'image. -### Hermes Agent +### Hermes Agent (temporairement non intégré) -Hermes est l'option "cerveau plus intelligent". Il fonctionne comme un méta-agent intégré, expose une API compatible OpenAI sur le port `8642` et délègue le travail de code en appelant le binaire `opencode` local que HolyCode fournit déjà. +v1.1.3 retire temporairement le service d'exécution Hermes, car ses dépendances actuelles ne sont pas encore compatibles avec les correctifs de sécurité requis. HolyCode ne démarre aucun processus Hermes et ne publie aucun port Hermes. -Activez-le avec : - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Définissez `API_SERVER_KEY` avant d'activer Hermes. - -L'état de Hermes vit sous `/home/opencode/.hermes`, suivant la même histoire de persistance que le reste de HolyCode. +Les données existantes dans `/home/opencode/.hermes` ne sont ni supprimées ni migrées. Retirez `ENABLE_HERMES=true` des anciennes configurations pour que v1.1.3 démarre normalement. Conservez ce répertoire pour une future restauration de l'intégration ou pour restaurer un instantané antérieur intact. ### Paperclip @@ -701,6 +697,8 @@ Si vous omettez cela, les fichiers dans votre espace de travail peuvent apparten Téléchargez la dernière image et recréez le conteneur. Vos données restent intactes. +Si vous effectuez une mise à jour depuis une version antérieure à `v1.1.3`, téléchargez le profil seccomp indiqué ci-dessus et ajoutez `security_opt` au service `holycode` avant de recréer le conteneur. + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode prend quelques secondes à s'initialiser. Attendez 10-15 secondes aprè
-Pourquoi HolyCode n'a-t-il pas besoin de SYS_ADMIN ou seccomp=unconfined ? +Comment HolyCode utilise-t-il le bac à sable Chromium ? -Chromium s'exécute avec `--no-sandbox` à l'intérieur du conteneur, ce qui est standard pour les configurations de navigateurs conteneurisés. Cela élimine le besoin de capacités `SYS_ADMIN` ou de `seccomp=unconfined` que certaines autres configurations de navigateurs Docker nécessitent. Le conteneur lui-même fournit la frontière d'isolation. +Chromium s'exécute sous l'utilisateur `opencode` avec son bac à sable intégré activé. Les fichiers Compose chargent automatiquement le profil restreint `config/chromium-seccomp.json`. -Si vous préférez utiliser le sandbox intégré de Chromium, ajoutez ce qui suit à votre fichier compose et supprimez `--no-sandbox` de la variable d'environnement `CHROMIUM_FLAGS` : +Si vous lancez le conteneur vous-même, chargez le même profil : ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +Ne désactivez pas le bac à sable et n'accordez pas de privilèges supplémentaires au conteneur. +
diff --git a/docs/translations/README.hi.md b/docs/translations/README.hi.md index ba57cca..b0e3102 100644 --- a/docs/translations/README.hi.md +++ b/docs/translations/README.hi.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **चरण 2.** `docker-compose.yaml` बनाएं। +Compose सेटअप HolyCode की Chromium seccomp प्रोफ़ाइल का उपयोग करता है। अगर आप repository clone से नहीं चला रहे हैं, तो पहले release copy डाउनलोड करें: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Paperclip के लिए कंटेनर पोर्ट ओवरराइड करें | | `PAPERCLIP_INSTANCE_ID` | `default` | आइसोलेटेड स्टेट के लिए लोकल Paperclip इंस्टेंस नाम | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (कोई नहीं) | Hermes को बंडल्ड मेटा-एजेंट API के रूप में शुरू करने के लिए `true` सेट करें | -| `HERMES_PORT` | `8642` | Hermes के लिए कंटेनर पोर्ट ओवरराइड करें | -| `API_SERVER_KEY` | (कोई नहीं) | Hermes को सक्रिय करने से पहले इसे सेट करें; एक वास्तविक Bearer token का उपयोग करें | +| `ENABLE_HERMES` | (कोई नहीं) | पुरानी सेटिंग; Hermes बंडल न होने तक v1.1.3 माइग्रेशन संदेश के साथ स्टार्टअप रोकता है | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | प्लगइन अपडेट मोड: `manual` (सिर्फ़ जो गायब हों उन्हें इंस्टॉल करता है और उपयोगकर्ता संस्करणों को बनाए रखता है) या `auto` (स्टार्टअप पर घोषित pins को sync करता है) | > प्लगइन टॉगल (`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`) कंटेनर रीस्टार्ट पर प्रभावी होते हैं। env var सेट करें और `docker compose down && up -d` चलाएं। @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` कंटेनर के अंदर पोर्ट `3100` पर Paperclip शुरू करता है। डैशबोर्ड खोलें, एक कंपनी बनाएं, फिर वहाँ से OpenCode-बैक्ड एजेंट हायर करें। Paperclip ऑटोमेटिक `~/.paperclip` में परसिस्ट होता है। -> `ENABLE_HERMES=true` कंटेनर के अंदर पोर्ट `8642` पर Hermes शुरू करता है। Hermes `~/.hermes` में परसिस्ट होता है, पहले से इंस्टॉल `opencode` बाइनरी उपयोग करता है, और OpenAI-कम्पेटिबल API एक्सपोज़ कर सकता है जबकि कोड काम HolyCode को वापस डेलीगेट करता है। +> v1.1.3 में Hermes अस्थायी रूप से बंडल नहीं है। अगर पुरानी इंस्टॉलेशन में `ENABLE_HERMES=true` बचा है, तो HolyCode एक छोटे माइग्रेशन संदेश के साथ स्टार्टअप रोक देता है। `/home/opencode/.hermes` को बिना बदलाव सुरक्षित रखा जाता है ताकि आगे इंटीग्रेशन लौटने या पुराना snapshot restore करने पर डेटा उपलब्ध रहे। > `GIT_USER_NAME` और `GIT_USER_EMAIL` केवल पहले बूट पर लागू होते हैं। दोबारा लागू करने के लिए, sentinel फ़ाइल हटाएं और रीस्टार्ट करें: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` फिर `docker compose restart`। @@ -427,7 +435,7 @@ services: > Release tags ठीक `vX.Y.Z` का उपयोग करते हैं. Docker image tags से `v` हटा रहता है. `v1.0.9` के बाद `v1.1.0`, `v1.1.9` के बाद `v1.2.0`, और `v1.9.9` के बाद `v2.0.0` आता है. `v1.0.10` से `v1.0.13` तक अपरिवर्तनीय हैं. -> v1.1.2 Debian Trixie, Python 3.13, npm 12.0.1 और NumPy 2.5.1 पर माइग्रेट करता है. OpenCode 1.18.2, Wrangler 4.111.0 और lazygit 0.63.1 पर अपडेट किए गए हैं. TypeScript 6.0.3 और Vercel 54.21.0 पर बने रहते हैं. Netlify केवल remote build और deploy कमांड के लिए समर्थित है. शामिल CLIProxyAPI sidecar हटाया हुआ रहता है; बाहरी रूप से प्रबंधित endpoint अभी भी समर्थित हैं. +> v1.1.3 में OpenCode 1.18.4, Claude Code 2.1.216, oh-my-openagent 4.19.0, s6-overlay 3.2.3.2, fzf 0.74.1, pnpm 11.15.1, Vite 8.1.5, Prettier 3.9.6, Wrangler 4.112.0, Prisma 7.9.0 और Lighthouse 13.4.1 पर अपडेट किए गए हैं। Paperclip 2026.707.0 पर बना रहता है। Hermes अस्थायी रूप से बंडल नहीं है; Vercel, LHCI, sharp-cli और concurrently को image से हटा दिया गया है। Netlify केवल remote build और deploy कमांड के लिए समर्थित है। बाहर से प्रबंधित CLIProxyAPI endpoint अभी भी समर्थित हैं।
@@ -470,7 +478,6 @@ services: | सर्विस | उद्देश्य | |---------|---------| -| Hermes Agent | MCP, मैसेजिंग एडेप्टर और OpenCode डेलीगेशन के साथ सेल्फ-इम्प्रूविंग मेटा-एजेंट | | Paperclip | लोकल एजेंट बोर्ड जो OpenCode वर्कर हायर करता है और हार्टबीट पर जगाता है | | Claude Code CLI | `ENABLE_CLAUDE_AUTH` के ज़रिए Claude सब्स्क्रिप्शन ऑथ फ्लो के लिए इंस्टॉल्ड | @@ -496,24 +503,13 @@ s6-overlay OpenCode और Xvfb को सुपरवाइज़ करता ## 🧩 बंडल्ड सर्विसेज़ -HolyCode अब OpenCode के ऊपर दो ऑप्शनल लेयर के साथ आता है। कंटेनर उपयोग करने के लिए आपको **इनकी जरूरत नहीं**। env var फ्लिप करें, कंटेनर रीस्टार्ट करें, और सर्विस नॉर्मल वेब UI के साथ शुरू हो जाती है। +Paperclip OpenCode के ऊपर ऑप्शनल सर्विस के रूप में बंडल रहता है। CLIProxyAPI इंटीग्रेशन बाहर से प्रबंधित endpoint का उपयोग करता है। Hermes अस्थायी रूप से image में शामिल नहीं है। -### Hermes Agent +### Hermes Agent (अस्थायी रूप से बंडल नहीं) -Hermes "स्मार्टर ब्रेन" ऑप्शन है। यह बंडल्ड मेटा-एजेंट के रूप में चलता है, पोर्ट `8642` पर OpenAI-कम्पेटिबल API एक्सपोज़ करता है, और HolyCode में पहले से शिप लोकल `opencode` बाइनरी को कॉल करके कोडिंग काम डेलीगेट करता है। +v1.1.3 Hermes runtime सर्विस को अस्थायी रूप से हटाता है क्योंकि इसकी मौजूदा dependencies अभी जरूरी security fixes के साथ compatible नहीं हैं। HolyCode कोई Hermes process शुरू नहीं करता और उसका port publish नहीं करता। -इसे इनेबल करें: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Hermes को सक्रिय करने से पहले `API_SERVER_KEY` सेट करें। - -Hermes स्टेट `/home/opencode/.hermes` में रहती है, HolyCode के बाकी हिस्से जैसी ही परसिस्टेंस स्टोरी के साथ। +`/home/opencode/.hermes` में मौजूद डेटा न हटाया जाता है और न migrate किया जाता है। v1.1.3 को सामान्य रूप से शुरू करने के लिए पुरानी configuration से `ENABLE_HERMES=true` हटाएं। इस directory को आगे इंटीग्रेशन लौटने या बिना बदलाव वाला पुराना snapshot restore करने के लिए सुरक्षित रखें। ### Paperclip @@ -701,6 +697,8 @@ environment: लेटेस्ट इमेज पुल करें और कंटेनर री-क्रिएट करें। आपका डेटा अनटच्ड रहता है। +अगर आप `v1.1.3` से पहले के release से अपग्रेड कर रहे हैं, तो ऊपर दी गई seccomp प्रोफ़ाइल डाउनलोड करें और कंटेनर को री-क्रिएट करने से पहले `holycode` service में `security_opt` जोड़ें। + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode को इनिशियलाइज़ होने में कु
-HolyCode को SYS_ADMIN या seccomp=unconfined की ज़रूरत क्यों नहीं? +HolyCode Chromium sandbox का उपयोग कैसे करता है? -Chromium कंटेनर के अंदर `--no-sandbox` के साथ चलता है, जो कंटेनराइज़्ड ब्राउज़र सेटअप के लिए स्टैंडर्ड है। यह `SYS_ADMIN` कैपेबिलिटी या `seccomp=unconfined` की ज़रूरत खत्म करता है जो कुछ अन्य Docker ब्राउज़र सेटअप को चाहिए। कंटेनर खुद इज़ोलेशन बाउंड्री प्रोवाइड करता है। +Chromium `opencode` user के रूप में अपने built-in sandbox के साथ चलता है। Compose files सीमित `config/chromium-seccomp.json` profile को अपने आप load करती हैं। -अगर आप Chromium का बिल्ट-इन सैंडबॉक्स उपयोग करना चाहते हैं, तो अपने compose फ़ाइल में यह जोड़ें और `CHROMIUM_FLAGS` एनवायरनमेंट वेरिएबल से `--no-sandbox` हटाएं: +अगर आप container खुद चलाते हैं, तो वही profile load करें: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +Sandbox को बंद न करें और container को अतिरिक्त privileges न दें। +
diff --git a/docs/translations/README.it.md b/docs/translations/README.it.md index bd39aa3..8d8a368 100644 --- a/docs/translations/README.it.md +++ b/docs/translations/README.it.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **Passo 2.** Crea un `docker-compose.yaml`. +La configurazione Compose usa il profilo seccomp Chromium di HolyCode. Se non stai lavorando da un clone del repository, scarica prima la copia della release: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Sovrascrive la porta del container usata da Paperclip | | `PAPERCLIP_INSTANCE_ID` | `default` | Nome dell'istanza Paperclip locale per stato isolato | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (nessuna) | Imposta su `true` per avviare Hermes come API meta-agente integrata | -| `HERMES_PORT` | `8642` | Sovrascrive la porta del container usata da Hermes | -| `API_SERVER_KEY` | (nessuna) | Impostalo prima di abilitare Hermes; usa un vero token Bearer | +| `ENABLE_HERMES` | (nessuna) | Impostazione precedente; v1.1.3 interrompe l'avvio con un avviso di migrazione finché Hermes non è incluso | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | Modalità aggiornamento plugin: `manual` (installa solo ciò che manca e conserva le versioni dell'utente) o `auto` (sincronizza i pin dichiarati all'avvio) | > I toggle dei plugin (`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`) hanno effetto al riavvio del container. Imposta la variabile d'ambiente ed esegui `docker compose down && up -d`. @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` avvia Paperclip sulla porta `3100` nel container. Apri il dashboard, crea un'azienda e assumi agenti OpenCode da lì. Paperclip persiste automaticamente in `~/.paperclip`. -> `ENABLE_HERMES=true` avvia Hermes sulla porta `8642` nel container. Hermes persiste in `~/.hermes`, usa il binario `opencode` già installato e può esporre un'API compatibile OpenAI delegando il lavoro di codice a HolyCode. +> Hermes non è temporaneamente incluso in v1.1.3. Se una vecchia installazione mantiene `ENABLE_HERMES=true`, HolyCode interrompe l'avvio con un breve avviso di migrazione. `/home/opencode/.hermes` resta invariata per un futuro ripristino dell'integrazione o per tornare a uno snapshot precedente. > `GIT_USER_NAME` e `GIT_USER_EMAIL` vengono applicati solo al primo avvio. Per riapplicarli, elimina il file sentinel e riavvia: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` poi `docker compose restart`. @@ -427,7 +435,7 @@ services: > I tag di release usano esattamente `vX.Y.Z`. I tag Docker omettono la `v`. Dopo `v1.0.9` usa `v1.1.0`, dopo `v1.1.9` usa `v1.2.0` e dopo `v1.9.9` usa `v2.0.0`. `v1.0.10` fino a `v1.0.13` restano immutabili. -> v1.1.2 migra a Debian Trixie con Python 3.13, npm 12.0.1 e NumPy 2.5.1. OpenCode viene aggiornato alla 1.18.2, Wrangler alla 4.111.0 e lazygit alla 0.63.1. TypeScript resta alla 6.0.3 e Vercel alla 54.21.0. Netlify supporta solo build e deploy remoti. Il sidecar CLIProxyAPI incluso resta rimosso; gli endpoint gestiti esternamente restano supportati. +> v1.1.3 aggiorna OpenCode alla 1.18.4, Claude Code alla 2.1.216, oh-my-openagent alla 4.19.0, s6-overlay alla 3.2.3.2, fzf alla 0.74.1, pnpm alla 11.15.1, Vite alla 8.1.5, Prettier alla 3.9.6, Wrangler alla 4.112.0, Prisma alla 7.9.0 e Lighthouse alla 13.4.1. Paperclip resta alla 2026.707.0. Hermes non è temporaneamente incluso; Vercel, LHCI, sharp-cli e concurrently sono stati rimossi dall'immagine. Netlify supporta solo build e deploy remoti. Gli endpoint CLIProxyAPI gestiti esternamente restano supportati.
@@ -470,7 +478,6 @@ Include i font Liberation, DejaVu, Noto e Noto Color Emoji per il corretto rende | Servizio | Scopo | |---------|---------| -| Hermes Agent | Meta-agente auto-migliorante con MCP, adattatori di messaggistica e delega OpenCode | | Paperclip | Tabellone agenti locale che assume lavoratori OpenCode e li sveglia su heartbeat | | Claude Code CLI | Installato per i flussi di autenticazione abbonamento Claude via `ENABLE_CLAUDE_AUTH` | @@ -496,24 +503,13 @@ s6-overlay supervisiona OpenCode e Xvfb. Se un processo va in crash, si riavvia ## 🧩 Servizi integrati -HolyCode ora include due livelli opzionali sopra OpenCode. **Non ne hai bisogno** per usare il container. Attiva la variabile d'ambiente, riavvia il container e il servizio si avvia insieme alla normale interfaccia web. +Paperclip resta incluso come servizio opzionale sopra OpenCode. L'integrazione CLIProxyAPI continua a usare un endpoint gestito esternamente. Hermes non è temporaneamente presente nell'immagine. -### Hermes Agent +### Hermes Agent (temporaneamente non incluso) -Hermes è l'opzione "cervello più intelligente". Funziona come meta-agente integrato, espone un'API compatibile OpenAI sulla porta `8642` e delega il lavoro di codice chiamando il binario `opencode` locale che HolyCode già include. +v1.1.3 rimuove temporaneamente il servizio runtime Hermes perché le dipendenze attuali non sono ancora compatibili con le correzioni di sicurezza richieste. HolyCode non avvia processi Hermes e non pubblica la relativa porta. -Attivalo con: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Imposta `API_SERVER_KEY` prima di abilitare Hermes. - -Lo stato di Hermes vive in `/home/opencode/.hermes`, seguendo la stessa storia di persistenza del resto di HolyCode. +I dati esistenti in `/home/opencode/.hermes` non vengono eliminati né migrati. Rimuovi `ENABLE_HERMES=true` dalle vecchie configurazioni per avviare normalmente v1.1.3. Conserva la directory per un futuro ripristino dell'integrazione o per ripristinare uno snapshot precedente rimasto intatto. ### Paperclip @@ -701,6 +697,8 @@ Se ometti questo, i file nel tuo workspace potrebbero essere di proprietà di ro Scarica l'ultima immagine e ricrea il container. I tuoi dati rimangono intatti. +Se aggiorni da una release precedente a `v1.1.3`, scarica il profilo seccomp indicato sopra e aggiungi `security_opt` al servizio `holycode` prima di ricreare il container. + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode impiega qualche secondo per inizializzarsi. Aspetta 10-15 secondi dopo
-Perché HolyCode non ha bisogno di SYS_ADMIN o seccomp=unconfined? +Come usa HolyCode la sandbox di Chromium? -Chromium viene eseguito con `--no-sandbox` all'interno del container, che è lo standard per le configurazioni di browser containerizzati. Questo elimina la necessità di capacità `SYS_ADMIN` o `seccomp=unconfined` che alcune altre configurazioni di browser Docker richiedono. Il container stesso fornisce il confine di isolamento. +Chromium viene eseguito come utente `opencode` con la sandbox integrata attiva. I file Compose caricano automaticamente il profilo limitato `config/chromium-seccomp.json`. -Se preferisci usare il sandbox integrato di Chromium, aggiungi quanto segue al tuo file compose e rimuovi `--no-sandbox` dalla variabile d'ambiente `CHROMIUM_FLAGS`: +Se avvii il container direttamente, carica lo stesso profilo: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +Non disattivare la sandbox e non concedere privilegi aggiuntivi al container. +
diff --git a/docs/translations/README.ja.md b/docs/translations/README.ja.md index e43bdf1..a26d48d 100644 --- a/docs/translations/README.ja.md +++ b/docs/translations/README.ja.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **ステップ 2.** `docker-compose.yaml` を作成します。 +Compose 設定では HolyCode の Chromium seccomp プロファイルを使用します。リポジトリのクローンから実行しない場合は、先にリリース版をダウンロードしてください。 + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Paperclip が使用するコンテナポートを上書き | | `PAPERCLIP_INSTANCE_ID` | `default` | 分離された状態のためのローカル Paperclip インスタンス名 | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (なし) | Hermes をバンドルされたメタエージェント API として起動するには `true` に設定 | -| `HERMES_PORT` | `8642` | Hermes が使用するコンテナポートを上書き | -| `API_SERVER_KEY` | (なし) | Hermes を有効にする前に設定してください。実際の Bearer トークンを使ってください | +| `ENABLE_HERMES` | (なし) | 旧設定。Hermes が同梱されていない間、v1.1.3 は移行メッセージを表示して起動を停止します | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | プラグイン更新モード:`manual`(不足分だけをインストールし、ユーザーのバージョンは保持)または `auto`(起動時に宣言済みの pin を同期) | > プラグインのトグル(`ENABLE_CLAUDE_AUTH`、`ENABLE_OH_MY_OPENAGENT`)はコンテナの再起動時に有効になります。env var を設定して `docker compose down && up -d` を実行してください。 @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` はコンテナ内のポート `3100` で Paperclip を起動します。ダッシュボードを開き、会社を作成し、そこから OpenCode バックのエージェントを雇用してください。Paperclip は自動的に `~/.paperclip` に永続化されます。 -> `ENABLE_HERMES=true` はコンテナ内のポート `8642` で Hermes を起動します。Hermes は `~/.hermes` に永続化し、すでにインストールされている `opencode` バイナリを使用し、コード作業を HolyCode に委任しながら OpenAI 互換 API を公開できます。 +> v1.1.3 では Hermes を一時的に同梱していません。以前の環境に `ENABLE_HERMES=true` が残っている場合、HolyCode は短い移行メッセージを表示して起動を停止します。将来の統合再開または以前のスナップショットへの復元に備えて、`/home/opencode/.hermes` は変更せず保持されます。 > `GIT_USER_NAME` と `GIT_USER_EMAIL` は初回起動時のみ適用されます。再適用するにはセンチネルファイルを削除して再起動してください:`docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` の後 `docker compose restart`。 @@ -427,7 +435,7 @@ services: > リリースタグは正確に `vX.Y.Z` を使います。Docker イメージタグでは `v` を付けません。`v1.0.9` の次は `v1.1.0`、`v1.1.9` の次は `v1.2.0`、`v1.9.9` の次は `v2.0.0` です。`v1.0.10` から `v1.0.13` までは不変です。 -> v1.1.2 では Debian Trixie、Python 3.13、npm 12.0.1、NumPy 2.5.1 に移行します。OpenCode は 1.18.2、Wrangler は 4.111.0、lazygit は 0.63.1 に更新します。TypeScript は 6.0.3、Vercel は 54.21.0 を維持します。Netlify はリモートのビルドとデプロイのみをサポートします。同梱の CLIProxyAPI サイドカーは削除されたままで、外部管理のエンドポイントは引き続き利用できます。 +> v1.1.3 では OpenCode を 1.18.4、Claude Code を 2.1.216、oh-my-openagent を 4.19.0、s6-overlay を 3.2.3.2、fzf を 0.74.1、pnpm を 11.15.1、Vite を 8.1.5、Prettier を 3.9.6、Wrangler を 4.112.0、Prisma を 7.9.0、Lighthouse を 13.4.1 に更新します。Paperclip は 2026.707.0 を維持します。Hermes は一時的に同梱せず、Vercel、LHCI、sharp-cli、concurrently はイメージから削除しました。Netlify はリモートのビルドとデプロイのみをサポートします。外部管理の CLIProxyAPI エンドポイントは引き続き利用できます。
@@ -470,7 +478,6 @@ services: | サービス | 目的 | |---------|---------| -| Hermes Agent | MCP、メッセージングアダプター、OpenCode 委任を備えた自己改善型メタエージェント | | Paperclip | OpenCode ワーカーを雇用してハートビートで起動するローカルエージェントボード | | Claude Code CLI | `ENABLE_CLAUDE_AUTH` による Claude サブスクリプション認証フロー用にインストール済み | @@ -496,24 +503,13 @@ s6-overlay が OpenCode と Xvfb を監視します。プロセスがクラッ ## 🧩 バンドルサービス -HolyCode は OpenCode の上に2つのオプションレイヤーを同梱するようになりました。コンテナを使用するために**これらは必要ありません**。環境変数を切り替え、コンテナを再起動すると、通常の Web UI と並んでサービスが起動します。 +Paperclip は OpenCode 上のオプションサービスとして引き続き同梱されます。CLIProxyAPI 統合は外部管理のエンドポイントを使用します。Hermes は一時的にイメージに含まれていません。 -### Hermes Agent +### Hermes Agent(一時的に同梱していません) -Hermes は「よりスマートな頭脳」オプションです。バンドルされたメタエージェントとして動作し、ポート `8642` で OpenAI 互換 API を公開し、HolyCode がすでに同梱しているローカルの `opencode` バイナリを呼び出してコーディング作業を委任します。 +v1.1.3 では、現在の依存関係が必要なセキュリティ修正にまだ対応していないため、Hermes ランタイムサービスを一時的に削除しています。HolyCode は Hermes プロセスを起動せず、Hermes のポートも公開しません。 -有効にするには: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Hermes を有効にする前に `API_SERVER_KEY` を設定してください。 - -Hermes の状態は `/home/opencode/.hermes` に保存され、HolyCode の他の部分と同じ永続化の仕組みに従います。 +`/home/opencode/.hermes` にある既存データは削除も移行もされません。v1.1.3 を通常どおり起動するには、以前の設定から `ENABLE_HERMES=true` を削除してください。将来の統合再開または変更されていない以前のスナップショットへの復元に備えて、このディレクトリを保持してください。 ### Paperclip @@ -701,6 +697,8 @@ environment: 最新イメージをプルしてコンテナを再作成します。データはそのまま残ります。 +`v1.1.3` より前のリリースから更新する場合は、上記の seccomp プロファイルをダウンロードし、コンテナを再作成する前に `holycode` サービスへ `security_opt` を追加してください。 + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode は初期化に数秒かかります。`docker compose up -d` 後、ブ
-HolyCode に SYS_ADMIN や seccomp=unconfined が不要な理由 +HolyCode で Chromium サンドボックスを使う方法 -Chromium はコンテナ内で `--no-sandbox` で実行されますが、これはコンテナ化されたブラウザセットアップの標準です。これにより、他の Docker ブラウザセットアップが必要とする `SYS_ADMIN` 機能や `seccomp=unconfined` が不要になります。コンテナ自体が隔離の境界を提供します。 +Chromium は `opencode` ユーザーとして実行され、組み込みサンドボックスが有効です。Compose ファイルは制限付きプロファイル `config/chromium-seccomp.json` を自動的に読み込みます。 -Chromium の組み込みサンドボックスを使用したい場合は、compose ファイルに以下を追加し、`CHROMIUM_FLAGS` 環境変数から `--no-sandbox` を削除してください: +コンテナを直接起動する場合も、同じプロファイルを読み込んでください: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +サンドボックスを無効にしたり、コンテナに追加権限を付与したりしないでください。 +
diff --git a/docs/translations/README.ko.md b/docs/translations/README.ko.md index ef6dd17..e1cadcf 100644 --- a/docs/translations/README.ko.md +++ b/docs/translations/README.ko.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **2단계.** `docker-compose.yaml`을 만듭니다. +Compose 설정은 HolyCode의 Chromium seccomp 프로필을 사용합니다. 저장소 복제본에서 실행하지 않는다면 먼저 릴리스 파일을 다운로드하세요. + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Paperclip이 사용하는 컨테이너 포트 재정의 | | `PAPERCLIP_INSTANCE_ID` | `default` | 격리된 상태를 위한 로컬 Paperclip 인스턴스 이름 | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (없음) | Hermes를 번들 메타 에이전트 API로 시작하려면 `true`로 설정 | -| `HERMES_PORT` | `8642` | Hermes가 사용하는 컨테이너 포트 재정의 | -| `API_SERVER_KEY` | (없음) | Hermes를 활성화하기 전에 설정하세요. 실제 Bearer 토큰을 사용하세요 | +| `ENABLE_HERMES` | (없음) | 이전 설정. Hermes가 번들되지 않은 동안 v1.1.3은 마이그레이션 안내와 함께 시작을 중단합니다 | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | 플러그인 업데이트 모드: `manual`(없을 때만 설치하고 사용자 버전은 유지) 또는 `auto`(시작 시 선언된 pin을 동기화) | > 플러그인 토글(`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`)은 컨테이너 재시작 시 적용됩니다. env var를 설정하고 `docker compose down && up -d`를 실행하세요. @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true`는 컨테이너 내 포트 `3100`에서 Paperclip을 시작합니다. 대시보드를 열고, 회사를 만들고, 거기서 OpenCode 기반 에이전트를 고용하세요. Paperclip은 자동으로 `~/.paperclip`에 영속화됩니다. -> `ENABLE_HERMES=true`는 컨테이너 내 포트 `8642`에서 Hermes를 시작합니다. Hermes는 `~/.hermes`에 영속화되고, 이미 설치된 `opencode` 바이너리를 사용하며, 코드 작업을 HolyCode로 다시 위임하면서 OpenAI 호환 API를 노출할 수 있습니다. +> v1.1.3에는 Hermes가 일시적으로 번들되지 않습니다. 이전 설치에 `ENABLE_HERMES=true`가 남아 있으면 HolyCode는 짧은 마이그레이션 안내와 함께 시작을 중단합니다. 향후 통합 복원이나 이전 스냅샷 복원을 위해 `/home/opencode/.hermes`는 변경 없이 보존됩니다. > `GIT_USER_NAME`과 `GIT_USER_EMAIL`은 첫 번째 부팅 시에만 적용됩니다. 재적용하려면 센티넬 파일을 삭제하고 재시작하세요: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` 그런 다음 `docker compose restart`. @@ -427,7 +435,7 @@ services: > 릴리스 태그는 정확히 `vX.Y.Z`를 사용합니다. Docker 이미지 태그에서는 `v`를 뺍니다. `v1.0.9` 다음은 `v1.1.0`, `v1.1.9` 다음은 `v1.2.0`, `v1.9.9` 다음은 `v2.0.0`입니다. `v1.0.10`부터 `v1.0.13`까지는 변경되지 않습니다. -> v1.1.2에서는 Debian Trixie, Python 3.13, npm 12.0.1, NumPy 2.5.1로 전환합니다. OpenCode는 1.18.2, Wrangler는 4.111.0, lazygit은 0.63.1로 업데이트합니다. TypeScript는 6.0.3, Vercel은 54.21.0을 유지합니다. Netlify는 원격 빌드와 배포만 지원합니다. 번들 CLIProxyAPI 사이드카는 제거된 상태이며 외부에서 관리하는 엔드포인트는 계속 지원됩니다. +> v1.1.3은 OpenCode를 1.18.4, Claude Code를 2.1.216, oh-my-openagent를 4.19.0, s6-overlay를 3.2.3.2, fzf를 0.74.1, pnpm을 11.15.1, Vite를 8.1.5, Prettier를 3.9.6, Wrangler를 4.112.0, Prisma를 7.9.0, Lighthouse를 13.4.1로 업데이트합니다. Paperclip은 2026.707.0을 유지합니다. Hermes는 일시적으로 번들되지 않으며 Vercel, LHCI, sharp-cli, concurrently는 이미지에서 제거되었습니다. Netlify는 원격 빌드와 배포만 지원합니다. 외부에서 관리하는 CLIProxyAPI 엔드포인트는 계속 지원됩니다.
@@ -470,7 +478,6 @@ services: | 서비스 | 용도 | |---------|---------| -| Hermes Agent | MCP, 메시징 어댑터, OpenCode 위임을 갖춘 자기 개선형 메타 에이전트 | | Paperclip | OpenCode 워커를 고용하고 하트비트로 깨우는 로컬 에이전트 보드 | | Claude Code CLI | `ENABLE_CLAUDE_AUTH`를 통한 Claude 구독 인증 흐름을 위해 설치됨 | @@ -496,24 +503,13 @@ s6-overlay가 OpenCode와 Xvfb를 감독합니다. 프로세스가 크래시하 ## 🧩 번들 서비스 -HolyCode는 이제 OpenCode 위에 두 가지 선택적 레이어를 함께 제공합니다. 컨테이너를 사용하는 데 **이것들이 필요하지 않습니다**. 환경 변수를 전환하고, 컨테이너를 재시작하면 일반 웹 UI와 함께 서비스가 시작됩니다. +Paperclip은 OpenCode 위의 선택적 서비스로 계속 번들됩니다. CLIProxyAPI 통합은 외부에서 관리하는 엔드포인트를 사용합니다. Hermes는 일시적으로 이미지에 포함되지 않습니다. -### Hermes Agent +### Hermes Agent (일시적으로 번들되지 않음) -Hermes는 "더 스마트한 두뇌" 옵션입니다. 번들된 메타 에이전트로 실행되고, 포트 `8642`에서 OpenAI 호환 API를 노출하며, HolyCode가 이미 제공하는 로컬 `opencode` 바이너리를 호출하여 코딩 작업을 위임합니다. +v1.1.3은 현재 의존성이 필요한 보안 수정과 아직 호환되지 않아 Hermes 런타임 서비스를 일시적으로 제거합니다. HolyCode는 Hermes 프로세스를 시작하거나 해당 포트를 게시하지 않습니다. -다음으로 활성화하세요: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Hermes를 활성화하기 전에 `API_SERVER_KEY`를 설정하세요. - -Hermes 상태는 `/home/opencode/.hermes`에 저장되며, HolyCode의 나머지 부분과 동일한 영속성 방식을 따릅니다. +`/home/opencode/.hermes`의 기존 데이터는 삭제되거나 마이그레이션되지 않습니다. v1.1.3을 정상적으로 시작하려면 이전 설정에서 `ENABLE_HERMES=true`를 제거하세요. 향후 통합 복원이나 변경되지 않은 이전 스냅샷 복원을 위해 이 디렉터리를 보존하세요. ### Paperclip @@ -701,6 +697,8 @@ environment: 최신 이미지를 풀하고 컨테이너를 재생성합니다. 데이터는 그대로 유지됩니다. +`v1.1.3` 이전 릴리스에서 업그레이드한다면 위의 seccomp 프로필을 다운로드하고 컨테이너를 재생성하기 전에 `holycode` 서비스에 `security_opt`를 추가하세요. + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode는 초기화하는 데 몇 초가 걸립니다. `docker compose up -d`
-HolyCode에 SYS_ADMIN이나 seccomp=unconfined가 필요 없는 이유 +HolyCode에서 Chromium 샌드박스를 사용하는 방법 -Chromium은 컨테이너 내에서 `--no-sandbox`로 실행되는데, 이는 컨테이너화된 브라우저 설정의 표준입니다. 이는 다른 Docker 브라우저 설정에서 필요한 `SYS_ADMIN` 기능이나 `seccomp=unconfined`의 필요성을 제거합니다. 컨테이너 자체가 격리 경계를 제공합니다. +Chromium은 `opencode` 사용자로 실행되며 내장 샌드박스가 활성화됩니다. Compose 파일은 제한된 `config/chromium-seccomp.json` 프로필을 자동으로 로드합니다. -Chromium의 내장 샌드박스를 사용하려면 compose 파일에 다음을 추가하고 `CHROMIUM_FLAGS` 환경 변수에서 `--no-sandbox`를 제거하세요: +컨테이너를 직접 실행하는 경우에도 같은 프로필을 로드하세요: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +샌드박스를 비활성화하거나 컨테이너에 추가 권한을 부여하지 마세요. +
diff --git a/docs/translations/README.pt.md b/docs/translations/README.pt.md index 621eceb..c8efe57 100644 --- a/docs/translations/README.pt.md +++ b/docs/translations/README.pt.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **Passo 2.** Crie um `docker-compose.yaml`. +A configuração do Compose usa o perfil seccomp do Chromium do HolyCode. Se você não estiver executando a partir de um clone do repositório, baixe primeiro a cópia da versão: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Substitui a porta do container usada pelo Paperclip | | `PAPERCLIP_INSTANCE_ID` | `default` | Nome da instância local do Paperclip para estado isolado | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (nenhuma) | Configure como `true` para iniciar o Hermes como API de meta-agente integrada | -| `HERMES_PORT` | `8642` | Substitui a porta do container usada pelo Hermes | -| `API_SERVER_KEY` | (nenhuma) | Configure-o antes de ativar o Hermes; use um token Bearer real | +| `ENABLE_HERMES` | (nenhuma) | Configuração antiga; a v1.1.3 interrompe a inicialização com um aviso de migração enquanto o Hermes não está incluído | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | Modo de atualização de plugins: `manual` (instala só o que falta e preserva as versões do usuário) ou `auto` (sincroniza os pins declarados na inicialização) | > Os toggles de plugins (`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`) têm efeito ao reiniciar o container. Configure a variável de ambiente e execute `docker compose down && up -d`. @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` inicia o Paperclip na porta `3100` dentro do container. Abra o painel, crie uma empresa e contrate agentes OpenCode de lá. O Paperclip persiste automaticamente em `~/.paperclip`. -> `ENABLE_HERMES=true` inicia o Hermes na porta `8642` dentro do container. O Hermes persiste em `~/.hermes`, usa o binário `opencode` já instalado e pode expor uma API compatível com OpenAI enquanto delega o trabalho de código de volta ao HolyCode. +> O Hermes não está incluído temporariamente na v1.1.3. Se uma instalação antiga mantiver `ENABLE_HERMES=true`, o HolyCode interrompe a inicialização com um aviso curto de migração. `/home/opencode/.hermes` permanece inalterado para uma futura restauração da integração ou para voltar a um snapshot anterior. > `GIT_USER_NAME` e `GIT_USER_EMAIL` são aplicados apenas na primeira inicialização. Para reaplicar, exclua o arquivo sentinela e reinicie: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` depois `docker compose restart`. @@ -427,7 +435,7 @@ services: > As tags de release usam exatamente `vX.Y.Z`. As tags de Docker omitem o `v`. Depois de `v1.0.9` use `v1.1.0`, depois de `v1.1.9` use `v1.2.0` e depois de `v1.9.9` use `v2.0.0`. `v1.0.10` a `v1.0.13` permanecem imutáveis. -> A v1.1.2 migra para Debian Trixie com Python 3.13, npm 12.0.1 e NumPy 2.5.1. O OpenCode é atualizado para 1.18.2, o Wrangler para 4.111.0 e o lazygit para 0.63.1. O TypeScript permanece em 6.0.3 e o Vercel em 54.21.0. O Netlify oferece suporte apenas a builds e deploys remotos. O sidecar CLIProxyAPI incluído continua removido; endpoints gerenciados externamente continuam compatíveis. +> A v1.1.3 atualiza o OpenCode para 1.18.4, o Claude Code para 2.1.216, o oh-my-openagent para 4.19.0, o s6-overlay para 3.2.3.2, o fzf para 0.74.1, o pnpm para 11.15.1, o Vite para 8.1.5, o Prettier para 3.9.6, o Wrangler para 4.112.0, o Prisma para 7.9.0 e o Lighthouse para 13.4.1. O Paperclip permanece na 2026.707.0. O Hermes não está incluído temporariamente; Vercel, LHCI, sharp-cli e concurrently foram removidos da imagem. O Netlify oferece suporte apenas a builds e deploys remotos. Endpoints CLIProxyAPI gerenciados externamente continuam compatíveis.
@@ -470,7 +478,6 @@ Inclui fontes Liberation, DejaVu, Noto e Noto Color Emoji para renderização co | Serviço | Propósito | |---------|---------| -| Hermes Agent | Meta-agente auto-aprimorável com MCP, adaptadores de mensagens e delegação ao OpenCode | | Paperclip | Quadro de agentes local que contrata trabalhadores OpenCode e os acorda por heartbeat | | Claude Code CLI | Instalado para fluxos de autenticação de assinatura Claude via `ENABLE_CLAUDE_AUTH` | @@ -496,24 +503,13 @@ O s6-overlay supervisiona OpenCode e Xvfb. Se um processo travar, ele reinicia a ## 🧩 Serviços integrados -O HolyCode agora vem com duas camadas opcionais sobre o OpenCode. Você **não precisa delas** para usar o container. Ative a variável de ambiente, reinicie o container e o serviço sobe junto com a interface web normal. +O Paperclip continua incluído como serviço opcional sobre o OpenCode. A integração CLIProxyAPI continua usando um endpoint gerenciado externamente. O Hermes não está temporariamente presente na imagem. -### Hermes Agent +### Hermes Agent (temporariamente não incluído) -O Hermes é a opção de "cérebro mais inteligente". Ele roda como um meta-agente integrado, expõe uma API compatível com OpenAI na porta `8642` e delega o trabalho de código chamando o binário `opencode` local que o HolyCode já inclui. +A v1.1.3 remove temporariamente o serviço de runtime do Hermes porque as dependências atuais ainda não são compatíveis com as correções de segurança exigidas. O HolyCode não inicia processos do Hermes nem publica a porta correspondente. -Ative com: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Defina `API_SERVER_KEY` antes de ativar o Hermes. - -O estado do Hermes vive em `/home/opencode/.hermes`, seguindo a mesma história de persistência do resto do HolyCode. +Os dados existentes em `/home/opencode/.hermes` não são excluídos nem migrados. Remova `ENABLE_HERMES=true` das configurações antigas para iniciar a v1.1.3 normalmente. Preserve o diretório para uma futura restauração da integração ou para restaurar um snapshot anterior que permaneceu intacto. ### Paperclip @@ -701,6 +697,8 @@ Se você pular isso, os arquivos no seu workspace podem ser de propriedade do ro Baixe a imagem mais recente e recrie o container. Seus dados permanecem intactos. +Ao atualizar de uma versão anterior à `v1.1.3`, baixe o perfil seccomp mostrado acima e adicione `security_opt` ao serviço `holycode` antes de recriar o container. + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ O OpenCode leva alguns segundos para inicializar. Aguarde 10-15 segundos após `
-Por que HolyCode não precisa de SYS_ADMIN ou seccomp=unconfined? +Como o HolyCode usa o sandbox do Chromium? -O Chromium roda com `--no-sandbox` dentro do container, que é o padrão para configurações de browser containerizadas. Isso elimina a necessidade de capacidades `SYS_ADMIN` ou `seccomp=unconfined` que algumas outras configurações de browser Docker requerem. O próprio container fornece a fronteira de isolamento. +O Chromium roda como o usuário `opencode` com o sandbox integrado ativado. Os arquivos Compose carregam automaticamente o perfil restrito `config/chromium-seccomp.json`. -Se preferir usar o sandbox integrado do Chromium, adicione o seguinte ao seu arquivo compose e remova `--no-sandbox` da variável de ambiente `CHROMIUM_FLAGS`: +Se você executar o container diretamente, carregue o mesmo perfil: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +Não desative o sandbox nem conceda privilégios adicionais ao container. +
diff --git a/docs/translations/README.ru.md b/docs/translations/README.ru.md index b33712c..6d1ca6e 100644 --- a/docs/translations/README.ru.md +++ b/docs/translations/README.ru.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **Шаг 2.** Создайте `docker-compose.yaml`. +Конфигурация Compose использует профиль seccomp Chromium из HolyCode. Если вы запускаете не из клона репозитория, сначала скачайте файл из релиза: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | Переопределяет порт контейнера для Paperclip | | `PAPERCLIP_INSTANCE_ID` | `default` | Имя локального экземпляра Paperclip для изолированного состояния | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (нет) | Установите `true` для запуска Hermes как встроенного мета-агентного API | -| `HERMES_PORT` | `8642` | Переопределяет порт контейнера для Hermes | -| `API_SERVER_KEY` | (нет) | Задайте его перед включением Hermes; используйте настоящий Bearer token | +| `ENABLE_HERMES` | (нет) | Устаревшая настройка; v1.1.3 останавливает запуск с сообщением о миграции, пока Hermes не входит в образ | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | Режим обновления плагинов: `manual` (устанавливает только недостающее и сохраняет версии пользователя) или `auto` (синхронизирует объявленные pin'ы при запуске) | > Переключатели плагинов (`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`) вступают в силу при перезапуске контейнера. Установите переменную и выполните `docker compose down && up -d`. @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` запускает Paperclip на порту `3100` внутри контейнера. Откройте панель управления, создайте компанию и наймите агентов OpenCode оттуда. Paperclip автоматически сохраняет состояние в `~/.paperclip`. -> `ENABLE_HERMES=true` запускает Hermes на порту `8642` внутри контейнера. Hermes сохраняет состояние в `~/.hermes`, использует уже установленный бинарный файл `opencode` и может предоставлять OpenAI-совместимый API, делегируя работу с кодом обратно в HolyCode. +> Hermes временно не входит в v1.1.3. Если в старой установке осталось `ENABLE_HERMES=true`, HolyCode останавливает запуск и выводит короткое сообщение о миграции. `/home/opencode/.hermes` сохраняется без изменений для будущего возврата интеграции или восстановления предыдущего снимка. > `GIT_USER_NAME` и `GIT_USER_EMAIL` применяются только при первом запуске. Для повторного применения удалите служебный файл и перезапустите: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` затем `docker compose restart`. @@ -427,7 +435,7 @@ services: > Теги релизов используют ровно `vX.Y.Z`. Docker-теги опускают `v`. После `v1.0.9` идёт `v1.1.0`, после `v1.1.9` идёт `v1.2.0`, а после `v1.9.9` идёт `v2.0.0`. Версии от `v1.0.10` до `v1.0.13` остаются неизменными. -> В v1.1.2 выполнен переход на Debian Trixie с Python 3.13, npm 12.0.1 и NumPy 2.5.1. OpenCode обновлён до 1.18.2, Wrangler до 4.111.0, а lazygit до 0.63.1. TypeScript остаётся на 6.0.3, а Vercel на 54.21.0. Netlify поддерживает только удалённые сборки и развёртывания. Встроенный sidecar CLIProxyAPI остаётся удалённым; внешние управляемые endpoint по-прежнему поддерживаются. +> В v1.1.3 OpenCode обновлён до 1.18.4, Claude Code до 2.1.216, oh-my-openagent до 4.19.0, s6-overlay до 3.2.3.2, fzf до 0.74.1, pnpm до 11.15.1, Vite до 8.1.5, Prettier до 3.9.6, Wrangler до 4.112.0, Prisma до 7.9.0, а Lighthouse до 13.4.1. Paperclip остаётся на 2026.707.0. Hermes временно не входит в образ; Vercel, LHCI, sharp-cli и concurrently удалены из образа. Netlify поддерживает только удалённые сборки и развёртывания. Внешние управляемые endpoint CLIProxyAPI по-прежнему поддерживаются.
@@ -470,7 +478,6 @@ services: | Сервис | Назначение | |---------|---------| -| Hermes Agent | Самосовершенствующийся мета-агент с MCP, адаптерами сообщений и делегированием OpenCode | | Paperclip | Локальная доска агентов, нанимающая работников OpenCode и пробуждающая их по heartbeat | | Claude Code CLI | Установлен для потоков аутентификации подписки Claude через `ENABLE_CLAUDE_AUTH` | @@ -496,24 +503,13 @@ s6-overlay следит за OpenCode и Xvfb. Если процесс пада ## 🧩 Встроенные сервисы -HolyCode теперь поставляется с двумя опциональными слоями поверх OpenCode. Они **не нужны** для использования контейнера. Включите переменную окружения, перезапустите контейнер — и сервис запустится рядом с обычным веб-интерфейсом. +Paperclip по-прежнему входит в образ как опциональный сервис поверх OpenCode. Интеграция CLIProxyAPI использует внешний управляемый endpoint. Hermes временно отсутствует в образе. -### Hermes Agent +### Hermes Agent (временно не входит в образ) -Hermes — это опция «умного мозга». Он работает как встроенный мета-агент, предоставляет OpenAI-совместимый API на порту `8642` и делегирует работу с кодом, вызывая локальный бинарный файл `opencode`, который HolyCode уже включает. +В v1.1.3 сервис Hermes временно удалён, потому что его текущие зависимости ещё не совместимы с обязательными исправлениями безопасности. HolyCode не запускает процесс Hermes и не публикует его порт. -Включите с помощью: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -Задайте `API_SERVER_KEY` перед включением Hermes. - -Состояние Hermes хранится в `/home/opencode/.hermes` и следует той же истории постоянства, что и остальная часть HolyCode. +Существующие данные в `/home/opencode/.hermes` не удаляются и не мигрируют. Удалите `ENABLE_HERMES=true` из старой конфигурации, чтобы v1.1.3 запускалась нормально. Сохраните этот каталог для будущего возврата интеграции или восстановления неизменённого предыдущего снимка. ### Paperclip @@ -701,6 +697,8 @@ environment: Скачайте последний образ и пересоздайте контейнер. Ваши данные остаются нетронутыми. +При обновлении с версии старше `v1.1.3` скачайте указанный выше профиль seccomp и добавьте `security_opt` в сервис `holycode` до пересоздания контейнера. + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode требует несколько секунд для инициали
-Почему HolyCode не требует SYS_ADMIN или seccomp=unconfined? +Как HolyCode использует песочницу Chromium? -Chromium запускается с `--no-sandbox` внутри контейнера, что является стандартом для контейнеризированных браузерных установок. Это исключает необходимость в возможностях `SYS_ADMIN` или `seccomp=unconfined`, которые требуют некоторые другие Docker-установки с браузером. Сам контейнер обеспечивает границу изоляции. +Chromium запускается от пользователя `opencode` со включённой встроенной песочницей. Файлы Compose автоматически загружают ограниченный профиль `config/chromium-seccomp.json`. -Если вы предпочитаете использовать встроенный песочница Chromium, добавьте следующее в compose-файл и уберите `--no-sandbox` из переменной окружения `CHROMIUM_FLAGS`: +При самостоятельном запуске контейнера загрузите тот же профиль: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +Не отключайте песочницу и не выдавайте контейнеру дополнительные привилегии. +
diff --git a/docs/translations/README.zh.md b/docs/translations/README.zh.md index e4f1e3c..c484ab6 100644 --- a/docs/translations/README.zh.md +++ b/docs/translations/README.zh.md @@ -92,6 +92,14 @@ docker pull coderluii/holycode:latest **第 2 步。** 创建 `docker-compose.yaml`。 +Compose 配置使用 HolyCode 的 Chromium seccomp 配置文件。如果你不是从仓库克隆目录运行,请先下载该版本的文件: + +```bash +mkdir -p config +curl -fsSLo config/chromium-seccomp.json \ + https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json +``` + ```yaml services: holycode: @@ -99,6 +107,8 @@ services: container_name: holycode restart: unless-stopped shm_size: 2g + security_opt: + - seccomp=./config/chromium-seccomp.json ports: - "4096:4096" volumes: @@ -365,9 +375,7 @@ services: | `PAPERCLIP_PORT` | `3100` | 覆盖 Paperclip 使用的容器端口 | | `PAPERCLIP_INSTANCE_ID` | `default` | 用于隔离状态的本地 Paperclip 实例名称 | | `PAPERCLIP_BIND` | `lan` | Paperclip reachability preset; `lan` binds inside Docker on `0.0.0.0` | -| `ENABLE_HERMES` | (无) | 设置为 `true` 以将 Hermes 作为捆绑的元代理 API 启动 | -| `HERMES_PORT` | `8642` | 覆盖 Hermes 使用的容器端口 | -| `API_SERVER_KEY` | (无) | 在启用 Hermes 之前设置它;请使用真实的 Bearer token | +| `ENABLE_HERMES` | (无) | 旧设置;在 Hermes 未捆绑期间,v1.1.3 会显示迁移提示并停止启动 | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | 插件更新模式:`manual`(只安装缺失项并保留用户版本)或 `auto`(启动时同步已声明的 pin) | > 插件开关(`ENABLE_CLAUDE_AUTH`、`ENABLE_OH_MY_OPENAGENT`)在容器重启时生效。设置环境变量并运行 `docker compose down && up -d`。 @@ -380,7 +388,7 @@ services: > `ENABLE_PAPERCLIP=true` 在容器内的端口 `3100` 上启动 Paperclip。打开仪表板,创建公司,然后在那里雇用 OpenCode 支持的代理。Paperclip 自动持久化到 `~/.paperclip`。 -> `ENABLE_HERMES=true` 在容器内的端口 `8642` 上启动 Hermes。Hermes 持久化到 `~/.hermes`,使用已安装的 `opencode` 二进制文件,并可以公开 OpenAI 兼容的 API,同时将代码工作委托回 HolyCode。 +> v1.1.3 暂时不捆绑 Hermes。如果旧安装中仍有 `ENABLE_HERMES=true`,HolyCode 会显示简短的迁移提示并停止启动。`/home/opencode/.hermes` 会保持不变,以便将来恢复集成或还原之前的快照。 > `GIT_USER_NAME` 和 `GIT_USER_EMAIL` 仅在首次启动时应用。要重新应用,删除哨兵文件并重启:`docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` 然后 `docker compose restart`。 @@ -427,7 +435,7 @@ services: > 发布标签严格使用 `vX.Y.Z`。Docker 镜像标签会去掉 `v`。`v1.0.9` 之后使用 `v1.1.0`,`v1.1.9` 之后使用 `v1.2.0`,`v1.9.9` 之后使用 `v2.0.0`。`v1.0.10` 到 `v1.0.13` 保持不可变。 -> v1.1.2 迁移到 Debian Trixie、Python 3.13、npm 12.0.1 和 NumPy 2.5.1。OpenCode 更新到 1.18.2,Wrangler 更新到 4.111.0,lazygit 更新到 0.63.1。TypeScript 保持在 6.0.3,Vercel 保持在 54.21.0。Netlify 仅支持远程构建和部署。内置 CLIProxyAPI sidecar 仍保持移除;外部管理的 endpoint 继续受支持。 +> v1.1.3 将 OpenCode 更新到 1.18.4、Claude Code 更新到 2.1.216、oh-my-openagent 更新到 4.19.0、s6-overlay 更新到 3.2.3.2、fzf 更新到 0.74.1、pnpm 更新到 11.15.1、Vite 更新到 8.1.5、Prettier 更新到 3.9.6、Wrangler 更新到 4.112.0、Prisma 更新到 7.9.0,并将 Lighthouse 更新到 13.4.1。Paperclip 保持在 2026.707.0。Hermes 暂时不捆绑;Vercel、LHCI、sharp-cli 和 concurrently 已从镜像中移除。Netlify 仅支持远程构建和部署。外部管理的 CLIProxyAPI endpoint 继续受支持。
@@ -470,7 +478,6 @@ services: | 服务 | 用途 | |---------|---------| -| Hermes Agent | 具有 MCP、消息适配器和 OpenCode 委托的自我改进元代理 | | Paperclip | 本地代理看板,雇用 OpenCode 工作者并在心跳时唤醒它们 | | Claude Code CLI | 通过 `ENABLE_CLAUDE_AUTH` 为 Claude 订阅认证流程安装 | @@ -496,24 +503,13 @@ s6-overlay 监督 OpenCode 和 Xvfb。如果进程崩溃,它会自动重启。 ## 🧩 捆绑服务 -HolyCode 现在在 OpenCode 之上附带两个可选层。使用容器**不需要**它们。切换环境变量,重启容器,服务就会在正常 Web UI 旁边启动。 +Paperclip 继续作为 OpenCode 之上的可选服务捆绑。CLIProxyAPI 集成继续使用外部管理的 endpoint。Hermes 暂时不在镜像中。 -### Hermes Agent +### Hermes Agent(暂时不捆绑) -Hermes 是"更智能大脑"选项。它作为捆绑的元代理运行,在端口 `8642` 上公开 OpenAI 兼容的 API,并通过调用 HolyCode 已附带的本地 `opencode` 二进制文件来委托编码工作。 +v1.1.3 暂时移除 Hermes 运行时服务,因为其当前依赖项尚未兼容所需的安全修复。HolyCode 不会启动 Hermes 进程,也不会发布其端口。 -使用以下方式启用: - -```yaml -environment: - - ENABLE_HERMES=true - - HERMES_PORT=8642 - - API_SERVER_KEY=replace-with-a-real-secret -``` - -在启用 Hermes 之前设置 `API_SERVER_KEY`。 - -Hermes 状态存储在 `/home/opencode/.hermes`,与 HolyCode 其余部分遵循相同的持久化方式。 +`/home/opencode/.hermes` 中的现有数据不会被删除或迁移。请从旧配置中移除 `ENABLE_HERMES=true`,让 v1.1.3 正常启动。保留该目录,以便将来恢复集成或还原未被修改的旧快照。 ### Paperclip @@ -701,6 +697,8 @@ environment: 拉取最新镜像并重新创建容器。你的数据保持不变。 +如果从 `v1.1.3` 之前的版本升级,请下载上面的 seccomp 配置文件,并在重新创建容器前把 `security_opt` 添加到 `holycode` 服务中。 + ```bash docker compose pull docker compose up -d @@ -790,19 +788,19 @@ OpenCode 需要几秒钟来初始化。`docker compose up -d` 后等待 10-15
-为什么 HolyCode 不需要 SYS_ADMIN 或 seccomp=unconfined? +HolyCode 如何使用 Chromium 沙盒? -Chromium 在容器内以 `--no-sandbox` 运行,这是容器化浏览器设置的标准做法。这消除了对 `SYS_ADMIN` 能力或 `seccomp=unconfined` 的需求,而某些其他 Docker 浏览器设置需要这些。容器本身提供隔离边界。 +Chromium 以 `opencode` 用户运行,并启用内置沙盒。Compose 文件会自动加载受限的 `config/chromium-seccomp.json` 配置。 -如果你希望使用 Chromium 的内置沙盒,请将以下内容添加到 compose 文件中,并从 `CHROMIUM_FLAGS` 环境变量中删除 `--no-sandbox`: +如果你自行启动容器,也要加载同一个配置: ```yaml -cap_add: - - SYS_ADMIN security_opt: - - seccomp=unconfined + - seccomp=./config/chromium-seccomp.json ``` +不要禁用沙盒,也不要为容器授予额外权限。 +
diff --git a/renovate.json b/renovate.json index 805c6fa..5d388c6 100644 --- a/renovate.json +++ b/renovate.json @@ -71,17 +71,6 @@ "versioningTemplate": "semver", "extractVersionTemplate": "^v?(?.+)$" }, - { - "customType": "regex", - "managerFilePatterns": ["/^Dockerfile$/"], - "matchStrings": [ - "ARG HERMES_AGENT_VERSION=(?[^\\s]+)" - ], - "depNameTemplate": "NousResearch/hermes-agent", - "datasourceTemplate": "github-tags", - "versioningTemplate": "semver", - "extractVersionTemplate": "^v?(?.+)$" - }, { "customType": "regex", "managerFilePatterns": ["/^Dockerfile$/"], @@ -155,25 +144,24 @@ "dependencyDashboardApproval": true }, { - "description": "Require manual review for runtime, service, TypeScript, Vercel, and plugin pins", + "description": "Require manual review for runtime, service, TypeScript, and plugin pins", "matchPackageNames": [ "@anthropic-ai/claude-code", + "cli/cli", "coderluii/holycode", "dandavison/delta", "docker/scout-cli", "eza-community/eza", - "hermes-agent", "jesseduffield/lazygit", "junegunn/fzf", "just-containers/s6-overlay", + "golang", "node", - "NousResearch/hermes-agent", "oh-my-openagent", "opencode-ai", "opencode-claude-auth", "paperclipai", - "typescript", - "vercel" + "typescript" ], "dependencyDashboardApproval": true }, diff --git a/s6-overlay/s6-rc.d/hermes/run b/s6-overlay/s6-rc.d/hermes/run deleted file mode 100644 index e653e41..0000000 --- a/s6-overlay/s6-rc.d/hermes/run +++ /dev/null @@ -1,16 +0,0 @@ -#!/command/with-contenv sh -opencode_home="/home/opencode" - -exec s6-setuidgid opencode env \ - HOME="$opencode_home" \ - USER="opencode" \ - LOGNAME="opencode" \ - XDG_CONFIG_HOME="$opencode_home/.config" \ - XDG_CACHE_HOME="$opencode_home/.cache" \ - XDG_DATA_HOME="$opencode_home/.local/share" \ - XDG_STATE_HOME="$opencode_home/.local/state" \ - API_SERVER_ENABLED=true \ - API_SERVER_HOST="${API_SERVER_HOST:-0.0.0.0}" \ - API_SERVER_PORT="${HERMES_PORT:-8642}" \ - HERMES_HOME="${HERMES_HOME:-$opencode_home/.hermes}" \ - hermes gateway run --no-supervise --accept-hooks diff --git a/s6-overlay/s6-rc.d/hermes/type b/s6-overlay/s6-rc.d/hermes/type deleted file mode 100644 index 5883cff..0000000 --- a/s6-overlay/s6-rc.d/hermes/type +++ /dev/null @@ -1 +0,0 @@ -longrun diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index cda0f9a..e7dfeb0 100644 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -13,7 +13,7 @@ WORKSPACE_DIR="/workspace" CLAUDE_AUTH_PLUGIN_NAME="opencode-claude-auth" CLAUDE_AUTH_PLUGIN_VERSION="2.0.0" OH_MY_OPENAGENT_PLUGIN_NAME="oh-my-openagent" -OH_MY_OPENAGENT_PLUGIN_VERSION="4.18.1" +OH_MY_OPENAGENT_PLUGIN_VERSION="4.19.0" sync_shipped_skills() { local source_skills_dir="/usr/local/share/holycode/skills" @@ -316,12 +316,9 @@ fi sync_shipped_skills if [ "${ENABLE_HERMES}" = "true" ]; then - export HERMES_HOME="${HERMES_HOME:-$OC_HOME/.hermes}" - mkdir -p "$HERMES_HOME" - chown "$PUID:$PGID" "$HERMES_HOME" 2>/dev/null || true - touch /etc/s6-overlay/user-bundles.d/user/contents.d/hermes -else - rm -f /etc/s6-overlay/user-bundles.d/user/contents.d/hermes + echo "[entrypoint] ERROR: The bundled Hermes is temporarily unavailable in v1.1.3 because its pinned dependencies have unresolved security updates." >&2 + echo "[entrypoint] Your /home/opencode/.hermes is preserved. Remove ENABLE_HERMES=true to start HolyCode, or run Hermes separately until bundling returns." >&2 + exit 1 fi if [ "${ENABLE_PAPERCLIP}" = "true" ]; then diff --git a/scripts/smoke_image.sh b/scripts/smoke_image.sh index 14229b5..a4ed491 100755 --- a/scripts/smoke_image.sh +++ b/scripts/smoke_image.sh @@ -2,6 +2,7 @@ set -euo pipefail image="${1:?usage: scripts/smoke_image.sh }" +seccomp_profile="${2:-config/chromium-seccomp.json}" image_label() { docker inspect --format "{{ index .Config.Labels \"$1\" }}" "$image" @@ -17,7 +18,13 @@ expected_pnpm="$(image_label io.holycode.version.pnpm)" expected_netlify="$(image_label io.holycode.version.netlify-cli)" expected_numpy="$(image_label io.holycode.version.numpy)" expected_wrangler="$(image_label io.holycode.version.wrangler)" -expected_vercel="$(image_label io.holycode.version.vercel)" +expected_vite="$(image_label io.holycode.version.vite)" +expected_prettier="$(image_label io.holycode.version.prettier)" +expected_prisma="$(image_label io.holycode.version.prisma)" +expected_lighthouse="$(image_label io.holycode.version.lighthouse)" +expected_s6="$(image_label io.holycode.version.s6-overlay)" +expected_fzf="$(image_label io.holycode.version.fzf)" +expected_github_cli="$(image_label io.holycode.version.github-cli)" secret_pattern='(_API_KEY|TOKEN|SECRET|PASSWORD)=[^[:space:]]+' @@ -31,7 +38,7 @@ if docker history --no-trunc "$image" | grep -Ei '(sk-[A-Za-z0-9_-]{20,}|ghp_[A- exit 1 fi -docker run --rm --entrypoint sh \ +docker run --rm --security-opt "seccomp=$seccomp_profile" --entrypoint sh \ -e EXPECTED_OPENCODE="$expected_opencode" \ -e EXPECTED_CLAUDE="$expected_claude" \ -e EXPECTED_PAPERCLIP="$expected_paperclip" \ @@ -42,17 +49,30 @@ docker run --rm --entrypoint sh \ -e EXPECTED_NETLIFY="$expected_netlify" \ -e EXPECTED_NUMPY="$expected_numpy" \ -e EXPECTED_WRANGLER="$expected_wrangler" \ - -e EXPECTED_VERCEL="$expected_vercel" \ + -e EXPECTED_VITE="$expected_vite" \ + -e EXPECTED_PRETTIER="$expected_prettier" \ + -e EXPECTED_PRISMA="$expected_prisma" \ + -e EXPECTED_LIGHTHOUSE="$expected_lighthouse" \ + -e EXPECTED_S6="$expected_s6" \ + -e EXPECTED_FZF="$expected_fzf" \ + -e EXPECTED_GITHUB_CLI="$expected_github_cli" \ "$image" -lc ' set -eu node --version | grep -E "^v[0-9]+\\." npm --version | grep -Fx "$EXPECTED_NPM" opencode --version | grep -Fx "$EXPECTED_OPENCODE" + test -d "/package/admin/s6-overlay-$EXPECTED_S6" + fzf --version | grep -E "^$EXPECTED_FZF([[:space:]]|$)" + test "$(command -v gh)" = "/usr/local/bin/gh" + gh --version | grep -F "gh version $EXPECTED_GITHUB_CLI" + ! dpkg-query -W gh >/dev/null 2>&1 test -f /usr/local/lib/node_modules/paperclipai/package.json test -f /usr/local/lib/node_modules/paperclipai/node_modules/@paperclipai/skills-catalog/generated/catalog.json node -e "console.log(require(\"/usr/local/lib/node_modules/paperclipai/package.json\").version)" | grep -Fx "$EXPECTED_PAPERCLIP" + (cd /usr/local/lib/node_modules/paperclipai && npm ls undici --all >/dev/null) + node --input-type=module -e "const {testEnvironment}=await import(\"file:///usr/local/lib/node_modules/paperclipai/node_modules/@paperclipai/adapter-cursor-cloud/dist/server/index.js\"); const result=await testEnvironment({adapterType:\"cursor_cloud\",config:{}}); if(result.status!==\"fail\" || !result.checks.some((check)=>check.code===\"cursor_cloud_api_key_missing\")) process.exit(1)" test -f /etc/s6-overlay/user-bundles.d/user/contents.d/opencode test -f /etc/s6-overlay/user-bundles.d/user/contents.d/xvfb test ! -e /etc/s6-overlay/s6-rc.d/user/contents.d/opencode @@ -61,13 +81,26 @@ docker run --rm --entrypoint sh \ python3 --version | grep -E "^Python 3\.13\." python3 -m pip --version python3 -m pip check + psql --version | grep -F "psql (PostgreSQL) 17." + ! dpkg-query -W postgresql-client >/dev/null 2>&1 python3 - </dev/null - node -e "const sharp=require(\"/usr/local/lib/node_modules/sharp-cli/node_modules/sharp\"); sharp({create:{width:1,height:1,channels:4,background:{r:0,g:0,b:0,alpha:1}}}).png().toBuffer().then(b=>{if(!b.length)process.exit(1)})" workerd_bin="$(find /usr/local/lib/node_modules/wrangler -path "*/workerd/bin/workerd" -type f -print -quit)" test -n "$workerd_bin" "$workerd_bin" --version >/dev/null + sharp_count=0 + while IFS= read -r package_json; do + sharp_dir="${package_json%/package.json}" + node -e "const sharp=require(process.argv[1]); sharp({create:{width:2,height:2,channels:4,background:{r:220,g:30,b:30,alpha:1}}}).png().toBuffer().then(buffer=>{if(buffer.length<10)process.exit(1)}).catch(error=>{console.error(error);process.exit(1)})" "$sharp_dir" + sharp_count=$((sharp_count + 1)) + done </dev/null netlify deploy --help >/dev/null test -z "$(find /usr/local/lib/node_modules/netlify-cli -path "*/@netlify/local-functions-proxy-*/bin/local-functions-proxy" -print -quit)" + grep -F "" /etc/ImageMagick-7/policy.xml >/dev/null + grep -F "" /etc/ImageMagick-7/policy.xml >/dev/null chromium --version + test -u /usr/lib/chromium/chrome-sandbox + runuser -u opencode -- chromium --headless --disable-gpu --disable-dev-shm-usage --dump-dom about:blank | grep -F "" + runuser -u opencode -- python3 -c "from playwright.sync_api import sync_playwright; from PIL import Image; p=sync_playwright().start(); b=p.chromium.launch(executable_path=\"/usr/bin/chromium\", args=[\"--disable-gpu\", \"--disable-dev-shm-usage\"]); page=b.new_page(viewport={\"width\": 320, \"height\": 200}); page.set_content(\"
\"); page.screenshot(path=\"/tmp/holycode-chromium.png\"); b.close(); p.stop(); image=Image.open(\"/tmp/holycode-chromium.png\").convert(\"RGB\"); assert image.getbbox() and len(image.getcolors(maxcolors=1000000) or []) > 1" test -s /usr/local/share/holycode/dpkg-inventory.txt mkdir -p /tmp/wrangler-modern /tmp/wrangler-legacy diff --git a/scripts/test_claude_auth.sh b/scripts/test_claude_auth.sh new file mode 100755 index 0000000..9760de8 --- /dev/null +++ b/scripts/test_claude_auth.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +image="${1:?usage: scripts/test_claude_auth.sh }" +host_home="${2:?usage: scripts/test_claude_auth.sh }" +credentials="$host_home/.claude/.credentials.json" +settings="$host_home/.claude.json" +volume="holycode-claude-auth-$$" + +test -f "$credentials" +test -f "$settings" + +# Keep Git for Windows from rewriting container-side bind mount paths. +export MSYS_NO_PATHCONV=1 + +cleanup() { + docker volume rm "$volume" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +docker volume create "$volume" >/dev/null +docker run --rm --entrypoint sh \ + -v "$credentials:/source/credentials.json:ro" \ + -v "$settings:/source/claude.json:ro" \ + -v "$volume:/home/opencode" \ + "$image" -lc ' + mkdir -p /home/opencode/.claude + cp /source/credentials.json /home/opencode/.claude/.credentials.json + cp /source/claude.json /home/opencode/.claude.json + chown -R opencode:opencode /home/opencode/.claude /home/opencode/.claude.json + sha256sum /home/opencode/.claude/.credentials.json /home/opencode/.claude.json > /home/opencode/.claude-auth-before + ' + +for _ in 1 2; do + docker run --rm --entrypoint sh \ + -v "$volume:/home/opencode" \ + "$image" -lc ' + runuser -u opencode -- claude auth status --json | + jq -e ".loggedIn == true and (.authMethod | length > 0)" >/dev/null + ' +done + +docker run --rm --entrypoint sh \ + -v "$volume:/home/opencode" \ + "$image" -lc ' + sha256sum --check --status /home/opencode/.claude-auth-before + ' + +echo "Claude authentication and recreation persistence passed" diff --git a/scripts/test_plugin_modes.sh b/scripts/test_plugin_modes.sh index f3aa45e..543592e 100755 --- a/scripts/test_plugin_modes.sh +++ b/scripts/test_plugin_modes.sh @@ -67,9 +67,9 @@ assert_plugin() { docker volume create "$volume" >/dev/null run_with_plugins manual assert_plugin opencode-claude-auth 2.0.0 -assert_plugin oh-my-openagent 4.18.1 +assert_plugin oh-my-openagent 4.19.0 wait_for_log "configured as 'opencode-claude-auth@2.0.0'; installed version 2.0.0" -wait_for_log "configured as 'oh-my-openagent@4.18.1'; installed version 4.18.1" +wait_for_log "configured as 'oh-my-openagent@4.19.0'; installed version 4.19.0" docker exec -u opencode \ -e HOME=/home/opencode \ diff --git a/scripts/test_upgrade_rollback.sh b/scripts/test_upgrade_rollback.sh index e5feda7..26fddc8 100755 --- a/scripts/test_upgrade_rollback.sh +++ b/scripts/test_upgrade_rollback.sh @@ -29,14 +29,16 @@ trap cleanup EXIT wait_for_services() { local name="$1" + local expect_hermes="$2" for _ in $(seq 1 180); do if docker exec "$name" curl -fsS --max-time 5 -o /dev/null http://localhost:4096/ 2>/dev/null && \ - docker exec "$name" curl -fsS --max-time 5 -o /dev/null http://localhost:3100/api/health 2>/dev/null && \ - docker exec "$name" curl -fsS --max-time 5 -o /dev/null \ + docker exec "$name" curl -fsS --max-time 5 -o /dev/null http://localhost:3100/api/health 2>/dev/null; then + if [ "$expect_hermes" != "true" ] || docker exec "$name" curl -fsS --max-time 5 -o /dev/null \ -H 'Authorization: Bearer holycode-upgrade-test-key' \ http://localhost:8642/v1/models 2>/dev/null; then - return 0 + return 0 + fi fi if [ "$(docker inspect -f '{{.State.Running}}' "$name" 2>/dev/null || true)" != "true" ]; then docker logs "$name" || true @@ -53,6 +55,7 @@ start_stack() { local image="$2" local home_volume="$3" local workspace_volume="$4" + local enable_hermes="$5" docker run -d --platform "$platform" --name "$name" \ -v "$home_volume:/home/opencode" \ @@ -60,13 +63,13 @@ start_stack() { -e PUID=2345 \ -e PGID=2345 \ -e ENABLE_PAPERCLIP=true \ - -e ENABLE_HERMES=true \ + -e ENABLE_HERMES="$enable_hermes" \ -e API_SERVER_KEY=holycode-upgrade-test-key \ -e CLIPROXYAPI_ENABLED=true \ -e CLIPROXYAPI_MODEL=holycode-upgrade-model \ -e CLIPROXYAPI_API_KEY=holycode-upgrade-provider-key \ "$image" >/dev/null - wait_for_services "$name" + wait_for_services "$name" "$enable_hermes" } clone_volume() { @@ -95,7 +98,7 @@ docker pull --platform "$platform" "$previous_image" >/dev/null docker volume create "$baseline_home" >/dev/null docker volume create "$baseline_workspace" >/dev/null -start_stack "$baseline_name" "$previous_image" "$baseline_home" "$baseline_workspace" +start_stack "$baseline_name" "$previous_image" "$baseline_home" "$baseline_workspace" true docker exec -u opencode "$baseline_name" sh -lc ' touch /home/opencode/.claude/holycode-upgrade-auth-marker touch /home/opencode/.hermes/holycode-upgrade-marker @@ -108,15 +111,15 @@ docker rm -f "$baseline_name" >/dev/null clone_volume "$baseline_home" "$upgrade_home" clone_volume "$baseline_workspace" "$upgrade_workspace" -start_stack "$upgrade_name" "$current_image" "$upgrade_home" "$upgrade_workspace" +start_stack "$upgrade_name" "$current_image" "$upgrade_home" "$upgrade_workspace" false assert_persisted_state "$upgrade_name" docker exec "$upgrade_name" node --version | grep -Fx "$current_node_version" docker restart "$upgrade_name" >/dev/null -wait_for_services "$upgrade_name" +wait_for_services "$upgrade_name" false assert_persisted_state "$upgrade_name" docker rm -f "$upgrade_name" >/dev/null -start_stack "$rollback_name" "$previous_image" "$baseline_home" "$baseline_workspace" +start_stack "$rollback_name" "$previous_image" "$baseline_home" "$baseline_workspace" true assert_persisted_state "$rollback_name" docker exec "$rollback_name" node --version | grep -Fx "$previous_node_version" diff --git a/scripts/validate_chromium_seccomp.py b/scripts/validate_chromium_seccomp.py new file mode 100644 index 0000000..de38e13 --- /dev/null +++ b/scripts/validate_chromium_seccomp.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Validate the vendored Chromium sandbox seccomp profile.""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +PROFILE = ROOT / "config" / "chromium-seccomp.json" +EXPECTED_SHA256 = "cc3e61cabda6bbc1e53e54d27ba4d55a9d3be829b6dd1a596f4a7b31b1cc7849" + + +def main() -> int: + content = PROFILE.read_bytes() + actual = hashlib.sha256(content).hexdigest() + if actual != EXPECTED_SHA256: + raise SystemExit(f"Chromium seccomp profile SHA-256 mismatch: {actual}") + + profile = json.loads(content) + if profile.get("defaultAction") != "SCMP_ACT_ERRNO": + raise SystemExit("Chromium seccomp profile must deny unlisted syscalls") + + namespace_rule = profile.get("syscalls", [{}])[0] + if namespace_rule.get("action") != "SCMP_ACT_ALLOW" or set( + namespace_rule.get("names", []) + ) != {"clone", "setns", "unshare"}: + raise SystemExit("Chromium seccomp profile namespace rule changed") + + print("Chromium seccomp profile validation passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/validate_npm_script_policy.py b/scripts/validate_npm_script_policy.py index d610099..2d80a58 100644 --- a/scripts/validate_npm_script_policy.py +++ b/scripts/validate_npm_script_policy.py @@ -1,15 +1,17 @@ #!/usr/bin/env python3 -"""Validate lifecycle scripts in globally installed npm packages.""" +"""Validate npm lifecycle scripts in globally installed packages.""" from __future__ import annotations import argparse import json +import re import subprocess import sys from pathlib import Path LIFECYCLE_SCRIPTS = ("preinstall", "install", "postinstall") +INTEGRITY_PATTERN = re.compile(r"^sha512-[A-Za-z0-9+/]+={0,2}$") def load_json(path: Path) -> dict: @@ -27,6 +29,14 @@ def is_package_root(package_json: Path) -> bool: ) +def registry_integrity(package_id: str) -> str: + return subprocess.check_output( + ["npm", "view", package_id, "dist.integrity"], + text=True, + stderr=subprocess.STDOUT, + ).strip() + + def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--policy", required=True, type=Path) @@ -36,8 +46,17 @@ def main() -> int: policy = load_json(args.policy) installed_npm = subprocess.check_output(["npm", "--version"], text=True).strip() + installed_arch = subprocess.check_output( + ["node", "-p", "process.arch"], text=True + ).strip() errors: list[str] = [] + expected_node_arch = {"amd64": "x64", "arm64": "arm64"}[args.target_arch] + if installed_arch != expected_node_arch: + errors.append( + f"target architecture {args.target_arch!r} does not match Node {installed_arch!r}" + ) + if policy.get("npmVersion") != installed_npm: errors.append( f"policy npmVersion {policy.get('npmVersion')!r} does not match installed {installed_npm!r}" @@ -52,22 +71,34 @@ def main() -> int: allowed = allowed if isinstance(allowed, dict) else {} blocked = blocked if isinstance(blocked, dict) else {} - expected: dict[str, dict[str, str]] = {} + expected: dict[str, dict] = {} active_decisions = {"allowed": 0, "blocked": 0} for decision, entries in (("allowed", allowed), ("blocked", blocked)): for package_id, entry in entries.items(): - architectures = entry.get("architectures", ["amd64", "arm64"]) + architectures = entry.get("architectures") + if not isinstance(architectures, list) or not architectures: + errors.append(f"{decision} entry {package_id} has no architectures") + continue + if any(arch not in ("amd64", "arm64") for arch in architectures): + errors.append( + f"{decision} entry {package_id} has invalid architectures {architectures!r}" + ) + continue if args.target_arch not in architectures: continue active_decisions[decision] += 1 scripts = entry.get("scripts") reason = entry.get("reason") + integrity = entry.get("integrity") if not isinstance(scripts, dict) or not scripts: errors.append(f"{decision} entry {package_id} has no scripts") continue if not isinstance(reason, str) or not reason.strip(): errors.append(f"{decision} entry {package_id} has no reason") - expected[package_id] = scripts + if not isinstance(integrity, str) or not INTEGRITY_PATTERN.fullmatch(integrity): + errors.append(f"{decision} entry {package_id} has no valid SHA-512 integrity") + continue + expected[package_id] = {"scripts": scripts, "integrity": integrity} discovered: dict[str, dict[str, str]] = {} for package_json in args.root.rglob("package.json"): @@ -99,11 +130,24 @@ def main() -> int: for package_id in sorted(expected.keys() - discovered.keys()): errors.append(f"policy entry is not installed: {package_id}") for package_id in sorted(discovered.keys() & expected.keys()): - if discovered[package_id] != expected[package_id]: + expected_scripts = expected[package_id]["scripts"] + if discovered[package_id] != expected_scripts: errors.append( - f"script mismatch for {package_id}: expected {expected[package_id]}, " + f"script mismatch for {package_id}: expected {expected_scripts}, " f"found {discovered[package_id]}" ) + try: + actual_integrity = registry_integrity(package_id) + except subprocess.CalledProcessError as error: + errors.append( + f"could not resolve registry integrity for {package_id}: {error.output.strip()}" + ) + continue + if actual_integrity != expected[package_id]["integrity"]: + errors.append( + f"integrity mismatch for {package_id}: expected " + f"{expected[package_id]['integrity']}, found {actual_integrity}" + ) if errors: for error in errors: diff --git a/scripts/validate_workflow_pins.py b/scripts/validate_workflow_pins.py index 0096daf..5bfd5f8 100644 --- a/scripts/validate_workflow_pins.py +++ b/scripts/validate_workflow_pins.py @@ -14,11 +14,11 @@ USES_RE = re.compile(r"^\s*uses:\s*([^@\s]+)@([^\s#]+)(?:\s*#\s*(\S+))?\s*$") REQUIRED_PINS = { - "actions/checkout": ("9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0", "v7.0.0"), + "actions/checkout": ("3d3c42e5aac5ba805825da76410c181273ba90b1", "v7.0.1"), + "actions/upload-artifact": ("043fb46d1a93c77aae656e7c1c64a875d1fc6a0a", "v7.0.1"), "docker/setup-qemu-action": ("96fe6ef7f33517b61c61be40b68a1882f3264fb8", "v4.2.0"), "docker/setup-buildx-action": ("bb05f3f5519dd87d3ba754cc423b652a5edd6d2c", "v4.2.0"), "docker/login-action": ("af1e73f918a031802d376d3c8bbc3fe56130a9b0", "v4.4.0"), - "docker/metadata-action": ("dc802804100637a589fabce1cb79ff13a1411302", "v6.2.0"), "docker/build-push-action": ("53b7df96c91f9c12dcc8a07bcb9ccacbed38856a", "v7.3.0"), "peter-evans/dockerhub-description": ("1b9a80c056b620d92cedb9d9b5a223409c68ddfa", "v5.0.0"), "aquasecurity/trivy-action": ("ed142fd0673e97e23eac54620cfb913e5ce36c25", "v0.36.0"), @@ -61,10 +61,10 @@ def collect_errors() -> list[str]: action for action in ( "actions/checkout", + "actions/upload-artifact", "docker/setup-qemu-action", "docker/setup-buildx-action", "docker/login-action", - "docker/metadata-action", "docker/build-push-action", "peter-evans/dockerhub-description", "aquasecurity/trivy-action", @@ -79,13 +79,17 @@ def collect_errors() -> list[str]: for required_text in ( "workflow_call:", "workflow_dispatch:", + "runner: ubuntu-24.04", + "runner: ubuntu-24.04-arm", "SCOUT_VERSION: 1.23.1", - "SCOUT_SHA256: 0f778f9d833f28bc6cccff95e33039849c0afcecafa38d9f46fe74bfd0915714", + "scout_sha256: 0f778f9d833f28bc6cccff95e33039849c0afcecafa38d9f46fe74bfd0915714", + "scout_sha256: 88eecb7273f19bd18300d70e6f85b2e7d784e9e4f3cbb4a2b400db6b8355a52a", "docker/scout-cli/releases/download/v${SCOUT_VERSION}", 'docker-scout cves "sbom://$SCOUT_SBOM"', "version: v0.72.0", - "previous_image:", - "inputs.previous_image", + "PREVIOUS_IMAGE: coderluii/holycode:1.1.2@sha256:65740c4d8aa416217391f53de4be984ea9f8dfd5f10553dead94db402645b537", + 'ref: ${{ github.sha }}', + "Pull exact candidate digest", "scripts/test_upgrade_rollback.sh", ): if required_text not in protected_text: @@ -93,13 +97,13 @@ def collect_errors() -> list[str]: if protected_text.count("scanners: vuln,secret") != 2: errors.append("protected-validation.yml must run both Trivy gates with vuln and secret scanners") if "trivyignores:" in protected_text: - errors.append("protected-validation.yml must not bypass the fixable-critical gate") + errors.append("protected-validation.yml must not bypass the fixable critical/high gate") if "eceasy/cli-proxy-api" in protected_text: errors.append("protected-validation.yml must not pull the removed CLIProxyAPI sidecar") pr_validation = WORKFLOWS / "pr-validation.yml" pr_text = pr_validation.read_text(encoding="utf-8") - if "renovate@43.263.9 renovate-config-validator --strict renovate.json" not in pr_text: + if "renovate@43.274.0 renovate-config-validator --strict renovate.json" not in pr_text: errors.append("pr-validation.yml must validate renovate.json with the audited Renovate pin") return errors diff --git a/tests/test_release_contract.py b/tests/test_release_contract.py new file mode 100644 index 0000000..cc5c117 --- /dev/null +++ b/tests/test_release_contract.py @@ -0,0 +1,207 @@ +import json +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +class ReleaseContractTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.dockerfile = (ROOT / "Dockerfile").read_text(encoding="utf-8") + cls.entrypoint = (ROOT / "scripts" / "entrypoint.sh").read_text(encoding="utf-8") + cls.smoke = (ROOT / "scripts" / "smoke_image.sh").read_text(encoding="utf-8") + cls.claude_auth = (ROOT / "scripts" / "test_claude_auth.sh").read_text(encoding="utf-8") + cls.publish = (ROOT / ".github" / "workflows" / "docker-publish.yml").read_text(encoding="utf-8") + cls.protected = (ROOT / ".github" / "workflows" / "protected-validation.yml").read_text(encoding="utf-8") + cls.pr_validation = (ROOT / ".github" / "workflows" / "pr-validation.yml").read_text(encoding="utf-8") + cls.readme = (ROOT / "README.md").read_text(encoding="utf-8") + cls.dockerhub = (ROOT / "docs" / "dockerhub-description.md").read_text(encoding="utf-8") + + def test_executable_scripts_use_lf_shebangs(self): + scripts = [ + ROOT / "scripts" / "validate_npm_script_policy.py", + ROOT / "scripts" / "validate_chromium_seccomp.py", + ROOT / "scripts" / "entrypoint.sh", + ROOT / "scripts" / "bootstrap.sh", + ROOT / "scripts" / "smoke_image.sh", + ROOT / "scripts" / "test_claude_auth.sh", + ROOT / "scripts" / "test_plugin_modes.sh", + ROOT / "scripts" / "test_upgrade_rollback.sh", + ] + + for script in scripts: + with self.subTest(script=script.name): + first_line = script.read_bytes().splitlines(keepends=True)[0] + self.assertNotIn(b"\r\n", first_line) + + def test_release_dependency_pins(self): + expected = ( + "ARG S6_OVERLAY_VERSION=3.2.3.2", + "ARG FZF_VERSION=0.74.1", + "ARG OPENCODE_VERSION=1.18.4", + "ARG CLAUDE_CODE_VERSION=2.1.216", + "ARG PAPERCLIP_VERSION=2026.707.0", + "ARG PNPM_VERSION=11.15.1", + "ARG VITE_VERSION=8.1.5", + "ARG PRETTIER_VERSION=3.9.6", + "ARG PRISMA_VERSION=7.9.0", + "ARG LIGHTHOUSE_VERSION=13.4.1", + "ARG WRANGLER_VERSION=4.112.0", + "requests==2.34.2", + "Pillow==12.3.0", + "postgresql-client-17 redis-tools sqlite3", + "matplotlib==3.11.1", + "tqdm==4.69.0", + "fastapi==0.139.2", + "packaging==26.2", + "wheel==0.47.0", + "rich==15.0.0", + ) + for value in expected: + with self.subTest(value=value): + self.assertIn(value, self.dockerfile) + self.assertNotIn("postgresql-client redis-tools sqlite3", self.dockerfile) + + def test_github_cli_is_rebuilt_with_fixed_go_toolchain(self): + self.assertIn( + "FROM golang:1.26.5-trixie@sha256:" + "4ee9ffa999b4583ce281939cdff828763083610292f252279a0cee77473bd9a7 " + "AS github-cli-builder", + self.dockerfile, + ) + self.assertIn("ARG GITHUB_CLI_VERSION=2.96.0", self.dockerfile) + self.assertIn( + "ARG GITHUB_CLI_REF=b300f2ec7ec9dc9addc39b2ad88c54097ded7ca0", + self.dockerfile, + ) + self.assertIn('test "$(git rev-parse HEAD)" = "${GITHUB_CLI_REF}"', self.dockerfile) + self.assertIn('go version -m /out/gh | grep -F "go1.26.5"', self.dockerfile) + self.assertIn("COPY --from=github-cli-builder /out/gh /usr/local/bin/gh", self.dockerfile) + self.assertNotIn("https://cli.github.com/packages", self.dockerfile) + self.assertIn("expected_github_cli", self.smoke) + self.assertIn('test "$(command -v gh)" = "/usr/local/bin/gh"', self.smoke) + self.assertIn('gh version $EXPECTED_GITHUB_CLI', self.smoke) + self.assertIn("! dpkg-query -W gh", self.smoke) + + def test_vulnerable_bundled_tools_are_removed(self): + for value in ( + "HERMES_AGENT_VERSION", + "HERMES_AGENT_REF", + "io.holycode.version.hermes", + "VERCEL_VERSION", + "io.holycode.version.vercel", + '"vercel@', + "concurrently@", + "@lhci/cli@", + "sharp-cli@", + ): + with self.subTest(value=value): + self.assertNotIn(value, self.dockerfile) + + for command in ("vercel", "concurrently", "lhci", "sharp"): + with self.subTest(command=command): + self.assertIn(f"! command -v {command}", self.smoke) + + def test_scanner_remediations_are_pinned(self): + self.assertIn("ARG PIP_VERSION=26.1.2", self.dockerfile) + self.assertIn("ARG PAPERCLIP_UNDICI_VERSION=6.27.0", self.dockerfile) + self.assertIn("python3-pip python3-wheel", self.dockerfile) + self.assertIn("undici@${PAPERCLIP_UNDICI_VERSION}", self.dockerfile) + self.assertIn( + "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + self.dockerfile, + ) + self.assertIn( + 'grep -F ""', + self.smoke, + ) + self.assertIn("npm ls undici --all", self.dockerfile) + self.assertIn("cursor_cloud_api_key_missing", self.smoke) + + def test_hermes_setting_fails_with_preservation_message(self): + self.assertIn('if [ "${ENABLE_HERMES}" = "true" ]; then', self.entrypoint) + self.assertIn("bundled Hermes is temporarily unavailable", self.entrypoint) + self.assertIn("/home/opencode/.hermes is preserved", self.entrypoint) + self.assertNotIn("contents.d/hermes", self.entrypoint) + + def test_chromium_sandbox_is_required(self): + self.assertNotIn("--no-sandbox", self.dockerfile) + + def test_claude_auth_bind_mounts_are_git_bash_safe(self): + self.assertIn("export MSYS_NO_PATHCONV=1", self.claude_auth) + + def test_protected_validation_uses_native_architecture_runners(self): + self.assertIn("runner: ubuntu-24.04", self.protected) + self.assertIn("runner: ubuntu-24.04-arm", self.protected) + self.assertIn("Build and push attested candidate", self.protected) + self.assertIn("Pull exact candidate digest", self.protected) + self.assertIn("chromium-sandbox", self.dockerfile) + self.assertIn("test -u /usr/lib/chromium/chrome-sandbox", self.dockerfile) + + def test_release_workflow_uses_v1_1_2_predecessor(self): + self.assertIn("PREVIOUS_VERSION: v1.1.2", self.protected) + self.assertIn( + "coderluii/holycode:1.1.2@sha256:65740c4d8aa416217391f53de4be984ea9f8dfd5f10553dead94db402645b537", + self.protected, + ) + self.assertIn("Require successful protected validation for this commit", self.publish) + self.assertIn("Download protected candidate digest", self.publish) + self.assertNotIn("docker/build-push-action", self.publish) + + def test_release_workflows_bind_and_promote_the_validated_candidate(self): + self.assertIn('[ "$REQUESTED_REF" = "$GITHUB_SHA" ]', self.protected) + self.assertIn('ref: ${{ github.sha }}', self.protected) + self.assertIn("docker pull --platform", self.protected) + self.assertIn("scout-fixable.sarif", self.protected) + self.assertIn("findings=\"$(jq '[.runs[].results[]?] | length'", self.protected) + self.assertIn("group: docker-release", self.publish) + self.assertIn("cancel-in-progress: false", self.publish) + self.assertIn("Require matching published GitHub release", self.publish) + self.assertIn(".name == $tag", self.publish) + self.assertIn("docker buildx imagetools create", self.publish) + self.assertIn('[ "$expected_digest" = "$CANDIDATE_DIGEST" ]', self.publish) + + def test_chromium_seccomp_migration_is_documented_everywhere(self): + profile_url = ( + "https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/" + "config/chromium-seccomp.json" + ) + for path in (ROOT / "docs" / "translations").glob("README.*.md"): + with self.subTest(path=path.name): + text = path.read_text(encoding="utf-8") + self.assertIn(profile_url, text) + self.assertGreaterEqual(text.count("security_opt:"), 2) + for name, text in (("README", self.readme), ("Docker Hub", self.dockerhub)): + with self.subTest(document=name): + self.assertIn(profile_url, text) + self.assertGreaterEqual(text.count("security_opt:"), 2) + + def test_workflow_dependency_and_security_pins(self): + checkout = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1" + self.assertIn(checkout, self.publish) + self.assertIn(checkout, self.protected) + self.assertIn(checkout, self.pr_validation) + self.assertIn("renovate@43.274.0", self.pr_validation) + self.assertIn("Trivy fixable critical and high gate", self.protected) + self.assertIn("Docker Scout fixable critical and high gate", self.protected) + self.assertIn("severity: CRITICAL,HIGH", self.protected) + + def test_lifecycle_policy_matches_release(self): + policy = json.loads((ROOT / "config" / "npm-global-script-policy.json").read_text(encoding="utf-8")) + self.assertIn("@anthropic-ai/claude-code@2.1.216", policy["allowScripts"]) + self.assertIn("opencode-ai@1.18.4", policy["allowScripts"]) + self.assertIn("prisma@7.9.0", policy["blockedScripts"]) + self.assertIn("@prisma/engines@7.9.0", policy["blockedScripts"]) + self.assertIn("workerd@1.20260714.1", policy["blockedScripts"]) + for decision in ("allowScripts", "blockedScripts"): + for package_id, entry in policy[decision].items(): + with self.subTest(package_id=package_id): + self.assertRegex(entry["integrity"], r"^sha512-") + self.assertTrue(entry["architectures"]) + + +if __name__ == "__main__": + unittest.main()