openclaw/scripts/e2e/plugin-binding-command-escape-docker.sh
Vincent Koc ec3a8343c5
fix(ci): keep plugin fixture validation compatible with capability consent (#131208)
* fix(ci): accept plugin fixture capabilities

* fix(ci): detect fixture capability consent support

Probe candidate command help through the existing bounded E2E runners so
positive fixtures accept capabilities only when that command supports it.
Preserve historical packages, probe failures, argv, and child exit status;
keep negative, no-op, and integrity-bound update coverage unapproved.

Replace duplicated probes and source-string checks with executable
compatibility regressions. Synchronize the RPC timeout test with real
process readiness before advancing its unchanged 100 ms deadline, while
retaining real process-group termination and cleanup.

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* fix(ci): run Docker lanes from trusted harness

Resolve package scripts and packaging tools from the trusted harness while
keeping candidate source, artifacts, registry identity, and preflight on
the frozen target. Remove candidate-script lane filtering and the duplicate
live command wrapper; preserve source inputs in source-built Docker lanes.

Prove the public scheduler boundary with distinct harness/target roots and
poisoned candidate scripts. Synchronize scheduler timeout fixtures with real
child readiness, retain real signals and kill grace, and handle empty QR
build arguments on Bash 3.

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* test(ci): retire superseded harness source assertions

Keep the workflow trust and pnpm PATH guards, but remove checks for the
old inline replacement and deleted duplicate live-script wrapper. The
public scheduler regressions now prove those behaviors through distinct
candidate/harness roots and quoted pinned pnpm commands.

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* test(ci): cover multi-build source roots

* fix(ci): select the harness toolchain before Docker lanes

Start prepared pnpm commands in the trusted harness before Corepack resolves
its package-manager pin. Preserve frozen candidate source and artifact
identity, and resolve relative executable and cache paths before changing cwd.

Keep quoted rerun environment values out of executable substitution. Cover
different toolchain pins, shell-sensitive paths, and generated reruns through
the public scheduler boundary.

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-27 21:34:12 -07:00

161 lines
5.1 KiB
Bash

#!/usr/bin/env bash
# Verifies the plugin-owned conversation binding command escape regression in
# Docker. The focused Vitest cases assert that real authorized commands escape,
# while unknown or unauthorized slash text stays with the bound plugin.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SOURCE_ROOT="${OPENCLAW_DOCKER_E2E_REPO_ROOT:-$ROOT_DIR}"
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
IMAGE_NAME="${OPENCLAW_PLUGIN_BINDING_COMMAND_ESCAPE_E2E_IMAGE:-openclaw-plugin-binding-command-escape-e2e}"
CONTAINER_NAME="openclaw-plugin-binding-command-escape-e2e-$$"
DOCKER_RUN_TIMEOUT="${OPENCLAW_PLUGIN_BINDING_COMMAND_ESCAPE_DOCKER_RUN_TIMEOUT:-900s}"
RUN_LOG="$(mktemp -t openclaw-plugin-binding-command-escape-log.XXXXXX)"
FOCUSED_TEST_REGEX="lets authorized gateway-style plugin commands escape plugin-owned bindings|keeps authorized unknown slash text in a plugin-owned binding routed to the bound plugin|keeps unauthorized plugin-owned binding slash replies suppressed while routed to the bound plugin"
cleanup() {
docker_e2e_docker_cmd rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
rm -f "$RUN_LOG"
}
trap cleanup EXIT
docker_e2e_build_or_reuse \
"$IMAGE_NAME" \
plugin-binding-command-escape \
"$ROOT_DIR/scripts/e2e/plugin-binding-command-escape.Dockerfile" \
"$SOURCE_ROOT"
echo "Running plugin binding command escape Docker E2E..."
set +e
DOCKER_COMMAND_TIMEOUT="$DOCKER_RUN_TIMEOUT" docker_e2e_docker_run_cmd run --rm \
--name "$CONTAINER_NAME" \
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
-e "FOCUSED_TEST_REGEX=$FOCUSED_TEST_REGEX" \
-e OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/tmp/openclaw-vitest-cache \
"$IMAGE_NAME" \
bash -lc '
set -euo pipefail
# Main has one aggregate entry; frozen candidates may split binding cases into a second file.
test_files=(src/auto-reply/reply/dispatch-from-config.test.ts)
if [[ -f src/auto-reply/reply/dispatch-from-config.lifecycle.test.ts ]]; then
test_files=(
src/auto-reply/reply/dispatch-from-config.delivery.test.ts
src/auto-reply/reply/dispatch-from-config.lifecycle.test.ts
src/auto-reply/reply/dispatch-from-config.test.ts
)
fi
corepack enable
node scripts/run-vitest.mjs "${test_files[@]}" --reporter=verbose -t "$FOCUSED_TEST_REGEX"
' \
>"$RUN_LOG" 2>&1
status=$?
set -e
if [ "$status" -ne 0 ]; then
echo "Docker plugin binding command escape smoke failed"
docker_e2e_print_log "$RUN_LOG"
exit "$status"
fi
if ! node - "$RUN_LOG" <<'NODE'
const fs = require("node:fs");
const { StringDecoder } = require("node:string_decoder");
const logPath = process.argv[2];
const scanBytes = 65536;
const maxLineChars = 4096;
const stat = fs.statSync(logPath);
const length = Math.min(stat.size, scanBytes);
const buffer = Buffer.alloc(scanBytes);
const fd = fs.openSync(logPath, "r");
const decoder = new StringDecoder("utf8");
let diagnosticTail = "";
let carry = "";
let discardingLongLine = false;
let invalidSummary = false;
let summaryCount = 0;
let totalPassed = 0;
let offset = 0;
function scanLine(line) {
const normalized = line.replace(/\x1B\[[0-?]*[ -/]*[@-~]/gu, "");
const match = normalized.match(/^\s*Tests\s+(\d+) passed\b/u);
if (!match) {
return;
}
const count = Number.parseInt(match[1], 10);
summaryCount += 1;
if (!Number.isSafeInteger(count) || count <= 0 || summaryCount > 2) {
invalidSummary = true;
return;
}
totalPassed += count;
}
function appendLineSegment(segment, ended) {
if (!discardingLongLine) {
if (carry.length + segment.length <= maxLineChars) {
carry += segment;
} else {
carry = "";
discardingLongLine = true;
}
}
if (!ended) {
return;
}
if (!discardingLongLine) {
scanLine(carry.endsWith("\r") ? carry.slice(0, -1) : carry);
}
carry = "";
discardingLongLine = false;
}
function scanText(text) {
let start = 0;
for (let newline = text.indexOf("\n"); newline !== -1; newline = text.indexOf("\n", start)) {
appendLineSegment(text.slice(start, newline), true);
start = newline + 1;
}
appendLineSegment(text.slice(start), false);
}
function isInvalidProof() {
return invalidSummary || summaryCount < 1 || summaryCount > 2 || totalPassed !== 3;
}
try {
while (true) {
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, offset);
if (bytesRead === 0) {
break;
}
offset += bytesRead;
scanText(decoder.write(buffer.subarray(0, bytesRead)));
}
scanText(decoder.end());
appendLineSegment("", true);
if (isInvalidProof() && length > 0) {
const bytesRead = fs.readSync(fd, buffer, 0, length, stat.size - length);
diagnosticTail = buffer.subarray(0, bytesRead).toString("utf8");
}
} finally {
fs.closeSync(fd);
}
if (isInvalidProof()) {
console.error("expected focused Vitest summary for exactly 3 passed tests");
console.error(
`saw ${summaryCount} summaries totaling ${totalPassed}; expected one aggregate or two split summaries`,
);
console.error(diagnosticTail);
process.exit(1);
}
NODE
then
echo "Docker plugin binding command escape smoke did not stay focused"
docker_e2e_print_log "$RUN_LOG"
exit 1
fi
echo "OK (3 focused tests)"