@hasna/recordings 0.2.10 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +350 -35
- package/bun.lock +3 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
- package/dist/cli/index.js +1728 -320
- package/dist/cli/macos-permissions.d.ts +13 -0
- package/dist/cli/macos-permissions.d.ts.map +1 -0
- package/dist/cli/options.d.ts +12 -0
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +2 -1
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/db/remote-storage.d.ts +5 -1
- package/dist/db/remote-storage.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +628 -147
- package/dist/lib/bun-runtime.d.ts +11 -0
- package/dist/lib/bun-runtime.d.ts.map +1 -0
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/enhancer.d.ts +10 -2
- package/dist/lib/enhancer.d.ts.map +1 -1
- package/dist/lib/install-maintenance.d.ts +24 -0
- package/dist/lib/install-maintenance.d.ts.map +1 -0
- package/dist/lib/machine.d.ts +2 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts +2 -1
- package/dist/lib/recorder.d.ts.map +1 -1
- package/dist/lib/recording-create-identity.d.ts +13 -0
- package/dist/lib/recording-create-identity.d.ts.map +1 -0
- package/dist/lib/release-install-policy.d.ts +31 -0
- package/dist/lib/release-install-policy.d.ts.map +1 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +977 -194
- package/dist/sdk/index.js +5 -1
- package/dist/sdk/v1.generated.d.ts +4 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud-config.d.ts +9 -0
- package/dist/server/cloud-config.d.ts.map +1 -0
- package/dist/server/cloud-readiness.d.ts +10 -0
- package/dist/server/cloud-readiness.d.ts.map +1 -0
- package/dist/server/cloud.d.ts +9 -18
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +1654 -304
- package/dist/server/migrate-command.d.ts +11 -0
- package/dist/server/migrate-command.d.ts.map +1 -0
- package/dist/server/openapi.d.ts +22 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts +8 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts +6 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +515 -80
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +7 -4
- package/packaging/macos/Empty.entitlements +5 -0
- package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
- package/packaging/macos/Verifier.entitlements +10 -0
- package/packaging/macos/artifact-verifier.sb +33 -0
- package/packaging/macos/build_release_pkg.sh +872 -0
- package/packaging/macos/managed_bootstrap.sh +623 -0
- package/packaging/macos/pkgutil_fingerprint.awk +37 -0
- package/packaging/macos/release_lifecycle.ts +463 -0
- package/packaging/macos/scripts/postinstall +565 -0
- package/packaging/macos/scripts/preinstall +297 -0
- package/scripts/build_companion_cli.sh +337 -0
- package/scripts/build_native_fs_guard.sh +59 -0
- package/scripts/generate-sdk.ts +19 -1
- package/scripts/install_macos_app.sh +1922 -77
- package/scripts/macos_artifact.ts +5316 -0
- package/scripts/migrate.ts +38 -10
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +1167 -0
- package/scripts/native_fs_guard.ts +158 -0
- package/scripts/release-guard.ts +20 -1
- package/scripts/resolve_tailscale_cli.sh +389 -0
- package/scripts/smoke_macos_app.sh +573 -0
- package/src/native/Recordings/App/ContentView.swift +83 -0
- package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
- package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
- package/src/native/Recordings/App/RecordingsApp.swift +367 -44
- package/src/native/Recordings/App/RecordingsListView.swift +130 -0
- package/src/native/Recordings/App/RecordingsStore.swift +206 -0
- package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
- package/src/native/Recordings/App/SidebarView.swift +212 -0
- package/src/native/Recordings/App/Theme.swift +87 -0
- package/src/native/Recordings/Package.resolved +5 -5
- package/src/native/Recordings/Package.swift +62 -2
- package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
- package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
- package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
- package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
- package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
- package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
- package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
- package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
- package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
- package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
- package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
- package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
- package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
- package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
- package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
- package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
- package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
- package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
- package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
- package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
- package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
- package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
- package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
- package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
- package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
- package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
- package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
- package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
- package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
- package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
- package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
- package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
- package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
- package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
- package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
- package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
- package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
- package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
- package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
- package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
- package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
- package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
- package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
- package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
- package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
- package/src/native/Recordings/build.sh +1990 -29
- package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
umask 077
|
|
4
|
+
|
|
5
|
+
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
|
|
6
|
+
export LC_ALL=C
|
|
7
|
+
export LANG=C
|
|
8
|
+
export TZ=UTC0
|
|
9
|
+
|
|
10
|
+
PACKAGE_BOOTSTRAP_ID="__RECORDINGS_PACKAGE_BOOTSTRAP_ID__"
|
|
11
|
+
AUTHORIZATION_ROOT="/private/var/db/com.hasna.recordings.bootstrap-authorization"
|
|
12
|
+
AUTHORIZATION_JOURNAL="${AUTHORIZATION_ROOT}/journal"
|
|
13
|
+
BROKER="/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
14
|
+
CLIENT="/Applications/Recordings.app/Contents/Helpers/recordings-update-client"
|
|
15
|
+
VERIFIER="/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
16
|
+
APP="/Applications/Recordings.app"
|
|
17
|
+
STATE_ROOT="/Library/Application Support/Hasna/Recordings/Updates"
|
|
18
|
+
TRUST_ROOT="/Library/Application Support/Hasna/Recordings/Trust"
|
|
19
|
+
BOOTSTRAP_ROOT="/Library/Application Support/Hasna/Recordings/Bootstrap"
|
|
20
|
+
RELEASE_SEQUENCE_FILE="${BOOTSTRAP_ROOT}/release-sequence"
|
|
21
|
+
KEY_EPOCH_FILE="${BOOTSTRAP_ROOT}/key-epoch"
|
|
22
|
+
SOURCE_SHA_FILE="${BOOTSTRAP_ROOT}/source-sha"
|
|
23
|
+
BROKER_POLICY="${TRUST_ROOT}/broker-policy.json"
|
|
24
|
+
VERIFIER_SANDBOX="${TRUST_ROOT}/artifact-verifier.sb"
|
|
25
|
+
BOOTSTRAP_MARKER="${TRUST_ROOT}/bootstrap-marker.json"
|
|
26
|
+
LAUNCHD_PLIST="/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
|
|
27
|
+
VERIFIER_USER="_recordingsverify"
|
|
28
|
+
VERIFIER_GROUP="_recordingsverify"
|
|
29
|
+
|
|
30
|
+
fail() {
|
|
31
|
+
echo "Recordings updater bootstrap failed: $*" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
path_exists_or_symlink() {
|
|
36
|
+
[ -e "$1" ] || [ -L "$1" ]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
require_no_extended_acl() {
|
|
40
|
+
local path="$1"
|
|
41
|
+
local acl_listing acl_status
|
|
42
|
+
acl_listing="$(/bin/ls -lade "$path")" || fail "could not inspect extended ACLs on $path"
|
|
43
|
+
if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
|
|
44
|
+
NR == 1 && length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
|
|
45
|
+
NR > 1 && $1 ~ /^[0-9]+:$/ { found = 1 }
|
|
46
|
+
END { exit found ? 0 : 1 }
|
|
47
|
+
'; then
|
|
48
|
+
fail "unexpected extended ACL on package trust path: $path"
|
|
49
|
+
else
|
|
50
|
+
acl_status=$?
|
|
51
|
+
[ "$acl_status" -eq 1 ] || fail "could not evaluate extended ACLs on $path"
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
read_journal_value() {
|
|
56
|
+
local key="$1"
|
|
57
|
+
/usr/bin/awk -F= -v key="$key" '
|
|
58
|
+
$1 == key { count += 1; value = substr($0, length(key) + 2) }
|
|
59
|
+
END { if (count != 1) exit 1; print value }
|
|
60
|
+
' "$AUTHORIZATION_JOURNAL"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
require_authorization_journal() {
|
|
64
|
+
path_exists_or_symlink "$AUTHORIZATION_ROOT" && \
|
|
65
|
+
[ -d "$AUTHORIZATION_ROOT" ] && [ ! -L "$AUTHORIZATION_ROOT" ] && \
|
|
66
|
+
[ "$(/usr/bin/stat -f '%u:%g:%Lp' "$AUTHORIZATION_ROOT")" = "0:0:700" ] || \
|
|
67
|
+
fail "authorization evidence is missing or unsafe"
|
|
68
|
+
[ -f "$AUTHORIZATION_JOURNAL" ] && [ ! -L "$AUTHORIZATION_JOURNAL" ] && \
|
|
69
|
+
[ "$(/usr/bin/stat -f '%u:%g:%Lp' "$AUTHORIZATION_JOURNAL")" = "0:0:400" ] && \
|
|
70
|
+
[ "$(/usr/bin/stat -f '%l' "$AUTHORIZATION_JOURNAL")" = "1" ] && \
|
|
71
|
+
[ "$(/usr/bin/wc -l <"$AUTHORIZATION_JOURNAL" | /usr/bin/tr -d ' ')" = "6" ] || \
|
|
72
|
+
fail "authorization journal is missing or unsafe"
|
|
73
|
+
require_no_extended_acl "$AUTHORIZATION_ROOT"
|
|
74
|
+
require_no_extended_acl "$AUTHORIZATION_JOURNAL"
|
|
75
|
+
[ "$(read_journal_value schema_version)" = "1" ] || fail "authorization journal schema is invalid"
|
|
76
|
+
JOURNAL_PHASE="$(read_journal_value phase)"
|
|
77
|
+
JOURNAL_PACKAGE_SHA256="$(read_journal_value package_sha256)"
|
|
78
|
+
JOURNAL_PACKAGE_BOOTSTRAP_ID="$(read_journal_value package_bootstrap_id)"
|
|
79
|
+
JOURNAL_INSTALLER_TEAM_ID="$(read_journal_value installer_team_id)"
|
|
80
|
+
JOURNAL_INSTALLER_CERTIFICATE_SHA256="$(read_journal_value installer_certificate_sha256)"
|
|
81
|
+
[ "$JOURNAL_PHASE" = "installer-started" ] || fail "authorization journal is not in Installer repair phase"
|
|
82
|
+
[[ "$JOURNAL_PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ ]] && \
|
|
83
|
+
[[ "$JOURNAL_PACKAGE_BOOTSTRAP_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] && \
|
|
84
|
+
[[ "$JOURNAL_INSTALLER_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] && \
|
|
85
|
+
[[ "$JOURNAL_INSTALLER_CERTIFICATE_SHA256" =~ ^[0-9a-f]{64}$ ]] || \
|
|
86
|
+
fail "authorization journal values are malformed"
|
|
87
|
+
[ "$JOURNAL_PACKAGE_BOOTSTRAP_ID" = "$PACKAGE_BOOTSTRAP_ID" ] || \
|
|
88
|
+
fail "authorization journal belongs to a different authenticated package"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
require_tree_without_extended_acl() {
|
|
92
|
+
local tree="$1"
|
|
93
|
+
local acl_listing acl_status
|
|
94
|
+
require_no_extended_acl "$tree"
|
|
95
|
+
[ -d "$tree" ] || return 0
|
|
96
|
+
acl_listing="$(/bin/ls -laeR "$tree")" || fail "could not recursively inspect extended ACLs on $tree"
|
|
97
|
+
if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
|
|
98
|
+
length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
|
|
99
|
+
$1 ~ /^[0-9]+:$/ { found = 1 }
|
|
100
|
+
END { exit found ? 0 : 1 }
|
|
101
|
+
'; then
|
|
102
|
+
fail "unexpected extended ACL inside package-owned tree: $tree"
|
|
103
|
+
else
|
|
104
|
+
acl_status=$?
|
|
105
|
+
[ "$acl_status" -eq 1 ] || fail "could not evaluate recursive extended ACLs on $tree"
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Prove every recursive mutation target before chmod/chown can traverse it.
|
|
110
|
+
# Recursive mutation roots are under root-owned, non-writable ancestry, so once
|
|
111
|
+
# every entry is root-owned and non-writable an unprivileged process cannot
|
|
112
|
+
# replace a validated path before the immediately following mutation. The app
|
|
113
|
+
# is validated with the same structure policy but is never recursively mutated,
|
|
114
|
+
# preserving the exact root:admin /Applications compatibility boundary.
|
|
115
|
+
package_entry_structure_is_safe() {
|
|
116
|
+
local entry="$1"
|
|
117
|
+
local expected_device="$2"
|
|
118
|
+
local mode owner links device
|
|
119
|
+
[ ! -L "$entry" ] || return 1
|
|
120
|
+
if [ -d "$entry" ]; then
|
|
121
|
+
:
|
|
122
|
+
elif [ -f "$entry" ]; then
|
|
123
|
+
links="$(/usr/bin/stat -f '%l' "$entry")" || return 1
|
|
124
|
+
[ "$links" = "1" ] || return 1
|
|
125
|
+
else
|
|
126
|
+
return 1
|
|
127
|
+
fi
|
|
128
|
+
owner="$(/usr/bin/stat -f '%u' "$entry")" || return 1
|
|
129
|
+
[ "$owner" = "0" ] || return 1
|
|
130
|
+
device="$(/usr/bin/stat -f '%d' "$entry")" || return 1
|
|
131
|
+
[ "$device" = "$expected_device" ] || return 1
|
|
132
|
+
mode="$(/usr/bin/stat -f '%Lp' "$entry")" || return 1
|
|
133
|
+
[ $((8#$mode & 8#022)) -eq 0 ] || return 1
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
require_safe_package_tree_structure() {
|
|
137
|
+
local tree="$1"
|
|
138
|
+
local entry listing tree_device invalid=0
|
|
139
|
+
[ -d "$tree" ] && [ ! -L "$tree" ] || \
|
|
140
|
+
fail "package-owned tree is missing, linked, or not a directory: $tree"
|
|
141
|
+
listing="$(/usr/bin/mktemp -t recordings-package-tree)" || \
|
|
142
|
+
fail "could not allocate package-tree inspection state"
|
|
143
|
+
tree_device="$(/usr/bin/stat -f '%d' "$tree")" || {
|
|
144
|
+
/bin/rm -f "$listing"
|
|
145
|
+
fail "could not inspect package-owned tree device: $tree"
|
|
146
|
+
}
|
|
147
|
+
if ! /usr/bin/find -x "$tree" -print0 >"$listing"; then
|
|
148
|
+
/bin/rm -f "$listing"
|
|
149
|
+
fail "could not enumerate package-owned tree: $tree"
|
|
150
|
+
fi
|
|
151
|
+
while IFS= read -r -d '' entry; do
|
|
152
|
+
if ! package_entry_structure_is_safe "$entry" "$tree_device"; then
|
|
153
|
+
invalid=1
|
|
154
|
+
break
|
|
155
|
+
fi
|
|
156
|
+
done <"$listing"
|
|
157
|
+
/bin/rm -f "$listing"
|
|
158
|
+
[ "$invalid" -eq 0 ] || \
|
|
159
|
+
fail "package-owned tree contains a link, hard link, special file, unsafe owner, or writable entry: $tree"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
require_safe_package_leaf_structure() {
|
|
163
|
+
local leaf="$1"
|
|
164
|
+
local leaf_device
|
|
165
|
+
leaf_device="$(/usr/bin/stat -f '%d' "$leaf")" || \
|
|
166
|
+
fail "could not inspect package-owned leaf device: $leaf"
|
|
167
|
+
[ -f "$leaf" ] && package_entry_structure_is_safe "$leaf" "$leaf_device" || \
|
|
168
|
+
fail "package-owned leaf is linked, hard-linked, special, unsafely owned, or writable: $leaf"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
require_root_owned_nonwritable_directory() {
|
|
172
|
+
local directory="$1"
|
|
173
|
+
local mode
|
|
174
|
+
[ -d "$directory" ] && [ ! -L "$directory" ] && \
|
|
175
|
+
[ "$(/usr/bin/stat -f '%u' "$directory")" = 0 ] || \
|
|
176
|
+
fail "target ancestry is missing, linked, or not root-owned: $directory"
|
|
177
|
+
mode="$(/usr/bin/stat -f '%Lp' "$directory")"
|
|
178
|
+
[ $((8#$mode & 8#022)) -eq 0 ] || \
|
|
179
|
+
fail "target ancestry is group/other writable: $directory"
|
|
180
|
+
require_no_extended_acl "$directory"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Standard macOS permits the admin group to install app bundles. Executable
|
|
184
|
+
# authority remains the pinned Developer ID/audit-token policy, so accept only
|
|
185
|
+
# that exact OS topology and reject every ACL or broader mode.
|
|
186
|
+
require_managed_applications_directory() {
|
|
187
|
+
local mode group_id
|
|
188
|
+
[ -d "/Applications" ] && [ ! -L "/Applications" ] && \
|
|
189
|
+
[ "$(/usr/bin/stat -f '%u' "/Applications")" = 0 ] || \
|
|
190
|
+
fail "canonical /Applications directory is missing, linked, or not root-owned"
|
|
191
|
+
require_no_extended_acl "/Applications"
|
|
192
|
+
mode="$(/usr/bin/stat -f '%Lp' "/Applications")"
|
|
193
|
+
group_id="$(/usr/bin/stat -f '%g' "/Applications")"
|
|
194
|
+
case "$mode:$group_id" in
|
|
195
|
+
755:*|775:80) ;;
|
|
196
|
+
*) fail "/Applications must be ACL-free mode 0755 or root:admin mode 0775" ;;
|
|
197
|
+
esac
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
require_expected_var_link() {
|
|
201
|
+
local var_target
|
|
202
|
+
[ -L "/var" ] || fail "canonical macOS /var system link is missing"
|
|
203
|
+
var_target="$(/usr/bin/readlink "/var")" || fail "could not inspect the macOS /var system link"
|
|
204
|
+
[ "$var_target" = "private/var" ] && \
|
|
205
|
+
[ "$(cd "/var" && /bin/pwd -P)" = "/private/var" ] || \
|
|
206
|
+
fail "macOS /var system link is noncanonical"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
validate_existing_mutation_root() {
|
|
210
|
+
local mutation_root="$1"
|
|
211
|
+
if path_exists_or_symlink "$mutation_root"; then
|
|
212
|
+
[ -d "$mutation_root" ] && [ ! -L "$mutation_root" ] && \
|
|
213
|
+
[ "$(/usr/bin/stat -f '%u:%g:%Lp' "$mutation_root")" = "0:0:700" ] || \
|
|
214
|
+
fail "interrupted package mutation root is linked or has ownership/mode drift: $mutation_root"
|
|
215
|
+
require_safe_package_tree_structure "$mutation_root"
|
|
216
|
+
require_tree_without_extended_acl "$mutation_root"
|
|
217
|
+
fi
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
[ "$(/usr/bin/id -u)" = "0" ] || fail "postinstall must run as root through Installer or MDM"
|
|
221
|
+
|
|
222
|
+
require_authorization_journal
|
|
223
|
+
require_expected_var_link
|
|
224
|
+
require_managed_applications_directory
|
|
225
|
+
for required_ancestor in \
|
|
226
|
+
"/" \
|
|
227
|
+
"/Library" \
|
|
228
|
+
"/Library/Application Support" \
|
|
229
|
+
"/Library/Application Support/Hasna" \
|
|
230
|
+
"/Library/Application Support/Hasna/Recordings" \
|
|
231
|
+
"/Library/PrivilegedHelperTools" \
|
|
232
|
+
"/Library/LaunchDaemons" \
|
|
233
|
+
"/private" \
|
|
234
|
+
"/private/var" \
|
|
235
|
+
"/private/var/db"; do
|
|
236
|
+
require_root_owned_nonwritable_directory "$required_ancestor"
|
|
237
|
+
done
|
|
238
|
+
for mutation_root in "$STATE_ROOT" "/var/db/com.hasna.recordings.updater"; do
|
|
239
|
+
validate_existing_mutation_root "$mutation_root"
|
|
240
|
+
done
|
|
241
|
+
|
|
242
|
+
for required in "$BROKER" "$CLIENT" "$VERIFIER" "$BROKER_POLICY" "$VERIFIER_SANDBOX" "$BOOTSTRAP_MARKER" "$LAUNCHD_PLIST" \
|
|
243
|
+
"$RELEASE_SEQUENCE_FILE" "$KEY_EPOCH_FILE" "$SOURCE_SHA_FILE"; do
|
|
244
|
+
[ -e "$required" ] || fail "package payload is missing $required"
|
|
245
|
+
[ ! -L "$required" ] || fail "package payload contains a symbolic-link trust boundary: $required"
|
|
246
|
+
require_no_extended_acl "$required"
|
|
247
|
+
done
|
|
248
|
+
release_sequence="$(/bin/cat "$RELEASE_SEQUENCE_FILE")"
|
|
249
|
+
key_epoch="$(/bin/cat "$KEY_EPOCH_FILE")"
|
|
250
|
+
source_sha="$(/bin/cat "$SOURCE_SHA_FILE")"
|
|
251
|
+
[[ "$release_sequence" =~ ^[1-9][0-9]*$ ]] || fail "bootstrap release sequence is invalid"
|
|
252
|
+
[[ "$key_epoch" =~ ^[1-9][0-9]*$ ]] || fail "bootstrap key epoch is invalid"
|
|
253
|
+
[[ "$source_sha" =~ ^[0-9a-f]{40}$ ]] || fail "bootstrap source commit is invalid"
|
|
254
|
+
PUBLIC_KEY="${TRUST_ROOT}/envelope-keys/${key_epoch}.raw"
|
|
255
|
+
[ -f "$PUBLIC_KEY" ] && [ ! -L "$PUBLIC_KEY" ] || fail "package payload is missing the epoch release-envelope key"
|
|
256
|
+
[ -d "$APP" ] && [ ! -L "$APP" ] || fail "package payload is missing the canonical app bundle"
|
|
257
|
+
[ "$(/usr/bin/stat -f '%z' "$PUBLIC_KEY")" = "32" ] || fail "release-envelope public key must be exactly 32 raw Ed25519 bytes"
|
|
258
|
+
for installed_package_tree in "$APP" "$TRUST_ROOT" "$BOOTSTRAP_ROOT"; do
|
|
259
|
+
require_safe_package_tree_structure "$installed_package_tree"
|
|
260
|
+
done
|
|
261
|
+
require_tree_without_extended_acl "$APP"
|
|
262
|
+
for installed_package_leaf in "$BROKER" "$VERIFIER" "$LAUNCHD_PLIST"; do
|
|
263
|
+
require_safe_package_leaf_structure "$installed_package_leaf"
|
|
264
|
+
done
|
|
265
|
+
|
|
266
|
+
find_unused_directory_id() {
|
|
267
|
+
local candidate=499
|
|
268
|
+
local user_ids group_ids
|
|
269
|
+
user_ids="$(/usr/bin/dscl . -list /Users UniqueID)"
|
|
270
|
+
group_ids="$(/usr/bin/dscl . -list /Groups PrimaryGroupID)"
|
|
271
|
+
while [ "$candidate" -ge 400 ]; do
|
|
272
|
+
if ! /usr/bin/awk -v value="$candidate" '$2 == value { found=1 } END { exit found ? 0 : 1 }' <<<"$user_ids" && \
|
|
273
|
+
! /usr/bin/awk -v value="$candidate" '$2 == value { found=1 } END { exit found ? 0 : 1 }' <<<"$group_ids"; then
|
|
274
|
+
printf '%s\n' "$candidate"
|
|
275
|
+
return 0
|
|
276
|
+
fi
|
|
277
|
+
candidate=$((candidate - 1))
|
|
278
|
+
done
|
|
279
|
+
return 1
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
read_directory_value() {
|
|
283
|
+
local record="$1"
|
|
284
|
+
local key="$2"
|
|
285
|
+
{ /usr/bin/dscl . -read "$record" "$key" 2>/dev/null || true; } | \
|
|
286
|
+
/usr/bin/awk -v key="$key:" '$1 == key { $1=""; sub(/^ /, ""); print; exit }'
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
ensure_directory_value() {
|
|
290
|
+
local record="$1"
|
|
291
|
+
local key="$2"
|
|
292
|
+
local expected="$3"
|
|
293
|
+
local label="$4"
|
|
294
|
+
local actual
|
|
295
|
+
if /usr/bin/dscl . -read "$record" "$key" >/dev/null 2>&1; then
|
|
296
|
+
actual="$(read_directory_value "$record" "$key")"
|
|
297
|
+
[ "$actual" = "$expected" ] || fail "$label has an unexpected existing value"
|
|
298
|
+
else
|
|
299
|
+
/usr/bin/dscl . -create "$record" "$key" "$expected"
|
|
300
|
+
fi
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
validate_numeric_system_id() {
|
|
304
|
+
local label="$1"
|
|
305
|
+
local value="$2"
|
|
306
|
+
case "$value" in ''|*[!0-9]*) fail "$label is not numeric" ;; esac
|
|
307
|
+
[ "$value" -ge 400 ] && [ "$value" -le 499 ] || fail "$label is outside the reserved verifier range"
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
validate_verifier_group() {
|
|
311
|
+
local gid real_name password members member_ids
|
|
312
|
+
gid="$(read_directory_value "/Groups/${VERIFIER_GROUP}" PrimaryGroupID)"
|
|
313
|
+
real_name="$(read_directory_value "/Groups/${VERIFIER_GROUP}" RealName)"
|
|
314
|
+
password="$(read_directory_value "/Groups/${VERIFIER_GROUP}" Password)"
|
|
315
|
+
members="$(read_directory_value "/Groups/${VERIFIER_GROUP}" GroupMembership)"
|
|
316
|
+
member_ids="$(read_directory_value "/Groups/${VERIFIER_GROUP}" GroupMembers)"
|
|
317
|
+
validate_numeric_system_id "verifier group PrimaryGroupID" "$gid"
|
|
318
|
+
[ "$real_name" = "Recordings Artifact Verifier" ] || fail "existing verifier group has an unexpected RealName"
|
|
319
|
+
case "$password" in \**|\*\*\** ) ;; *) fail "existing verifier group is not password-disabled" ;; esac
|
|
320
|
+
[ -z "$members" ] && [ -z "$member_ids" ] || fail "verifier group must not grant supplementary membership"
|
|
321
|
+
printf '%s\n' "$gid"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
validate_verifier_user() {
|
|
325
|
+
local expected_gid="$1"
|
|
326
|
+
local uid real_name password hidden
|
|
327
|
+
uid="$(read_directory_value "/Users/${VERIFIER_USER}" UniqueID)"
|
|
328
|
+
real_name="$(read_directory_value "/Users/${VERIFIER_USER}" RealName)"
|
|
329
|
+
password="$(read_directory_value "/Users/${VERIFIER_USER}" Password)"
|
|
330
|
+
hidden="$(read_directory_value "/Users/${VERIFIER_USER}" IsHidden)"
|
|
331
|
+
validate_numeric_system_id "verifier user UniqueID" "$uid"
|
|
332
|
+
[ "$real_name" = "Recordings Artifact Verifier" ] || fail "existing verifier account has an unexpected RealName"
|
|
333
|
+
case "$password" in \**|\*\*\** ) ;; *) fail "existing verifier account is not password-disabled" ;; esac
|
|
334
|
+
[ "$hidden" = "1" ] || fail "existing verifier account is not hidden"
|
|
335
|
+
[ "$(read_directory_value "/Users/${VERIFIER_USER}" UserShell)" = "/usr/bin/false" ] || \
|
|
336
|
+
fail "existing verifier account has an interactive shell"
|
|
337
|
+
[ "$(read_directory_value "/Users/${VERIFIER_USER}" NFSHomeDirectory)" = "/var/empty" ] || \
|
|
338
|
+
fail "existing verifier account has an unexpected home"
|
|
339
|
+
[ "$(read_directory_value "/Users/${VERIFIER_USER}" PrimaryGroupID)" = "$expected_gid" ] || \
|
|
340
|
+
fail "existing verifier account has an unexpected primary group"
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if ! /usr/bin/dscl . -read "/Groups/${VERIFIER_GROUP}" >/dev/null 2>&1; then
|
|
344
|
+
/usr/bin/dscl . -create "/Groups/${VERIFIER_GROUP}"
|
|
345
|
+
fi
|
|
346
|
+
echo "Recordings updater will repair interrupted verifier group initialization when expected attributes are absent." >&2
|
|
347
|
+
ensure_directory_value "/Groups/${VERIFIER_GROUP}" RealName "Recordings Artifact Verifier" "verifier group RealName"
|
|
348
|
+
if /usr/bin/dscl . -read "/Groups/${VERIFIER_GROUP}" Password >/dev/null 2>&1; then
|
|
349
|
+
verifier_group_password="$(read_directory_value "/Groups/${VERIFIER_GROUP}" Password)"
|
|
350
|
+
case "$verifier_group_password" in \**|\*\*\** ) ;; *) fail "existing verifier group is not password-disabled" ;; esac
|
|
351
|
+
else
|
|
352
|
+
/usr/bin/dscl . -create "/Groups/${VERIFIER_GROUP}" Password "*"
|
|
353
|
+
fi
|
|
354
|
+
if /usr/bin/dscl . -read "/Groups/${VERIFIER_GROUP}" PrimaryGroupID >/dev/null 2>&1; then
|
|
355
|
+
verifier_gid="$(read_directory_value "/Groups/${VERIFIER_GROUP}" PrimaryGroupID)"
|
|
356
|
+
validate_numeric_system_id "verifier group PrimaryGroupID" "$verifier_gid"
|
|
357
|
+
else
|
|
358
|
+
verifier_gid="$(find_unused_directory_id)" || fail "no unused system account ID is available"
|
|
359
|
+
/usr/bin/dscl . -create "/Groups/${VERIFIER_GROUP}" PrimaryGroupID "$verifier_gid"
|
|
360
|
+
fi
|
|
361
|
+
verifier_gid="$(validate_verifier_group)"
|
|
362
|
+
|
|
363
|
+
if ! /usr/bin/dscl . -read "/Users/${VERIFIER_USER}" >/dev/null 2>&1; then
|
|
364
|
+
/usr/bin/dscl . -create "/Users/${VERIFIER_USER}"
|
|
365
|
+
fi
|
|
366
|
+
echo "Recordings updater will repair interrupted verifier account initialization when expected attributes are absent." >&2
|
|
367
|
+
ensure_directory_value "/Users/${VERIFIER_USER}" RealName "Recordings Artifact Verifier" "verifier account RealName"
|
|
368
|
+
if /usr/bin/dscl . -read "/Users/${VERIFIER_USER}" Password >/dev/null 2>&1; then
|
|
369
|
+
verifier_user_password="$(read_directory_value "/Users/${VERIFIER_USER}" Password)"
|
|
370
|
+
case "$verifier_user_password" in \**|\*\*\** ) ;; *) fail "existing verifier account is not password-disabled" ;; esac
|
|
371
|
+
else
|
|
372
|
+
/usr/bin/dscl . -create "/Users/${VERIFIER_USER}" Password "*"
|
|
373
|
+
fi
|
|
374
|
+
if /usr/bin/dscl . -read "/Users/${VERIFIER_USER}" UniqueID >/dev/null 2>&1; then
|
|
375
|
+
verifier_uid="$(read_directory_value "/Users/${VERIFIER_USER}" UniqueID)"
|
|
376
|
+
validate_numeric_system_id "verifier user UniqueID" "$verifier_uid"
|
|
377
|
+
else
|
|
378
|
+
verifier_uid="$(find_unused_directory_id)" || fail "no unused system account ID is available"
|
|
379
|
+
/usr/bin/dscl . -create "/Users/${VERIFIER_USER}" UniqueID "$verifier_uid"
|
|
380
|
+
fi
|
|
381
|
+
ensure_directory_value "/Users/${VERIFIER_USER}" PrimaryGroupID "$verifier_gid" "verifier account PrimaryGroupID"
|
|
382
|
+
ensure_directory_value "/Users/${VERIFIER_USER}" UserShell "/usr/bin/false" "verifier account UserShell"
|
|
383
|
+
ensure_directory_value "/Users/${VERIFIER_USER}" NFSHomeDirectory "/var/empty" "verifier account NFSHomeDirectory"
|
|
384
|
+
ensure_directory_value "/Users/${VERIFIER_USER}" IsHidden 1 "verifier account IsHidden"
|
|
385
|
+
validate_verifier_user "$verifier_gid"
|
|
386
|
+
|
|
387
|
+
/bin/mkdir -p \
|
|
388
|
+
"$STATE_ROOT/staging" \
|
|
389
|
+
"$STATE_ROOT/journal" \
|
|
390
|
+
"$STATE_ROOT/rollback" \
|
|
391
|
+
"$TRUST_ROOT/envelope-keys" \
|
|
392
|
+
"/var/db/com.hasna.recordings.updater"
|
|
393
|
+
for created_directory in \
|
|
394
|
+
"$STATE_ROOT" \
|
|
395
|
+
"$STATE_ROOT/staging" \
|
|
396
|
+
"$STATE_ROOT/journal" \
|
|
397
|
+
"$STATE_ROOT/rollback" \
|
|
398
|
+
"$TRUST_ROOT" \
|
|
399
|
+
"$TRUST_ROOT/envelope-keys" \
|
|
400
|
+
"$BOOTSTRAP_ROOT" \
|
|
401
|
+
"/var/db/com.hasna.recordings.updater"; do
|
|
402
|
+
require_root_owned_nonwritable_directory "$created_directory"
|
|
403
|
+
done
|
|
404
|
+
|
|
405
|
+
# Re-prove the complete recursive targets immediately before mutation. Strip
|
|
406
|
+
# ACLs one target at a time and revalidate that target before proceeding.
|
|
407
|
+
for package_owned_tree in \
|
|
408
|
+
"$STATE_ROOT" \
|
|
409
|
+
"$TRUST_ROOT" \
|
|
410
|
+
"$BOOTSTRAP_ROOT" \
|
|
411
|
+
"/var/db/com.hasna.recordings.updater"; do
|
|
412
|
+
require_safe_package_tree_structure "$package_owned_tree"
|
|
413
|
+
/bin/chmod -RN "$package_owned_tree"
|
|
414
|
+
require_tree_without_extended_acl "$package_owned_tree"
|
|
415
|
+
done
|
|
416
|
+
for package_owned_leaf in "$BROKER" "$VERIFIER" "$LAUNCHD_PLIST"; do
|
|
417
|
+
require_safe_package_leaf_structure "$package_owned_leaf"
|
|
418
|
+
/bin/chmod -N "$package_owned_leaf"
|
|
419
|
+
require_no_extended_acl "$package_owned_leaf"
|
|
420
|
+
done
|
|
421
|
+
/usr/sbin/chown -R root:wheel "$STATE_ROOT" "$TRUST_ROOT" "$BOOTSTRAP_ROOT" "/var/db/com.hasna.recordings.updater"
|
|
422
|
+
/usr/sbin/chown root:wheel "$BROKER" "$VERIFIER" "$PUBLIC_KEY" "$BROKER_POLICY" "$VERIFIER_SANDBOX" "$BOOTSTRAP_MARKER" "$LAUNCHD_PLIST"
|
|
423
|
+
/bin/chmod 0555 "$BROKER" "$VERIFIER"
|
|
424
|
+
[ "$(/usr/bin/stat -f '%Lp' "$CLIENT")" = "755" ] || fail "package update client mode is not 0755"
|
|
425
|
+
/bin/chmod 0700 "$STATE_ROOT" "$STATE_ROOT/staging" "$STATE_ROOT/journal" "$STATE_ROOT/rollback"
|
|
426
|
+
/bin/chmod 0700 "/var/db/com.hasna.recordings.updater"
|
|
427
|
+
/bin/chmod 0755 "$TRUST_ROOT"
|
|
428
|
+
/bin/chmod 0755 "$TRUST_ROOT/envelope-keys"
|
|
429
|
+
/bin/chmod 0444 "$PUBLIC_KEY" "$BROKER_POLICY" "$VERIFIER_SANDBOX" "$BOOTSTRAP_MARKER" "$LAUNCHD_PLIST"
|
|
430
|
+
|
|
431
|
+
for package_owned_tree in \
|
|
432
|
+
"$APP" \
|
|
433
|
+
"$STATE_ROOT" \
|
|
434
|
+
"$TRUST_ROOT" \
|
|
435
|
+
"$BOOTSTRAP_ROOT" \
|
|
436
|
+
"/var/db/com.hasna.recordings.updater"; do
|
|
437
|
+
require_tree_without_extended_acl "$package_owned_tree"
|
|
438
|
+
done
|
|
439
|
+
for package_owned_leaf in "$BROKER" "$VERIFIER" "$LAUNCHD_PLIST"; do
|
|
440
|
+
require_no_extended_acl "$package_owned_leaf"
|
|
441
|
+
done
|
|
442
|
+
|
|
443
|
+
/usr/bin/plutil -lint "$LAUNCHD_PLIST" >/dev/null
|
|
444
|
+
/usr/bin/plutil -lint "$BROKER_POLICY" >/dev/null
|
|
445
|
+
/usr/bin/plutil -lint "$BOOTSTRAP_MARKER" >/dev/null
|
|
446
|
+
EXPECTED_TEAM_ID="$(/usr/bin/plutil -extract signing_team_identifier raw -o - "$BROKER_POLICY")"
|
|
447
|
+
[[ "$EXPECTED_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] || fail "broker policy TeamIdentifier is invalid"
|
|
448
|
+
[ "$(/usr/bin/plutil -extract lifecycle raw -o - "$BROKER_POLICY")" = "bootstrap-v1-app-updates-only" ] || \
|
|
449
|
+
fail "broker policy lifecycle is unsupported"
|
|
450
|
+
[ "$(/usr/bin/plutil -extract root_maintenance_supported raw -o - "$BROKER_POLICY")" = "false" ] || \
|
|
451
|
+
fail "broker policy must disable root maintenance"
|
|
452
|
+
[ "$(/usr/bin/plutil -extract key_rotation_supported raw -o - "$BROKER_POLICY")" = "false" ] || \
|
|
453
|
+
fail "broker policy must disable key rotation"
|
|
454
|
+
[ "$(/usr/bin/plutil -extract initial_key_epoch raw -o - "$BROKER_POLICY")" = "$key_epoch" ] || \
|
|
455
|
+
fail "broker policy initial key epoch does not match the bootstrap"
|
|
456
|
+
[ "$(/usr/bin/plutil -extract allowed_key_epochs.0 raw -o - "$BROKER_POLICY")" = "$key_epoch" ] || \
|
|
457
|
+
fail "broker policy does not pin the bootstrap key epoch"
|
|
458
|
+
[ "$(/usr/bin/plutil -extract allowed_key_epochs json -o - "$BROKER_POLICY" | /usr/bin/tr -d '[:space:]')" = "[${key_epoch}]" ] || \
|
|
459
|
+
fail "broker policy authorizes unsupported key-epoch transitions"
|
|
460
|
+
[ "$(/usr/bin/plutil -extract lifecycle raw -o - "$BOOTSTRAP_MARKER")" = "bootstrap-v1-app-updates-only" ] || \
|
|
461
|
+
fail "bootstrap marker lifecycle is unsupported"
|
|
462
|
+
[ "$(/usr/bin/plutil -extract root_maintenance_supported raw -o - "$BOOTSTRAP_MARKER")" = "false" ] || \
|
|
463
|
+
fail "bootstrap marker must disable root maintenance"
|
|
464
|
+
[ "$(/usr/bin/plutil -extract key_rotation_supported raw -o - "$BOOTSTRAP_MARKER")" = "false" ] || \
|
|
465
|
+
fail "bootstrap marker must disable key rotation"
|
|
466
|
+
|
|
467
|
+
verify_entitlement_allowlist() {
|
|
468
|
+
local code_path="$1"
|
|
469
|
+
local policy="$2"
|
|
470
|
+
local entitlement_plist
|
|
471
|
+
entitlement_plist="$(/usr/bin/mktemp -t recordings-entitlements)"
|
|
472
|
+
if ! /usr/bin/codesign -d --entitlements :- "$code_path" >"$entitlement_plist" 2>/dev/null || \
|
|
473
|
+
[ ! -s "$entitlement_plist" ]; then
|
|
474
|
+
/bin/rm -f "$entitlement_plist"
|
|
475
|
+
[ "$policy" = "none" ] || fail "$code_path is missing required entitlements"
|
|
476
|
+
return 0
|
|
477
|
+
fi
|
|
478
|
+
/usr/bin/plutil -lint "$entitlement_plist" >/dev/null || fail "$code_path has malformed signed entitlements"
|
|
479
|
+
case "$policy" in
|
|
480
|
+
app)
|
|
481
|
+
[ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.app-sandbox' "$entitlement_plist")" = "false" ] || fail "$code_path app sandbox entitlement changed"
|
|
482
|
+
[ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.device.audio-input' "$entitlement_plist")" = "true" ] || fail "$code_path audio entitlement changed"
|
|
483
|
+
[ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.automation.apple-events' "$entitlement_plist")" = "true" ] || fail "$code_path automation entitlement changed"
|
|
484
|
+
/usr/libexec/PlistBuddy -c 'Delete :com.apple.security.app-sandbox' "$entitlement_plist"
|
|
485
|
+
/usr/libexec/PlistBuddy -c 'Delete :com.apple.security.device.audio-input' "$entitlement_plist"
|
|
486
|
+
/usr/libexec/PlistBuddy -c 'Delete :com.apple.security.automation.apple-events' "$entitlement_plist"
|
|
487
|
+
;;
|
|
488
|
+
verifier)
|
|
489
|
+
[ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.cs.allow-jit' "$entitlement_plist")" = "true" ] || fail "$code_path JIT entitlement changed"
|
|
490
|
+
[ "$(/usr/libexec/PlistBuddy -c 'Print :com.apple.security.cs.allow-unsigned-executable-memory' "$entitlement_plist")" = "true" ] || fail "$code_path executable-memory entitlement changed"
|
|
491
|
+
/usr/libexec/PlistBuddy -c 'Delete :com.apple.security.cs.allow-jit' "$entitlement_plist"
|
|
492
|
+
/usr/libexec/PlistBuddy -c 'Delete :com.apple.security.cs.allow-unsigned-executable-memory' "$entitlement_plist"
|
|
493
|
+
;;
|
|
494
|
+
none) ;;
|
|
495
|
+
*) fail "unknown entitlement policy" ;;
|
|
496
|
+
esac
|
|
497
|
+
remaining="$(/usr/libexec/PlistBuddy -c Print "$entitlement_plist" | /usr/bin/tr -d '[:space:]')"
|
|
498
|
+
/bin/rm -f "$entitlement_plist"
|
|
499
|
+
[ "$remaining" = "Dict{}" ] || fail "$code_path has entitlements outside its allowlist"
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
verify_release_code() {
|
|
503
|
+
local code_path="$1"
|
|
504
|
+
local expected_identifier="$2"
|
|
505
|
+
local entitlement_policy="$3"
|
|
506
|
+
local details authority team identifier timestamp flags
|
|
507
|
+
/usr/bin/codesign --verify --strict --all-architectures --verbose=2 "$code_path"
|
|
508
|
+
details="$(/usr/bin/codesign -d --verbose=4 "$code_path" 2>&1)"
|
|
509
|
+
authority="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Authority=/ { print $2; exit }')"
|
|
510
|
+
team="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^TeamIdentifier=/ { print $2; exit }')"
|
|
511
|
+
identifier="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Identifier=/ { print $2; exit }')"
|
|
512
|
+
timestamp="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Timestamp=/ { print $2; exit }')"
|
|
513
|
+
flags="$(printf '%s\n' "$details" | /usr/bin/sed -n 's/^CodeDirectory .*flags=[^(]*(\([^)]*\)).*/\1/p' | /usr/bin/head -n 1)"
|
|
514
|
+
[[ "$authority" == "Developer ID Application:"* ]] || fail "$code_path is not Developer ID Application signed"
|
|
515
|
+
[ "$team" = "$EXPECTED_TEAM_ID" ] || fail "$code_path has an unexpected TeamIdentifier"
|
|
516
|
+
[ "$identifier" = "$expected_identifier" ] || fail "$code_path has an unexpected signing identifier"
|
|
517
|
+
case ",$flags," in *,runtime,*) ;; *) fail "$code_path lacks hardened runtime" ;; esac
|
|
518
|
+
case "$timestamp" in ''|none|None|NONE) fail "$code_path lacks a trusted timestamp" ;; esac
|
|
519
|
+
verify_entitlement_allowlist "$code_path" "$entitlement_policy"
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
verify_release_code "$BROKER" "com.hasna.recordings.updater" none
|
|
523
|
+
verify_release_code "$CLIENT" "com.hasna.recordings.update-client" none
|
|
524
|
+
verify_release_code "$VERIFIER" "com.hasna.recordings.artifact-verifier" verifier
|
|
525
|
+
verify_release_code "$APP" "com.hasna.recordings" app
|
|
526
|
+
/usr/bin/codesign --verify --deep --strict --all-architectures --verbose=2 "$APP"
|
|
527
|
+
|
|
528
|
+
# Close the ancestry-check-to-launch window after all package mutations and
|
|
529
|
+
# code validation. An ACL added anywhere in the launch path aborts bootstrap.
|
|
530
|
+
require_managed_applications_directory
|
|
531
|
+
for launch_ancestor in \
|
|
532
|
+
"/" \
|
|
533
|
+
"/Library" \
|
|
534
|
+
"/Library/Application Support" \
|
|
535
|
+
"/Library/Application Support/Hasna" \
|
|
536
|
+
"/Library/Application Support/Hasna/Recordings" \
|
|
537
|
+
"/Library/PrivilegedHelperTools" \
|
|
538
|
+
"/Library/LaunchDaemons" \
|
|
539
|
+
"/private" \
|
|
540
|
+
"/private/var" \
|
|
541
|
+
"/private/var/db"; do
|
|
542
|
+
require_root_owned_nonwritable_directory "$launch_ancestor"
|
|
543
|
+
done
|
|
544
|
+
require_expected_var_link
|
|
545
|
+
for launch_tree in "$APP" "$STATE_ROOT" "$TRUST_ROOT" "$BOOTSTRAP_ROOT" "/var/db/com.hasna.recordings.updater"; do
|
|
546
|
+
require_tree_without_extended_acl "$launch_tree"
|
|
547
|
+
done
|
|
548
|
+
for launch_leaf in "$BROKER" "$VERIFIER" "$LAUNCHD_PLIST"; do
|
|
549
|
+
require_no_extended_acl "$launch_leaf"
|
|
550
|
+
done
|
|
551
|
+
|
|
552
|
+
if ! /bin/launchctl print system/com.hasna.recordings.updater >/dev/null 2>&1; then
|
|
553
|
+
/bin/launchctl bootstrap system "$LAUNCHD_PLIST"
|
|
554
|
+
fi
|
|
555
|
+
/bin/launchctl enable system/com.hasna.recordings.updater
|
|
556
|
+
/bin/launchctl kickstart -k system/com.hasna.recordings.updater
|
|
557
|
+
/bin/launchctl print system/com.hasna.recordings.updater >/dev/null
|
|
558
|
+
|
|
559
|
+
# The external signed bootstrap envelope is generated only after the final PKG
|
|
560
|
+
# is stapled, because it binds that exact PKG digest. Managed deployment passes
|
|
561
|
+
# that sidecar to the broker after Installer succeeds. Postinstall deliberately
|
|
562
|
+
# leaves release-state.json absent; until bootstrap initialization completes the
|
|
563
|
+
# broker is status-only and rejects every update request.
|
|
564
|
+
[ ! -e "/var/db/com.hasna.recordings.updater/release-state.json" ] || \
|
|
565
|
+
fail "a bootstrap PKG must not overwrite existing anti-rollback state"
|