Skip to content

Commit 0601d2f

Browse files
committed
init
0 parents  commit 0601d2f

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# build artifacts
2+
EntitlementJail.app/
3+
EntitlementJail.zip
4+
5+
# da mac
6+
.DS_Store

EntitlementJail.entitlements

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
3+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4+
<plist version="1.0">
5+
<dict>
6+
<key>com.apple.security.app-sandbox</key>
7+
<true/>
8+
</dict>
9+
</plist>

README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Entitlement Jail
2+
3+
Run an arbitrary command inside a macOS `.app` that is signed with specific entitlements (by default: App Sandbox). This is useful for quickly testing how a tool behaves when "jailed" by a sandbox/entitlement set.
4+
5+
The app executable is a tiny Rust wrapper in `runner/` that `exec()`s the command you pass, so the child process inherits the app's sandbox/entitlements.
6+
7+
## Requirements
8+
9+
- macOS 14+ (see `LSMinimumSystemVersion` in the app `Info.plist`)
10+
- Rust toolchain (to build `runner/`)
11+
- Xcode Command Line Tools (for `codesign`)
12+
13+
## Build
14+
15+
1. Build the runner:
16+
17+
```sh
18+
cd runner
19+
cargo build --release
20+
cd ..
21+
```
22+
23+
2. Create an app bundle and copy the binary in:
24+
25+
```sh
26+
APP=EntitlementJail.app
27+
mkdir -p "$APP/Contents/MacOS"
28+
cp runner/target/release/runner "$APP/Contents/MacOS/entitlement-jail"
29+
```
30+
31+
3. Add an `Info.plist`:
32+
33+
```sh
34+
cat > "$APP/Contents/Info.plist" <<'PLIST'
35+
<?xml version="1.0" encoding="UTF-8"?>
36+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
37+
<plist version="1.0">
38+
<dict>
39+
<key>CFBundleExecutable</key><string>entitlement-jail</string>
40+
<key>CFBundleIdentifier</key><string>com.yourteam.entitlement-jail</string>
41+
<key>CFBundleName</key><string>EntitlementJail</string>
42+
<key>CFBundlePackageType</key><string>APPL</string>
43+
<key>CFBundleShortVersionString</key><string>1.0</string>
44+
<key>CFBundleVersion</key><string>1</string>
45+
<key>LSMinimumSystemVersion</key><string>14.0</string>
46+
</dict>
47+
</plist>
48+
PLIST
49+
```
50+
51+
4. Sign the app with the entitlements in `EntitlementJail.entitlements` (ad-hoc signing is fine for local testing):
52+
53+
```sh
54+
codesign --force --deep --sign - --entitlements EntitlementJail.entitlements "$APP"
55+
```
56+
57+
## Usage
58+
59+
Run the app's executable and pass the command to "jail":
60+
61+
```sh
62+
./EntitlementJail.app/Contents/MacOS/entitlement-jail /usr/bin/id
63+
./EntitlementJail.app/Contents/MacOS/entitlement-jail /bin/ls "$HOME/Desktop"
64+
```
65+
66+
## Customizing entitlements
67+
68+
Edit `EntitlementJail.entitlements`, then re-run the `codesign ...` step.
69+
70+
For example, to allow outbound network access, add:
71+
72+
```xml
73+
<key>com.apple.security.network.client</key>
74+
<true/>
75+
```

build-macos.sh

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Usage:
5+
# ./build-macos.sh
6+
# IDENTITY='Developer ID Application: ...' ./build-macos.sh
7+
#
8+
# Produces:
9+
# EntitlementJail.app
10+
# EntitlementJail.zip (ready for notarytool submit)
11+
12+
APP_NAME="EntitlementJail"
13+
APP_BUNDLE="${APP_NAME}.app"
14+
ZIP_NAME="${APP_NAME}.zip"
15+
16+
# Paths in this repo
17+
RUNNER_MANIFEST="runner/Cargo.toml"
18+
ENTITLEMENTS_PLIST="EntitlementJail.entitlements"
19+
INFO_PLIST_TEMPLATE="Info.plist"
20+
21+
# Optional: embed extra payloads if present
22+
EMBED_FENCERUNNER_PATH="${EMBED_FENCERUNNER_PATH:-}" # e.g. /path/to/fencerunner
23+
EMBED_PROBES_DIR="${EMBED_PROBES_DIR:-}" # e.g. /path/to/probes
24+
25+
# Signing identity. Prefer env override; otherwise require user to set it.
26+
IDENTITY="${IDENTITY:-}"
27+
28+
if [[ -z "${IDENTITY}" ]]; then
29+
cat <<'EOF' 1>&2
30+
ERROR: IDENTITY is not set.
31+
32+
Set it to your Developer ID Application identity string, for example:
33+
IDENTITY='Developer ID Application: Adam Hyland (42D369QV8E)' ./build-macos.sh
34+
35+
You can find valid identities via:
36+
security find-identity -v -p codesigning
37+
EOF
38+
exit 2
39+
fi
40+
41+
echo "==> Building Rust runner"
42+
cargo build --manifest-path "${RUNNER_MANIFEST}" --release
43+
44+
# Find the built binary. (Assumes standard Cargo layout.)
45+
RUNNER_BIN="runner/target/release/runner"
46+
if [[ ! -x "${RUNNER_BIN}" ]]; then
47+
echo "ERROR: expected runner binary at ${RUNNER_BIN}" 1>&2
48+
exit 2
49+
fi
50+
51+
echo "==> Assembling app bundle: ${APP_BUNDLE}"
52+
rm -rf "${APP_BUNDLE}"
53+
mkdir -p "${APP_BUNDLE}/Contents/MacOS"
54+
mkdir -p "${APP_BUNDLE}/Contents/Resources"
55+
56+
# Copy Info.plist
57+
if [[ ! -f "${INFO_PLIST_TEMPLATE}" ]]; then
58+
echo "ERROR: missing ${INFO_PLIST_TEMPLATE} at repo root" 1>&2
59+
exit 2
60+
fi
61+
cp "${INFO_PLIST_TEMPLATE}" "${APP_BUNDLE}/Contents/Info.plist"
62+
63+
# Install main executable
64+
cp "${RUNNER_BIN}" "${APP_BUNDLE}/Contents/MacOS/entitlement-jail"
65+
chmod +x "${APP_BUNDLE}/Contents/MacOS/entitlement-jail"
66+
67+
# Optional: embed fencerunner
68+
if [[ -n "${EMBED_FENCERUNNER_PATH}" ]]; then
69+
if [[ ! -x "${EMBED_FENCERUNNER_PATH}" ]]; then
70+
echo "ERROR: EMBED_FENCERUNNER_PATH is set but not executable: ${EMBED_FENCERUNNER_PATH}" 1>&2
71+
exit 2
72+
fi
73+
echo "==> Embedding fencerunner: ${EMBED_FENCERUNNER_PATH}"
74+
cp "${EMBED_FENCERUNNER_PATH}" "${APP_BUNDLE}/Contents/MacOS/fencerunner"
75+
chmod +x "${APP_BUNDLE}/Contents/MacOS/fencerunner"
76+
fi
77+
78+
# Optional: embed probes directory
79+
if [[ -n "${EMBED_PROBES_DIR}" ]]; then
80+
if [[ ! -d "${EMBED_PROBES_DIR}" ]]; then
81+
echo "ERROR: EMBED_PROBES_DIR is set but not a directory: ${EMBED_PROBES_DIR}" 1>&2
82+
exit 2
83+
fi
84+
echo "==> Embedding probes dir: ${EMBED_PROBES_DIR}"
85+
rsync -a --delete "${EMBED_PROBES_DIR}/" "${APP_BUNDLE}/Contents/Resources/probes/"
86+
fi
87+
88+
# Sanity check entitlements
89+
if [[ ! -f "${ENTITLEMENTS_PLIST}" ]]; then
90+
echo "ERROR: missing entitlements plist: ${ENTITLEMENTS_PLIST}" 1>&2
91+
exit 2
92+
fi
93+
94+
echo "==> Codesigning (Developer ID + hardened runtime + entitlements)"
95+
# Sign nested code first if you embed anything executable beyond the main binary.
96+
# --deep is convenient here; if you later add more embedded executables/frameworks,
97+
# consider an explicit signing pass over each nested code item.
98+
codesign \
99+
--force \
100+
--deep \
101+
--options runtime \
102+
--timestamp \
103+
--entitlements "${ENTITLEMENTS_PLIST}" \
104+
-s "${IDENTITY}" \
105+
"${APP_BUNDLE}"
106+
107+
echo "==> Verifying signature + entitlements"
108+
codesign --verify --deep --strict --verbose=2 "${APP_BUNDLE}"
109+
codesign --display --entitlements - "${APP_BUNDLE}" >/dev/null
110+
111+
echo "==> Creating zip (for notarization): ${ZIP_NAME}"
112+
rm -f "${ZIP_NAME}"
113+
/usr/bin/ditto -c -k --keepParent "${APP_BUNDLE}" "${ZIP_NAME}"
114+
115+
echo
116+
echo "DONE:"
117+
echo " - ${APP_BUNDLE}"
118+
echo " - ${ZIP_NAME}"
119+
echo
120+
echo "Next (notarize with your saved profile):"
121+
cat <<EOF
122+
xcrun notarytool submit "${ZIP_NAME}" --keychain-profile "dev-profile" --wait
123+
xcrun stapler staple "${APP_BUNDLE}"
124+
spctl -a -vv "${APP_BUNDLE}"
125+
EOF

0 commit comments

Comments
 (0)