diff --git a/.github/scripts/build-codex-package-archive.sh b/.github/scripts/build-codex-package-archive.sh
index 4592e78ce5..a0ed4a5f34 100644
--- a/.github/scripts/build-codex-package-archive.sh
+++ b/.github/scripts/build-codex-package-archive.sh
@@ -10,6 +10,8 @@ Usage: build-codex-package-archive.sh \
--archive-dir
\
[--bwrap-bin ] \
[--code-mode-host-bin ] \
+ [--rg-bin ] \
+ [--zsh-bin ] \
[--zsh-manifest ] \
[--codex-command-runner-bin ] \
[--codex-windows-sandbox-setup-bin ] \
@@ -56,6 +58,14 @@ while [[ $# -gt 0 ]]; do
code_mode_host_bin_provided="true"
shift 2
;;
+ --rg-bin)
+ resource_args+=(--rg-bin "${2:?--rg-bin requires a value}")
+ shift 2
+ ;;
+ --zsh-bin)
+ resource_args+=(--zsh-bin "${2:?--zsh-bin requires a value}")
+ shift 2
+ ;;
--zsh-manifest)
resource_args+=(--zsh-manifest "${2:?--zsh-manifest requires a value}")
shift 2
diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml
index a6e056030e..a1d27caeba 100644
--- a/.github/workflows/rust-release.yml
+++ b/.github/workflows/rust-release.yml
@@ -597,6 +597,63 @@ jobs:
--report-dir "${report_dir}/${binary}"
done
+ - name: Fetch, sign, and notarize pinned macOS helpers
+ if: ${{ matrix.bundle == 'primary' }}
+ shell: bash
+ env:
+ TARGET: ${{ matrix.target }}
+ APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
+ APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
+ APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
+ run: |
+ set -euo pipefail
+
+ signed_root="${GITHUB_WORKSPACE}/signed-resources/${TARGET}"
+ zsh_manifest="${RUNNER_TEMP}/codex-zsh-${TARGET}"
+ mkdir -p "$signed_root"
+ curl -fsSL \
+ "https://github.com/${GITHUB_REPOSITORY}/releases/download/${CODEX_ZSH_RELEASE_TAG}/codex-zsh" \
+ -o "$zsh_manifest"
+
+ PYTHONPATH="${GITHUB_WORKSPACE}/scripts" python3 - "$TARGET" "$signed_root" "$zsh_manifest" <<'PY'
+ import shutil
+ import sys
+ from pathlib import Path
+
+ from codex_package.ripgrep import fetch_rg
+ from codex_package.targets import TARGET_SPECS
+ from codex_package.zsh import resolve_zsh_bin
+
+ spec = TARGET_SPECS[sys.argv[1]]
+ signed_root = Path(sys.argv[2])
+ zsh_bin = resolve_zsh_bin(spec, Path(sys.argv[3]))
+ if zsh_bin is None:
+ raise RuntimeError(f"Pinned zsh release is missing {spec.target}")
+ shutil.copy2(fetch_rg(spec), signed_root / "rg")
+ shutil.copy2(zsh_bin, signed_root / "zsh")
+ PY
+
+ for resource in rg zsh; do
+ binary="${signed_root}/${resource}"
+ report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}/${resource}"
+ mkdir -p "$report_dir"
+ chmod 0755 "$binary"
+ .github/scripts/macos-signing/sign_macos_code.sh \
+ --target "$binary" \
+ --identity unused \
+ --deep false \
+ --identifier "com.openai.codex.${resource}" \
+ --options runtime \
+ --timestamp true
+
+ rcodesign print-signature-info "$binary" \
+ >"${report_dir}/signature-info.yaml"
+
+ .github/scripts/macos-signing/notarize_macos_binary_with_rcodesign.sh \
+ --binary "$binary" \
+ --report-dir "$report_dir"
+ done
+
- name: Upload signed macOS binaries
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
@@ -604,6 +661,14 @@ jobs:
path: signed-macos/${{ matrix.target }}/*
if-no-files-found: error
+ - name: Upload signed macOS helpers
+ if: ${{ matrix.bundle == 'primary' }}
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: ${{ matrix.target }}-signed-resources
+ path: signed-resources/${{ matrix.target }}/*
+ if-no-files-found: error
+
- name: Upload binary signing verification
if: ${{ always() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
@@ -659,6 +724,12 @@ jobs:
name: ${{ matrix.artifact_name }}-signed-binaries
path: codex-rs/target/${{ matrix.target }}/release
+ - name: Download signed macOS helpers
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ matrix.target }}-signed-resources
+ path: codex-rs/signed-resources/${{ matrix.target }}
+
- name: Verify signed macOS binaries
shell: bash
run: |
@@ -669,6 +740,12 @@ jobs:
codesign --verify --strict --verbose=2 "$binary_path"
done
+ for resource in rg zsh; do
+ resource_path="signed-resources/${{ matrix.target }}/${resource}"
+ chmod 0755 "$resource_path"
+ codesign --verify --strict --verbose=2 "$resource_path"
+ done
+
- name: Build unsigned macOS DMG
if: ${{ matrix.build_dmg == 'true' }}
shell: bash
@@ -730,14 +807,6 @@ jobs:
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
done
- - name: Download packaged zsh manifest
- shell: bash
- run: |
- set -euo pipefail
- curl -fsSL \
- "https://github.com/${GITHUB_REPOSITORY}/releases/download/${CODEX_ZSH_RELEASE_TAG}/codex-zsh" \
- -o "${RUNNER_TEMP}/codex-zsh"
-
- name: Build Codex package archive
shell: bash
env:
@@ -750,7 +819,8 @@ jobs:
--bundle "$BUNDLE" \
--entrypoint-dir "target/${TARGET}/release" \
--archive-dir "dist/${TARGET}" \
- --zsh-manifest "${RUNNER_TEMP}/codex-zsh"
+ --rg-bin "signed-resources/${TARGET}/rg" \
+ --zsh-bin "signed-resources/${TARGET}/zsh"
- name: Build Python runtime wheel
if: ${{ matrix.bundle == 'primary' }}
@@ -1056,6 +1126,23 @@ jobs:
verify_signed_binary "${package_dir}/bin/${package_entrypoint}" "$package_entrypoint"
verify_signed_binary "${package_dir}/bin/codex-code-mode-host" "codex-code-mode-host"
+ for resource in \
+ "${package_dir}/codex-path/rg" \
+ "${package_dir}/codex-resources/zsh/bin/zsh"
+ do
+ chmod 0755 "$resource"
+ lipo "$resource" -verify_arch "$expected_arch"
+ codesign --verify --strict --verbose=2 "$resource"
+ entitlements="$(mktemp)"
+ codesign -d --entitlements :- "$resource" >"$entitlements"
+ if [[ -s "$entitlements" ]]; then
+ echo "Bundled helper $resource must not have code-signing entitlements." >&2
+ plutil -p "$entitlements" >&2
+ exit 1
+ fi
+ rm -f "$entitlements"
+ done
+
if [[ "${{ matrix.verify_dmg }}" != "true" ]]; then
exit 0
fi
diff --git a/scripts/codex_package/README.md b/scripts/codex_package/README.md
index 7850bbd354..cf38cfa38f 100644
--- a/scripts/codex_package/README.md
+++ b/scripts/codex_package/README.md
@@ -78,6 +78,6 @@ The patched zsh fork used by `shell_zsh_fork` is fetched from the DotSlash
manifest at `scripts/codex_package/codex-zsh` when the selected target has a
matching prebuilt artifact. Downloaded archives are cached under
`$TMPDIR/codex-package/-zsh` and installed at
-`codex-resources/zsh/bin/zsh`. Pass `--zsh-manifest` to use a different
-DotSlash manifest, such as the manifest published with a standalone zsh
-artifact release.
+`codex-resources/zsh/bin/zsh`. Pass `--zsh-bin` to package a prebuilt, signed
+executable, or `--zsh-manifest` to use a different DotSlash manifest, such as
+the manifest published with a standalone zsh artifact release.
diff --git a/scripts/codex_package/cli.py b/scripts/codex_package/cli.py
index f461d0df43..595158d2dc 100644
--- a/scripts/codex_package/cli.py
+++ b/scripts/codex_package/cli.py
@@ -98,7 +98,8 @@ def parse_args() -> argparse.Namespace:
"targets, bwrap is built with Cargo."
),
)
- parser.add_argument(
+ zsh_source = parser.add_mutually_exclusive_group()
+ zsh_source.add_argument(
"--zsh-manifest",
type=Path,
help=(
@@ -106,6 +107,11 @@ def parse_args() -> argparse.Namespace:
"scripts/codex_package/codex-zsh."
),
)
+ zsh_source.add_argument(
+ "--zsh-bin",
+ type=Path,
+ help="Optional prebuilt zsh executable instead of fetching from a manifest.",
+ )
parser.add_argument(
"--codex-command-runner-bin",
type=Path,
@@ -182,7 +188,7 @@ def main() -> int:
entrypoint_bin=source_outputs.entrypoint_bin,
code_mode_host_bin=source_outputs.code_mode_host_bin,
rg_bin=resolve_rg_bin(spec, args.rg_bin),
- zsh_bin=resolve_zsh_bin(spec, args.zsh_manifest),
+ zsh_bin=resolve_zsh_bin(spec, args.zsh_manifest, zsh_bin=args.zsh_bin),
bwrap_bin=source_outputs.bwrap_bin,
codex_command_runner_bin=source_outputs.codex_command_runner_bin,
codex_windows_sandbox_setup_bin=source_outputs.codex_windows_sandbox_setup_bin,
diff --git a/scripts/codex_package/test_layout.py b/scripts/codex_package/test_layout.py
index 157bce5e9c..b0810cac99 100644
--- a/scripts/codex_package/test_layout.py
+++ b/scripts/codex_package/test_layout.py
@@ -15,6 +15,56 @@ from codex_package.targets import TARGET_SPECS
class PackageLayoutTest(unittest.TestCase):
+ def test_macos_package_preserves_prebuilt_resource_binaries(self) -> None:
+ for variant_name in ("codex", "codex-app-server"):
+ for target in ("aarch64-apple-darwin", "x86_64-apple-darwin"):
+ with self.subTest(variant=variant_name, target=target):
+ with tempfile.TemporaryDirectory() as temp_dir:
+ root = Path(temp_dir)
+ package_dir = root / "package"
+ package_dir.mkdir()
+ rg_bin = touch_executable(root / "signed-rg")
+ zsh_bin = touch_executable(root / "signed-zsh")
+ rg_bin.write_bytes(b"signed ripgrep binary")
+ zsh_bin.write_bytes(b"signed zsh binary")
+ variant = PACKAGE_VARIANTS[variant_name]
+ spec = TARGET_SPECS[target]
+ inputs = PackageInputs(
+ entrypoint_bin=touch_executable(
+ root / variant.executable_stem
+ ),
+ code_mode_host_bin=touch_executable(
+ root / "codex-code-mode-host"
+ ),
+ rg_bin=rg_bin,
+ zsh_bin=zsh_bin,
+ bwrap_bin=None,
+ codex_command_runner_bin=None,
+ codex_windows_sandbox_setup_bin=None,
+ )
+
+ build_package_dir(package_dir, "1.2.3", variant, spec, inputs)
+ validate_package_dir(
+ package_dir, variant, spec, include_zsh=True
+ )
+
+ self.assertEqual(
+ {
+ "rg": (package_dir / "codex-path" / "rg").read_bytes(),
+ "zsh": (
+ package_dir
+ / "codex-resources"
+ / "zsh"
+ / "bin"
+ / "zsh"
+ ).read_bytes(),
+ },
+ {
+ "rg": b"signed ripgrep binary",
+ "zsh": b"signed zsh binary",
+ },
+ )
+
def test_app_server_package_places_code_mode_host_beside_entrypoint(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
diff --git a/scripts/codex_package/test_zsh.py b/scripts/codex_package/test_zsh.py
index b4f536cfc2..86d3c9fea8 100644
--- a/scripts/codex_package/test_zsh.py
+++ b/scripts/codex_package/test_zsh.py
@@ -16,6 +16,20 @@ from codex_package.zsh import resolve_zsh_bin
class ResolveZshBinTest(unittest.TestCase):
+ def test_uses_prebuilt_executable_override(self) -> None:
+ with tempfile.TemporaryDirectory() as temp_dir:
+ signed_zsh = Path(temp_dir) / "signed-zsh"
+ signed_zsh.write_bytes(b"signed zsh binary")
+ signed_zsh.chmod(0o755)
+
+ with patch("codex_package.zsh.fetch_dotslash_executable") as fetch:
+ zsh_bin = resolve_zsh_bin(
+ TARGET_SPECS["aarch64-apple-darwin"], zsh_bin=signed_zsh
+ )
+
+ self.assertEqual(zsh_bin, signed_zsh.resolve())
+ fetch.assert_not_called()
+
def test_uses_manifest_override(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
diff --git a/scripts/codex_package/zsh.py b/scripts/codex_package/zsh.py
index 4ec2db502c..35c62a3854 100644
--- a/scripts/codex_package/zsh.py
+++ b/scripts/codex_package/zsh.py
@@ -5,6 +5,7 @@ from pathlib import Path
from .dotslash import fetch_dotslash_executable
from .targets import REPO_ROOT
from .targets import TargetSpec
+from .targets import resolve_input_path
ZSH_MANIFEST = REPO_ROOT / "scripts" / "codex_package" / "codex-zsh"
@@ -14,7 +15,12 @@ ZSH_RESOURCE_PATH = Path("zsh") / "bin" / "zsh"
def resolve_zsh_bin(
spec: TargetSpec,
manifest_path: Path | None = None,
+ *,
+ zsh_bin: Path | None = None,
) -> Path | None:
+ if zsh_bin is not None:
+ return resolve_input_path(zsh_bin, "zsh executable", "--zsh-bin")
+
return fetch_dotslash_executable(
spec,
manifest_path=manifest_path or ZSH_MANIFEST,