Files
Jarvis/scripts/verify-codesign-settings.sh
james.maesandClaude Opus 4.7 3928c35ae1 fix(05-review): WR-07 verify helpers keep CODE_SIGNING_ALLOWED = NO
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>
2026-04-25 16:38:19 -05:00

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"