This is an automated email from the ASF dual-hosted git repository. paulk-asert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 7f4fb8afd4c72bb5da84bb74fc9181c607f4dce8 Author: Paul King <[email protected]> AuthorDate: Sun May 17 15:26:09 2026 +1000 AI readiness: sandboxed reproducers --- .agents/skills/groovy-reproducer/SKILL.md | 22 +++- .../groovy-reproducer/run-reproducer-sandboxed.sh | 143 +++++++++++++++++++++ 2 files changed, 163 insertions(+), 2 deletions(-) diff --git a/.agents/skills/groovy-reproducer/SKILL.md b/.agents/skills/groovy-reproducer/SKILL.md index fb5c235a03..1ebfde651b 100644 --- a/.agents/skills/groovy-reproducer/SKILL.md +++ b/.agents/skills/groovy-reproducer/SKILL.md @@ -404,8 +404,20 @@ For each reproducer: triage of the issue and always goes through this gate. This is a project-wide rule — see [`AGENTS.md`](../../../AGENTS.md#untrusted-input-and-confirmation). - A sandboxed run (container/VM) is the escalation for a - flagged or uneasy case, not a routine requirement. + A sandboxed run is the escalation for a flagged or uneasy + case, not a routine requirement: the *run-sandboxed* choice + invokes [`run-reproducer-sandboxed.sh`](run-reproducer-sandboxed.sh) + `--line {3|4|5} <file>` — a locked-down Docker run against the + pinned latest released Groovy of that line (the community + Docker Official `groovy` image, jdk21 variant; Apache Groovy + publishes no images of its own). There is no `6` line — the + project publishes no 6.x alpha/beta images, so 6.x/`master` + (`6.0.0-SNAPSHOT`) is the separate local-build path (Option B) + and a `fixed-on-master` verdict is **never** claimed from a + released-line sandbox run. It is blast-radius reduction, not a + jail. Record the resolved version + sha256 the script prints + into `verdict.json` (`rev` = the concrete version, never the + moving "latest" label; `safety_review.isolation = sandbox`). 7. **Run with bounded resources.** Timeout (60s default), capture stdout + stderr + exit code + runtime. Record the command verbatim. Run with **`-Dgroovy.grape.enable=false` by @@ -674,6 +686,12 @@ Before recording a verdict: pre-screen the step-6 gate runs over the adapted reproducer; a shipped helper (per the AGENTS token-economy policy), explicitly shallow and not a substitute for the human review. +- [`run-reproducer-sandboxed.sh`](run-reproducer-sandboxed.sh) — + the step-6 *run-sandboxed* escalation: a hardened, ephemeral + Docker run against the pinned latest released Groovy of line + 3/4/5 (community Docker Official `groovy` image, jdk21). + Blast-radius reduction, not a jail; no 6.x line; does not + cover the `master` build (Option B). - `.agents/skills/groovy-triage/SKILL.md` — single-issue caller; AI guardrails over the triage methodology in [`CONTRIBUTING.md`](../../../CONTRIBUTING.md#triaging-issues-and-pull-requests). diff --git a/.agents/skills/groovy-reproducer/run-reproducer-sandboxed.sh b/.agents/skills/groovy-reproducer/run-reproducer-sandboxed.sh new file mode 100755 index 0000000000..6281708d16 --- /dev/null +++ b/.agents/skills/groovy-reproducer/run-reproducer-sandboxed.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# run-reproducer-sandboxed.sh — run ONE script / self-contained @Test +# reproducer inside a locked-down Docker container against a *pinned +# released* Groovy (Option A). Invoked by the step-6 safety gate in +# SKILL.md when the human chooses "run-sandboxed". +# +# This is BLAST-RADIUS REDUCTION, not a jail. Container escape exists +# (shared kernel; the Docker daemon is host-root-equivalent). It sits +# *behind* the pre-screen + human gate, never instead of them. It does +# NOT cover: the current master build (that is Option B — a different +# path), @Grab/network (network is disabled), seccomp/AppArmor beyond +# Docker defaults, rootless Docker, or multi-file/project reproducers. +# +# The `docker run` below IS the documented manual equivalent (per +# AGENTS.md "Helper mechanisms and token economy"): run it by hand if +# you need to adapt it. +# +# Usage: +# run-reproducer-sandboxed.sh --line {3|4|5} <reproducer-file> [--timeout S] +# run-reproducer-sandboxed.sh --image <ref> <reproducer-file> [--timeout S] +# +# --line N latest *released* Groovy of major line N (3|4|5), +# resolved via the pin table below. There is no 6.x +# entry: the community Docker Official images do not +# publish 6.x alpha/beta; testing 6.x / master +# (6.0.0-SNAPSHOT) is the separate local-build path +# (Option B), which this script refuses. +# --image REF explicit image ref/digest, bypassing the table. +# +# Records, on stderr, the concrete image AND its sha256 digest so the +# caller can stamp verdict.json (`rev` = the resolved Groovy version, +# never the moving "latest" label; `safety_review.isolation` = sandbox). +set -euo pipefail + +# --------------------------------------------------------------------------- +# Image pin table. +# +# Source: the Docker Official `groovy` image (https://hub.docker.com/_/groovy), +# built by the community project https://github.com/groovy/docker-groovy. +# Apache Groovy does NOT publish any images under apache/groovy. The +# jdk21 variant is used for all lines (per the docker-groovy +# groovy-N/jdk21/Dockerfile layout). There is deliberately no 6.x line: +# the project does not publish alpha/beta images; 6.x/master is Option B. +# +# Tags are mutable — these pin the *current* latest patch per line; the +# run resolves and records the immutable sha256 (RESOLVED_DIGEST) so the +# verdict is reproducible regardless. Bump these as new patch releases +# publish (a deliberate, reviewed edit — do not auto-track "latest"). +# Override per-run with --image <ref> for a specific patch/JDK/digest. +# --------------------------------------------------------------------------- +RATIFIED_IMAGE_3='groovy:3.0.25-jdk21' +RATIFIED_IMAGE_4='groovy:4.0.32-jdk21' +RATIFIED_IMAGE_5='groovy:5.0.6-jdk21' + +TIMEOUT=60 +LINE='' +IMAGE='' +FILE='' + +die() { echo "run-reproducer-sandboxed: $*" >&2; exit 2; } + +while [[ $# -gt 0 ]]; do + case "$1" in + --line) LINE="${2:?--line needs a value}"; shift 2 ;; + --image) IMAGE="${2:?--image needs a value}"; shift 2 ;; + --timeout) TIMEOUT="${2:?--timeout needs a value}"; shift 2 ;; + -h|--help) awk '/^# run-reproducer-sandboxed\.sh/,/^set -euo pipefail/' "$0" | grep '^#' | sed 's/^# \{0,1\}//'; exit 0 ;; + -*) die "unknown option: $1" ;; + *) FILE="$1"; shift ;; + esac +done + +[[ -n "$FILE" ]] || die "no reproducer file given (see --help)" +[[ -r "$FILE" ]] || die "cannot read reproducer file: $FILE" +command -v docker >/dev/null 2>&1 || die "docker not found on PATH — this is the Option-A sandbox path and requires Docker" + +# Reject master / SNAPSHOT here — that is Option B (mounted fresh build), +# a different path. Never silently substitute (no-silent-fallback rule). +# Reject master / SNAPSHOT / 6.x here — there is no released community +# image for those; that is Option B (mounted fresh build), a different +# path. Never silently substitute (no-silent-fallback rule). +case "${LINE}" in + m|master|snapshot|SNAPSHOT) die "--line '$LINE' is the master build (Option B), not this script. Run the local-build path instead." ;; + 6) die "no community-published release image for Groovy 6.x (no alpha/beta images on hub.docker.com/_/groovy). Test 6.x/master via the local-build path (Option B)." ;; +esac + +if [[ -z "$IMAGE" ]]; then + [[ -n "$LINE" ]] || die "need --line {3|4|5} or --image <ref>" + case "$LINE" in + 3) IMAGE="$RATIFIED_IMAGE_3" ;; + 4) IMAGE="$RATIFIED_IMAGE_4" ;; + 5) IMAGE="$RATIFIED_IMAGE_5" ;; + *) die "--line must be one of 3 4 5 (got '$LINE')" ;; + esac +fi + +# Fail fast if the image does not exist / is unreachable (Docker images +# lag releases; surface that, do not substitute another line). +echo "run-reproducer-sandboxed: pulling ${IMAGE} ..." >&2 +docker pull "$IMAGE" >&2 || die "image not available: ${IMAGE} (Docker publish often lags a release; do not substitute another version — escalate to Option B or an explicit --image)" + +DIGEST="$(docker image inspect --format '{{ if .RepoDigests }}{{ index .RepoDigests 0 }}{{ end }}' "$IMAGE" 2>/dev/null || true)" +echo "RESOLVED_IMAGE=${IMAGE}" >&2 +echo "RESOLVED_DIGEST=${DIGEST:-<none>}" >&2 + +SCRATCH="$(cd "$(dirname "$FILE")" && pwd)" +BASE="$(basename "$FILE")" + +# Hardened, ephemeral. Load-bearing: --network none (no exfil/pull), +# --read-only + tmpfs (no persistence), only the throwaway scratch dir +# mounted rw, no $HOME, no docker socket. The rest are one-flag wins. +set -x +timeout "${TIMEOUT}" docker run --rm \ + --network none \ + --read-only --tmpfs /tmp \ + --env HOME=/tmp \ + --volume "${SCRATCH}:/work:rw" \ + --workdir /work \ + --cap-drop=ALL --security-opt=no-new-privileges \ + --pids-limit=256 --memory=1g --memory-swap=1g --cpus=2 \ + "$IMAGE" \ + groovy "/work/${BASE}" +rc=$? +set +x +exit $rc
