From be371d3a4a0922dc95768f75aabd94d44966db1d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 18 Oct 2026 20:19:39 -0700 Subject: [PATCH 1/2] ci: move macOS release proof to Xcode 27 --- .github/actionlint.yaml | 1 + .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++++++-- .github/workflows/release.yml | 23 ++++++++++++++++++----- CHANGELOG.md | 2 ++ README.md | 5 +++++ go.mod | 2 ++ scripts/build-npm-package.mjs | 3 ++- scripts/build-release.sh | 6 +++++- 8 files changed, 66 insertions(+), 9 deletions(-) diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 8b6e5ee..8e2a3ec 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -3,3 +3,4 @@ self-hosted-runner: - crabbox - openclaw - clawscan + - xcode-27 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7ed7ab..ea6c033 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,9 @@ on: permissions: contents: read +env: + MACOSX_DEPLOYMENT_TARGET: '13.0' + concurrency: group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} @@ -30,9 +33,17 @@ jobs: uses: rhysd/actionlint@914e7df21a07ef503a81201c76d2b11c789d3fca # v1.7.12 test: - name: Test - runs-on: ubuntu-latest + name: ${{ matrix.check_name }} + runs-on: ${{ matrix.os }} timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + check_name: Test + - os: xcode-27 + check_name: Test (macOS 27) steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -67,6 +78,7 @@ jobs: run: go build ./cmd/clawscan - name: Test Docker mount paths and permissions + if: runner.os == 'Linux' run: | docker pull alpine:3.23 node --test scripts/docker-mounts.test.mjs @@ -78,6 +90,23 @@ jobs: node --test scripts/release-notes.test.mjs node scripts/build-npm-package.mjs --version v0.0.0 --pack --smoke + - name: Verify macOS minimum and both architectures + if: runner.os == 'macOS' + run: | + if ! arch -x86_64 /usr/bin/true; then + softwareupdate --install-rosetta --agree-to-license + fi + arch -x86_64 /usr/bin/true + for npm_arch in arm64 x64; do + binary="dist/npm/package/binaries/darwin-${npm_arch}/clawscan" + minos=$(xcrun vtool -show-build "$binary" | awk '$1 == "minos" { print $2 }') + test "$minos" = 13.0 + done + dist/npm/package/binaries/darwin-arm64/clawscan --version + dist/npm/package/binaries/darwin-arm64/clawscan --help + arch -x86_64 dist/npm/package/binaries/darwin-x64/clawscan --version + arch -x86_64 dist/npm/package/binaries/darwin-x64/clawscan --help + - name: Test runtime update branch push run: node --test scripts/push-runtime-tool-update-branch.test.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c81f479..63008a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,13 +15,15 @@ on: required: false permissions: {} +env: + MACOSX_DEPLOYMENT_TARGET: '13.0' concurrency: group: release-${{ github.event.inputs.version || github.ref_name }} cancel-in-progress: false jobs: build: - runs-on: macos-15 + runs-on: xcode-27 permissions: actions: read contents: read @@ -99,10 +101,10 @@ jobs: strategy: matrix: include: - - runner: macos-15 + - runner: xcode-27 arch: arm64 npm_arch: arm64 - - runner: macos-15-intel + - runner: xcode-27 arch: amd64 npm_arch: x64 runs-on: ${{ matrix.runner }} @@ -129,8 +131,19 @@ jobs: cmp "$binary" "npm/package/binaries/darwin-${NPM_ARCH}/clawscan" codesign --verify --strict --check-notarization -R=notarized "$binary" codesign -dv --verbose=4 "$binary" 2>&1 | grep -Fx 'TeamIdentifier=FWJYW4S8P8' - "$binary" --version - "$binary" --help + minos=$(xcrun vtool -show-build "$binary" | awk '$1 == "minos" { print $2 }') + test "$minos" = 13.0 + if [ "$ARCH" = amd64 ]; then + if ! arch -x86_64 /usr/bin/true; then + softwareupdate --install-rosetta --agree-to-license + fi + arch -x86_64 /usr/bin/true + arch -x86_64 "$binary" --version + arch -x86_64 "$binary" --help + else + "$binary" --version + "$binary" --help + fi npm: timeout-minutes: 45 diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f1deb..0a355f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Move macOS CI and release verification to Xcode 27 with Rosetta for x86_64, retain the existing macOS 13 minimum in both release and npm binaries, and select Go 1.27.2 to fix reachable standard-library vulnerabilities while keeping the Go 1.27.1 source minimum. + - Refresh bundled Cisco, Snyk, Socket, Claude Code, and Codex tools while retaining the existing Node.js and Python runtime floors and two-day dependency cooldown. - Preserve Docker sandbox paths containing commas or quotation marks without changing read-only or writable mount permissions. Thanks @SebTardif (#64). - Use one shared autoreview installation from `openclaw/agent-skills`; repository entrypoints now receive upstream fixes without copied helpers or test suites. diff --git a/README.md b/README.md index c2f10b7..ca1282a 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,11 @@ Install ClawScan: npm install -g @openclaw/clawscan ``` +The macOS binaries support macOS 13 or later on Apple Silicon and Intel. CI uses +the Apple Silicon `xcode-27` runner and verifies x86_64 binaries under Rosetta. +Release and npm builds pin the macOS 13 deployment target and check both binaries' minimum OS metadata. +Source builds require Go 1.27.1 or later; the module selects the patched Go 1.27.2 build toolchain. + Command-backed scanners and judges run in ClawScan's Docker runtime by default, so keep Docker running for local scans. diff --git a/go.mod b/go.mod index 1816cfb..f3a9c93 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/openclaw/clawscan go 1.27.1 +toolchain go1.27.2 + require ( github.com/alchemy/json5 v0.2.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 diff --git a/scripts/build-npm-package.mjs b/scripts/build-npm-package.mjs index 0346c6b..c23d15b 100755 --- a/scripts/build-npm-package.mjs +++ b/scripts/build-npm-package.mjs @@ -175,7 +175,7 @@ async function stagePackage(options) { "build", "-trimpath", "-ldflags", - ldflags, + target.goos === "darwin" ? `${ldflags} -macos=13.0` : ldflags, "-o", join(binaryDir, binaryNameForTarget(target)), "github.com/openclaw/clawscan/cmd/clawscan", @@ -186,6 +186,7 @@ async function stagePackage(options) { GOOS: target.goos, GOARCH: target.goarch, CGO_ENABLED: "0", + MACOSX_DEPLOYMENT_TARGET: "13.0", }, }, ); diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 08a4ec4..cf46d97 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -33,7 +33,11 @@ for platform in "${platforms[@]}"; do fi mkdir -p "$workdir" - GOOS="$os" GOARCH="$arch" CGO_ENABLED=0 go build -trimpath -ldflags "$ldflags" -o "${workdir}/${binary}" "$package" + target_ldflags="$ldflags" + if [[ "$os" == darwin ]]; then + target_ldflags="$target_ldflags -macos=13.0" + fi + MACOSX_DEPLOYMENT_TARGET=13.0 GOOS="$os" GOARCH="$arch" CGO_ENABLED=0 go build -trimpath -ldflags "$target_ldflags" -o "${workdir}/${binary}" "$package" cp README.md "${workdir}/README.md" if [[ "$os" == "windows" ]]; then From 8a3d8fbac21a10ee6ac8bd119b94c70fa99c87eb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 18 Oct 2026 20:25:38 -0700 Subject: [PATCH 2/2] test: isolate mocked scanner fixtures from Docker --- CHANGELOG.md | 1 + internal/runner/runner_test.go | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a355f0..e1b6076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Move macOS CI and release verification to Xcode 27 with Rosetta for x86_64, retain the existing macOS 13 minimum in both release and npm binaries, and select Go 1.27.2 to fix reachable standard-library vulnerabilities while keeping the Go 1.27.1 source minimum. +- Keep mocked scanner/context tests independent of an installed Docker CLI while preserving sandbox enforcement checks. - Refresh bundled Cisco, Snyk, Socket, Claude Code, and Codex tools while retaining the existing Node.js and Python runtime floors and two-day dependency cooldown. - Preserve Docker sandbox paths containing commas or quotation marks without changing read-only or writable mount permissions. Thanks @SebTardif (#64). diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index c57ba71..e050f58 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -1674,13 +1674,14 @@ func TestNewArtifactAlwaysIncludesPassingGate(t *testing.T) { } func TestRunWritesScannerOnlyArtifact(t *testing.T) { + t.Setenv("PATH", t.TempDir()) dir := t.TempDir() target := filepath.Join(dir, "skill") if err := os.Mkdir(target, 0o755); err != nil { t.Fatal(err) } out := filepath.Join(dir, "run.json") - opts, err := ParseArgs([]string{target, "--scanner", "skillspector", "--output", out}) + opts, err := ParseArgs([]string{target, "--scanner", "skillspector", "--sandbox", "off", "--output", out}) if err != nil { t.Fatal(err) } @@ -4737,6 +4738,7 @@ func TestValidateArtifactInspectionReceiptStillRequiresChallengedFile(t *testing } func TestRunLoadsExplicitContextJSON(t *testing.T) { + t.Setenv("PATH", t.TempDir()) dir := t.TempDir() target := filepath.Join(dir, "skill") if err := os.MkdirAll(target, 0o755); err != nil { @@ -4751,6 +4753,7 @@ func TestRunLoadsExplicitContextJSON(t *testing.T) { Target: target, ContextPath: contextPath, Scanners: []string{"skillspector"}, + Sandbox: SandboxOptions{Mode: SandboxModeOff}, } artifact, err := Run(opts, RunContext{ Env: map[string]string{}, @@ -4767,6 +4770,7 @@ func TestRunLoadsExplicitContextJSON(t *testing.T) { } func TestRunRejectsInvalidContextJSON(t *testing.T) { + t.Setenv("PATH", t.TempDir()) dir := t.TempDir() target := filepath.Join(dir, "skill") if err := os.MkdirAll(target, 0o755); err != nil { @@ -4780,6 +4784,7 @@ func TestRunRejectsInvalidContextJSON(t *testing.T) { Target: target, ContextPath: contextPath, Scanners: []string{"skillspector"}, + Sandbox: SandboxOptions{Mode: SandboxModeOff}, }, RunContext{ Env: map[string]string{}, ScannerRunner: staticScannerRunner{results: map[string]ScannerResult{