Helpers ship with CODE_SIGNING_ALLOWED = NO so codesign.sh can apply per-helper identity (deepest-first walker, per-helper entitlements). If a developer flips this to YES via Xcode UI, helpers get signed with the parent target's identity and per-helper entitlements get stripped (MCP-06 / R1 H-B2 hazard). Adds two pbxproj lint rules in verify-codesign-settings.sh: - Rule 4: any `CODE_SIGNING_ALLOWED = YES` anywhere fails the build. - Rule 5: each of the three helper targets (mcp-time, mcp-clipboard, mcp-applescript) must declare `CODE_SIGNING_ALLOWED = NO` at least once in its buildSettings. Awk walks the pbxproj alphabetically (CODE_SIGNING_ALLOWED comes before PRODUCT_NAME inside each buildSettings block) and asserts the held value when hitting the PRODUCT_NAME line. Verified by perturbing the local pbxproj to YES — script exits 1 with the expected error message. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
93 lines
4.5 KiB
Bash
Executable File
93 lines
4.5 KiB
Bash
Executable File
#!/bin/bash
|
|
# verify-codesign-settings.sh — pbxproj linter for codesign hygiene.
|
|
#
|
|
# Fails the build if the pbxproj carries:
|
|
# (1) CodeSignOnCopy = YES anywhere — Xcode's "Code Sign On Copy" re-signs
|
|
# nested helpers with the parent target's identity, stripping per-helper
|
|
# entitlements (MCP-06 / R1 H-B2).
|
|
# (2) --deep anywhere in codesign-adjacent settings — TN2206 / Pitfall #3.
|
|
# (3) OTHER_CODE_SIGN_FLAGS missing --options=runtime or --timestamp —
|
|
# Hardened Runtime + secure timestamp must be on every sign call.
|
|
#
|
|
# Strips NeXTSTEP-plist comments (// and /* */) before grep so the linter is
|
|
# not self-invalidated by a comment that happens to mention a forbidden flag.
|
|
set -euo pipefail
|
|
|
|
PBXPROJ="${SRCROOT:-$(pwd)}/Jarvis.xcodeproj/project.pbxproj"
|
|
if [ ! -f "$PBXPROJ" ]; then
|
|
echo "error: pbxproj not found at $PBXPROJ" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Strip // line comments and /* ... */ block comments before grep.
|
|
STRIPPED="$(/usr/bin/sed 's|//.*$||; s|/\*.*\*/||g' "$PBXPROJ")"
|
|
|
|
# Rule 1: CodeSignOnCopy = YES must NOT appear anywhere in the pbxproj.
|
|
if echo "$STRIPPED" | /usr/bin/grep -E 'CodeSignOnCopy[[:space:]]*=[[:space:]]*YES' > /dev/null; then
|
|
echo "error: pbxproj contains 'CodeSignOnCopy = YES' — forbidden (MCP-06 / R1 H-B2)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Rule 2: --deep flag must NOT appear anywhere codesign-adjacent.
|
|
if echo "$STRIPPED" | /usr/bin/grep -E -- '(--deep|"--deep")' > /dev/null; then
|
|
echo "error: pbxproj contains '--deep' flag — forbidden (TN2206 / Pitfall #3)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Rule 3: OTHER_CODE_SIGN_FLAGS must include --options=runtime and --timestamp.
|
|
if ! echo "$STRIPPED" | /usr/bin/grep -E 'OTHER_CODE_SIGN_FLAGS.*--options=runtime' > /dev/null; then
|
|
echo "error: pbxproj OTHER_CODE_SIGN_FLAGS must include --options=runtime" >&2
|
|
exit 1
|
|
fi
|
|
if ! echo "$STRIPPED" | /usr/bin/grep -E 'OTHER_CODE_SIGN_FLAGS.*--timestamp' > /dev/null; then
|
|
echo "error: pbxproj OTHER_CODE_SIGN_FLAGS must include --timestamp" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Rule 4 (WR-07 REVIEW 05): each helper target MUST keep
|
|
# `CODE_SIGNING_ALLOWED = NO` for both Debug and Release. The codesign.sh
|
|
# postBuildScript handles per-helper signing with deepest-first walk and
|
|
# per-helper entitlements. If a developer flips CODE_SIGNING_ALLOWED on
|
|
# (e.g., via Xcode UI), Xcode will sign helpers with the parent target's
|
|
# identity, stripping per-helper entitlements (MCP-06 / R1 H-B2 hazard).
|
|
#
|
|
# We can't assert "every helper has NO" without parsing target/config
|
|
# pairs out of the pbxproj. Instead we assert: every PRESENCE of
|
|
# CODE_SIGNING_ALLOWED in helper-target-context must be `= NO`. xcodegen
|
|
# only writes CODE_SIGNING_ALLOWED for targets that override the base; if
|
|
# any line says `CODE_SIGNING_ALLOWED = YES` anywhere in the pbxproj, fail.
|
|
if echo "$STRIPPED" | /usr/bin/grep -E 'CODE_SIGNING_ALLOWED[[:space:]]*=[[:space:]]*YES' > /dev/null; then
|
|
echo "error: pbxproj contains 'CODE_SIGNING_ALLOWED = YES' — helper targets must keep this NO so codesign.sh applies per-helper identity (WR-07)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Rule 5 (WR-07): explicit positive check that the three helper targets
|
|
# each declare CODE_SIGNING_ALLOWED = NO at least once in the pbxproj —
|
|
# defense-in-depth so a deletion of the project.yml override gets caught.
|
|
#
|
|
# Strategy: pbxproj buildSettings are emitted alphabetically, so
|
|
# CODE_SIGNING_ALLOWED comes BEFORE PRODUCT_NAME inside a target's
|
|
# settings block. Walk the file holding the most-recent
|
|
# CODE_SIGNING_ALLOWED value in a state variable; when we hit
|
|
# `PRODUCT_NAME = $helper`, assert the held value was NO. A target
|
|
# without an explicit toggle would inherit base settings (also NO under
|
|
# project.yml) — but xcodegen emits the override every time so absence
|
|
# would mean someone deleted the override, which is what WR-07 catches.
|
|
for HELPER in mcp-time mcp-clipboard mcp-applescript; do
|
|
if ! /usr/bin/awk -v h="$HELPER" '
|
|
/buildSettings[[:space:]]*=/ { last_csa = "" }
|
|
/CODE_SIGNING_ALLOWED[[:space:]]*=[[:space:]]*NO/ { last_csa = "NO" }
|
|
/CODE_SIGNING_ALLOWED[[:space:]]*=[[:space:]]*YES/ { last_csa = "YES" }
|
|
$0 ~ "PRODUCT_NAME[[:space:]]*=[[:space:]]*\""h"\"" {
|
|
if (last_csa == "NO") { found = 1 }
|
|
else if (last_csa == "YES") { print "YES detected for "h > "/dev/stderr"; exit 2 }
|
|
}
|
|
END { exit (found ? 0 : 1) }
|
|
' "$PBXPROJ"; then
|
|
echo "error: helper target '$HELPER' is missing 'CODE_SIGNING_ALLOWED = NO' in pbxproj — WR-07 requires per-helper signing via codesign.sh" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
echo "verify-codesign-settings: pbxproj lint passed"
|