Add opt-in provisioned macOS CLI release candidates (#44307)

## What changed

- Add a tag-release workflow gated by `CODEX_PROVISIONED_MACOS_CANDIDATE` for Apple Silicon and Intel macOS candidate artifacts.
- Package the CLI in `CodexCLI.app` with an embedded provisioning profile and a relocatable `bin/codex` launcher. Validate independently supplied profile, certificate, and team expectations before signing.
- Sign and notarize candidates, then verify signatures, entitlements, architecture, stapling, and Gatekeeper acceptance on macOS. Retain verified artifacts after package smoke tests pass.
- Filter code-mode smoke-test requests to `/v1/responses` so analytics requests are excluded from response parsing.

## Testing

Add tests for profile validation, launcher relocation and symlink handling, standard and provisioned signing flows, and rejection of signing, notarization, identity, and entitlement failures. The candidate workflow runs package smoke tests, including sandboxed code mode.

GitOrigin-RevId: f214f6a23dd10df62cd72a2c63c20cb0864fbc9c
This commit is contained in:
Eric Burke
2026-09-09 21:18:11 +00:00
committed by copyberry
parent 7c88f037d9
commit 45eec73b11
8 changed files with 1143 additions and 2 deletions

View File

@@ -0,0 +1,229 @@
"""Prepare and verify provisioned CLI bundles using an explicitly approved profile."""
import argparse
import hashlib
import json
import os
import plistlib
import re
import shutil
import subprocess
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
BUNDLE_ID = "com.openai.codex.cli"
APP = Path("CodexCLI.app")
EXECUTABLE = APP / "Contents/MacOS/codex"
SIGNING = Path(__file__).resolve().parent
HELPERS = ("bin/codex-code-mode-host", "codex-path/rg", "codex-resources/zsh/bin/zsh")
LAUNCHER = """#!/bin/sh
set -eu
entry="$0"
while [ -L "$entry" ]; do
parent=$(CDPATH= cd -P -- "$(dirname -- "$entry")" && pwd)
entry=$(readlink "$entry")
case "$entry" in /*) ;; *) entry="$parent/$entry" ;; esac
done
bin_dir=$(CDPATH= cd -P -- "$(dirname -- "$entry")" && pwd)
exec "$bin_dir/../CodexCLI.app/Contents/MacOS/codex" "$@"
"""
@dataclass(frozen=True)
class ProfileConfiguration:
"""Independent release expectations; never infer these from the signed package."""
profile: Path
profile_sha256: str
certificate_sha256: str
team_id: str
def __post_init__(self):
for name in ("profile_sha256", "certificate_sha256"):
if not re.fullmatch(r"[0-9a-f]{64}", getattr(self, name)):
raise ValueError(f"{name} must be a lowercase SHA-256 digest")
if not re.fullmatch(r"[A-Z0-9]{10}", self.team_id):
raise ValueError("team_id must be a ten-character Apple Team ID")
def validate_signing_certificate(self, certificate: Path):
certificate_der = subprocess.check_output(
["openssl", "x509", "-in", str(certificate), "-outform", "DER"]
)
if hashlib.sha256(certificate_der).hexdigest() != self.certificate_sha256:
raise ValueError("AKV signing certificate is not authorized by the profile")
def load_profile(configuration: ProfileConfiguration):
# Pin the portal-approved CMS as well as checking its signature. -noverify
# skips OpenSSL's non-Apple trust store, not CMS signature verification.
if (
hashlib.sha256(configuration.profile.read_bytes()).hexdigest()
!= configuration.profile_sha256
):
raise ValueError("Provisioning profile changed; review and update its pin")
profile = plistlib.loads(
subprocess.check_output(
[
"openssl",
"cms",
"-verify",
"-inform",
"DER",
"-noverify",
"-in",
str(configuration.profile),
]
)
)
validate_profile(profile, configuration)
return profile
def validate_profile(profile, configuration: ProfileConfiguration):
allowed = profile["Entitlements"]
if (
profile["TeamIdentifier"] != [configuration.team_id]
or profile["ApplicationIdentifierPrefix"] != [configuration.team_id]
or allowed["com.apple.application-identifier"]
!= f"{configuration.team_id}.{BUNDLE_ID}"
or allowed["com.apple.developer.team-identifier"] != configuration.team_id
or allowed["keychain-access-groups"] != [f"{configuration.team_id}.*"]
or allowed.get("get-task-allow", False)
or not profile.get("ProvisionsAllDevices")
or "ProvisionedDevices" in profile
):
raise ValueError("Profile does not authorize the CLI Developer ID identity")
now = datetime.now(timezone.utc).replace(tzinfo=None)
if not profile["CreationDate"] <= now < profile["ExpirationDate"]:
raise ValueError("Provisioning profile is not currently valid")
if [
hashlib.sha256(cert).hexdigest() for cert in profile["DeveloperCertificates"]
] != [configuration.certificate_sha256]:
raise ValueError("Profile does not authorize the approved signing certificate")
def entitlements(configuration: ProfileConfiguration):
base = plistlib.loads((SIGNING / "codex.entitlements.plist").read_bytes())
return {
**base,
"com.apple.application-identifier": f"{configuration.team_id}.{BUNDLE_ID}",
"com.apple.developer.team-identifier": configuration.team_id,
"keychain-access-groups": [f"{configuration.team_id}.{BUNDLE_ID}"],
}
def prepare(package, reports, configuration: ProfileConfiguration):
load_profile(configuration)
metadata = json.loads((package / "codex-package.json").read_text())
if (
metadata["variant"] != "codex"
or metadata["layoutVersion"] != 1
or metadata["target"] not in ("aarch64-apple-darwin", "x86_64-apple-darwin")
or metadata["entrypoint"] != "bin/codex"
):
raise ValueError("Expected a canonical macOS CLI package")
for relative in ("bin/codex", *HELPERS):
path = package / relative
if path.is_symlink() or not path.is_file() or not os.access(path, os.X_OK):
raise ValueError(f"Expected a regular executable: {relative}")
# Refuse to overwrite a previously prepared or signed bundle.
(package / EXECUTABLE).parent.mkdir(parents=True, exist_ok=False)
(package / "bin/codex").rename(package / EXECUTABLE)
(package / "bin/codex").write_text(LAUNCHER)
(package / "bin/codex").chmod(0o755)
contents = package / APP / "Contents"
shutil.copyfile(configuration.profile, contents / "embedded.provisionprofile")
(contents / "Info.plist").write_bytes(
plistlib.dumps(
{
"CFBundleIdentifier": BUNDLE_ID,
"CFBundleExecutable": "codex",
"CFBundleName": "Codex CLI",
"CFBundlePackageType": "APPL",
"CFBundleVersion": "1",
}
)
)
reports.mkdir(parents=True, exist_ok=True)
# Keep expected entitlements separate from the shared verifier's extracted
# codex-entitlements.plist, otherwise it would overwrite its own expectation.
(reports / "codex-provisioned-entitlements.plist").write_bytes(
plistlib.dumps(entitlements(configuration))
)
def verify(package, reports, expected_target, configuration: ProfileConfiguration):
"""Check profile and certificate pins; the signing driver verifies code."""
load_profile(configuration)
if (
package / APP / "Contents/embedded.provisionprofile"
).read_bytes() != configuration.profile.read_bytes():
raise ValueError("Embedded profile differs from the reviewed profile")
if (package / "bin/codex").read_text() != LAUNCHER:
raise ValueError("Unexpected CLI launcher")
metadata = json.loads((package / "codex-package.json").read_text())
if metadata["target"] != expected_target:
raise ValueError(
"Package target differs from the requested verification target"
)
reports.mkdir(parents=True, exist_ok=True)
(reports / "codex-provisioned-entitlements.plist").write_bytes(
plistlib.dumps(entitlements(configuration))
)
# sign_macos_cli_package.py owns shared architecture, signature and entitlement
# checks. Here, require the exact certificate authorized by the profile.
for relative in (EXECUTABLE, *(Path(helper) for helper in HELPERS)):
binary = package / relative
target = package / APP if relative == EXECUTABLE else binary
prefix = reports / f"{binary.name}-cert-"
subprocess.run(
["codesign", "-d", f"--extract-certificates={prefix}", str(target)],
check=True,
)
if (
hashlib.sha256(Path(f"{prefix}0").read_bytes()).hexdigest()
!= configuration.certificate_sha256
):
raise ValueError(f"Unexpected signing certificate: {relative}")
def parse_profile_args(parser, configuration: ProfileConfiguration | None):
"""Require independent profile expectations unless the caller supplies them."""
if configuration is None:
parser.add_argument("--profile", type=Path, required=True)
parser.add_argument("--profile-sha256", required=True)
parser.add_argument("--certificate-sha256", required=True)
parser.add_argument("--team-id", required=True)
args = parser.parse_args()
if configuration is None:
configuration = ProfileConfiguration(
args.profile, args.profile_sha256, args.certificate_sha256, args.team_id
)
return args, configuration
def main(configuration: ProfileConfiguration | None = None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("operation", choices=("prepare", "verify", "validate-profile"))
parser.add_argument("--package", type=Path, default=Path("package"))
parser.add_argument("--reports", type=Path, default=Path("provisioned-reports"))
parser.add_argument("--certificate", type=Path)
parser.add_argument(
"--target", choices=("aarch64-apple-darwin", "x86_64-apple-darwin")
)
args, configuration = parse_profile_args(parser, configuration)
if args.certificate:
configuration.validate_signing_certificate(args.certificate)
if args.operation == "validate-profile":
load_profile(configuration)
elif args.operation == "prepare":
prepare(args.package, args.reports, configuration)
else:
if not args.target:
parser.error("--target is required for verification")
verify(args.package, args.reports, args.target, configuration)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,177 @@
"""Sign and verify macOS CLI packages using the shared AKV and native tools."""
import argparse
import os
import subprocess
import sys
from pathlib import Path
import provisioned_macos_cli_package as bundle
def main(configuration: bundle.ProfileConfiguration | None = None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("operation", choices=("sign", "verify"))
parser.add_argument("package", type=Path)
args, configuration = bundle.parse_profile_args(parser, configuration)
package = args.package.resolve(strict=True)
if not package.is_dir():
parser.error("package must be an extracted CLI package directory")
repo_root = os.environ.get("CODEX_REPO_ROOT")
if not repo_root:
parser.error("CODEX_REPO_ROOT must be set")
reports = Path(repo_root) / "signing-verification"
reports.mkdir(parents=True, exist_ok=True)
provisioned = os.environ.get("PROVISIONED_MACOS", "false") == "true"
if provisioned:
if args.operation == "sign":
configuration.validate_signing_certificate(
Path(os.environ["OAI_AKV_SIGNING_CERTIFICATE_PEM"])
)
bundle.prepare(package, reports, configuration)
else:
bundle.verify(package, reports, os.environ["TARGET"], configuration)
for relative in ("bin/codex", *bundle.HELPERS):
binary = package / relative
name = binary.name
target = binary
identifier = name
entitlements = bundle.SIGNING / f"{name}.entitlements.plist"
if provisioned and relative == "bin/codex":
binary = package / bundle.EXECUTABLE
target = package / bundle.APP
identifier = bundle.BUNDLE_ID
entitlements = reports / "codex-provisioned-entitlements.plist"
if args.operation == "sign":
command = [
"bash",
str(bundle.SIGNING / "sign_macos_code.sh"),
"--target",
str(target),
"--identity",
"unused",
"--deep",
"false",
"--options",
"runtime",
"--timestamp",
"true",
]
if relative.startswith("bin/"):
command.extend(
["--identifier", identifier, "--entitlements", str(entitlements)]
)
else:
command.extend(["--identifier", f"com.openai.codex.{name}"])
subprocess.run(command, check=True)
with (reports / f"{name}-signature.yaml").open("wb") as output:
subprocess.run(
["rcodesign", "print-signature-info", str(binary)],
stdout=output,
check=True,
)
if not provisioned:
subprocess.run(
[
"bash",
str(bundle.SIGNING / "notarize_macos_binary_with_akv.sh"),
"--binary",
str(binary),
"--report-dir",
str(reports / name),
],
check=True,
)
else:
target_triple = os.environ["TARGET"]
architectures = {
"aarch64-apple-darwin": "arm64",
"x86_64-apple-darwin": "x86_64",
}
if target_triple not in architectures:
raise ValueError(f"Unexpected macOS target: {target_triple}")
subprocess.run(
["lipo", str(binary), "-verify_arch", architectures[target_triple]],
check=True,
)
subprocess.run(
[
"codesign",
"--verify",
"--strict",
"--verbose=2",
"--test-requirement",
# Require Apple's Developer ID Application certificate chain.
(
"=anchor apple generic"
" and certificate 1[field.1.2.840.113635.100.6.2.6] exists"
" and certificate leaf[field.1.2.840.113635.100.6.1.13] exists"
),
str(target),
],
check=True,
)
signature = reports / f"{name}-signature.txt"
with signature.open("wb") as output:
subprocess.run(
["codesign", "-d", "--verbose=4", str(target)],
stderr=output,
check=True,
)
actual = reports / f"{name}-entitlements.plist"
with actual.open("wb") as output:
subprocess.run(
["codesign", "-d", "--entitlements", ":-", str(target)],
stdout=output,
check=True,
)
if relative.startswith("bin/"):
expected = reports / f"{name}-expected.plist"
subprocess.run(
[
"plutil",
"-convert",
"xml1",
"-o",
str(expected),
str(entitlements),
],
check=True,
)
subprocess.run(["plutil", "-convert", "xml1", str(actual)], check=True)
subprocess.run(["diff", "-u", str(expected), str(actual)], check=True)
elif actual.stat().st_size:
raise ValueError(
f"Bundled helper {name} must not have signing entitlements"
)
if provisioned and args.operation == "sign":
archive = Path(os.environ["RUNNER_TEMP"]) / "provisioned-cli.zip"
subprocess.run(["zip", "-q", "-r", str(archive), "."], cwd=package, check=True)
command = [
sys.executable,
str(bundle.SIGNING / "notarize_with_akv.py"),
"--file",
str(archive),
"--report-log",
str(reports / "notarization-developer-log.json"),
"--max-wait-seconds",
"1200",
]
with (
(reports / "notarization.log").open("w") as output,
subprocess.Popen(
command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
) as process,
):
assert process.stdout is not None
for line in process.stdout:
print(line, end="", flush=True)
output.write(line)
if returncode := process.wait():
raise subprocess.CalledProcessError(returncode, command)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,217 @@
"""Exercise profile validation, relocation and signing without release credentials."""
import copy
import hashlib
import json
import plistlib
import shutil
import ssl
import subprocess
import tarfile
import tempfile
import unittest
from dataclasses import replace
from datetime import datetime, timedelta, timezone
from pathlib import Path
import provisioned_macos_cli_package as bundle
class ProvisioningTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
directory = tempfile.TemporaryDirectory()
cls.addClassCleanup(directory.cleanup)
root = Path(directory.name)
key = root / "test-key.pem"
certificate = root / "test-certificate.pem"
subprocess.run(
[
"openssl",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-nodes",
"-keyout",
str(key),
"-out",
str(certificate),
"-days",
"1",
"-subj",
"/CN=CLI provisioning test/OU=TESTTEAM01",
],
check=True,
capture_output=True,
)
cert_der = ssl.PEM_cert_to_DER_cert(certificate.read_text())
now = datetime.now(timezone.utc).replace(tzinfo=None, microsecond=0)
cls.profile = {
"TeamIdentifier": ["TESTTEAM01"],
"ApplicationIdentifierPrefix": ["TESTTEAM01"],
"Entitlements": {
"com.apple.application-identifier": "TESTTEAM01.com.openai.codex.cli",
"com.apple.developer.team-identifier": "TESTTEAM01",
"keychain-access-groups": ["TESTTEAM01.*"],
},
"CreationDate": now - timedelta(days=1),
"ExpirationDate": now + timedelta(days=1),
"ProvisionsAllDevices": True,
"DeveloperCertificates": [cert_der],
}
payload = root / "profile.plist"
payload.write_bytes(plistlib.dumps(cls.profile))
profile_path = root / "test.provisionprofile"
subprocess.run(
[
"openssl",
"cms",
"-sign",
"-binary",
"-nodetach",
"-outform",
"DER",
"-in",
str(payload),
"-signer",
str(certificate),
"-inkey",
str(key),
"-out",
str(profile_path),
],
check=True,
capture_output=True,
)
cls.configuration = bundle.ProfileConfiguration(
profile=profile_path,
profile_sha256=hashlib.sha256(profile_path.read_bytes()).hexdigest(),
certificate_sha256=hashlib.sha256(cert_der).hexdigest(),
team_id="TESTTEAM01",
)
class ProvisionedCliTests(ProvisioningTestCase):
def test_profile_configuration_fails_closed(self):
self.assertEqual(bundle.load_profile(self.configuration), self.profile)
for changes in (
{"profile_sha256": "0" * 64},
{"certificate_sha256": "0" * 64},
{"team_id": "OTHERTEAM1"},
{"profile_sha256": ""},
{"certificate_sha256": ""},
{"team_id": ""},
):
with self.subTest(changes=changes), self.assertRaises(ValueError):
bundle.load_profile(replace(self.configuration, **changes))
def test_rejects_profile_with_wrong_identity_certificate_or_validity(self):
for field, value in (
("TeamIdentifier", ["OTHERTEAM"]),
("DeveloperCertificates", [b"another certificate"]),
(
"ExpirationDate",
datetime(2000, 1, 1, tzinfo=timezone.utc).replace(tzinfo=None),
),
(
"CreationDate",
datetime(2099, 1, 1, tzinfo=timezone.utc).replace(tzinfo=None),
),
("ProvisionsAllDevices", False),
("ProvisionedDevices", ["device"]),
):
with self.subTest(field=field):
profile = copy.deepcopy(self.profile)
profile[field] = value
with self.assertRaises(ValueError):
bundle.validate_profile(profile, self.configuration)
for field, value in (
(
"com.apple.application-identifier",
f"{self.configuration.team_id}.another.app",
),
("keychain-access-groups", [f"{self.configuration.team_id}.another.app"]),
("get-task-allow", True),
):
with self.subTest(field=field):
profile = copy.deepcopy(self.profile)
profile["Entitlements"][field] = value
with self.assertRaises(ValueError):
bundle.validate_profile(profile, self.configuration)
def test_archive_relocation_copy_and_symlink_preserve_launcher(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
package = root / "original package"
metadata = {
"variant": "codex",
"layoutVersion": 1,
"target": "aarch64-apple-darwin",
"entrypoint": "bin/codex",
}
for relative in ("bin/codex", *bundle.HELPERS):
path = package / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text('#!/bin/sh\nprintf "%s\\n" "$@"\nexit 23\n')
path.chmod(0o755)
(package / "codex-package.json").write_text(json.dumps(metadata))
original_binary = (package / "bin/codex").read_bytes()
bundle.prepare(package, root / "reports", self.configuration)
with self.assertRaises(FileExistsError):
bundle.prepare(package, root / "reports", self.configuration)
with self.assertRaisesRegex(ValueError, "Package target differs"):
bundle.verify(
package, root / "reports", "x86_64-apple-darwin", self.configuration
)
self.assertEqual(
(package / bundle.EXECUTABLE).read_bytes(), original_binary
)
self.assertEqual(
json.loads((package / "codex-package.json").read_text()), metadata
)
archive_path = root / "package.tar.gz"
with tarfile.open(archive_path, "w:gz") as archive:
archive.add(package, arcname=".")
relocated = root / "relocated package"
with tarfile.open(archive_path) as archive:
archive.extractall(relocated, filter="data")
shutil.rmtree(package)
# npm currently dereferences symlinks when copying packages. The
# launcher remains a script and the provisioned executable stays put.
copied = root / "copied package"
shutil.copytree(relocated, copied)
link = root / "installed codex"
link.symlink_to(Path("copied package/bin/codex"))
absolute_link = root / "absolute codex"
absolute_link.symlink_to(link)
for entry in (
relocated / "bin/codex",
copied / "bin/codex",
link,
absolute_link,
):
with self.subTest(entry=entry):
result = subprocess.run(
[str(entry), "space argument", "*", "--flag"],
cwd="/",
capture_output=True,
text=True,
check=False,
)
self.assertEqual(
(result.returncode, result.stdout, result.stderr),
(23, "space argument\n*\n--flag\n", ""),
)
(copied / bundle.APP / "Contents/embedded.provisionprofile").write_bytes(
b"tampered"
)
with self.assertRaisesRegex(ValueError, "Embedded profile differs"):
bundle.verify(
copied, root / "reports", "aarch64-apple-darwin", self.configuration
)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,293 @@
"""Exercise package signing and native verification with generated credentials."""
import itertools
import json
import os
import shutil
import ssl
import subprocess
import sys
import tempfile
import unittest
import zipfile
from pathlib import Path
import provisioned_macos_cli_package as bundle
from test_provisioned_macos_cli_package import ProvisioningTestCase
NATIVE_TOOL = """#!/usr/bin/env python3
import json, os, plistlib, sys
from pathlib import Path
name = Path(sys.argv[0]).name
args = sys.argv[1:]
with open(os.environ["CALL_LOG"], "a") as log:
log.write(json.dumps([name, *args]) + "\\n")
if name == "codesign":
for arg in args:
if arg.startswith("--extract-certificates="):
Path(arg.split("=", 1)[1] + "0").write_bytes(Path(os.environ["MOCK_CERTIFICATE"]).read_bytes())
if "--test-requirement" in args and os.environ.get("MOCK_WRONG_SIGNING_IDENTITY"):
print("code failed to satisfy specified code requirement(s)", file=sys.stderr)
sys.exit(1)
if "--entitlements" in args:
target = Path(args[-1]).name
if target == "CodexCLI.app":
source = Path(os.environ["CODEX_REPO_ROOT"]) / "signing-verification/codex-provisioned-entitlements.plist"
elif target in ("codex", "codex-code-mode-host"):
source = Path(os.environ["MOCK_SIGNING_SCRIPTS"]) / (target + ".entitlements.plist")
else:
sys.exit(0)
if os.environ.get("MOCK_WRONG_ENTITLEMENTS"):
sys.stdout.buffer.write(plistlib.dumps({"unexpected": True}))
else:
sys.stdout.buffer.write(source.read_bytes())
elif name == "plutil":
source = Path(args[-1])
target = Path(args[args.index("-o") + 1]) if "-o" in args else source
target.write_bytes(plistlib.dumps(plistlib.loads(source.read_bytes())))
"""
class MacosSigningTests(ProvisioningTestCase):
def fixture(self, root, provisioned, target="aarch64-apple-darwin"):
source = Path(bundle.__file__).parent
common = Path(os.path.commonpath((source, bundle.SIGNING)))
helpers = root / source.relative_to(common)
scripts = root / bundle.SIGNING.relative_to(common)
helpers.mkdir(parents=True, exist_ok=True)
scripts.mkdir(parents=True, exist_ok=True)
for name in ("provisioned_macos_cli_package.py", "sign_macos_cli_package.py"):
shutil.copyfile(source / name, helpers / name)
for path in bundle.SIGNING.glob("*.entitlements.plist"):
shutil.copyfile(path, scripts / path.name)
(root / "record.py").write_text(
"import json, os, sys\n"
"with open(os.environ['CALL_LOG'], 'a') as log:\n"
" log.write(json.dumps(sys.argv[1:]) + '\\n')\n"
"if os.environ.get('FAIL_TOOL') == sys.argv[1]:\n"
" print('fixture stdout', flush=True)\n"
" print('fixture stderr', file=sys.stderr, flush=True)\n"
" sys.exit(17)\n"
)
for name in ("sign_macos_code.sh", "notarize_macos_binary_with_akv.sh"):
(scripts / name).write_text(
'python3 "$CODEX_REPO_ROOT/record.py" "$(basename "$0")" "$@"\n'
)
(scripts / "notarize_with_akv.py").write_text(
"import os, runpy, sys\n"
"sys.argv.insert(1, 'notarize_with_akv.py')\n"
"runpy.run_path(os.environ['CODEX_REPO_ROOT'] + '/record.py')\n"
)
for name in ("rcodesign", "codesign", "lipo", "plutil"):
path = root / name
path.write_text(NATIVE_TOOL)
path.chmod(0o755)
certificate_der = self.profile["DeveloperCertificates"][0]
certificate = root / "certificate.pem"
certificate.write_text(ssl.DER_cert_to_PEM_cert(certificate_der))
(root / "certificate.der").write_bytes(certificate_der)
package = root / "package with spaces"
for relative in ("bin/codex", *bundle.HELPERS):
path = package / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("#!/bin/sh\nexit 0\n")
path.chmod(0o755)
(package / "codex-package.json").write_text(
json.dumps(
{
"variant": "codex",
"layoutVersion": 1,
"target": target,
"entrypoint": "bin/codex",
}
)
)
env = {
key: value
for key, value in os.environ.items()
if key not in ("GITHUB_WORKSPACE", "CODEX_PROVISIONING_HELPER")
}
env.update(
CODEX_REPO_ROOT=str(root),
RUNNER_TEMP=str(root),
TARGET=target,
PATH=f"{root}{os.pathsep}{os.environ['PATH']}",
CALL_LOG=str(root / "calls.jsonl"),
PROVISIONED_MACOS=str(provisioned).lower(),
OAI_AKV_SIGNING_CERTIFICATE_PEM=str(certificate),
MOCK_CERTIFICATE=str(root / "certificate.der"),
MOCK_SIGNING_SCRIPTS=str(scripts),
)
return package, helpers / "sign_macos_cli_package.py", env
def run_driver(self, driver, operation, package, env):
return subprocess.run(
[
sys.executable,
str(driver),
operation,
str(package),
"--profile",
str(self.configuration.profile),
"--profile-sha256",
self.configuration.profile_sha256,
"--certificate-sha256",
self.configuration.certificate_sha256,
"--team-id",
self.configuration.team_id,
],
env=env,
capture_output=True,
text=True,
timeout=30,
check=False,
)
def test_standard_and_provisioned_signing_and_verification(self):
for provisioned, target in itertools.product(
(False, True), ("aarch64-apple-darwin", "x86_64-apple-darwin")
):
with (
self.subTest(provisioned=provisioned, target=target),
tempfile.TemporaryDirectory() as directory,
):
root = Path(directory).resolve()
package, driver, env = self.fixture(root, provisioned, target)
result = self.run_driver(driver, "sign", package, env)
self.assertEqual(result.returncode, 0, result.stderr)
result = self.run_driver(driver, "verify", package, env)
self.assertEqual(result.returncode, 0, result.stderr)
calls = [
json.loads(line)
for line in (root / "calls.jsonl").read_text().splitlines()
]
signing = [call for call in calls if call[0] == "sign_macos_code.sh"]
self.assertEqual(
[call[call.index("--target") + 1] for call in signing],
[
str(package / relative)
for relative in (
bundle.APP if provisioned else "bin/codex",
*bundle.HELPERS,
)
],
)
self.assertEqual(
signing[0][signing[0].index("--entitlements") + 1],
str(
root
/ "signing-verification/codex-provisioned-entitlements.plist"
)
if provisioned
else str(
Path(env["MOCK_SIGNING_SCRIPTS"]) / "codex.entitlements.plist"
),
)
self.assertEqual(
["--entitlements" in call for call in signing],
[True, True, False, False],
)
self.assertEqual(
[call[0] for call in calls if call[0].startswith("notarize")],
["notarize_with_akv.py"]
if provisioned
else ["notarize_macos_binary_with_akv.sh"] * 4,
)
self.assertEqual(
[call[1:] for call in calls if call[0] == "lipo"],
[
[
str(package / relative),
"-verify_arch",
"arm64" if target.startswith("aarch64") else "x86_64",
]
for relative in (
bundle.EXECUTABLE if provisioned else "bin/codex",
*bundle.HELPERS,
)
],
)
if provisioned:
with zipfile.ZipFile(root / "provisioned-cli.zip") as archive:
self.assertEqual(
archive.read(
"CodexCLI.app/Contents/embedded.provisionprofile"
),
self.configuration.profile.read_bytes(),
)
self.assertEqual(
archive.read("bin/codex").decode(), bundle.LAUNCHER
)
self.assertTrue(
all(
helper in archive.namelist()
for helper in bundle.HELPERS
)
)
def test_signing_and_notarization_failures_stop_the_driver(self):
for tool in ("sign_macos_code.sh", "notarize_with_akv.py"):
with self.subTest(tool=tool), tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
package, driver, env = self.fixture(root, True)
result = self.run_driver(
driver, "sign", package, {**env, "FAIL_TOOL": tool}
)
self.assertNotEqual(result.returncode, 0)
calls = [
json.loads(line)
for line in (root / "calls.jsonl").read_text().splitlines()
]
self.assertEqual(calls[-1][0], tool)
if tool == "notarize_with_akv.py":
self.assertEqual(
(root / "signing-verification/notarization.log").read_text(),
"fixture stdout\nfixture stderr\n",
)
self.assertIn("fixture stdout\nfixture stderr\n", result.stdout)
else:
self.assertFalse((root / "provisioned-cli.zip").exists())
def test_verification_rejects_wrong_signing_identity_and_entitlements(self):
for provisioned, failure in itertools.product(
(False, True), ("MOCK_WRONG_SIGNING_IDENTITY", "MOCK_WRONG_ENTITLEMENTS")
):
with (
self.subTest(provisioned=provisioned, failure=failure),
tempfile.TemporaryDirectory() as directory,
):
root = Path(directory).resolve()
package, driver, env = self.fixture(root, provisioned)
signed = self.run_driver(driver, "sign", package, env)
self.assertEqual(signed.returncode, 0, signed.stderr)
result = self.run_driver(
driver, "verify", package, {**env, failure: "true"}
)
self.assertNotEqual(result.returncode, 0)
if failure == "MOCK_WRONG_SIGNING_IDENTITY":
self.assertIn(
"code failed to satisfy specified code requirement(s)",
result.stderr,
)
else:
self.assertIn("unexpected", result.stdout)
def test_cli_requires_independent_profile_configuration(self):
with tempfile.TemporaryDirectory() as directory:
result = subprocess.run(
[
sys.executable,
str(Path(bundle.__file__).with_name("sign_macos_cli_package.py")),
"verify",
directory,
],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 2)
self.assertIn("the following arguments are required", result.stderr)
if __name__ == "__main__":
unittest.main()

View File

@@ -43,8 +43,8 @@ jobs:
- name: Test standalone installer
run: python3 -m unittest discover -s scripts/install -p 'test_*.py'
- name: Test macOS notarization
run: python3 -m unittest discover -s .github/scripts/macos-signing -p 'test_notarize_with_akv.py'
- name: Test macOS signing and notarization
run: python3 -m unittest discover -s .github/scripts/macos-signing -p 'test_*.py'
- name: Setup pnpm
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5

View File

@@ -0,0 +1,213 @@
# Optional side artifacts only; no release, npm, installer, or DMG publication.
# Required secrets in the codesigning environment:
# - CODEX_CLI_PROVISIONING_PROFILE_BASE64: profile approved for public distribution.
# - CODEX_CLI_PROVISIONING_PROFILE_SHA256: reviewed lowercase SHA-256 of that profile.
# - CODEX_CLI_PROVISIONING_TEAM_ID: expected ten-character Apple Team ID.
# - AKV_CODESIGN_CERTIFICATE_SHA256: required signing certificate pin.
# Set repository variable CODEX_PROVISIONED_MACOS_CANDIDATE to true to enable
# candidates on normal tag releases; unset it or set it to false to disable them.
name: Provisioned macOS CLI candidate
on:
workflow_call:
permissions:
contents: read
env:
CODEX_REPO_ROOT: ${{ github.workspace }}
jobs:
sign:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/rust-v') && vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 45
environment:
name: codesigning
deployment: false
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
target: [aarch64-apple-darwin, x86_64-apple-darwin]
env:
TARGET: ${{ matrix.target }}
PROVISIONED_MACOS: "true"
PROFILE_SHA256: ${{ secrets.CODEX_CLI_PROVISIONING_PROFILE_SHA256 }}
CERTIFICATE_SHA256: ${{ secrets.AKV_CODESIGN_CERTIFICATE_SHA256 }}
TEAM_ID: ${{ secrets.CODEX_CLI_PROVISIONING_TEAM_ID }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
- name: Validate the independently approved public-distribution profile
env:
PROFILE_BASE64: ${{ secrets.CODEX_CLI_PROVISIONING_PROFILE_BASE64 }}
run: |
python3 - <<'PY'
import base64
import os
from pathlib import Path
profile = Path(os.environ["RUNNER_TEMP"]) / "codex-cli.provisionprofile"
profile.write_bytes(base64.b64decode("".join(os.environ["PROFILE_BASE64"].split()), validate=True))
PY
python3 .github/scripts/macos-signing/provisioned_macos_cli_package.py validate-profile \
--profile "$RUNNER_TEMP/codex-cli.provisionprofile" --profile-sha256 "$PROFILE_SHA256" \
--certificate-sha256 "$CERTIFICATE_SHA256" --team-id "$TEAM_ID"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.target }}
path: standard
- name: Extract the verified package from this tag-release run
run: |
python3 - <<'PY'
import json
import os
import tarfile
from pathlib import Path
with tarfile.open(f"standard/codex-package-{os.environ['TARGET']}.tar.gz") as archive:
archive.extractall("package", filter="data")
metadata = json.loads(Path("package/codex-package.json").read_text())
if metadata["target"] != os.environ["TARGET"]:
raise ValueError("Package target does not match this release job")
PY
- name: Set up the existing AKV signer
uses: ./.github/actions/setup-akv-pkcs11-codesigning
with:
rcodesign-blob-uri: ${{ secrets.AKV_CODESIGN_RCODESIGN_BLOB_URI }}
rcodesign-sha256: ${{ secrets.AKV_CODESIGN_RCODESIGN_SHA256 }}
akv-pkcs11-library-blob-uri: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_BLOB_URI }}
akv-pkcs11-library-sha256: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_SHA256 }}
azure-client-id: ${{ secrets.AKV_CODESIGN_AZURE_CLIENT_ID }}
azure-tenant-id: ${{ secrets.AKV_CODESIGN_TENANT }}
azure-subscription-id: ${{ secrets.AKV_CODESIGN_SUBSCRIPTION }}
key-vault-name: ${{ secrets.AKV_CODESIGN_KEY_VAULT_NAME }}
key-name: ${{ secrets.AKV_CODESIGN_KEY_NAME }}
key-version: ${{ secrets.AKV_CODESIGN_KEY_VERSION || '' }}
certificate-sha256: ${{ secrets.AKV_CODESIGN_CERTIFICATE_SHA256 }}
- name: Sign and notarize the provisioned bundle
env:
APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
run: |
python3 .github/scripts/macos-signing/sign_macos_cli_package.py sign package \
--profile "$RUNNER_TEMP/codex-cli.provisionprofile" --profile-sha256 "$PROFILE_SHA256" \
--certificate-sha256 "$CERTIFICATE_SHA256" --team-id "$TEAM_ID"
mkdir signed
tar -czf "signed/codex-provisioned-package-$TARGET.tar.gz" -C package \
bin codex-resources codex-path codex-package.json CodexCLI.app
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: provisioned-macos-signed-candidate-${{ matrix.target }}
path: signed/*.tar.gz
if-no-files-found: error
- name: Retain signing reports
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: provisioned-macos-signing-reports-${{ matrix.target }}
path: signing-verification/
if-no-files-found: warn
verify:
needs: sign
runs-on: macos-15-xlarge
timeout-minutes: 30
environment:
name: codesigning
deployment: false
# Read independent profile/certificate expectations without a signing token.
permissions:
contents: read
id-token: none
strategy:
fail-fast: false
matrix:
target: [aarch64-apple-darwin, x86_64-apple-darwin]
env:
TARGET: ${{ matrix.target }}
PROVISIONED_MACOS: "true"
PROFILE_SHA256: ${{ secrets.CODEX_CLI_PROVISIONING_PROFILE_SHA256 }}
CERTIFICATE_SHA256: ${{ secrets.AKV_CODESIGN_CERTIFICATE_SHA256 }}
TEAM_ID: ${{ secrets.CODEX_CLI_PROVISIONING_TEAM_ID }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: provisioned-macos-signed-candidate-${{ matrix.target }}
path: signed
- name: Extract candidate and load independent verification profile
env:
PROFILE_BASE64: ${{ secrets.CODEX_CLI_PROVISIONING_PROFILE_BASE64 }}
run: |
python3 - <<'PY'
import base64
import os
import tarfile
from pathlib import Path
profile = Path(os.environ["RUNNER_TEMP"]) / "codex-cli.provisionprofile"
profile.write_bytes(base64.b64decode("".join(os.environ["PROFILE_BASE64"].split()), validate=True))
with tarfile.open(f"signed/codex-provisioned-package-{os.environ['TARGET']}.tar.gz") as archive:
archive.extractall("package", filter="data")
PY
- name: Verify signatures, profile, architecture, stapling and Gatekeeper
run: |
profile_args=(--profile "$RUNNER_TEMP/codex-cli.provisionprofile"
--profile-sha256 "$PROFILE_SHA256" --certificate-sha256 "$CERTIFICATE_SHA256"
--team-id "$TEAM_ID")
python3 .github/scripts/macos-signing/sign_macos_cli_package.py verify package "${profile_args[@]}"
xcrun stapler staple package/CodexCLI.app
xcrun stapler validate package/CodexCLI.app
spctl --assess --type execute --verbose=4 package/CodexCLI.app
python3 .github/scripts/macos-signing/sign_macos_cli_package.py verify package "${profile_args[@]}"
mkdir verified
tar -czf "verified/codex-provisioned-package-$TARGET.tar.gz" -C package \
bin codex-resources codex-path codex-package.json CodexCLI.app
(cd verified && shasum -a 256 "codex-provisioned-package-$TARGET.tar.gz" > SHA256SUMS)
- uses: ./.github/actions/setup-ci
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.3"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.target }}-app-server
path: app-server
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.target }}-symbols
path: symbols
- name: Test the exact verified package, including sandboxed code mode
working-directory: scripts/codex_package/smoke_tests
env:
PYTHONPATH: ${{ github.workspace }}/sdk/python/src:${{ github.workspace }}/sdk/python/tests
run: |
# This repackaging does not change debug code. The normal release's
# primary symbols omit app-server symbols, so skip symbol-only tests.
uv run --frozen pytest -v --compression gzip --package-target "$TARGET" \
--cli-archive "$CODEX_REPO_ROOT/verified/codex-provisioned-package-$TARGET.tar.gz" \
--app-server-archive "$CODEX_REPO_ROOT/app-server/codex-app-server-package-$TARGET.tar.gz" \
--symbols-archive "$CODEX_REPO_ROOT/symbols/codex-symbols-$TARGET.tar.gz" \
test_codex_package.py -k 'not debug_symbols'
- name: Retain verified candidate only after package tests pass
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: provisioned-macos-verified-candidate-${{ matrix.target }}
path: verified/*
if-no-files-found: error
- name: Retain verification reports
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: provisioned-macos-verification-reports-${{ matrix.target }}
path: signing-verification/
if-no-files-found: warn

View File

@@ -1394,6 +1394,16 @@ jobs:
path: codex-rs/dist/${{ matrix.target }}/*
if-no-files-found: error
# Separate opt-in candidates never enter the normal release download patterns.
provisioned-macos-candidate:
needs: finalize-macos
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/rust-v') && vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/rust-release-provisioned-macos.yml
secrets: inherit
build-windows:
needs: tag-check
uses: ./.github/workflows/rust-release-windows.yml