feat: add source runtime bundle target (#2277)

This commit is contained in:
José Almeida
2026-08-07 12:58:53 +01:00
committed by GitHub
parent 88002a60a3
commit 774ce6d9b4
15 changed files with 699 additions and 42 deletions
+15 -2
View File
@@ -4,7 +4,6 @@ class Kandev < Formula
desc "Manage tasks, orchestrate agents, review changes, and ship value"
homepage "https://github.com/kdlbs/kandev"
license "AGPL-3.0-only"
version "__VERSION__"
on_macos do
if Hardware::CPU.arm?
@@ -37,6 +36,20 @@ class Kandev < Formula
end
test do
assert_match "kandev launcher", shell_output("#{bin}/kandev --help")
assert_equal "v#{version}", shell_output("#{bin}/kandev --version").strip
ENV["KANDEV_HOME_DIR"] = testpath.to_s
ENV["KANDEV_DATABASE_PATH"] = (testpath/"kandev.db").to_s
ENV["KANDEV_SERVER_HOST"] = "127.0.0.1"
port = free_port
pid = spawn bin/"kandev", "--headless", "--port", port.to_s
health_url = "http://127.0.0.1:#{port}/health"
curl = "curl --silent --show-error --fail --retry 30 --retry-connrefused --retry-delay 1"
health = shell_output("#{curl} #{health_url}")
assert_match '"status":"ok"', health
assert_match "<title>Kandev</title>", shell_output("#{curl} http://127.0.0.1:#{port}/")
ensure
Process.kill("TERM", pid) if pid
Process.wait(pid) if pid
end
end
+73 -5
View File
@@ -1,13 +1,15 @@
#!/usr/bin/env bash
# Finalize the dist/kandev/ release layout from already-built pieces.
# Validate a release layout assembled from already-built pieces.
# Usage: package-bundle.sh [--bundle-dir DIR]
# Caller must have run, in this order:
# - Vite assets synced into apps/backend/internal/webapp/embedded/generated
# - go build ./cmd/{kandev,agentctl} plus remote agentctl helpers into dist/kandev/bin/...
# After this: dist/kandev/bin is ready to install or tar.
# - go build ./cmd/{kandev,agentctl} plus remote agentctl helpers into the bundle's bin directory
# The default bundle is dist/kandev; package managers may select another path.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BUNDLE="$ROOT_DIR/dist/kandev"
DARWIN_HELPER_VALIDATOR="$ROOT_DIR/scripts/release/validate-darwin-arm64-helper.mjs"
REMOTE_AGENTCTL_HELPERS=(
agentctl-linux-amd64
agentctl-linux-arm64
@@ -15,21 +17,87 @@ REMOTE_AGENTCTL_HELPERS=(
agentctl-darwin-amd64
)
if [ ! -f "$BUNDLE/bin/kandev" ] && [ ! -f "$BUNDLE/bin/kandev.exe" ]; then
usage() {
echo "usage: $0 [--bundle-dir DIR]" >&2
}
case "$#" in
0) ;;
2)
if [ "$1" != "--bundle-dir" ] || [ -z "$2" ]; then
usage
exit 2
fi
BUNDLE="$2"
;;
*)
usage
exit 2
;;
esac
if [ "$BUNDLE" = "/" ]; then
echo "bundle directory must not be /" >&2
exit 2
fi
launcher="kandev"
if [ ! -f "$BUNDLE/bin/$launcher" ] && [ -f "$BUNDLE/bin/kandev.exe" ]; then
launcher="kandev.exe"
fi
if [ ! -f "$BUNDLE/bin/$launcher" ]; then
echo "Missing native launcher in $BUNDLE/bin; build cmd/kandev first" >&2
exit 1
fi
if [ ! -x "$BUNDLE/bin/$launcher" ]; then
echo "Runtime binary $launcher is not executable in $BUNDLE/bin" >&2
exit 1
fi
if [ ! -f "$BUNDLE/bin/agentctl" ] && [ ! -f "$BUNDLE/bin/agentctl.exe" ]; then
agentctl="agentctl"
if [ ! -f "$BUNDLE/bin/$agentctl" ] && [ -f "$BUNDLE/bin/agentctl.exe" ]; then
agentctl="agentctl.exe"
fi
if [ ! -f "$BUNDLE/bin/$agentctl" ]; then
echo "Missing agentctl in $BUNDLE/bin; build cmd/agentctl first" >&2
exit 1
fi
if [ ! -x "$BUNDLE/bin/$agentctl" ]; then
echo "Runtime binary $agentctl is not executable in $BUNDLE/bin" >&2
exit 1
fi
for helper in "${REMOTE_AGENTCTL_HELPERS[@]}"; do
if [ ! -f "$BUNDLE/bin/$helper" ]; then
echo "Missing remote agentctl helper $helper in $BUNDLE/bin; run make -C apps/backend build-agentctl-remote first" >&2
exit 1
fi
if [ ! -x "$BUNDLE/bin/$helper" ]; then
echo "Runtime binary $helper is not executable in $BUNDLE/bin" >&2
exit 1
fi
if [ "$helper" = "agentctl-darwin-arm64" ]; then
if ! command -v node >/dev/null 2>&1; then
echo "Node.js is required to validate $helper" >&2
exit 1
fi
node "$DARWIN_HELPER_VALIDATOR" "$BUNDLE/bin/$helper"
fi
done
while IFS= read -r -d '' entry; do
artifact="${entry##*/}"
expected=false
for required in "$launcher" "$agentctl" "${REMOTE_AGENTCTL_HELPERS[@]}"; do
if [ "$artifact" = "$required" ]; then
expected=true
break
fi
done
if [ "$expected" != true ]; then
echo "Unexpected runtime artifact $artifact in $BUNDLE/bin" >&2
exit 1
fi
done < <(find "$BUNDLE/bin" -mindepth 1 -maxdepth 1 -print0)
echo "Bundle assembled at $BUNDLE"
+292
View File
@@ -0,0 +1,292 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
PACKAGE_SCRIPT="$ROOT_DIR/scripts/release/package-bundle.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
write_macho() {
local path="$1"
local signature="$2"
node - "$path" "$signature" <<'NODE'
const fs = require("node:fs");
const path = process.argv[2];
const signature = process.argv[3];
const headerSize = 32;
const loadCommandSize = 16;
const codeDirectorySize = 83;
const superBlobSize = 20 + codeDirectorySize;
const signatureOffset = headerSize + loadCommandSize;
const data = Buffer.alloc(signatureOffset + (signature === "signed" ? superBlobSize : 0));
data.writeUInt32LE(0xfeedfacf, 0);
data.writeUInt32LE(0x0100000c, 4);
data.writeUInt32LE(1, 16);
data.writeUInt32LE(loadCommandSize, 20);
data.writeUInt32LE(signature === "unsigned" ? 0x1b : 0x1d, headerSize);
data.writeUInt32LE(loadCommandSize, headerSize + 4);
if (signature !== "unsigned") {
data.writeUInt32LE(signatureOffset, headerSize + 8);
data.writeUInt32LE(superBlobSize, headerSize + 12);
}
if (signature === "signed") {
data.writeUInt32BE(0xfade0cc0, signatureOffset);
data.writeUInt32BE(superBlobSize, signatureOffset + 4);
data.writeUInt32BE(1, signatureOffset + 8);
data.writeUInt32BE(0, signatureOffset + 12);
data.writeUInt32BE(20, signatureOffset + 16);
const codeDirectoryOffset = signatureOffset + 20;
data.writeUInt32BE(0xfade0c02, codeDirectoryOffset);
data.writeUInt32BE(codeDirectorySize, codeDirectoryOffset + 4);
data.writeUInt32BE(0x20001, codeDirectoryOffset + 8);
data.writeUInt32BE(0x2, codeDirectoryOffset + 12);
data.writeUInt32BE(51, codeDirectoryOffset + 16);
data.writeUInt32BE(44, codeDirectoryOffset + 20);
data.writeUInt32BE(0, codeDirectoryOffset + 24);
data.writeUInt32BE(1, codeDirectoryOffset + 28);
data.writeUInt32BE(signatureOffset, codeDirectoryOffset + 32);
data.writeUInt8(32, codeDirectoryOffset + 36);
data.writeUInt8(2, codeDirectoryOffset + 37);
data.writeUInt8(12, codeDirectoryOffset + 39);
data.write("kandev\0", codeDirectoryOffset + 44, "utf8");
}
fs.writeFileSync(path, data, { mode: 0o755 });
NODE
}
make_complete_bundle() {
local bundle_dir="$1"
mkdir -p "$bundle_dir/bin"
touch \
"$bundle_dir/bin/kandev" \
"$bundle_dir/bin/agentctl" \
"$bundle_dir/bin/agentctl-linux-amd64" \
"$bundle_dir/bin/agentctl-linux-arm64" \
"$bundle_dir/bin/agentctl-darwin-arm64" \
"$bundle_dir/bin/agentctl-darwin-amd64"
chmod +x "$bundle_dir/bin/"*
write_macho "$bundle_dir/bin/agentctl-darwin-arm64" signed
}
custom_bundle="$TMP_DIR/custom bundle"
make_complete_bundle "$custom_bundle"
if ! bash "$PACKAGE_SCRIPT" --bundle-dir "$custom_bundle" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator rejected a complete custom bundle: $(cat "$TMP_DIR/err")"
fi
grep -Fq "Bundle assembled at $custom_bundle" "$TMP_DIR/out" ||
fail "validator did not report the custom bundle path"
extra_bundle="$TMP_DIR/extra artifact"
cp -R "$custom_bundle" "$extra_bundle"
touch "$extra_bundle/bin/debugger"
chmod +x "$extra_bundle/bin/debugger"
if bash "$PACKAGE_SCRIPT" --bundle-dir "$extra_bundle" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator accepted a seventh runtime artifact"
fi
grep -Fq "Unexpected runtime artifact debugger" "$TMP_DIR/err" ||
fail "extra-artifact error was not actionable"
missing_bundle="$TMP_DIR/missing helper"
cp -R "$custom_bundle" "$missing_bundle"
rm "$missing_bundle/bin/agentctl-linux-arm64"
if bash "$PACKAGE_SCRIPT" --bundle-dir "$missing_bundle" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator accepted a bundle with a missing remote helper"
fi
grep -Fq "Missing remote agentctl helper agentctl-linux-arm64" "$TMP_DIR/err" ||
fail "missing-helper error was not actionable"
non_executable_bundle="$TMP_DIR/non-executable helper"
cp -R "$custom_bundle" "$non_executable_bundle"
chmod -x "$non_executable_bundle/bin/agentctl-darwin-arm64"
if bash "$PACKAGE_SCRIPT" --bundle-dir "$non_executable_bundle" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator accepted a non-executable runtime binary"
fi
grep -Fq "Runtime binary agentctl-darwin-arm64 is not executable" "$TMP_DIR/err" ||
fail "non-executable-binary error was not actionable"
unsigned_bundle="$TMP_DIR/unsigned darwin helper"
cp -R "$custom_bundle" "$unsigned_bundle"
write_macho "$unsigned_bundle/bin/agentctl-darwin-arm64" unsigned
if bash "$PACKAGE_SCRIPT" --bundle-dir "$unsigned_bundle" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator accepted an unsigned darwin/arm64 helper"
fi
grep -Fq "agentctl-darwin-arm64 is not code-signed" "$TMP_DIR/err" ||
fail "unsigned-helper error was not actionable"
invalid_signature_bundle="$TMP_DIR/invalid darwin signature"
cp -R "$custom_bundle" "$invalid_signature_bundle"
write_macho "$invalid_signature_bundle/bin/agentctl-darwin-arm64" invalid-signature
if bash "$PACKAGE_SCRIPT" --bundle-dir "$invalid_signature_bundle" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator accepted a darwin/arm64 helper with missing signature data"
fi
grep -Fq "agentctl-darwin-arm64 does not contain a valid code signature" "$TMP_DIR/err" ||
fail "invalid-signature error was not actionable"
unparsable_bundle="$TMP_DIR/unparsable darwin helper"
cp -R "$custom_bundle" "$unparsable_bundle"
printf 'not a Mach-O\n' > "$unparsable_bundle/bin/agentctl-darwin-arm64"
chmod +x "$unparsable_bundle/bin/agentctl-darwin-arm64"
if bash "$PACKAGE_SCRIPT" --bundle-dir "$unparsable_bundle" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator accepted an unparsable darwin/arm64 helper"
fi
grep -Fq "agentctl-darwin-arm64 is not a parsable thin darwin/arm64 Mach-O" "$TMP_DIR/err" ||
fail "unparsable-helper error was not actionable"
if bash "$PACKAGE_SCRIPT" --bundle-dir / >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator accepted the filesystem root as a bundle"
fi
grep -Fq "bundle directory must not be /" "$TMP_DIR/err" ||
fail "unsafe-root error was not actionable"
if bash "$PACKAGE_SCRIPT" --unknown >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "validator accepted an unknown argument"
fi
grep -Fq "usage:" "$TMP_DIR/err" || fail "invalid arguments did not print usage"
if ! make --dry-run -C "$ROOT_DIR/apps/backend" build-runtime \
VERSION=1.2.3 GOFLAGS=-trimpath >"$TMP_DIR/backend-dry-run" 2>"$TMP_DIR/err"; then
fail "backend runtime target is unavailable: $(cat "$TMP_DIR/err")"
fi
expected_runtime_binaries="$(printf '%s\n' \
bin/agentctl \
bin/agentctl-darwin-amd64 \
bin/agentctl-darwin-arm64 \
bin/agentctl-linux-amd64 \
bin/agentctl-linux-arm64 \
bin/kandev)"
actual_runtime_binaries="$(grep -oE 'bin/[[:alnum:]_.-]+' "$TMP_DIR/backend-dry-run" | sort -u)"
[ "$actual_runtime_binaries" = "$expected_runtime_binaries" ] ||
fail "backend runtime target built an unexpected binary set: $actual_runtime_binaries"
grep -Fq -- "-tags fts5" "$TMP_DIR/backend-dry-run" ||
fail "backend runtime target omitted the fts5 build tag"
grep -Fq -- "-X main.Version=1.2.3" "$TMP_DIR/backend-dry-run" ||
fail "backend runtime target did not forward VERSION"
runtime_output="$TMP_DIR/runtime output"
if ! make --dry-run -C "$ROOT_DIR" runtime-bundle \
PNPM="pnpm with current" \
GOFLAGS=-trimpath \
RUNTIME_VERSION=1.2.3 \
RUNTIME_BUNDLE_DIR="$runtime_output" \
>"$TMP_DIR/runtime-dry-run" 2>"$TMP_DIR/err"; then
fail "root runtime target is unavailable: $(cat "$TMP_DIR/err")"
fi
grep -Fq "pnpm with current --filter @kandev/web build" "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not build the Vite web package"
grep -Fq "internal/webapp/embedded/generated" "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not sync embedded web assets"
grep -Fq "build-runtime" "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not invoke the minimal backend target"
grep -Fq "VERSION=\"1.2.3\"" "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not forward RUNTIME_VERSION"
grep -Fq "GOFLAGS=\"-trimpath\"" "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not forward GOFLAGS"
grep -Fq -- 'requested_bundle_dir='"\"$runtime_output\"" "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not retain the selected output directory"
grep -Fq -- '--bundle-dir "$staging_bundle_dir"' "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not validate the staged bundle"
grep -Fq 'resolved_bundle_dir=' "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not canonicalize the bundle directory before writing"
grep -Fq 'pwd -P' "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not resolve symlinked bundle directories"
grep -Fq 'mktemp -d "$resolved_bundle_dir/.runtime-bundle.XXXXXX"' "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not create a clean staging bundle"
if grep -Fq "rm -rf \"$runtime_output/bin\"" "$TMP_DIR/runtime-dry-run"; then
fail "runtime target recursively deleted through an unresolved bundle path"
fi
for binary in \
kandev \
agentctl \
agentctl-linux-amd64 \
agentctl-linux-arm64 \
agentctl-darwin-arm64 \
agentctl-darwin-amd64; do
grep -Fq "apps/backend/bin/$binary" "$TMP_DIR/runtime-dry-run" ||
fail "runtime target did not package $binary"
done
if grep -Eq 'pnpm( with current)? .*install|playwright install' "$TMP_DIR/runtime-dry-run"; then
fail "runtime target attempted to install dependencies or Playwright"
fi
runtime_backend="$TMP_DIR/runtime backend"
mkdir -p "$runtime_backend/bin"
for binary in \
kandev \
agentctl \
agentctl-linux-amd64 \
agentctl-linux-arm64 \
agentctl-darwin-arm64 \
agentctl-darwin-amd64; do
printf 'new %s\n' "$binary" > "$runtime_backend/bin/$binary"
chmod +x "$runtime_backend/bin/$binary"
done
write_macho "$runtime_backend/bin/agentctl-darwin-arm64" signed
failed_copy_bundle="$TMP_DIR/failed copy bundle"
make_complete_bundle "$failed_copy_bundle"
cp -R "$failed_copy_bundle/bin" "$TMP_DIR/failed copy snapshot"
rm "$runtime_backend/bin/agentctl-darwin-amd64"
if make -C "$ROOT_DIR" runtime-bundle \
MAKE=true \
BACKEND_DIR="$runtime_backend" \
RUNTIME_VERSION=1.2.3 \
RUNTIME_BUNDLE_DIR="$failed_copy_bundle" \
>"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "runtime target ignored a failed binary copy"
fi
if ! diff -r "$TMP_DIR/failed copy snapshot" "$failed_copy_bundle/bin" >/dev/null; then
fail "failed binary copy changed the previous runtime bundle"
fi
printf 'new agentctl-darwin-amd64\n' > "$runtime_backend/bin/agentctl-darwin-amd64"
chmod +x "$runtime_backend/bin/agentctl-darwin-amd64"
stale_bundle="$TMP_DIR/stale bundle"
make_complete_bundle "$stale_bundle"
touch "$stale_bundle/bin/debugger"
chmod +x "$stale_bundle/bin/debugger"
if ! make -C "$ROOT_DIR" runtime-bundle \
MAKE=true \
BACKEND_DIR="$runtime_backend" \
RUNTIME_VERSION=1.2.3 \
RUNTIME_BUNDLE_DIR="$stale_bundle" \
>"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "repeat runtime bundle failed instead of replacing stale artifacts: $(cat "$TMP_DIR/err")"
fi
[ ! -e "$stale_bundle/bin/debugger" ] ||
fail "repeat runtime bundle retained a stale artifact"
root_symlink="$TMP_DIR/root symlink"
ln -s / "$root_symlink"
if make -C "$ROOT_DIR" runtime-bundle \
MAKE=true \
RUNTIME_BUNDLE_DIR="$root_symlink" \
>"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
fail "runtime target accepted a bundle directory resolving to the filesystem root"
fi
grep -Fq "RUNTIME_BUNDLE_DIR must not resolve to /" "$TMP_DIR/out" ||
fail "symlinked-root error was not actionable"
if ! make --dry-run -C "$ROOT_DIR" service-bundle \
RUNTIME_VERSION=1.2.3 \
SERVICE_BUNDLE_DIR="$TMP_DIR/service bundle" \
>"$TMP_DIR/service-dry-run" 2>"$TMP_DIR/err"; then
fail "service bundle dry run failed: $(cat "$TMP_DIR/err")"
fi
grep -Fq 'RUNTIME_VERSION="1.2.3"' "$TMP_DIR/service-dry-run" ||
fail "service bundle replaced the release SemVer with another version"
echo "PASS: runtime bundle contract"
-1
View File
@@ -118,7 +118,6 @@ mkdir -p "$(dirname "$FORMULA_PATH")"
GITHUB_BASE="https://github.com/kdlbs/kandev/releases/download/${TAG}"
sed \
-e "s|__VERSION__|$VERSION|g" \
-e "s|__GITHUB_BASE__|$GITHUB_BASE|g" \
-e "s|__SHA_MACOS_ARM64__|$SHA_MACOS_ARM64|g" \
-e "s|__SHA_MACOS_X64__|$SHA_MACOS_X64|g" \
@@ -0,0 +1,141 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
const MACHO_MAGIC_64_LE = 0xfeedfacf;
const CPU_TYPE_ARM64 = 0x0100000c;
const LC_CODE_SIGNATURE = 0x1d;
const MACHO_HEADER_SIZE = 32;
const LOAD_COMMAND_HEADER_SIZE = 8;
const LINKEDIT_DATA_COMMAND_SIZE = 16;
const CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0;
const CSMAGIC_CODEDIRECTORY = 0xfade0c02;
const CSSLOT_CODEDIRECTORY = 0;
const CODE_DIRECTORY_BASE_SIZE = 44;
function fail(file, message) {
process.stderr.write(`Runtime binary ${path.basename(file)} ${message}\n`);
process.exit(1);
}
function isValidCodeDirectory(blob, signedDataOffset) {
if (blob.length < CODE_DIRECTORY_BASE_SIZE) return false;
if (blob.readUInt32BE(0) !== CSMAGIC_CODEDIRECTORY) return false;
const length = blob.readUInt32BE(4);
if (length < CODE_DIRECTORY_BASE_SIZE || length > blob.length) return false;
const version = blob.readUInt32BE(8);
const hashOffset = blob.readUInt32BE(16);
const identifierOffset = blob.readUInt32BE(20);
const specialSlotCount = blob.readUInt32BE(24);
const codeSlotCount = blob.readUInt32BE(28);
const codeLimit = blob.readUInt32BE(32);
const hashSize = blob.readUInt8(36);
const hashType = blob.readUInt8(37);
const pageSize = blob.readUInt8(39);
if (version < 0x20000 || hashSize === 0 || hashType === 0 || pageSize > 31) return false;
if (identifierOffset < CODE_DIRECTORY_BASE_SIZE || identifierOffset >= length) return false;
const identifierEnd = blob.indexOf(0, identifierOffset);
if (identifierEnd < identifierOffset || identifierEnd >= length) return false;
if (codeSlotCount === 0 || codeLimit === 0 || codeLimit > signedDataOffset) return false;
const specialHashBytes = specialSlotCount * hashSize;
const codeHashBytes = codeSlotCount * hashSize;
if (hashOffset < CODE_DIRECTORY_BASE_SIZE + specialHashBytes) return false;
if (hashOffset > length || codeHashBytes > length - hashOffset) return false;
return true;
}
function isValidSuperBlob(data, dataOffset, dataSize) {
if (dataSize < 12 || dataOffset > data.length || dataSize > data.length - dataOffset) return false;
const blob = data.subarray(dataOffset, dataOffset + dataSize);
if (blob.readUInt32BE(0) !== CSMAGIC_EMBEDDED_SIGNATURE) return false;
const length = blob.readUInt32BE(4);
const count = blob.readUInt32BE(8);
if (length < 12 || length > blob.length || count > Math.floor((length - 12) / 8)) return false;
const indexEnd = 12 + count * 8;
let hasCodeDirectory = false;
for (let index = 0; index < count; index += 1) {
const entryOffset = 12 + index * 8;
const slotType = blob.readUInt32BE(entryOffset);
const nestedOffset = blob.readUInt32BE(entryOffset + 4);
if (nestedOffset < indexEnd || nestedOffset > length - 8) return false;
const nestedLength = blob.readUInt32BE(nestedOffset + 4);
if (nestedLength < 8 || nestedLength > length - nestedOffset) return false;
if (slotType === CSSLOT_CODEDIRECTORY) {
const nestedBlob = blob.subarray(nestedOffset, nestedOffset + nestedLength);
if (!isValidCodeDirectory(nestedBlob, dataOffset)) return false;
hasCodeDirectory = true;
}
}
return hasCodeDirectory;
}
function codeSignatureStatus(data) {
const commandCount = data.readUInt32LE(16);
const commandBytes = data.readUInt32LE(20);
if (
commandBytes > data.length - MACHO_HEADER_SIZE ||
commandCount > Math.floor(commandBytes / LOAD_COMMAND_HEADER_SIZE)
) {
return "malformed";
}
const commandsEnd = MACHO_HEADER_SIZE + commandBytes;
let offset = MACHO_HEADER_SIZE;
for (let index = 0; index < commandCount; index += 1) {
if (offset + LOAD_COMMAND_HEADER_SIZE > commandsEnd) return "malformed";
const command = data.readUInt32LE(offset);
const commandSize = data.readUInt32LE(offset + 4);
if (commandSize < LOAD_COMMAND_HEADER_SIZE || commandSize > commandsEnd - offset) {
return "malformed";
}
if (command === LC_CODE_SIGNATURE) {
if (commandSize !== LINKEDIT_DATA_COMMAND_SIZE) return "invalid";
const dataOffset = data.readUInt32LE(offset + 8);
const dataSize = data.readUInt32LE(offset + 12);
return isValidSuperBlob(data, dataOffset, dataSize) ? "valid" : "invalid";
}
offset += commandSize;
}
return offset === commandsEnd ? "missing" : "malformed";
}
const file = process.argv[2];
if (!file) {
process.stderr.write(`usage: ${path.basename(process.argv[1])} FILE\n`);
process.exit(2);
}
let data;
try {
data = fs.readFileSync(file);
} catch (error) {
fail(file, `could not be read: ${error.message}`);
}
if (
data.length < MACHO_HEADER_SIZE ||
data.readUInt32LE(0) !== MACHO_MAGIC_64_LE ||
data.readUInt32LE(4) !== CPU_TYPE_ARM64
) {
fail(file, "is not a parsable thin darwin/arm64 Mach-O");
}
const signatureStatus = codeSignatureStatus(data);
if (signatureStatus === "malformed") {
fail(file, "is not a parsable thin darwin/arm64 Mach-O");
}
if (signatureStatus === "invalid") {
fail(file, "does not contain a valid code signature");
}
if (signatureStatus === "missing") {
fail(file, "is not code-signed; Apple Silicon will refuse to run it");
}