diff --git a/.githooks/pre-push b/.githooks/pre-push index 0b8393fbf..0e470e426 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -15,6 +15,7 @@ REMOTE_URL="${2:-}" NULL_SHA="0000000000000000000000000000000000000000" WARNINGS=() +REQUIRES_PREFLIGHT=0 warn() { WARNINGS+=("$1") @@ -127,8 +128,27 @@ while read -r local_ref local_sha remote_ref remote_sha; do if [ -n "$maintainer_changes" ]; then warn "Push changes maintainer-owned infrastructure paths; confirm reviewer coverage." fi + + if git diff --quiet "$review_range" -- \ + '.github/workflows/**' \ + '.github/scripts/**' \ + '.githooks/**' \ + 'Dockerfile' \ + 'Dockerfile.*'; then + : + else + REQUIRES_PREFLIGHT=1 + fi done +if [ "$REQUIRES_PREFLIGHT" -eq 1 ]; then + echo "Running required pre-push security gate for workflow, hook, script, or container changes." >&2 + if ! .github/scripts/preflight-safety-checks.sh --require-tools; then + echo "[FAIL] Pre-push security gate failed." >&2 + exit 1 + fi +fi + if [ "${#WARNINGS[@]}" -eq 0 ]; then exit 0 fi diff --git a/.github/instructions/workflow-governance.instructions.md b/.github/instructions/workflow-governance.instructions.md index 9990f5d6e..b7fa733f7 100644 --- a/.github/instructions/workflow-governance.instructions.md +++ b/.github/instructions/workflow-governance.instructions.md @@ -39,6 +39,10 @@ run: | source .github/scripts/sanitize-sed.sh ``` +The blocking pre-flight and risk gates enforce the first three lines of this +prologue in order for every new or modified Bash `run:` block. The sanitizer +remains mandatory before writing to `GITHUB_STEP_SUMMARY` or `GITHUB_OUTPUT`. + PowerShell-only workflows and jobs do not need `BASH_ENV`. Use an explicit PowerShell shell on each step or `defaults.run.shell` at job/workflow scope. diff --git a/.github/scripts/check-workflow-bash-prologue.py b/.github/scripts/check-workflow-bash-prologue.py new file mode 100644 index 000000000..0f8b55841 --- /dev/null +++ b/.github/scripts/check-workflow-bash-prologue.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +############################################################### +# +# Copyright (c) 2026 International Color Consortium. +# All rights reserved. +# https://color.org +# +# Intent: Enforce the required Bash workflow security prologue. +# +############################################################### +"""Reject changed Bash workflow steps that omit credential and token hardening.""" + +import argparse +import subprocess +import sys + +import yaml +from yaml.nodes import MappingNode, SequenceNode + + +REQUIRED_PROLOGUE = ( + "set -euo pipefail", + 'git config --global credential.helper ""', + "unset GITHUB_TOKEN || true", +) + + +def as_mapping(value): + return value if isinstance(value, dict) else {} + + +def configured_shell(workflow, job, step): + for owner in (step, job, workflow): + shell = owner.get("shell") + if shell: + return str(shell) + defaults = as_mapping(owner.get("defaults")) + run_defaults = as_mapping(defaults.get("run")) + shell = run_defaults.get("shell") + if shell: + return str(shell) + return "" + + +def is_bash_step(workflow, job, step): + shell = configured_shell(workflow, job, step).lower() + if shell: + return "bash" in shell + + runner = str(job.get("runs-on", "")).lower() + return "windows" not in runner + + +def executable_lines(run): + return [ + (number, line.strip()) + for number, line in enumerate(str(run).splitlines(), start=1) + if line.strip() and not line.lstrip().startswith("#") + ] + + +def prologue_issues(run): + lines = executable_lines(run) + issues = [] + + for index, required in enumerate(REQUIRED_PROLOGUE): + if index >= len(lines) or lines[index][1] != required: + found = lines[index][1] if index < len(lines) else "end of run block" + issues.append(f"expected `{required}` before `{found}`") + + return issues + + +def changed_lines(path, base_ref): + commands = ( + ("git", "diff", "--unified=0", f"{base_ref}...HEAD", "--", path), + ("git", "diff", "--cached", "--unified=0", "--", path), + ("git", "diff", "--unified=0", "--", path), + ) + lines = set() + + for command in commands: + result = subprocess.run(command, check=False, capture_output=True, text=True) + for line in result.stdout.splitlines(): + if not line.startswith("@@"): + continue + marker = line.split("+", 1)[1].split(" ", 1)[0] + start, separator, count = marker.partition(",") + line_start = int(start) + line_count = int(count) if separator else 1 + if line_count: + lines.update(range(line_start, line_start + line_count)) + else: + lines.add(line_start) + + if lines: + return lines + + tracked = subprocess.run( + ("git", "ls-files", "--error-unmatch", "--", path), + check=False, + capture_output=True, + text=True, + ) + if tracked.returncode: + with open(path, "r", encoding="utf-8") as handle: + return set(range(1, len(handle.readlines()) + 1)) + return lines + + +def mapping_value(node, key): + if not isinstance(node, MappingNode): + return None + for key_node, value_node in node.value: + if key_node.value == key: + return value_node + return None + + +def step_line_ranges(path): + with open(path, "r", encoding="utf-8") as handle: + root = yaml.compose(handle) + jobs = mapping_value(root, "jobs") + if not isinstance(jobs, MappingNode): + return {} + + ranges = {} + for job_name, job_node in jobs.value: + steps = mapping_value(job_node, "steps") + if not isinstance(steps, SequenceNode): + continue + for index, step in enumerate(steps.value, start=1): + ranges[(str(job_name.value), index)] = ( + step.start_mark.line + 1, + step.end_mark.line + 1, + ) + return ranges + + +def check_workflow(path, workflow, changed=None, ranges=None): + findings = [] + jobs = as_mapping(workflow.get("jobs")) + + for job_name, job_value in jobs.items(): + job = as_mapping(job_value) + steps = job.get("steps", []) + if not isinstance(steps, list): + continue + + for index, step_value in enumerate(steps, start=1): + step = as_mapping(step_value) + if "run" not in step or not is_bash_step(workflow, job, step): + continue + if changed is not None: + start, end = ranges.get((str(job_name), index), (0, 0)) + if not any(start <= line <= end for line in changed): + continue + + step_name = str(step.get("name", f"step {index}")) + for issue in prologue_issues(step["run"]): + findings.append(f"{path}: job {job_name}, step {index} ({step_name}): {issue}") + + return findings + + +def run_self_test(): + valid = { + "jobs": { + "check": { + "runs-on": "ubuntu-latest", + "steps": [{"run": "\n".join(REQUIRED_PROLOGUE)}], + } + } + } + cases = ( + ( + "missing-credential-helper", + "set -euo pipefail\nunset GITHUB_TOKEN || true", + ), + ( + "missing-token-unset", + 'set -euo pipefail\ngit config --global credential.helper ""', + ), + ( + "out-of-order-prologue", + 'git config --global credential.helper ""\nset -euo pipefail\nunset GITHUB_TOKEN || true', + ), + ) + + if check_workflow("valid.yml", valid): + raise AssertionError("valid fixture reported a finding") + + for name, run in cases: + invalid = { + "jobs": { + "check": { + "runs-on": "ubuntu-latest", + "steps": [{"run": run}], + } + } + } + if not check_workflow(f"{name}.yml", invalid): + raise AssertionError(f"{name} fixture did not report a finding") + + print("[OK] Bash prologue checker fixtures: 4") + + +def main(arguments): + parser = argparse.ArgumentParser() + parser.add_argument("--self-test", action="store_true") + parser.add_argument("--changed", action="store_true") + parser.add_argument("--base", default="HEAD") + parser.add_argument("workflows", nargs="*") + options = parser.parse_args(arguments) + + if options.self_test: + if options.changed or options.workflows: + parser.error("--self-test cannot be combined with workflow paths") + run_self_test() + return 0 + + if not options.workflows: + parser.error("at least one workflow path is required") + + findings = [] + for path in options.workflows: + try: + with open(path, "r", encoding="utf-8") as handle: + workflow = yaml.safe_load(handle) or {} + except (OSError, UnicodeDecodeError, yaml.YAMLError) as error: + findings.append(f"{path}: unable to parse workflow: {error}") + continue + + if not isinstance(workflow, dict): + findings.append(f"{path}: workflow root must be a mapping") + continue + changed = changed_lines(path, options.base) if options.changed else None + ranges = step_line_ranges(path) if options.changed else None + findings.extend(check_workflow(path, workflow, changed, ranges)) + + for finding in findings: + print(f"[FAIL] {finding}", file=sys.stderr) + return 1 if findings else 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/.github/scripts/preflight-safety-checks.sh b/.github/scripts/preflight-safety-checks.sh index 44e1f2107..6962bb47d 100755 --- a/.github/scripts/preflight-safety-checks.sh +++ b/.github/scripts/preflight-safety-checks.sh @@ -96,6 +96,13 @@ run_workflow_cache_policy() { .github/scripts/check-workflow-cache-policy.sh .github/workflows } +run_workflow_bash_prologue_policy() { + python3 .github/scripts/check-workflow-bash-prologue.py \ + --changed \ + --base "$base_ref" \ + "${workflow_files[@]}" +} + workflow_trigger_names() { local wf="$1" python3 - "$wf" <<'PY' @@ -997,6 +1004,8 @@ for path in sys.argv[1:]: print(f"[OK] {path}") PY + run_check "workflow Bash prologue policy" run_workflow_bash_prologue_policy + if command -v actionlint >/dev/null 2>&1; then run_check "actionlint" actionlint -no-color "${workflow_files[@]}" else @@ -1029,6 +1038,13 @@ else echo "" fi +if [ -f .github/scripts/check-workflow-bash-prologue.py ]; then + run_check "workflow Bash prologue checker fixtures" \ + python3 .github/scripts/check-workflow-bash-prologue.py --self-test +else + skip_or_fail "check-workflow-bash-prologue.py" +fi + if [ "${#script_files[@]}" -gt 0 ]; then if command -v shellcheck >/dev/null 2>&1; then run_check "shellcheck" shellcheck "${script_files[@]}" diff --git a/.github/workflows/ci-afl-smoke.yml b/.github/workflows/ci-afl-smoke.yml index cf21b5659..454598dd4 100644 --- a/.github/workflows/ci-afl-smoke.yml +++ b/.github/workflows/ci-afl-smoke.yml @@ -179,9 +179,9 @@ jobs: TARGET_SHA: ${{ inputs.target_sha }} run: | set -euo pipefail - source "$TRUSTED_SCRIPTS/sanitize-sed.sh" git config --global credential.helper "" unset GITHUB_TOKEN || true + source "$TRUSTED_SCRIPTS/sanitize-sed.sh" case "$TARGET_SHA" in *[!0-9a-fA-F]*) echo "ERROR: invalid target SHA: $(sanitize_line "$TARGET_SHA")" >&2 @@ -214,9 +214,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" source "$TRUSTED_SCRIPTS/sanitize-sed.sh" echo "AFL targets: $(sanitize_line "$ICCDEV_AFL_TARGETS_INPUT")" echo "AFL seconds: $(sanitize_line "$ICCDEV_AFL_SECONDS_INPUT")" diff --git a/.github/workflows/ci-cfl-smoke.yml b/.github/workflows/ci-cfl-smoke.yml index 667bb4be3..d86849642 100644 --- a/.github/workflows/ci-cfl-smoke.yml +++ b/.github/workflows/ci-cfl-smoke.yml @@ -148,9 +148,9 @@ jobs: TARGET_SHA: ${{ inputs.target_sha }} run: | set -euo pipefail - source "$TRUSTED_SCRIPTS/sanitize-sed.sh" git config --global credential.helper "" unset GITHUB_TOKEN || true + source "$TRUSTED_SCRIPTS/sanitize-sed.sh" case "$TARGET_SHA" in *[!0-9a-fA-F]*) echo "ERROR: invalid target SHA: $(sanitize_line "$TARGET_SHA")" >&2 diff --git a/.github/workflows/ci-docker-pr.yml b/.github/workflows/ci-docker-pr.yml index c82cc7039..dca1c4990 100644 --- a/.github/workflows/ci-docker-pr.yml +++ b/.github/workflows/ci-docker-pr.yml @@ -57,9 +57,9 @@ jobs: TARGET_SHA: ${{ inputs.target_sha }} run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" case "$TARGET_SHA" in *[!0-9a-fA-F]*) @@ -113,9 +113,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" case "$TARGET_SHA" in *[!0-9a-fA-F]*) @@ -161,6 +161,8 @@ jobs: docker run --rm "$TEST_IMAGE" bash -lc ' set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true command -v git gh clang clang++ gcc g++ cmake make realpath llvm-symbolizer command -v afl-fuzz afl-showmap iccdev-fuzz-env command -v jq curl shellcheck llvm-cov llvm-profdata file @@ -216,6 +218,8 @@ jobs: -e "WARNING_POLICY=${WARNING_POLICY}" \ "$TEST_IMAGE" bash -lc ' set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true work="$(mktemp -d)" source_dir="$work/iccDEV" mkdir -p "$source_dir" diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 2d27bf03f..2f5123eb2 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -388,9 +388,9 @@ jobs: IMAGE_REF: ${{ steps.image.outputs.name }}@${{ steps.build-push.outputs.digest }} run: | set -euo pipefail - source .github/scripts/sanitize-sed.sh git config --global credential.helper "" unset GITHUB_TOKEN || true + source .github/scripts/sanitize-sed.sh if [ "$PUBLISH_IMAGE" = "true" ]; then TEST_IMAGE="${IMAGE_REF}" docker pull "${IMAGE_REF}" @@ -486,9 +486,9 @@ jobs: IMAGE_REF: ${{ steps.image.outputs.name }}@${{ steps.build-push.outputs.digest }} run: | set -euo pipefail - source .github/scripts/sanitize-sed.sh git config --global credential.helper "" unset GITHUB_TOKEN || true + source .github/scripts/sanitize-sed.sh if [ "$PUBLISH_IMAGE" = "true" ]; then TEST_IMAGE="${IMAGE_REF}" docker pull "${IMAGE_REF}" @@ -524,9 +524,9 @@ jobs: IMAGE_REF: ${{ steps.image.outputs.name }}@${{ steps.build-push.outputs.digest }} run: | set -euo pipefail - source .github/scripts/sanitize-sed.sh git config --global credential.helper "" unset GITHUB_TOKEN || true + source .github/scripts/sanitize-sed.sh if [ "$PUBLISH_IMAGE" = "true" ]; then TEST_IMAGE="${IMAGE_REF}" docker pull "${IMAGE_REF}" @@ -586,6 +586,8 @@ jobs: docker run --rm "$TEST_IMAGE" bash -lc ' set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true command -v git gh clang clang++ gcc g++ cmake make realpath afl-fuzz afl-showmap llvm-symbolizer command -v clang-tidy cppcheck scan-build valgrind gdb gdbserver lldb lld ld.lld lcov gcovr jq curl shellcheck command -v llvm-cov llvm-profdata file strace @@ -643,6 +645,8 @@ jobs: docker run --rm "$TEST_IMAGE" bash -lc ' set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true test "$PWD" = /workspace/iccDEV test -d .git test -f IccProfLib/IccProfile.cpp @@ -912,6 +916,7 @@ jobs: run: | set -euo pipefail git config --global credential.helper "" + unset GITHUB_TOKEN || true source .github/scripts/sanitize-sed.sh ORG="InternationalColorConsortium" diff --git a/.github/workflows/ci-iccdev-tool-tests.yml b/.github/workflows/ci-iccdev-tool-tests.yml index b6ad2a958..34b8d921e 100644 --- a/.github/workflows/ci-iccdev-tool-tests.yml +++ b/.github/workflows/ci-iccdev-tool-tests.yml @@ -271,9 +271,9 @@ jobs: WORKFLOW_SHA: ${{ inputs.target_sha || github.event.pull_request.head.sha || github.sha }} run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" echo "Ref: ${WORKFLOW_REF}" echo "Commit: ${WORKFLOW_SHA}" actual_sha="$(git rev-parse HEAD)" @@ -956,9 +956,9 @@ jobs: DEBIAN_FRONTEND: noninteractive run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" bash .github/scripts/ci-safe-apt-update.sh sudo apt-get install -y --no-install-recommends \ build-essential cmake \ diff --git a/.github/workflows/ci-issue-1948-segmented-curve-repro.yml b/.github/workflows/ci-issue-1948-segmented-curve-repro.yml index 4c561396e..6e90784e4 100644 --- a/.github/workflows/ci-issue-1948-segmented-curve-repro.yml +++ b/.github/workflows/ci-issue-1948-segmented-curve-repro.yml @@ -223,6 +223,8 @@ jobs: - name: Install clang and build dependencies run: | set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true apt-get update -qq # llvm-N is not optional padding: it carries llvm-ar and llvm-ranlib, # and without them CMake's check_ipo_supported probe fails to archive diff --git a/.github/workflows/ci-latest-release.yml b/.github/workflows/ci-latest-release.yml index c8cb547d3..d11dc84e1 100644 --- a/.github/workflows/ci-latest-release.yml +++ b/.github/workflows/ci-latest-release.yml @@ -86,9 +86,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" export DEBIAN_FRONTEND=noninteractive bash .github/scripts/ci-safe-apt-update.sh sudo apt-get \ @@ -106,9 +106,9 @@ jobs: MATRIX_COMPILER: ${{ matrix.compiler }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" if [ "${MATRIX_COMPILER}" = "gcc" ]; then echo "CC=gcc" >> "$GITHUB_ENV" echo "CXX=g++" >> "$GITHUB_ENV" @@ -122,9 +122,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source .github/scripts/sanitize-sed.sh { echo "Compiler Version:" @@ -139,9 +139,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" cd Build cmake -DCMAKE_INSTALL_PREFIX="$HOME/.local" \ -DCMAKE_BUILD_TYPE=Release \ @@ -152,9 +152,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" cd Build make -j"$(nproc)" - name: Verify CMake Cache @@ -163,9 +163,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" if test -f Build/CMakeCache.txt; then echo "[OK] Build OK" | tee -a "$GITHUB_STEP_SUMMARY" else @@ -178,9 +178,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" stage="staging/iccDEV-Testing" mkdir -p "$stage" # 1. Copy Testing contents (exclude Fuzzing) @@ -270,9 +270,9 @@ jobs: MATRIX_COMPILER: ${{ matrix.compiler }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" { echo "### Linux Build Summary (${MATRIX_COMPILER})" echo "- Build Directory: Build/" @@ -296,9 +296,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" echo "Installing Homebrew dependencies..." packages=(libpng nlohmann-json libxml2 wxwidgets libtiff jpeg-turbo) missing=() @@ -320,9 +320,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" echo "Setting up CMake build configuration..." cd Build sudo rm -rf /Library/Frameworks/Mono.framework/Headers/png.h @@ -346,9 +346,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" echo "Starting build process..." cd Build make -j"$(sysctl -n hw.ncpu)" @@ -360,9 +360,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" stage="staging/iccDEV-Testing" mkdir -p "$stage" # 1. Copy Testing contents (exclude Fuzzing) @@ -491,9 +491,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" { echo "### macOS Build Summary" echo "- Build Directory: Build/" @@ -526,10 +526,10 @@ jobs: env: POWERSHELL_TELEMETRY_OPTOUT: "1" run: | - git config --global credential.helper "" Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' + git config --global credential.helper "" # --- Disable telemetry sources (pwsh / dotnet / gh) --- $env:POWERSHELL_TELEMETRY_OPTOUT = "1" @@ -618,11 +618,11 @@ jobs: - name: Install dependencies and build shell: pwsh -NoProfile -NoLogo -NonInteractive -Command {0} run: | - git config --global credential.helper "" Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $PSDefaultParameterValues['*:ErrorAction'] = 'Stop' $ProgressPreference = 'SilentlyContinue' + git config --global credential.helper "" if (Test-Path Env:GITHUB_TOKEN) { Remove-Item Env:GITHUB_TOKEN -ErrorAction SilentlyContinue } @@ -910,9 +910,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" rm -rf emsdk git init emsdk git -C emsdk remote add origin https://github.com/emscripten-core/emsdk.git @@ -1729,9 +1729,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" source .github/scripts/sanitize-sed.sh bash .github/scripts/ci-safe-apt-update.sh @@ -1797,9 +1797,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" mkdir -p release-assets @@ -1838,9 +1838,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" SANITIZER="$GITHUB_WORKSPACE/.github/scripts/sanitize-sed.sh" if [[ -f "$SANITIZER" ]]; then @@ -1908,9 +1908,9 @@ jobs: GH_RUN_ID: ${{ github.run_id }} run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" release_tag="ci-latest-${GH_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}" @@ -1974,9 +1974,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" release_tag="ci-latest-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}" { echo "### Release Summary" diff --git a/.github/workflows/ci-pr-action.yml b/.github/workflows/ci-pr-action.yml index 0bbabbd15..8889890be 100644 --- a/.github/workflows/ci-pr-action.yml +++ b/.github/workflows/ci-pr-action.yml @@ -113,6 +113,7 @@ jobs: fast_lane: ${{ steps.diffcheck.outputs.fast_lane }} include_windows: ${{ steps.diffcheck.outputs.include_windows }} ctest_recent_limit: ${{ steps.diffcheck.outputs.ctest_recent_limit }} + ctest_mode: ${{ steps.diffcheck.outputs.ctest_mode }} warning_policy: ${{ steps.diffcheck.outputs.warning_policy }} target_sha: ${{ steps.diffcheck.outputs.target_sha }} target_repository: ${{ steps.diffcheck.outputs.target_repository }} @@ -143,8 +144,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" + unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" - name: Check for source changes id: diffcheck @@ -166,9 +168,9 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - - git config --add safe.directory "$PWD" + git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source "$GITHUB_WORKSPACE/base/.github/scripts/sanitize-sed.sh" ci_scope="${CI_SCOPE:-auto}" @@ -179,6 +181,11 @@ jobs: include_windows=true ctest_recent_limit=0 warning_policy=fail + # Default to the same full CTest surface ci-regression-checks runs, + # so a scope resolving ctest_recent_limit=0 ("run all selected + # tests") does not silently diverge by excluding slow/calculator + # labels. Only the fast-lane scope narrows this below. + ctest_mode=full case "$ci_scope" in auto|full|source|governance|docs|fast-lane) @@ -221,6 +228,7 @@ jobs: write_github_output fast_lane "$fast_lane" write_github_output include_windows "$include_windows" write_github_output ctest_recent_limit "$ctest_recent_limit" + write_github_output ctest_mode "$ctest_mode" write_github_output warning_policy "$warning_policy" write_github_output target_sha "$head_sha" write_github_output target_repository "$target_repository" @@ -283,6 +291,7 @@ jobs: ci_scope=fast-lane include_windows=false ctest_recent_limit=1 + ctest_mode=fast warning_policy=fail } @@ -315,6 +324,7 @@ jobs: full|source) include_windows=true ctest_recent_limit=0 + ctest_mode=full warning_policy=fail ;; fast-lane) @@ -468,9 +478,9 @@ jobs: DOCS_ONLY: ${{ steps.diffcheck.outputs.docs_only }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # --- load trusted canonical sanitizers from the checked-out base --- TRUSTED_SANITIZER="$GITHUB_WORKSPACE/base/.github/scripts/sanitize-sed.sh" @@ -578,8 +588,9 @@ jobs: DOCS_ONLY: ${{ needs.detect-src.outputs.docs_only }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" + unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # Load canonical sanitizers from trusted checkout TRUSTED_SANITIZER="$GITHUB_WORKSPACE/base/.github/scripts/sanitize-sed.sh" if [[ -f "$TRUSTED_SANITIZER" ]]; then @@ -642,9 +653,10 @@ jobs: DOCS_ONLY: ${{ needs.detect-src.outputs.docs_only || 'false' }} run: | set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true git config --add safe.directory "$PWD" - git config --global credential.helper "" # load trusted canonical sanitizers TRUSTED_SANITIZER="$GITHUB_WORKSPACE/base/.github/scripts/sanitize-sed.sh" @@ -715,6 +727,7 @@ jobs: fast-lane: ${{ steps.v.outputs.fast_lane }} include-windows: ${{ steps.v.outputs.include_windows }} ctest-recent-limit: ${{ steps.v.outputs.ctest_recent_limit }} + ctest-mode: ${{ steps.v.outputs.ctest_mode }} warning-policy: ${{ steps.v.outputs.warning_policy }} steps: @@ -736,6 +749,7 @@ jobs: FAST_LANE: ${{ needs.detect-src.outputs.fast_lane || 'false' }} INCLUDE_WINDOWS: ${{ needs.detect-src.outputs.include_windows || 'true' }} CTEST_RECENT_LIMIT: ${{ needs.detect-src.outputs.ctest_recent_limit || '0' }} + CTEST_MODE: ${{ needs.detect-src.outputs.ctest_mode || 'full' }} WARNING_POLICY: ${{ needs.detect-src.outputs.warning_policy || 'fail' }} run: | set -euo pipefail @@ -748,7 +762,7 @@ jobs: local value="$2" case "$key" in - regression_image_tag|fast_lane|include_windows|ctest_recent_limit|warning_policy) ;; + regression_image_tag|fast_lane|include_windows|ctest_recent_limit|ctest_mode|warning_policy) ;; *) echo "Invalid output key: $key" >&2 exit 1 @@ -768,6 +782,7 @@ jobs: write_github_output fast_lane "$FAST_LANE" write_github_output include_windows "$INCLUDE_WINDOWS" write_github_output ctest_recent_limit "$CTEST_RECENT_LIMIT" + write_github_output ctest_mode "$CTEST_MODE" write_github_output warning_policy "$WARNING_POLICY" risk-gate: @@ -799,7 +814,7 @@ jobs: with: regression_image_tag: ${{ needs.validate-inputs.outputs.regression-image-tag }} tool_build_type: Debug - ctest_mode: fast + ctest_mode: ${{ needs.validate-inputs.outputs.ctest-mode }} ctest_recent_limit: ${{ needs.validate-inputs.outputs.ctest-recent-limit }} warning_policy: ${{ needs.validate-inputs.outputs.warning-policy }} target_sha: ${{ needs.detect-src.outputs.target_sha }} @@ -881,8 +896,9 @@ jobs: DOCKER_RESULT: ${{ needs['docker-ci'].result }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" + unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # load canonical sanitizers from trusted checkout TRUSTED_SANITIZER="$GITHUB_WORKSPACE/base/.github/scripts/sanitize-sed.sh" diff --git a/.github/workflows/ci-pr-gcc15.yml b/.github/workflows/ci-pr-gcc15.yml index e07ae2134..7c6b05612 100644 --- a/.github/workflows/ci-pr-gcc15.yml +++ b/.github/workflows/ci-pr-gcc15.yml @@ -66,9 +66,9 @@ jobs: # preflight: allow-codeql-actions reason=readonly-build-target-checkout run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" source "$GITHUB_WORKSPACE/trusted-base/.github/scripts/sanitize-sed.sh" test "$(git rev-parse HEAD)" = "$TARGET_SHA" diff --git a/.github/workflows/ci-pr-lint.yml b/.github/workflows/ci-pr-lint.yml index 5633906cf..ce2900111 100644 --- a/.github/workflows/ci-pr-lint.yml +++ b/.github/workflows/ci-pr-lint.yml @@ -113,9 +113,9 @@ jobs: - name: Validate lint inputs run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # shellcheck disable=SC1091 source trusted/.github/scripts/sanitize-sed.sh @@ -150,9 +150,9 @@ jobs: - name: Install lint dependencies run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # shellcheck disable=SC1091 source trusted/.github/scripts/sanitize-sed.sh @@ -182,9 +182,9 @@ jobs: PR_BASE_REF: ${{ github.base_ref || 'master' }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # shellcheck disable=SC1091 source trusted/.github/scripts/sanitize-sed.sh @@ -237,9 +237,9 @@ jobs: CXX: clang++ run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # shellcheck disable=SC1091 source trusted/.github/scripts/sanitize-sed.sh @@ -263,9 +263,9 @@ jobs: if: steps.files.outputs.file_count != '0' && env.RUN_CPPCHECK_INPUT == 'true' run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # shellcheck disable=SC1091 source trusted/.github/scripts/sanitize-sed.sh @@ -306,9 +306,9 @@ jobs: if: steps.files.outputs.file_count != '0' && env.RUN_CLANG_TIDY_INPUT == 'true' run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # shellcheck disable=SC1091 source trusted/.github/scripts/sanitize-sed.sh @@ -333,9 +333,9 @@ jobs: SELECTED_LINT_SCOPE: ${{ steps.files.outputs.lint_scope || 'unknown' }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" # shellcheck disable=SC1091 source trusted/.github/scripts/sanitize-sed.sh diff --git a/.github/workflows/ci-pr-risk-security-analysis.yml b/.github/workflows/ci-pr-risk-security-analysis.yml index a1cc6682c..062686eaa 100644 --- a/.github/workflows/ci-pr-risk-security-analysis.yml +++ b/.github/workflows/ci-pr-risk-security-analysis.yml @@ -112,6 +112,7 @@ jobs: ANALYSIS_TARGET: ${{ github.event.pull_request.number && 'Pull request' || inputs.analysis_target || 'Current branch workflows' }} PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || '' }} GIT_REF: ${{ github.event.pull_request.head.sha || inputs.git_ref || github.ref }} + BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }} SEVERITY_THRESHOLD: ${{ inputs.severity_threshold || 'HIGH' }} FAIL_ON_FINDINGS: ${{ github.event_name == 'pull_request' && 'true' || inputs.fail_on_findings && 'true' || 'false' }} SCAN_ROOT: ${{ github.workspace }}/target @@ -467,6 +468,78 @@ jobs: echo "" >> "$RISK_REPORT_PATH" exit 1 + - name: "1c. Bash Prologue Governance Audit" + if: always() + env: + BASH_ENV: /dev/null + # preflight: allow-codeql-actions reason=readonly-target-analysis-checkout + run: | + set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true + + cd "$SCAN_ROOT" + SANITIZER="$TRUSTED_SCRIPTS/sanitize-sed.sh" + if [[ -f "$SANITIZER" ]]; then + # shellcheck disable=SC1090 + source "$SANITIZER" + else + sanitize_line() { printf '%s' "$1"; } + fi + + echo "## 1c. Bash Prologue Governance Audit" >> "$RISK_REPORT_PATH" + echo "" >> "$RISK_REPORT_PATH" + + checker="$TRUSTED_SCRIPTS/check-workflow-bash-prologue.py" + if [ ! -x "$checker" ]; then + echo "[WARN] Trusted base lacks the Bash prologue checker; enforcement begins after this bootstrap change merges." >> "$RISK_REPORT_PATH" + exit 0 + fi + + mapfile -t workflow_files < <( + find .github/workflows -maxdepth 1 -type f \ + \( -name '*.yml' -o -name '*.yaml' \) | sort + ) + if [ "${#workflow_files[@]}" -eq 0 ]; then + echo "[HIGH] Bash prologue policy violation: no workflow files found" >> "$RISK_REPORT_PATH" + exit 1 + fi + + mapfile -t changed_workflows < <( + git diff --name-only --diff-filter=ACMRT "$BASE_SHA"...HEAD -- .github/workflows | + awk '/\.ya?ml$/' + ) + if [ "${#changed_workflows[@]}" -eq 0 ]; then + echo "[OK] No changed workflow files require Bash prologue validation" >> "$RISK_REPORT_PATH" + echo "" >> "$RISK_REPORT_PATH" + exit 0 + fi + + policy_log="$(mktemp)" + set +e + python3 "$checker" --changed --base "$BASE_SHA" "${changed_workflows[@]}" >"$policy_log" 2>&1 + policy_status=$? + set -e + + if [ "$policy_status" -eq 0 ]; then + echo "[OK] All Bash workflow steps have the required security prologue" >> "$RISK_REPORT_PATH" + echo "" >> "$RISK_REPORT_PATH" + exit 0 + fi + + echo "[HIGH] Bash prologue policy violation detected" >> "$RISK_REPORT_PATH" + echo "" >> "$RISK_REPORT_PATH" + echo "
Bash prologue findings" >> "$RISK_REPORT_PATH" + echo "" >> "$RISK_REPORT_PATH" + while IFS= read -r line; do + [ -n "$line" ] || continue + echo "- \`$(sanitize_line "$line")\`" >> "$RISK_REPORT_PATH" + done < "$policy_log" + echo "" >> "$RISK_REPORT_PATH" + echo "
" >> "$RISK_REPORT_PATH" + echo "" >> "$RISK_REPORT_PATH" + exit 1 + - name: "2. Dangerous Trigger Detection" if: always() env: diff --git a/.github/workflows/ci-pr-unix.yml b/.github/workflows/ci-pr-unix.yml index 57d9f477e..49d22581e 100644 --- a/.github/workflows/ci-pr-unix.yml +++ b/.github/workflows/ci-pr-unix.yml @@ -135,9 +135,9 @@ jobs: MATRIX_BUILD_TYPE: ${{ matrix.build_type }} run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" source .trusted-workflow/.github/scripts/sanitize-sed.sh OS="${MATRIX_OS}" @@ -171,9 +171,9 @@ jobs: PR_OS: ${{ matrix.os }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source .trusted-workflow/.github/scripts/sanitize-sed.sh safe_event="$(sanitize_line "${PR_EVENT_NAME}")" safe_base="$(sanitize_ref "${PR_BASE_REF}")" @@ -211,9 +211,9 @@ jobs: MATRIX_COMPILER: ${{ matrix.compiler }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source .trusted-workflow/.github/scripts/sanitize-sed.sh case "$MATRIX_COMPILER" in gcc-15) @@ -264,9 +264,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source .trusted-workflow/.github/scripts/sanitize-sed.sh brew install cmake llvm wxwidgets libpng libtiff libxml2 nlohmann-json jpeg-turbo @@ -278,9 +278,9 @@ jobs: COMPILER_VERSION: ${{ inputs.compiler-version || 'system' }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source .trusted-workflow/.github/scripts/sanitize-sed.sh case "$COMPILER_VERSION" in system|gcc-15) ;; @@ -307,9 +307,9 @@ jobs: ENABLE_LTO: ${{ inputs.enable-lto && 'ON' || 'OFF' }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source .trusted-workflow/.github/scripts/sanitize-sed.sh echo "Configuring CMake project..." mkdir -p Build @@ -332,9 +332,9 @@ jobs: WARNING_POLICY: ${{ inputs.warning-policy || 'warn' }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source .trusted-workflow/.github/scripts/sanitize-sed.sh echo "Start build..." cd Build @@ -417,9 +417,9 @@ jobs: PROFILE_TEST_MODE: ${{ inputs.profile-test-mode || 'full' }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" source .trusted-workflow/.github/scripts/sanitize-sed.sh echo "Creating profiles..." cd Testing @@ -470,9 +470,9 @@ jobs: WARNING_POLICY: ${{ inputs.warning-policy || 'warn' }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" if [ ! -r .trusted-workflow/.github/scripts/sanitize-sed.sh ]; then echo "ERROR: trusted sanitizer helper not found or not readable" >&2 exit 1 diff --git a/.github/workflows/ci-pr-wasm.yml b/.github/workflows/ci-pr-wasm.yml index 3adbd4ee1..e5a5634a1 100644 --- a/.github/workflows/ci-pr-wasm.yml +++ b/.github/workflows/ci-pr-wasm.yml @@ -110,9 +110,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Install Emscripten SDK env: diff --git a/.github/workflows/ci-sanitizer-tests.yml b/.github/workflows/ci-sanitizer-tests.yml index dbdf7e15d..78d845176 100644 --- a/.github/workflows/ci-sanitizer-tests.yml +++ b/.github/workflows/ci-sanitizer-tests.yml @@ -63,9 +63,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" # preflight: allow-pr-script-execution reason=sanitizer-under-test - name: Verify sanitize-sed.sh exists @@ -74,6 +74,8 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true if [ ! -r .github/scripts/sanitize-sed.sh ]; then echo "ERROR: sanitize-sed.sh not found" >&2 exit 1 @@ -88,6 +90,8 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true bash .github/tests/test_sanitization.sh # preflight: allow-pr-script-execution reason=sanitizer-under-test @@ -97,6 +101,8 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true bash .github/tests/test-xss-signatures.sh # preflight: allow-pr-script-execution reason=sanitizer-under-test @@ -106,6 +112,8 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true bash .github/tests/test-xss-extended.sh # preflight: allow-pr-script-execution reason=sanitizer-under-test @@ -122,6 +130,8 @@ jobs: SIMULATED_HEAD_REF_HEX: "666561747572652ff09fa496f09fa69e2d68696464656e2d6368617273" run: | set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true source .github/scripts/sanitize-sed.sh SIMULATED_HEAD_REF="$(printf '%s' "${SIMULATED_HEAD_REF_HEX}" | xxd -r -p)" @@ -197,6 +207,8 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail + git config --global credential.helper "" + unset GITHUB_TOKEN || true if [ ! -r "$TRUSTED_SCRIPTS/sanitize-sed.sh" ]; then echo "sanitize-sed.sh missing - cannot generate summary" >&2 exit 0 diff --git a/.github/workflows/ci-vcpkg-ports.yml b/.github/workflows/ci-vcpkg-ports.yml index b0c2c1008..92eb58c92 100644 --- a/.github/workflows/ci-vcpkg-ports.yml +++ b/.github/workflows/ci-vcpkg-ports.yml @@ -65,9 +65,9 @@ jobs: BASH_ENV: /dev/null run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" # Pin vcpkg to builtin-baseline from vcpkg.json for reproducibility git clone https://github.com/microsoft/vcpkg.git "$RUNNER_TEMP/vcpkg" git -C "$RUNNER_TEMP/vcpkg" checkout eae1680538b86f962455c27abca2aad0dc304a4d @@ -101,9 +101,9 @@ jobs: VCPKG_KEEP_ENV_VARS: VCPKG_ICCDEV_SOURCE run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" TRIPLET="$([ "$RUNNER_OS" = "macOS" ] && echo "arm64-osx" || echo "x64-linux")" if [ "$RUNNER_OS" = "Linux" ]; then export VCPKG_FORCE_SYSTEM_BINARIES=1 @@ -176,9 +176,9 @@ jobs: RUNNER_OS: ${{ runner.os }} run: | set -euo pipefail - git config --global --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$PWD" # Load sanitizers from trusted base checkout. if [ -f "$TRUSTED_SCRIPTS/sanitize-sed.sh" ]; then # shellcheck disable=SC1090 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c29b751b..6afdd9052 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,9 +65,9 @@ jobs: - name: Bootstrap pinned vcpkg run: | set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" source .github/scripts/sanitize-sed.sh git clone https://github.com/microsoft/vcpkg.git "$RUNNER_TEMP/vcpkg" diff --git a/.github/workflows/update-labels.yml b/.github/workflows/update-labels.yml index 1166eddab..d22ac7074 100644 --- a/.github/workflows/update-labels.yml +++ b/.github/workflows/update-labels.yml @@ -68,9 +68,9 @@ jobs: GH_REPOSITORY: ${{ github.repository }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" .github/scripts/sync-labels.sh @@ -84,8 +84,9 @@ jobs: EVENT_PR_NUMBER: ${{ github.event.pull_request.number }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" + unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" EVENT_PR="${EVENT_PR_NUMBER}" @@ -143,8 +144,9 @@ jobs: PR_LIST: ${{ steps.pr_list.outputs.prs }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" + unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" RESULTS_FILE=$(mktemp) PROCESSED=0 @@ -319,9 +321,9 @@ jobs: PRS_LABELED: ${{ steps.process.outputs.labeled || '0' }} run: | set -euo pipefail - git config --add safe.directory "$PWD" git config --global credential.helper "" unset GITHUB_TOKEN || true + git config --add safe.directory "$PWD" SANITIZER=".github/scripts/sanitize-sed.sh" if [[ -f "$SANITIZER" ]]; then diff --git a/Dockerfile b/Dockerfile index 914eb79a1..c444ccefb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ ENV DEBIAN_FRONTEND=noninteractive # Package versions are pinned to the digest-pinned Ubuntu base validated on master. RUN apt-get update \ && apt-get install -y --no-install-recommends \ - build-essential=12.12ubuntu2 \ + build-essential=12.12ubuntu2.26.04.2 \ cmake=4.2.3-2ubuntu2 \ gcc=4:15.2.0-5ubuntu1 \ g++=4:15.2.0-5ubuntu1 \ @@ -26,7 +26,7 @@ RUN apt-get update \ libxml2-16=2.15.2+dfsg-0.1ubuntu0.1 \ libxml2-dev=2.15.2+dfsg-0.1ubuntu0.1 \ nlohmann-json3-dev=3.12.0.really.3.12.0.really.3.11.3-3build1 \ - libtiff-dev=4.7.0-3ubuntu4 \ + libtiff-dev=4.7.0-3ubuntu5 \ libjpeg-dev=8c-2ubuntu12 \ libpng-dev=1.6.57-1 \ zlib1g-dev=1:1.3.dfsg+really1.3.1-1ubuntu3 \ @@ -84,7 +84,7 @@ LABEL org.opencontainers.image.title="iccDEV Build Container" \ RUN apt-get update && apt-get install -y --no-install-recommends \ libc6=2.43-2ubuntu2.3 \ libxml2-16=2.15.2+dfsg-0.1ubuntu0.1 \ - libtiff6=4.7.0-3ubuntu4 \ + libtiff6=4.7.0-3ubuntu5 \ libjpeg8=8c-2ubuntu12 \ libpng16-16t64=1.6.57-1 \ libasan8=16-20260322-1ubuntu1 \ diff --git a/Dockerfile.ci-regression b/Dockerfile.ci-regression index 34390284b..4ded7e305 100644 --- a/Dockerfile.ci-regression +++ b/Dockerfile.ci-regression @@ -28,14 +28,14 @@ RUN apt-get update \ afl++=4.33c-1.1ubuntu1 \ bash=5.3-2ubuntu1 \ binutils=2.46-3ubuntu2 \ - build-essential=12.12ubuntu2 \ + build-essential=12.12ubuntu2.26.04.2 \ ca-certificates=20260601~26.04.1 \ clang-22=1:22.1.2-1ubuntu1 \ clang-tidy-22=1:22.1.2-1ubuntu1 \ clang-tools-22=1:22.1.2-1ubuntu1 \ cmake=4.2.3-2ubuntu2 \ cppcheck=2.19.0-3 \ - curl=8.18.0-1ubuntu2.3 \ + curl=8.18.0-1ubuntu2.4 \ diffutils=1:3.12-1 \ file=1:5.46-5build2 \ g++=4:15.2.0-5ubuntu1 \ @@ -50,11 +50,11 @@ RUN apt-get update \ libclang-rt-22-dev=1:22.1.2-1ubuntu1 \ libclang-rt-21-dev=1:21.1.8-6ubuntu1 \ libjpeg-dev=8c-2ubuntu12 \ - libtiff-tools=4.7.0-3ubuntu4 \ + libtiff-tools=4.7.0-3ubuntu5 \ liblzma-dev=5.8.3-1 \ libpng-dev=1.6.57-1 \ libssl-dev=3.5.5-1ubuntu3.3 \ - libtiff-dev=4.7.0-3ubuntu4 \ + libtiff-dev=4.7.0-3ubuntu5 \ libwxgtk3.2-dev=3.2.9+dfsg-1 \ zlib1g=1:1.3.dfsg+really1.3.1-1ubuntu3 \ lld-22=1:22.1.2-1ubuntu1 \ diff --git a/docs/regression-container.md b/docs/regression-container.md index 35b98d142..95c174193 100644 --- a/docs/regression-container.md +++ b/docs/regression-container.md @@ -425,6 +425,44 @@ Repository rules intentionally differ by branch: deletion. Its hosted `ci-pr-action` and `ci-docker` runs are dispatched after the push rather than configured as pre-push required contexts. +## Apt Package Version Drift (ci-docker Failures) + +`Dockerfile` and `Dockerfile.ci-regression` pin exact `apt-get install` +`package=version` strings against the digest-pinned Ubuntu base image so +builds stay reproducible. The Ubuntu 26.04 apt archive still republishes point +releases for the same base digest (for example a `-3ubuntu4` package becoming +`-3ubuntu5`, or a `build-essential` metapackage gaining a distro revision +suffix), and a stale pin then fails `apt-get install` with +`E: Unable to satisfy dependencies` or `E: Version '' for '' was +not found`, which surfaces as a `ci-docker` job failure with exit code `100`. + +Diagnose and fix a drifted pin: + +```bash +# 1. Reproduce locally against the exact pinned base digest. +docker build --no-cache -f Dockerfile -t iccdev-local-ubuntu-test . +docker build --no-cache -f Dockerfile.ci-regression -t iccdev-local-regression-test . + +# 2. Regenerate current candidate versions for every pinned package in a +# disposable container from the same base digest (replace PKGS with the +# package list from the failing Dockerfile). +docker run --rm ubuntu:26.04@sha256: bash -c ' + set -euo pipefail + apt-get update -qq + for p in PKGS; do + v=$(apt-cache policy "$p" | awk "/Candidate:/{print \$2}") + echo "$p=$v" + done' +``` + +Update only the packages whose candidate version differs from the pin; do not +relax exact pins to unpinned installs. Apply the same pin fix consistently +across every maintained container file that shares the pin (`Dockerfile` and +`Dockerfile.ci-regression`; `Dockerfile.mcp` intentionally tracks unpinned +security updates and is unaffected), confirm with a clean local `docker build`, +then carry the fix to other maintained branches per the branch-parity step +above only when a maintainer has requested that branch be updated. + ## Maintainer Report Report: