Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ jobs:

# The runner worker retains an Actions runtime token in an ancestor
# environment even after shell variables are unset. Execute all
# pull-request-controlled tests in a separate PID namespace with a
# pull-request-controlled tests in Docker's default private PID namespace with a
# read-only trusted tree and no host Docker socket. The image is
# pinned to the reviewed linux/amd64 manifest digest.
if [ "${OPENCODE_COVERAGE_SANDBOXED:-0}" != "1" ]; then
Expand All @@ -544,7 +544,6 @@ jobs:
sandbox_status=0
docker run --rm --init \
--name "opencode-coverage-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
--pid private \
--pids-limit 2048 \
--memory 14g \
--cpus 4 \
Expand Down
22 changes: 17 additions & 5 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -504,12 +504,24 @@ jobs:
exit 1
;;
esac
# pip can preserve an existing console-script mode instead of applying
# the process umask, so normalize the resolved artifact itself before
# pinning its digest. The runtime gate still fails closed if anything
# relaxes these bits after this trusted installation step.
chmod go-w -- "$strix_executable"
strix_scripts_root="$(python3 -c 'import sysconfig; print(sysconfig.get_path("scripts"))')"
if [ -z "$strix_scripts_root" ] || [[ "$strix_scripts_root" != /* ]] \
|| [ ! -d "$strix_scripts_root" ] || [ -L "$strix_scripts_root" ]; then
echo "::error::Pinned Strix installation did not produce a trusted absolute scripts root."
exit 1
fi
case "$strix_executable" in
"$strix_scripts_root"/*) ;;
*)
echo "::error::Pinned Strix executable is outside the trusted scripts root."
exit 1
;;
esac
# pip and the hosted tool cache can preserve collaborative write bits
# even after a private install umask. Normalize both the containing
# scripts root and resolved console script before pinning their
# identity; the runtime gate still fails closed on later relaxation.
chmod go-w -- "$strix_scripts_root" "$strix_executable"
strix_executable_sha256="$(python3 - "$strix_executable" <<'PY'
import hashlib
from pathlib import Path
Expand Down
6 changes: 3 additions & 3 deletions requirements-strix-ci-hashes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1166,9 +1166,9 @@ markupsafe==3.0.3 \
--hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
# via jinja2
mcp==1.28.0 \
--hash=sha256:559d3f9943674cafbe5744c5d3794f3237e8b47f9bbc58e20c0fad680d8487c2 \
--hash=sha256:9c1e7cf3a9125557e418ecd4fed8e9adddce81b0dfdae4d6601d700f5beb71a4
mcp==1.28.1 \
--hash=sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df \
--hash=sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683
# via openai-agents
mdit-py-plugins==0.6.1 \
--hash=sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d \
Expand Down
55 changes: 45 additions & 10 deletions scripts/ci/opencode_review_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ def load_event(path: Path) -> Mapping[str, object]:
try:
event = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
print(f"::error::Could not read GitHub event payload for OpenCode review context: {exc}", file=sys.stderr)
print(
f"::error::Could not read GitHub event payload for OpenCode review context: {exc}",
file=sys.stderr,
)
raise SystemExit(1) from exc
if not isinstance(event, dict):
print("::error::GitHub event payload for OpenCode review context was not a JSON object.", file=sys.stderr)
print(
"::error::GitHub event payload for OpenCode review context was not a JSON object.",
file=sys.stderr,
)
raise SystemExit(1)
return event

Expand All @@ -39,25 +45,50 @@ def object_value(value: object) -> Mapping[str, object]:
return value if isinstance(value, dict) else {}


def resolve_context(event: Mapping[str, object], default_repository: str) -> dict[str, str]:
def resolve_context(
event: Mapping[str, object], default_repository: str
) -> dict[str, str]:
"""Resolve and validate the OpenCode review context values."""
inputs = object_value(event.get("inputs"))
client_payload = object_value(event.get("client_payload"))
pull_request = object_value(event.get("pull_request"))
base = object_value(pull_request.get("base"))
head = object_value(pull_request.get("head"))
base_repo = object_value(base.get("repo"))
values = {
"GH_REPOSITORY": str(
base_repo.get("full_name") or inputs.get("target_repository") or default_repository or ""
base_repo.get("full_name")
or inputs.get("target_repository")
or client_payload.get("target_repository")
or default_repository
or ""
).strip(),
"PR_NUMBER": str(
pull_request.get("number")
or inputs.get("pr_number")
or client_payload.get("pr_number")
or ""
).strip(),
"PR_BASE_SHA": str(
base.get("sha")
or inputs.get("pr_base_sha")
or client_payload.get("pr_base_sha")
or ""
).strip(),
"PR_HEAD_SHA": str(
head.get("sha")
or inputs.get("pr_head_sha")
or client_payload.get("pr_head_sha")
or ""
).strip(),
"PR_NUMBER": str(pull_request.get("number") or inputs.get("pr_number") or "").strip(),
"PR_BASE_SHA": str(base.get("sha") or inputs.get("pr_base_sha") or "").strip(),
"PR_HEAD_SHA": str(head.get("sha") or inputs.get("pr_head_sha") or "").strip(),
}
values["HEAD_SHA"] = values["PR_HEAD_SHA"]
for name, pattern in CONTEXT_VALIDATORS.items():
if not pattern.fullmatch(values[name]):
print(f"::error::Invalid OpenCode review context value for {name}.", file=sys.stderr)
print(
f"::error::Invalid OpenCode review context value for {name}.",
file=sys.stderr,
)
raise SystemExit(1)
# Free-text PR metadata for the review-language signal. It is arbitrary
# author text, so it is not pattern-validated; it stays shell-safe because
Expand All @@ -73,7 +104,9 @@ def resolve_context(event: Mapping[str, object], default_repository: str) -> dic
def write_shell_exports(path: Path, values: Mapping[str, str]) -> None:
"""Write validated values as shell export statements."""
path.write_text(
"".join(f"export {name}={shlex.quote(value)}\n" for name, value in values.items()),
"".join(
f"export {name}={shlex.quote(value)}\n" for name, value in values.items()
),
encoding="utf-8",
)

Expand All @@ -83,7 +116,9 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--event-path", required=True, type=Path)
parser.add_argument("--env-file", required=True, type=Path)
parser.add_argument("--default-repository", default=os.environ.get("GITHUB_REPOSITORY", ""))
parser.add_argument(
"--default-repository", default=os.environ.get("GITHUB_REPOSITORY", "")
)
return parser.parse_args(argv)


Expand Down
13 changes: 11 additions & 2 deletions scripts/ci/strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,16 @@ pull_request_metadata_env_present() {

pull_request_head_blob_required() {
[ "${GITHUB_EVENT_NAME:-}" = "pull_request_target" ] ||
{ [ "${GITHUB_EVENT_NAME:-}" = "workflow_dispatch" ] && pull_request_metadata_env_present; }
{
case "${GITHUB_EVENT_NAME:-}" in
workflow_dispatch | repository_dispatch)
pull_request_metadata_env_present
;;
*)
return 1
;;
esac
}
}

is_valid_git_commit_sha() {
Expand Down Expand Up @@ -794,7 +803,7 @@ is_pull_request_event() {
pull_request | pull_request_target)
github_event_payload_has_pull_request
;;
workflow_dispatch)
workflow_dispatch | repository_dispatch)
pull_request_metadata_env_present
;;
*)
Expand Down
67 changes: 65 additions & 2 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'STRIX_EXECUTABLE_SHA256=%s' "Strix workflow pins the installed executable digest before scanning"
assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'STRIX_EXECUTABLE_ROOT=%s' "Strix workflow pins the installed executable root before scanning"
assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'umask 022' "Strix workflow creates the credential-bearing executable without group/world write access"
assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'chmod go-w -- "$strix_executable"' "Strix workflow normalizes the resolved executable before hashing"
assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'chmod go-w -- "$strix_scripts_root" "$strix_executable"' "Strix workflow normalizes the installation root and resolved executable before hashing"
assert_file_contains "$GATE_SCRIPT" 'STRIX_EXECUTABLE_PATH must name the trusted installed Strix executable' "Strix gate requires an explicit trusted executable path"
assert_file_contains "$GATE_SCRIPT" 'did not match the pinned SHA-256 digest' "Strix gate rejects executable substitution after trusted installation"
assert_file_contains "$GATE_SCRIPT" 'STRIX_EXECUTABLE_PATH must be outside the untrusted scan target' "Strix executable cannot come from the scan target"
Expand Down Expand Up @@ -5359,6 +5359,23 @@ PY
STRIX_EXECUTABLE_SHA256="0000000000000000000000000000000000000000000000000000000000000000"
)
fi
if [ "$scenario" = "pr-executable-root-group-writable" ]; then
local fake_strix_sha256
fake_strix_sha256="$(python3 - "$fake_strix" <<'PY'
import hashlib
from pathlib import Path
import sys

print(hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest())
PY
)"
env_cmd+=(
IS_PR_EVIDENCE_RUN="true"
STRIX_EXECUTABLE_ROOT="$bin_dir"
STRIX_EXECUTABLE_SHA256="$fake_strix_sha256"
)
chmod 0775 "$bin_dir"
fi
if [ "$scenario" = "pr-executable-group-writable" ]; then
chmod 0775 "$fake_strix"
fi
Expand Down Expand Up @@ -5653,6 +5670,16 @@ run_filtered_gate_case_if_requested() {
"" \
""
;;
pr-executable-root-group-writable)
run_gate_case "pr-executable-root-group-writable" \
"vertex_ai/ready-primary" \
"" \
"1" \
"pinned Strix installation root must not be group/world writable" \
"0" \
"" \
""
;;
vertex-primary-hallucinated-endpoint-fallback-success)
run_gate_case "vertex-primary-hallucinated-endpoint-fallback-success" \
"vertex_ai/hallucination-primary" \
Expand Down Expand Up @@ -6029,6 +6056,19 @@ run_filtered_gate_case_if_requested() {
"1" \
"Container build manifest changed; materialized full PR-head blob scope"
;;
repository-dispatch-pr-scope-uses-head-blob)
run_pull_request_target_head_scope_case \
"repository-dispatch-pr-scope-uses-head-blob" \
"backend/db/models.py" \
"BASE_DISPATCH_CONTENT_SHOULD_NOT_BE_SCANNED" \
"HEAD_DISPATCH_CONTENT_SHOULD_BE_SCANNED" \
"0" \
"0" \
"__PR_SCOPE__" \
"0" \
"Materialized PR-head changed-file scope" \
"repository_dispatch"
;;
*)
record_failure "unknown STRIX_TEST_CASE_FILTER '${STRIX_TEST_CASE_FILTER:-}'"
;;
Expand All @@ -6052,6 +6092,7 @@ run_pull_request_target_head_scope_case() {
local target_path="${7-.}"
local expected_full_head_scope="${8-$disable_pr_scoping}"
local expected_scope_message="${9-}"
local github_event_name="${10-pull_request_target}"

local tmp_dir
tmp_dir="$(mktemp -d)"
Expand Down Expand Up @@ -6170,7 +6211,8 @@ EOF
PATH="$bin_dir:$PATH" \
STRIX_EXECUTABLE_PATH="$bin_dir/strix" \
STRIX_INPUT_FILE_ROOT="$tmp_dir" \
GITHUB_EVENT_NAME="pull_request_target" \
GITHUB_EVENT_NAME="$github_event_name" \
PR_NUMBER="123" \
PR_BASE_SHA="$base_sha" \
PR_HEAD_SHA="$head_sha" \
STRIX_TEST_CHANGED_FILES_OVERRIDE="$changed_file" \
Expand Down Expand Up @@ -8585,6 +8627,18 @@ run_pull_request_target_head_scope_case \
"0" \
"__PR_SCOPE__"

run_pull_request_target_head_scope_case \
"repository-dispatch-pr-scope-uses-head-blob" \
"backend/db/models.py" \
"BASE_DISPATCH_CONTENT_SHOULD_NOT_BE_SCANNED" \
"HEAD_DISPATCH_CONTENT_SHOULD_BE_SCANNED" \
"0" \
"0" \
"__PR_SCOPE__" \
"0" \
"Materialized PR-head changed-file scope" \
"repository_dispatch"

run_pull_request_target_head_scope_case \
"pull-request-target-added-file-uses-head-blob" \
"src/new_module.py" \
Expand Down Expand Up @@ -8776,6 +8830,15 @@ run_gate_case "pr-executable-group-writable" \
"" \
""

run_gate_case "pr-executable-root-group-writable" \
"vertex_ai/ready-primary" \
"" \
"1" \
"pinned Strix installation root must not be group/world writable" \
"0" \
"" \
""

run_gate_case "runtime-env-forwarding" \
"gemini/gemini-pro-3.1-preview" \
"" \
Expand Down
36 changes: 23 additions & 13 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,27 +321,40 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch():
assert "ACTIONS_RUNTIME_TOKEN GH_TOKEN GITHUB_TOKEN" in measure_step
assert "secrets." not in measure_step
assert "COVERAGE_SOURCE_WORKDIR: ${{ runner.temp }}/pr-head" in workflow
assert "python3 -I - \"$COVERAGE_SOURCE_ARCHIVE\" \"$COVERAGE_SOURCE_WORKDIR\"" in workflow
assert (
'python3 -I - "$COVERAGE_SOURCE_ARCHIVE" "$COVERAGE_SOURCE_WORKDIR"' in workflow
)
assert "member.isfile() or member.isdir()" in workflow
assert 'bundle.extractall(destination, members=members, filter="data")' in workflow
assert 'tar -xf "$COVERAGE_SOURCE_ARCHIVE"' not in workflow
assert "docker.io/library/ubuntu@sha256:" in measure_step
assert "apt-get install --no-install-recommends -y" in measure_step
assert "--require-hashes" in measure_step
assert "--cap-drop ALL" in measure_step
assert "--pid private" in measure_step
# Docker already creates a private PID namespace by default. Passing the
# unsupported literal `private` makes hosted-runner Docker exit 125 before
# any coverage evidence can run.
assert "--pid private" not in measure_step
assert "--pid host" not in measure_step
assert "Docker's default private PID namespace" in measure_step
assert 'measure_step_script="$(realpath "$0")"' in measure_step
assert 'source=${measure_step_script},target=/trusted-measure-step.sh,readonly' in measure_step
assert (
"source=${measure_step_script},target=/trusted-measure-step.sh,readonly"
in measure_step
)
assert "target=/trusted,readonly" in measure_step
assert "target=/work" in measure_step
assert "/var/run/docker.sock" not in measure_step
assert "OPENCODE_SANDBOX_UID=65532" in measure_step
assert 'chown -R --no-dereference' in measure_step
assert "chown -R --no-dereference" in measure_step
assert 'setpriv \\\n --reuid "$OPENCODE_SANDBOX_UID"' in measure_step
assert 'pkill -KILL -u "$OPENCODE_SANDBOX_UID"' in measure_step
assert 'python3 -I - "$1"' in measure_step
assert "python3 -I -c 'import pytest_cov'" in measure_step
assert 'python3 -I "$GITHUB_WORKSPACE/scripts/ci/sanitize_github_output_summary.py"' in measure_step
assert (
'python3 -I "$GITHUB_WORKSPACE/scripts/ci/sanitize_github_output_summary.py"'
in measure_step
)
assert "CARGO_HOME=/work/.opencode-sandbox-home/.cargo" in measure_step
assert 'PATH="/work/.opencode-sandbox-home/.cargo/bin:${PATH}"' in measure_step
assert "cargo llvm-cov --version" not in measure_step
Expand Down Expand Up @@ -1577,7 +1590,9 @@ def test_opencode_privileged_review_security_boundaries_are_fail_closed():
coverage_end = workflow.index("\n opencode-review-target:", coverage_start)
coverage_job = workflow[coverage_start:coverage_end]
syntax_step = coverage_job.index(" - name: Enforce changed-file syntax gate\n")
measure_step = coverage_job.index(" - name: Measure test and docstring evidence\n")
measure_step = coverage_job.index(
" - name: Measure test and docstring evidence\n"
)
measure = coverage_job[measure_step:]
target_start = coverage_end + 1
target_job = workflow[target_start:]
Expand Down Expand Up @@ -1814,9 +1829,7 @@ def test_opencode_approve_review_publication_failure_fails_closed():

assert "APPROVE_PUBLICATION_FAILED" in workflow
assert "APPROVE_PUBLICATION_SKIPPED" not in workflow
assert (
"OpenCode approve review publication failed for head" in workflow
)
assert "OpenCode approve review publication failed for head" in workflow
assert (
"skipping non-authoritative overview comment mutation so the required approval check can finish promptly"
in workflow
Expand All @@ -1840,10 +1853,7 @@ def test_opencode_approve_review_publication_failure_fails_closed():
assert "the pull request advanced from event head" in workflow
assert "This pull request has been updated since you started reviewing" in workflow
assert "Central fast approval published APPROVE review" in workflow
assert (
"an unpublished approval cannot satisfy review governance"
in workflow
)
assert "an unpublished approval cannot satisfy review governance" in workflow
assert re.search(
r'if \[ "\$event" = "APPROVE" \]; then[\s\S]{0,1600}return 1',
workflow,
Expand Down
Loading
Loading