From 3f55c5b74a7a076517f42ef5f2892d71f76272be Mon Sep 17 00:00:00 2001 From: Philipp Wagner Date: Sat, 22 Aug 2026 16:23:57 +0200 Subject: [PATCH] Sign and notarize macOS builds jpackage cannot produce a distributable macOS bundle on its own. It ad-hoc signs the embedded runtime and then re-runs codesign on the same files without --force, which codesign rejects; and "--type dmg --app-image" re-signs the app it is handed, replacing a Developer ID signature with an ad-hoc one. So the build now creates an unsigned app-image, signs it from the inside out, and wraps it with hdiutil. Apple's notary service also unpacks JARs and checks the native libraries inside them, which sqlite-jdbc ships for both architectures. Those are signed before the bundle is sealed, since rewriting a JAR afterwards would invalidate the seal. A preflight check verifies Apple's two criteria locally, so a missed binary costs seconds rather than a round trip to the notary service. Two long-standing defects surfaced while testing and are fixed here: the bundle identifier defaulted to the main class's package name (kst4contest.view instead of de.x08.KST4Contest), and every release reported version 1.0 in Finder because --app-version was never passed. Neither affects existing users: the app keeps its settings in ~/.praktiKST, independent of the bundle ID. Both workflows call the same script the local Mac uses, so the two cannot drift apart. Signing needs a keychain that can answer a UI prompt, which a runner cannot, so ci-import-cert.sh creates a throwaway keychain whose password is generated per job and discarded with it. Notarization goes through an App Store Connect API key and needs no keychain at all. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WYcmHra3YndA1ahkHeNdJ2 --- .github/workflows/nightly-artifacts.yml | 49 +++-- .github/workflows/tagged-release.yml | 48 ++-- .gitignore | 3 + packaging/macos/build-signed-dmg.sh | 265 +++++++++++++++++++++++ packaging/macos/ci-import-cert.sh | 64 ++++++ packaging/macos/kst4contest.entitlements | 21 ++ 6 files changed, 406 insertions(+), 44 deletions(-) create mode 100755 packaging/macos/build-signed-dmg.sh create mode 100755 packaging/macos/ci-import-cert.sh create mode 100644 packaging/macos/kst4contest.entitlements diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml index d45ab8b..4e813f3 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -7,6 +7,7 @@ on: paths: - "src/**" - "packaging/icons/**" + - "packaging/macos/**" - "pom.xml" - "mvnw" - "mvnw.cmd" @@ -634,34 +635,38 @@ jobs: - name: Ensure mvnw is executable run: chmod +x mvnw - - name: Build JAR and copy runtime dependencies - run: | - ./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs - cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar - - - name: Build macOS DMG with jpackage - run: | - mkdir -p dist - ADD_MODULES="$(java packaging/AddModules.java)" - jpackage \ - --type dmg \ - --name KST4Contest \ - --icon packaging/icons/kst4contest.icns \ - --input target/dist-libs \ - --main-jar app.jar \ - --main-class kst4contest.view.Kst4ContestApplication \ - --module-path target/dist-libs \ - --add-modules "$ADD_MODULES" \ - --dest dist - + - name: Import signing certificate env: - MACOSX_DEPLOYMENT_TARGET: "13.0" + MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} + MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} + run: ./packaging/macos/ci-import-cert.sh + + # Builds the jar, signs the app bundle and every native library inside it, + # wraps it into a DMG and has Apple notarize the result. Same script the + # local Mac uses, so the two cannot drift apart. + - name: Build signed and notarized DMG + env: + MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} + NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + NOTARY_ISSUER: ${{ secrets.MACOS_NOTARY_ISSUER }} + run: | + printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8" + export NOTARY_KEY="$RUNNER_TEMP/notary.p8" + ./packaging/macos/build-signed-dmg.sh + + - name: Remove signing credentials + if: always() + run: | + rm -f "$RUNNER_TEMP/notary.p8" + if [ -n "${SIGNING_KEYCHAIN:-}" ]; then + security delete-keychain "$SIGNING_KEYCHAIN" || true + fi - name: Rename DMG artifact run: | DMG=$(ls dist/*.dmg | head -n 1) if [ -z "$DMG" ]; then - echo "No DMG produced by jpackage" && exit 1 + echo "No DMG produced by the build" && exit 1 fi mv "$DMG" "dist/${ASSET_BASENAME}-macos-${ARCH}.dmg" diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml index 0541144..716f2b5 100644 --- a/.github/workflows/tagged-release.yml +++ b/.github/workflows/tagged-release.yml @@ -522,35 +522,39 @@ jobs: - name: Ensure mvnw is executable run: chmod +x mvnw - - name: Build JAR and copy runtime dependencies - run: | - ./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs - cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar - - - name: Build macOS DMG with jpackage - run: | - mkdir -p dist - ADD_MODULES="$(java packaging/AddModules.java)" - jpackage \ - --type dmg \ - --name KST4Contest \ - --icon packaging/icons/kst4contest.icns \ - --input target/dist-libs \ - --main-jar app.jar \ - --main-class kst4contest.view.Kst4ContestApplication \ - --module-path target/dist-libs \ - --add-modules "$ADD_MODULES" \ - --dest dist - + - name: Import signing certificate env: - MACOSX_DEPLOYMENT_TARGET: "13.0" + MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} + MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} + run: ./packaging/macos/ci-import-cert.sh + + # Builds the jar, signs the app bundle and every native library inside it, + # wraps it into a DMG and has Apple notarize the result. Same script the + # local Mac uses, so the two cannot drift apart. + - name: Build signed and notarized DMG + env: + MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} + NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + NOTARY_ISSUER: ${{ secrets.MACOS_NOTARY_ISSUER }} + run: | + printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8" + export NOTARY_KEY="$RUNNER_TEMP/notary.p8" + ./packaging/macos/build-signed-dmg.sh + + - name: Remove signing credentials + if: always() + run: | + rm -f "$RUNNER_TEMP/notary.p8" + if [ -n "${SIGNING_KEYCHAIN:-}" ]; then + security delete-keychain "$SIGNING_KEYCHAIN" || true + fi - name: Rename DMG artifact run: | ARCH=$(uname -m) DMG=$(ls dist/*.dmg | head -n 1) if [ -z "$DMG" ]; then - echo "No DMG produced by jpackage" && exit 1 + echo "No DMG produced by the build" && exit 1 fi mv "$DMG" "dist/KST4Contest-${{ github.ref_name }}-macos-${ARCH}.dmg" diff --git a/.gitignore b/.gitignore index ae13c6e..4c9f50a 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ website/_site/ # Local secrets for act testing .secrets + +# Apple notarization private keys - never commit these +*.p8 diff --git a/packaging/macos/build-signed-dmg.sh b/packaging/macos/build-signed-dmg.sh new file mode 100755 index 0000000..bbcaa92 --- /dev/null +++ b/packaging/macos/build-signed-dmg.sh @@ -0,0 +1,265 @@ +#!/usr/bin/env bash +# +# Local signed (and optionally notarized) macOS build. +# +# jpackage cannot sign the app itself: it ad-hoc signs the embedded runtime and +# then re-runs codesign on the same files without --force, which codesign +# rejects with "is already signed". So this builds an unsigned app-image, signs +# it from the inside out ourselves, and only then wraps it into a DMG. +# +# Required: +# SIGNING_IDENTITY The name part of the Developer ID Application certificate, +# without the "Developer ID Application: " prefix. Example: +# SIGNING_IDENTITY="Philipp Wagner (ABCDE12345)" +# List available ones with: +# security find-identity -v -p codesigning +# +# Optional: +# Notarization, either as three separate values (what CI uses)... +# NOTARY_KEY Path to the App Store Connect .p8 private key +# NOTARY_KEY_ID The key's ID, also part of the .p8 filename +# NOTARY_ISSUER The issuer UUID, shown above the key list in the portal +# ...or as a keychain profile previously created with +# NOTARY_PROFILE xcrun notarytool store-credentials +# +# With neither, the build is signed but not notarized -- enough to test +# locally, not enough to distribute. +# +set -euo pipefail + +cd "$(dirname "$0")/../.." +REPO_ROOT="$PWD" + +BUNDLE_ID="de.x08.KST4Contest" +ENTITLEMENTS="packaging/macos/kst4contest.entitlements" + +if [ -z "${SIGNING_IDENTITY:-}" ]; then + echo "SIGNING_IDENTITY is not set. Available signing identities:" >&2 + security find-identity -v -p codesigning >&2 || true + exit 1 +fi +FULL_IDENTITY="Developer ID Application: $SIGNING_IDENTITY" + +# notarytool takes either an API key triple or a stored keychain profile. The +# triple needs no keychain at all, which is why CI uses it. +NOTARY_ARGS=() +if [ -n "${NOTARY_KEY:-}" ] && [ -n "${NOTARY_KEY_ID:-}" ] && [ -n "${NOTARY_ISSUER:-}" ]; then + NOTARY_ARGS=(--key "$NOTARY_KEY" --key-id "$NOTARY_KEY_ID" --issuer "$NOTARY_ISSUER") +elif [ -n "${NOTARY_PROFILE:-}" ]; then + NOTARY_ARGS=(--keychain-profile "$NOTARY_PROFILE") +fi + +echo "==> Building JAR and collecting runtime dependencies" +chmod +x mvnw +./mvnw -B -DskipTests package \ + dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs +JAR="$(ls -t target/praktiKST-*.jar | head -n 1)" +cp "$JAR" target/dist-libs/app.jar + +# jpackage only accepts a numeric major[.minor[.patch]] as the macOS bundle +# version, so a Maven qualifier like "-nightly" has to be trimmed off. +POM_VERSION="${JAR##*/praktiKST-}" +POM_VERSION="${POM_VERSION%.jar}" +APP_VERSION="$(printf '%s' "$POM_VERSION" | sed -e 's/[^0-9.].*$//' -e 's/\.*$//')" +[ -n "$APP_VERSION" ] || { echo "Could not derive app version from $JAR" >&2; exit 1; } +echo "==> Version: $POM_VERSION -> bundle version $APP_VERSION" + +echo "==> Step 1/4: jpackage app-image (unsigned)" +rm -rf dist +mkdir -p dist +ADD_MODULES="$(java packaging/AddModules.java)" + +MACOSX_DEPLOYMENT_TARGET="13.0" jpackage \ + --type app-image \ + --name KST4Contest \ + --app-version "$APP_VERSION" \ + --icon packaging/icons/kst4contest.icns \ + --input target/dist-libs \ + --main-jar app.jar \ + --main-class kst4contest.view.Kst4ContestApplication \ + --module-path target/dist-libs \ + --add-modules "$ADD_MODULES" \ + --mac-package-identifier "$BUNDLE_ID" \ + --mac-package-name KST4Contest \ + --dest dist/appimage + +APP="dist/appimage/KST4Contest.app" +[ -d "$APP" ] || { echo "jpackage produced no app image" >&2; exit 1; } + +echo "==> Step 2/4: signing bundle contents (this takes a few minutes)" + +# Apple's notary service unpacks JARs and checks the native libraries inside +# them. sqlite-jdbc ships libsqlitejdbc.dylib for both architectures that way, +# and an unsigned binary in there fails the whole submission. So sign those +# first: the app bundle's seal covers Contents/app, and rewriting a JAR +# afterwards would invalidate it. +echo " scanning jars for native libraries" +find "$APP/Contents/app" -name '*.jar' -type f | while read -r JARPATH; do + # Only unpack jars that can plausibly hold a native library. Note the + # plain grep: "grep -q" exits at the first match, which hands unzip a + # SIGPIPE, and under "set -o pipefail" that failure becomes the pipeline's + # status -- inverting this very test. + if ! unzip -l "$JARPATH" | grep -E '\.(dylib|jnilib|so)$' >/dev/null; then + continue + fi + + JARABS="$(cd "$(dirname "$JARPATH")" && pwd)/$(basename "$JARPATH")" + JARTMP="$(mktemp -d)" + unzip -q "$JARABS" -d "$JARTMP" + + NATIVES="$(mktemp)" + ( cd "$JARTMP" && find . -type f \( -name '*.dylib' -o -name '*.jnilib' -o -name '*.so' \) \ + | while read -r n; do + if [ "$(file --mime-type -b "$n")" = "application/x-mach-binary" ]; then + printf '%s\n' "${n#./}" + fi + done ) > "$NATIVES" + + if [ -s "$NATIVES" ]; then + echo " $(basename "$JARPATH"): $(wc -l < "$NATIVES" | tr -d ' ') native lib(s)" + ( cd "$JARTMP" && xargs -I {} codesign --force --timestamp --options runtime \ + --sign "$FULL_IDENTITY" {} < "$NATIVES" ) + # Update in place rather than repacking, so the rest of the jar -- + # manifest, module descriptor, entry order -- stays byte for byte. + ( cd "$JARTMP" && xargs jar --update --file "$JARABS" < "$NATIVES" ) + fi + + rm -rf "$JARTMP" "$NATIVES" +done + +# Every Mach-O file has to carry its own signature before the enclosing bundle +# can be sealed, so collect them first. jpackage leaves them ad-hoc signed, +# hence --force on every call. +# file(1) pads its output into columns when given several arguments at once, +# so ask it one file at a time with -b and get an unambiguous answer. +MACHO_LIST="$(mktemp)" +find "$APP" -type f -print0 | while IFS= read -r -d '' f; do + case "$(file --mime-type -b "$f")" in + application/x-mach-binary) printf '%s\n' "$f" ;; + esac +done > "$MACHO_LIST" + +COUNT="$(wc -l < "$MACHO_LIST" | tr -d ' ')" +echo " $COUNT Mach-O files to sign" + +# codesign contacts Apple's timestamp server on every call, so run a handful in +# parallel or this takes far longer than it needs to. +xargs -P 8 -I {} codesign --force --timestamp --options runtime \ + --sign "$FULL_IDENTITY" {} < "$MACHO_LIST" +rm -f "$MACHO_LIST" + +# The embedded JDK is a bundle in its own right and must be sealed before the +# app that contains it. +echo " sealing embedded runtime" +codesign --force --timestamp --options runtime \ + --sign "$FULL_IDENTITY" "$APP/Contents/runtime" + +# Entitlements go on the outermost bundle: the hardened runtime derives the +# process's entitlements from the main executable's signature. +echo " sealing app bundle" +codesign --force --timestamp --options runtime \ + --entitlements "$ENTITLEMENTS" \ + --sign "$FULL_IDENTITY" "$APP" + +# Apple rejects the whole submission over a single unsigned native library, and +# a round trip to the notary service costs minutes. Check its two criteria -- +# a Developer ID authority and a secure timestamp -- locally first. +echo " preflight: verifying native libraries inside jars" +PREFLIGHT_ERRORS="$(mktemp)" +find "$APP/Contents/app" -name '*.jar' -type f | while read -r JARPATH; do + if ! unzip -l "$JARPATH" | grep -E '\.(dylib|jnilib|so)$' >/dev/null; then + continue + fi + CHECKTMP="$(mktemp -d)" + unzip -q "$JARPATH" -d "$CHECKTMP" + find "$CHECKTMP" -type f \( -name '*.dylib' -o -name '*.jnilib' -o -name '*.so' \) \ + | while read -r NATIVE; do + [ "$(file --mime-type -b "$NATIVE")" = "application/x-mach-binary" ] || continue + INFO="$(codesign -dv --verbose=2 "$NATIVE" 2>&1 || true)" + LABEL="$(basename "$JARPATH")/${NATIVE#"$CHECKTMP"/}" + printf '%s' "$INFO" | grep -q "Authority=Developer ID Application" \ + || echo "$LABEL: not signed with a Developer ID certificate" >> "$PREFLIGHT_ERRORS" + printf '%s' "$INFO" | grep -q "Timestamp=" \ + || echo "$LABEL: signature has no secure timestamp" >> "$PREFLIGHT_ERRORS" + done + rm -rf "$CHECKTMP" +done +if [ -s "$PREFLIGHT_ERRORS" ]; then + echo "ERROR: these would fail notarization:" >&2 + sed 's/^/ /' "$PREFLIGHT_ERRORS" >&2 + rm -f "$PREFLIGHT_ERRORS" + exit 1 +fi +rm -f "$PREFLIGHT_ERRORS" +echo " preflight ok" + +echo "==> Step 3/4: building the dmg" + +# Not with jpackage: "jpackage --type dmg --app-image" re-signs the app it is +# handed, replacing our Developer ID signature with an ad-hoc one and dropping +# the hardened runtime flag. hdiutil copies the bundle verbatim instead. +DMG="dist/KST4Contest-${APP_VERSION}.dmg" +STAGE="$(mktemp -d)" +# ditto rather than cp -R: it preserves the extended attributes the code +# signature depends on. +ditto "$APP" "$STAGE/KST4Contest.app" +ln -s /Applications "$STAGE/Applications" + +hdiutil create -volname "KST4Contest" -srcfolder "$STAGE" \ + -ov -format UDZO -quiet "$DMG" +rm -rf "$STAGE" +[ -f "$DMG" ] || { echo "hdiutil produced no DMG" >&2; exit 1; } + +# Signing the DMG itself is not what Gatekeeper judges -- that is the .app +# inside -- but Apple expects the container to be signed too. +codesign --force --timestamp --sign "$FULL_IDENTITY" "$DMG" +echo "==> Built $DMG" + +echo "==> Step 4/4: verification" + +if [ ${#NOTARY_ARGS[@]} -gt 0 ]; then + echo " submitting for notarization (waits for Apple's verdict)" + # Without a timeout a stalled submission would hang a CI job forever. + xcrun notarytool submit "$DMG" "${NOTARY_ARGS[@]}" --wait --timeout 30m + echo " stapling ticket" + xcrun stapler staple "$DMG" +else + echo " no notarization credentials set, skipping notarization" +fi + +# Everything below inspects the app as it actually ships, mounted from the DMG, +# rather than the staging copy on disk. +MOUNT_POINT="$(mktemp -d)" +hdiutil attach "$DMG" -nobrowse -quiet -mountpoint "$MOUNT_POINT" +trap 'hdiutil detach "$MOUNT_POINT" -quiet 2>/dev/null || hdiutil detach "$MOUNT_POINT" -force -quiet 2>/dev/null || true' EXIT +SHIPPED_APP="$MOUNT_POINT/KST4Contest.app" + +echo "--- codesign --verify on the app inside the DMG ---" +codesign --verify --deep --strict --verbose=2 "$SHIPPED_APP" + +echo "--- app identity ---" +codesign -dv --verbose=2 "$SHIPPED_APP" 2>&1 | grep -iE "identifier|authority|teamidentifier|flags" + +# An ad-hoc signature here means something along the way re-signed the bundle. +if codesign -dv "$SHIPPED_APP" 2>&1 | grep -q "adhoc"; then + echo "ERROR: the app inside the DMG is ad-hoc signed, not Developer ID signed" >&2 + exit 1 +fi + +echo "--- entitlements as signed ---" +codesign -d --entitlements - --xml "$SHIPPED_APP" 2>/dev/null | plutil -convert xml1 -o - - | grep -E "key|true|false" + +echo "--- dmg identity ---" +codesign -dv --verbose=2 "$DMG" 2>&1 | grep -iE "authority|teamidentifier" | head -2 + +echo "--- spctl assessment ---" +# Without notarization this reports "rejected"; that is expected. +spctl --assess --type execute --verbose=4 "$SHIPPED_APP" || true + +if [ ${#NOTARY_ARGS[@]} -gt 0 ]; then + echo "--- stapler validate ---" + xcrun stapler validate "$DMG" +fi + +echo +echo "Done: $REPO_ROOT/$DMG" diff --git a/packaging/macos/ci-import-cert.sh b/packaging/macos/ci-import-cert.sh new file mode 100755 index 0000000..e2dd87a --- /dev/null +++ b/packaging/macos/ci-import-cert.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# +# Import the Developer ID certificate into a throwaway keychain on a CI runner. +# +# A runner cannot answer the keychain's authorization dialog, so the login +# keychain is unusable there. This creates a dedicated keychain instead, whose +# password is generated here and needed nowhere else -- it is discarded with the +# keychain at the end of the job. +# +# Reads from the environment: +# MACOS_CERT_P12 base64 of the exported .p12 +# MACOS_CERT_PASSWORD the password that .p12 was exported with +# +# Exports to $GITHUB_ENV: +# SIGNING_IDENTITY for packaging/macos/build-signed-dmg.sh +# SIGNING_KEYCHAIN so the cleanup step knows what to delete +# +set -euo pipefail + +: "${MACOS_CERT_P12:?MACOS_CERT_P12 is not set}" +: "${MACOS_CERT_PASSWORD:?MACOS_CERT_PASSWORD is not set}" +: "${RUNNER_TEMP:?RUNNER_TEMP is not set}" +: "${GITHUB_ENV:?GITHUB_ENV is not set}" + +KEYCHAIN="$RUNNER_TEMP/kst4contest-signing.keychain-db" +KEYCHAIN_PASSWORD="$(uuidgen)" +CERT="$RUNNER_TEMP/cert.p12" + +printf '%s' "$MACOS_CERT_P12" | base64 --decode > "$CERT" + +security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" +# Keychains re-lock after five minutes by default, which would strand a build +# halfway through signing. +security set-keychain-settings -lut 21600 "$KEYCHAIN" +security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" + +security import "$CERT" -k "$KEYCHAIN" -P "$MACOS_CERT_PASSWORD" \ + -T /usr/bin/codesign -T /usr/bin/security +rm -f "$CERT" + +# Lets codesign reach the private key without the UI prompt a runner has no way +# of answering. +security set-key-partition-list -S apple-tool:,apple:,codesign: \ + -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" >/dev/null + +# codesign searches the keychain list, so the new keychain has to be on it -- +# added to whatever the runner already had, not in place of it. +EXISTING_KEYCHAINS="$(security list-keychains -d user | sed -e 's/^[[:space:]]*"//' -e 's/"$//')" +# shellcheck disable=SC2086 +security list-keychains -d user -s "$KEYCHAIN" $EXISTING_KEYCHAINS + +IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN" \ + | sed -n 's/.*"Developer ID Application: \(.*\)".*/\1/p' | head -n 1)" + +if [ -z "$IDENTITY" ]; then + echo "No 'Developer ID Application' identity found in the imported certificate." >&2 + echo "What the keychain does contain:" >&2 + security find-identity -v -p codesigning "$KEYCHAIN" >&2 + exit 1 +fi + +echo "Imported identity: Developer ID Application: $IDENTITY" +echo "SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV" +echo "SIGNING_KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV" diff --git a/packaging/macos/kst4contest.entitlements b/packaging/macos/kst4contest.entitlements new file mode 100644 index 0000000..139d88e --- /dev/null +++ b/packaging/macos/kst4contest.entitlements @@ -0,0 +1,21 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + + com.apple.security.cs.disable-library-validation + + + + com.apple.security.network.client + + +