@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.
Files changed (167) hide show
  1. package/README.md +350 -35
  2. package/bun.lock +3 -0
  3. package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
  4. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
  5. package/dist/cli/index.js +1728 -320
  6. package/dist/cli/macos-permissions.d.ts +13 -0
  7. package/dist/cli/macos-permissions.d.ts.map +1 -0
  8. package/dist/cli/options.d.ts +12 -0
  9. package/dist/cli/options.d.ts.map +1 -1
  10. package/dist/db/pg-migrations.d.ts.map +1 -1
  11. package/dist/db/recordings.d.ts +2 -1
  12. package/dist/db/recordings.d.ts.map +1 -1
  13. package/dist/db/remote-storage.d.ts +5 -1
  14. package/dist/db/remote-storage.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +628 -147
  18. package/dist/lib/bun-runtime.d.ts +11 -0
  19. package/dist/lib/bun-runtime.d.ts.map +1 -0
  20. package/dist/lib/config.d.ts +8 -1
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/enhancer.d.ts +10 -2
  23. package/dist/lib/enhancer.d.ts.map +1 -1
  24. package/dist/lib/install-maintenance.d.ts +24 -0
  25. package/dist/lib/install-maintenance.d.ts.map +1 -0
  26. package/dist/lib/machine.d.ts +2 -0
  27. package/dist/lib/machine.d.ts.map +1 -0
  28. package/dist/lib/recorder.d.ts +2 -1
  29. package/dist/lib/recorder.d.ts.map +1 -1
  30. package/dist/lib/recording-create-identity.d.ts +13 -0
  31. package/dist/lib/recording-create-identity.d.ts.map +1 -0
  32. package/dist/lib/release-install-policy.d.ts +31 -0
  33. package/dist/lib/release-install-policy.d.ts.map +1 -0
  34. package/dist/lib/transcriber.d.ts.map +1 -1
  35. package/dist/mcp/index.d.ts.map +1 -1
  36. package/dist/mcp/index.js +977 -194
  37. package/dist/sdk/index.js +5 -1
  38. package/dist/sdk/v1.generated.d.ts +4 -0
  39. package/dist/sdk/v1.generated.d.ts.map +1 -1
  40. package/dist/server/cloud-config.d.ts +9 -0
  41. package/dist/server/cloud-config.d.ts.map +1 -0
  42. package/dist/server/cloud-readiness.d.ts +10 -0
  43. package/dist/server/cloud-readiness.d.ts.map +1 -0
  44. package/dist/server/cloud.d.ts +9 -18
  45. package/dist/server/cloud.d.ts.map +1 -1
  46. package/dist/server/index.js +1654 -304
  47. package/dist/server/migrate-command.d.ts +11 -0
  48. package/dist/server/migrate-command.d.ts.map +1 -0
  49. package/dist/server/openapi.d.ts +22 -0
  50. package/dist/server/openapi.d.ts.map +1 -1
  51. package/dist/server/repo.d.ts +8 -1
  52. package/dist/server/repo.d.ts.map +1 -1
  53. package/dist/server/serve.d.ts +6 -1
  54. package/dist/server/serve.d.ts.map +1 -1
  55. package/dist/server/v1.d.ts.map +1 -1
  56. package/dist/storage.js +515 -80
  57. package/dist/store.d.ts +3 -1
  58. package/dist/store.d.ts.map +1 -1
  59. package/dist/types/index.d.ts +10 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/package.json +7 -4
  63. package/packaging/macos/Empty.entitlements +5 -0
  64. package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
  65. package/packaging/macos/Verifier.entitlements +10 -0
  66. package/packaging/macos/artifact-verifier.sb +33 -0
  67. package/packaging/macos/build_release_pkg.sh +872 -0
  68. package/packaging/macos/managed_bootstrap.sh +623 -0
  69. package/packaging/macos/pkgutil_fingerprint.awk +37 -0
  70. package/packaging/macos/release_lifecycle.ts +463 -0
  71. package/packaging/macos/scripts/postinstall +565 -0
  72. package/packaging/macos/scripts/preinstall +297 -0
  73. package/scripts/build_companion_cli.sh +337 -0
  74. package/scripts/build_native_fs_guard.sh +59 -0
  75. package/scripts/generate-sdk.ts +19 -1
  76. package/scripts/install_macos_app.sh +1922 -77
  77. package/scripts/macos_artifact.ts +5316 -0
  78. package/scripts/migrate.ts +38 -10
  79. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
  80. package/scripts/native/recordings_fs_guard.c +1167 -0
  81. package/scripts/native_fs_guard.ts +158 -0
  82. package/scripts/release-guard.ts +20 -1
  83. package/scripts/resolve_tailscale_cli.sh +389 -0
  84. package/scripts/smoke_macos_app.sh +573 -0
  85. package/src/native/Recordings/App/ContentView.swift +83 -0
  86. package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
  87. package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
  88. package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
  89. package/src/native/Recordings/App/RecordingsApp.swift +367 -44
  90. package/src/native/Recordings/App/RecordingsListView.swift +130 -0
  91. package/src/native/Recordings/App/RecordingsStore.swift +206 -0
  92. package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
  93. package/src/native/Recordings/App/SidebarView.swift +212 -0
  94. package/src/native/Recordings/App/Theme.swift +87 -0
  95. package/src/native/Recordings/Package.resolved +5 -5
  96. package/src/native/Recordings/Package.swift +62 -2
  97. package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
  98. package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
  99. package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
  100. package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
  101. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
  102. package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
  103. package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
  104. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
  105. package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
  106. package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
  107. package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
  108. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
  109. package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
  110. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
  111. package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
  112. package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
  113. package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
  114. package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
  115. package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
  116. package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
  117. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
  118. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
  119. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
  120. package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
  121. package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
  122. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
  123. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
  124. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
  125. package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
  126. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
  127. package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
  128. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
  129. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
  130. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
  131. package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
  132. package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
  133. package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
  134. package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
  135. package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
  136. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
  137. package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
  138. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
  139. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
  140. package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
  141. package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
  142. package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
  143. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
  144. package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
  145. package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
  146. package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
  147. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
  148. package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
  149. package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
  150. package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
  151. package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
  152. package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
  153. package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
  154. package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
  155. package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
  156. package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
  157. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
  158. package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
  159. package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
  160. package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
  161. package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
  162. package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
  163. package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
  164. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
  165. package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
  166. package/src/native/Recordings/build.sh +1990 -29
  167. package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
@@ -0,0 +1,158 @@
1
+ import { createRequire } from "node:module";
2
+ import { dirname, isAbsolute, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { lstatSync } from "node:fs";
5
+
6
+ export type NativeMetadata = {
7
+ dev: bigint;
8
+ ino: bigint;
9
+ uid: number;
10
+ mode: number;
11
+ nlink: number;
12
+ size: bigint;
13
+ type: "file" | "directory" | "symlink" | "special";
14
+ };
15
+
16
+ export type NativeHandle = object;
17
+
18
+ export type NativeFsGuard = {
19
+ openTrustedHome(path: string, uid: number): NativeHandle;
20
+ openDirAt(parent: NativeHandle, leaf: string): NativeHandle;
21
+ openRegularAt(
22
+ parent: NativeHandle,
23
+ leaf: string,
24
+ access: "read" | "createExclusive",
25
+ ): NativeHandle;
26
+ readDir(directory: NativeHandle): string[];
27
+ statHandle(handle: NativeHandle): NativeMetadata;
28
+ statAt(parent: NativeHandle, leaf: string): NativeMetadata | null;
29
+ mkdirAt(parent: NativeHandle, leaf: string, mode: number): NativeHandle;
30
+ linkNoReplaceAt(
31
+ sourceParent: NativeHandle,
32
+ sourceLeaf: string,
33
+ destinationParent: NativeHandle,
34
+ destinationLeaf: string,
35
+ ): boolean;
36
+ renameNoReplaceAt(
37
+ sourceParent: NativeHandle,
38
+ sourceLeaf: string,
39
+ destinationParent: NativeHandle,
40
+ destinationLeaf: string,
41
+ ): void;
42
+ renameHandleNoReplaceAt(
43
+ sourceParent: NativeHandle,
44
+ sourceLeaf: string,
45
+ source: NativeHandle,
46
+ destinationParent: NativeHandle,
47
+ destinationLeaf: string,
48
+ ): void;
49
+ renameReplaceAt(
50
+ sourceParent: NativeHandle,
51
+ sourceLeaf: string,
52
+ destinationParent: NativeHandle,
53
+ destinationLeaf: string,
54
+ ): void;
55
+ unlinkFileAt(parent: NativeHandle, leaf: string): void;
56
+ unlinkDirAt(parent: NativeHandle, leaf: string): void;
57
+ sameBinding(parent: NativeHandle, leaf: string, child: NativeHandle): boolean;
58
+ fsyncHandle(handle: NativeHandle): void;
59
+ handleHasNoExtendedAcl(handle: NativeHandle): boolean;
60
+ chmodHandle(handle: NativeHandle, mode: number): void;
61
+ writeFileAt(parent: NativeHandle, leaf: string, contents: Buffer, mode: number): void;
62
+ readRegularAt(parent: NativeHandle, leaf: string, maximumBytes: number): Buffer;
63
+ sha256RegularAt(parent: NativeHandle, leaf: string): string;
64
+ sha256Handle(handle: NativeHandle): string;
65
+ copyRegularNoReplaceAt(
66
+ sourceParent: NativeHandle,
67
+ sourceLeaf: string,
68
+ destinationParent: NativeHandle,
69
+ destinationLeaf: string,
70
+ temporaryLeaf: string,
71
+ crashDuringCopy: boolean,
72
+ crashAfterPublish: boolean,
73
+ ): boolean;
74
+ removeTreeAt(parent: NativeHandle, leaf: string): void;
75
+ removeTreeHandleAt(parent: NativeHandle, leaf: string, directory: NativeHandle): void;
76
+ unlinkFileHandleAt(parent: NativeHandle, leaf: string, file: NativeHandle): void;
77
+ close(handle: NativeHandle): void;
78
+ };
79
+
80
+ let loadedGuard: NativeFsGuard | undefined;
81
+
82
+ function addonPath(): string {
83
+ if (process.platform === "darwin") {
84
+ return join(
85
+ dirname(fileURLToPath(import.meta.url)),
86
+ "native",
87
+ "prebuilds",
88
+ "darwin-universal",
89
+ "recordings_fs_guard.node",
90
+ );
91
+ }
92
+ const testOverride = process.env.RECORDINGS_TEST_FS_GUARD_ADDON;
93
+ if (testOverride) {
94
+ if (!isAbsolute(testOverride)) {
95
+ throw new Error("native filesystem guard test addon path must be absolute");
96
+ }
97
+ return testOverride;
98
+ }
99
+ return join(
100
+ dirname(fileURLToPath(import.meta.url)),
101
+ "..",
102
+ "node_modules",
103
+ ".cache",
104
+ "recordings-native-fs-guard",
105
+ "recordings_fs_guard.node",
106
+ );
107
+ }
108
+
109
+ export function nativeFsGuard(): NativeFsGuard {
110
+ if (loadedGuard) return loadedGuard;
111
+ const path = addonPath();
112
+ const details = lstatSync(path);
113
+ const uid = process.getuid?.();
114
+ if (
115
+ !details.isFile() ||
116
+ details.isSymbolicLink() ||
117
+ uid === undefined ||
118
+ details.uid !== uid ||
119
+ (details.mode & 0o022) !== 0
120
+ ) {
121
+ throw new Error("native filesystem guard has an unsafe type, owner, or mode");
122
+ }
123
+ const loaded = createRequire(import.meta.url)(path) as Partial<NativeFsGuard>;
124
+ for (const name of [
125
+ "openTrustedHome",
126
+ "openDirAt",
127
+ "openRegularAt",
128
+ "readDir",
129
+ "statHandle",
130
+ "statAt",
131
+ "mkdirAt",
132
+ "linkNoReplaceAt",
133
+ "renameNoReplaceAt",
134
+ "renameHandleNoReplaceAt",
135
+ "renameReplaceAt",
136
+ "unlinkFileAt",
137
+ "unlinkDirAt",
138
+ "sameBinding",
139
+ "fsyncHandle",
140
+ "handleHasNoExtendedAcl",
141
+ "chmodHandle",
142
+ "writeFileAt",
143
+ "readRegularAt",
144
+ "sha256RegularAt",
145
+ "sha256Handle",
146
+ "copyRegularNoReplaceAt",
147
+ "removeTreeAt",
148
+ "removeTreeHandleAt",
149
+ "unlinkFileHandleAt",
150
+ "close",
151
+ ] as const) {
152
+ if (typeof loaded[name] !== "function") {
153
+ throw new Error(`native filesystem guard is missing ${name}`);
154
+ }
155
+ }
156
+ loadedGuard = loaded as NativeFsGuard;
157
+ return loadedGuard;
158
+ }
@@ -18,6 +18,9 @@ const repoRoot = process.cwd();
18
18
  const roots = [
19
19
  "package.json",
20
20
  "README.md",
21
+ "LICENSE",
22
+ "Dockerfile.package",
23
+ "bun.lock",
21
24
  "scripts",
22
25
  "src/storage.ts",
23
26
  "src/index.ts",
@@ -29,9 +32,11 @@ const roots = [
29
32
  "src/native/Recordings/App",
30
33
  "src/native/Recordings/RecordingsLib",
31
34
  "src/native/Recordings/RecordingsTests",
35
+ "src/native/Recordings/Updater",
32
36
  "src/native/Recordings/Package.swift",
33
37
  "src/native/Recordings/Package.resolved",
34
38
  "src/native/Recordings/build.sh",
39
+ "packaging/macos",
35
40
  "dist",
36
41
  ];
37
42
 
@@ -73,6 +78,19 @@ const legacyDatabasePatterns: PatternCheck[] = [
73
78
  },
74
79
  ];
75
80
 
81
+ // These exact public CA locations are transport trust material, not the retired
82
+ // client-side database integration. No other token receives an exception.
83
+ const legacyDatabaseName = ["r", "d", "s"].join("");
84
+ const approvedPublicCaTokens = [
85
+ `/etc/ssl/certs/${legacyDatabaseName}-global-bundle.pem`,
86
+ `https://truststore.pki.${legacyDatabaseName}.amazonaws.com/global/global-bundle.pem`,
87
+ ];
88
+
89
+ export function contentForLegacyDatabaseScan(relativeFile: string, content: string): string {
90
+ if (relativeFile !== "Dockerfile.package") return content;
91
+ return approvedPublicCaTokens.reduce((value, token) => value.split(token).join(""), content);
92
+ }
93
+
76
94
  function isText(buffer: Buffer): boolean {
77
95
  return !buffer.includes(0);
78
96
  }
@@ -105,8 +123,9 @@ for (const file of roots.flatMap((root) => collectFiles(join(repoRoot, root))))
105
123
  }
106
124
  }
107
125
 
126
+ const legacyDatabaseContent = contentForLegacyDatabaseScan(relativeFile, content);
108
127
  for (const check of legacyDatabasePatterns) {
109
- if (check.pattern.test(content)) {
128
+ if (check.pattern.test(legacyDatabaseContent)) {
110
129
  findings.push({ file: relativeFile, marker: check.label, kind: "retired-cloud" });
111
130
  }
112
131
  }
@@ -0,0 +1,389 @@
1
+ #!/bin/bash
2
+
3
+ # Resolve the Tailscale CLI without evaluating shell-provided command text.
4
+
5
+ recordings_tailscale_standard_app_cli() {
6
+ printf '%s\n' '/Applications/Tailscale.app/Contents/MacOS/Tailscale'
7
+ }
8
+
9
+ recordings_tailscale_standard_app() {
10
+ printf '%s\n' '/Applications/Tailscale.app'
11
+ }
12
+
13
+ recordings_real_host_kernel() {
14
+ local kernel
15
+ if ! kernel="$(/usr/bin/uname -s)"; then
16
+ echo "Could not determine the host platform with pinned /usr/bin/uname." >&2
17
+ return 1
18
+ fi
19
+ case "$kernel" in
20
+ Darwin|Linux) printf '%s\n' "$kernel" ;;
21
+ *)
22
+ echo "Unsupported host platform from pinned /usr/bin/uname: ${kernel:-<empty>}." >&2
23
+ return 1
24
+ ;;
25
+ esac
26
+ }
27
+
28
+ recordings_validate_tailscale_cli_shape() {
29
+ local candidate="$1"
30
+
31
+ case "$candidate" in
32
+ /*) ;;
33
+ *)
34
+ echo "Resolved Tailscale CLI path must be absolute." >&2
35
+ return 1
36
+ ;;
37
+ esac
38
+ if [[ "$candidate" == *$'\n'* ]] || [[ "$candidate" == *$'\r'* ]]; then
39
+ echo "Resolved Tailscale CLI path is malformed." >&2
40
+ return 1
41
+ fi
42
+ }
43
+
44
+ recordings_validate_tailscale_cli() {
45
+ local candidate="$1"
46
+
47
+ recordings_validate_tailscale_cli_shape "$candidate" || return 1
48
+ if [ ! -f "$candidate" ] || [ ! -x "$candidate" ]; then
49
+ echo "Resolved Tailscale CLI path is not an executable file." >&2
50
+ return 1
51
+ fi
52
+ }
53
+
54
+ recordings_resolve_tailscale_cli() {
55
+ local candidate
56
+
57
+ candidate="$(builtin type -P tailscale 2>/dev/null || true)"
58
+ if [ -n "$candidate" ]; then
59
+ recordings_validate_tailscale_cli_shape "$candidate" || return 1
60
+ if [ -f "$candidate" ] && [ -x "$candidate" ]; then
61
+ printf '%s\n' "$candidate"
62
+ return 0
63
+ fi
64
+ fi
65
+ candidate="$(recordings_tailscale_standard_app_cli)"
66
+ recordings_validate_tailscale_cli "$candidate" || return 1
67
+ printf '%s\n' "$candidate"
68
+ }
69
+
70
+ recordings_tailscale_stat_owner_and_mode() {
71
+ local candidate="$1"
72
+ local real_host_kernel="$2"
73
+
74
+ case "$real_host_kernel" in
75
+ Darwin) /usr/bin/stat -f '%u %Lp' "$candidate" ;;
76
+ *) /usr/bin/stat -c '%u %a' "$candidate" ;;
77
+ esac
78
+ }
79
+
80
+ recordings_validate_trusted_tailscale_app_cli() {
81
+ local candidate="$1"
82
+ local real_host_kernel="$2"
83
+ local canonical
84
+ local current_uid
85
+ local owner
86
+ local mode
87
+ local other_permissions
88
+ local group_permissions
89
+
90
+ recordings_validate_tailscale_cli_shape "$candidate" || return 1
91
+ if [ ! -f "$candidate" ] || [ ! -x "$candidate" ]; then
92
+ echo "Trusted Tailscale CLI path is not an executable file." >&2
93
+ return 1
94
+ fi
95
+ if [ -L "$candidate" ]; then
96
+ echo "Trusted Tailscale CLI must be a canonical non-symlink trusted executable." >&2
97
+ return 1
98
+ fi
99
+ canonical="$(/usr/bin/realpath "$candidate" 2>/dev/null)" || {
100
+ echo "Trusted Tailscale CLI could not be resolved canonically." >&2
101
+ return 1
102
+ }
103
+ if [ "$canonical" != "$candidate" ]; then
104
+ echo "Trusted Tailscale CLI must be a canonical non-symlink trusted executable." >&2
105
+ return 1
106
+ fi
107
+
108
+ read -r owner mode <<EOF
109
+ $(recordings_tailscale_stat_owner_and_mode "$candidate" "$real_host_kernel")
110
+ EOF
111
+ current_uid="$(/usr/bin/id -u)"
112
+ case "$owner" in
113
+ 0|"$current_uid") ;;
114
+ *)
115
+ echo "Trusted Tailscale CLI has an unsafe owner." >&2
116
+ return 1
117
+ ;;
118
+ esac
119
+ case "$mode" in
120
+ [0-7][0-7][0-7]|0[0-7][0-7][0-7]) ;;
121
+ *)
122
+ echo "Trusted Tailscale CLI has an invalid mode." >&2
123
+ return 1
124
+ ;;
125
+ esac
126
+ other_permissions="${mode: -1}"
127
+ group_permissions="${mode: -2:1}"
128
+ case "$group_permissions$other_permissions" in
129
+ *[2367]*)
130
+ echo "Trusted Tailscale CLI must not be group- or world-writable." >&2
131
+ return 1
132
+ ;;
133
+ esac
134
+ }
135
+
136
+ recordings_validate_private_tailscale_snapshot_parent() {
137
+ local snapshot_parent="$1"
138
+ local real_host_kernel="$2"
139
+ local canonical
140
+ local owner
141
+ local mode
142
+
143
+ recordings_validate_tailscale_cli_shape "$snapshot_parent" || return 1
144
+ if [ ! -d "$snapshot_parent" ] || [ -L "$snapshot_parent" ]; then
145
+ echo "Tailscale snapshot parent must be an existing private directory." >&2
146
+ return 1
147
+ fi
148
+ canonical="$(/usr/bin/realpath "$snapshot_parent" 2>/dev/null)" || {
149
+ echo "Tailscale snapshot parent could not be resolved canonically." >&2
150
+ return 1
151
+ }
152
+ if [ "$canonical" != "$snapshot_parent" ]; then
153
+ echo "Tailscale snapshot parent must be a canonical non-symlink directory." >&2
154
+ return 1
155
+ fi
156
+ read -r owner mode <<EOF
157
+ $(recordings_tailscale_stat_owner_and_mode "$snapshot_parent" "$real_host_kernel")
158
+ EOF
159
+ if [ "$owner" != "$(/usr/bin/id -u)" ] || [ "$mode" != 700 ]; then
160
+ echo "Tailscale snapshot parent must be owned by the current user with mode 700." >&2
161
+ return 1
162
+ fi
163
+ }
164
+
165
+ recordings_require_tailscale_snapshot_tool() {
166
+ local label="$1"
167
+ local executable="$2"
168
+
169
+ case "$executable" in
170
+ /*) ;;
171
+ *)
172
+ echo "$label must be an absolute executable path." >&2
173
+ return 1
174
+ ;;
175
+ esac
176
+ if [ ! -f "$executable" ] || [ ! -x "$executable" ]; then
177
+ echo "$label is missing or is not executable." >&2
178
+ return 1
179
+ fi
180
+ }
181
+
182
+ recordings_run_tailscale_snapshot_tool() {
183
+ local real_host_kernel="$1"
184
+ local snapshot_parent="$2"
185
+ local executable="$3"
186
+ shift 3
187
+
188
+ if [ "$real_host_kernel" = "Darwin" ]; then
189
+ /usr/bin/env -i \
190
+ HOME=/tmp \
191
+ PATH=/usr/bin:/bin:/usr/sbin:/sbin \
192
+ TMPDIR="$snapshot_parent" \
193
+ "$executable" "$@"
194
+ else
195
+ # Test-only allowlist. This entire branch is unreachable on a real Darwin
196
+ # host because the kernel probe and branch are both fixed above the test
197
+ # overrides in recordings_resolve_trusted_tailscale_app_cli.
198
+ /usr/bin/env -i \
199
+ HOME=/tmp \
200
+ PATH=/usr/bin:/bin:/usr/sbin:/sbin \
201
+ TMPDIR="$snapshot_parent" \
202
+ MARKER_DIRECTORY="${MARKER_DIRECTORY:-}" \
203
+ "$executable" "$@"
204
+ fi
205
+ }
206
+
207
+ recordings_tailscale_details_have_official_identity() {
208
+ local details="$1"
209
+ local found_identifier=0
210
+ local found_team=0
211
+ local line
212
+
213
+ while IFS= read -r line; do
214
+ case "$line" in
215
+ Identifier=io.tailscale.ipn.macsys) found_identifier=1 ;;
216
+ TeamIdentifier=W5364U7YZB) found_team=1 ;;
217
+ esac
218
+ done <<EOF
219
+ $details
220
+ EOF
221
+ if [ "$found_team" -ne 1 ]; then
222
+ echo "Tailscale code does not carry the official TeamIdentifier W5364U7YZB." >&2
223
+ return 1
224
+ fi
225
+ if [ "$found_identifier" -ne 1 ]; then
226
+ echo "Tailscale code does not carry the official bundle identifier io.tailscale.ipn.macsys." >&2
227
+ return 1
228
+ fi
229
+ }
230
+
231
+ recordings_verify_official_tailscale_code() {
232
+ local code_path="$1"
233
+ local label="$2"
234
+ local real_host_kernel="$3"
235
+ local snapshot_parent="$4"
236
+ local codesign_executable="$5"
237
+ local requirement
238
+ local details
239
+
240
+ requirement='anchor apple generic and certificate leaf[subject.OU] = "W5364U7YZB" and identifier "io.tailscale.ipn.macsys"'
241
+ local -a verification_arguments=(--verify --strict --all-architectures --verbose=2)
242
+ if [ -d "$code_path" ]; then
243
+ verification_arguments+=(--deep)
244
+ fi
245
+ verification_arguments+=(-R "$requirement" "$code_path")
246
+ if ! recordings_run_tailscale_snapshot_tool \
247
+ "$real_host_kernel" "$snapshot_parent" "$codesign_executable" \
248
+ "${verification_arguments[@]}"; then
249
+ echo "$label does not satisfy the official TeamIdentifier W5364U7YZB and official bundle identifier io.tailscale.ipn.macsys." >&2
250
+ return 1
251
+ fi
252
+ details="$(recordings_run_tailscale_snapshot_tool \
253
+ "$real_host_kernel" "$snapshot_parent" "$codesign_executable" \
254
+ -d --verbose=4 "$code_path" 2>&1)" || {
255
+ echo "$label signing identity could not be read." >&2
256
+ return 1
257
+ }
258
+ recordings_tailscale_details_have_official_identity "$details"
259
+ }
260
+
261
+ recordings_verify_official_tailscale_app_and_cli() {
262
+ local app="$1"
263
+ local cli="$2"
264
+ local real_host_kernel="$3"
265
+ local snapshot_parent="$4"
266
+ local codesign_executable="$5"
267
+ local canonical_app
268
+
269
+ if [ ! -d "$app" ] || [ -L "$app" ]; then
270
+ echo "Trusted Tailscale app must be a canonical non-symlink directory." >&2
271
+ return 1
272
+ fi
273
+ canonical_app="$(/usr/bin/realpath "$app" 2>/dev/null)" || {
274
+ echo "Trusted Tailscale app could not be resolved canonically." >&2
275
+ return 1
276
+ }
277
+ if [ "$canonical_app" != "$app" ]; then
278
+ echo "Trusted Tailscale app must be a canonical non-symlink directory." >&2
279
+ return 1
280
+ fi
281
+ recordings_validate_trusted_tailscale_app_cli "$cli" "$real_host_kernel" || return 1
282
+ recordings_verify_official_tailscale_code \
283
+ "$app" "Tailscale.app" "$real_host_kernel" "$snapshot_parent" "$codesign_executable" || return 1
284
+ recordings_verify_official_tailscale_code \
285
+ "$cli" "Tailscale CLI" "$real_host_kernel" "$snapshot_parent" "$codesign_executable"
286
+ }
287
+
288
+ recordings_resolve_trusted_tailscale_app_cli() {
289
+ local snapshot_parent="${1:-}"
290
+ local real_host_kernel
291
+ local source_app
292
+ local source_cli
293
+ local snapshot_root
294
+ local snapshot_app
295
+ local snapshot_cli
296
+ local codesign_executable
297
+ local ditto_executable
298
+
299
+ # This probe is deliberately absolute and cannot be replaced by the
300
+ # installer's test tool overrides or caller PATH.
301
+ real_host_kernel="$(recordings_real_host_kernel)" || return 1
302
+ if [ "$real_host_kernel" = "Darwin" ]; then
303
+ source_app='/Applications/Tailscale.app'
304
+ codesign_executable='/usr/bin/codesign'
305
+ ditto_executable='/usr/bin/ditto'
306
+ else
307
+ # Test-only: Linux fixtures cannot authenticate or materialize the macOS
308
+ # /Applications bundle. These overrides are structurally unreachable on
309
+ # Darwin because the real kernel branch above pins every production input.
310
+ source_app="${RECORDINGS_TEST_TRUSTED_TAILSCALE_APP:-$(recordings_tailscale_standard_app)}"
311
+ codesign_executable="${RECORDINGS_TEST_TAILSCALE_CODESIGN_EXECUTABLE:-/usr/bin/codesign}"
312
+ ditto_executable="${RECORDINGS_TEST_TAILSCALE_DITTO_EXECUTABLE:-/usr/bin/ditto}"
313
+ fi
314
+ recordings_validate_private_tailscale_snapshot_parent "$snapshot_parent" "$real_host_kernel" || return 1
315
+ recordings_require_tailscale_snapshot_tool "Tailscale codesign verifier" "$codesign_executable" || return 1
316
+ recordings_require_tailscale_snapshot_tool "Tailscale app copier" "$ditto_executable" || return 1
317
+
318
+ source_cli="$source_app/Contents/MacOS/Tailscale"
319
+ recordings_verify_official_tailscale_app_and_cli \
320
+ "$source_app" "$source_cli" "$real_host_kernel" "$snapshot_parent" "$codesign_executable" || return 1
321
+
322
+ snapshot_root="$snapshot_parent/tailscale-identity-snapshot"
323
+ snapshot_app="$snapshot_root/Tailscale.app"
324
+ snapshot_cli="$snapshot_app/Contents/MacOS/Tailscale"
325
+ if [ -e "$snapshot_root" ] || [ -L "$snapshot_root" ]; then
326
+ echo "Tailscale identity snapshot destination already exists." >&2
327
+ return 1
328
+ fi
329
+ /bin/mkdir -m 700 "$snapshot_root" || return 1
330
+ if ! recordings_run_tailscale_snapshot_tool \
331
+ "$real_host_kernel" "$snapshot_parent" "$ditto_executable" "$source_app" "$snapshot_app"; then
332
+ echo "Could not copy the authenticated Tailscale app into the private snapshot." >&2
333
+ return 1
334
+ fi
335
+ if ! recordings_verify_official_tailscale_app_and_cli \
336
+ "$snapshot_app" "$snapshot_cli" "$real_host_kernel" "$snapshot_parent" "$codesign_executable"; then
337
+ echo "Tailscale app snapshot was not authenticated after copying." >&2
338
+ return 1
339
+ fi
340
+ printf '%s\n' "$snapshot_cli"
341
+ }
342
+
343
+ recordings_run_trusted_tailscale_status() {
344
+ local snapshot_cli="$1"
345
+ local snapshot_parent="$2"
346
+ local real_host_kernel
347
+ local expected_cli
348
+ local snapshot_app
349
+ local codesign_executable
350
+
351
+ real_host_kernel="$(recordings_real_host_kernel)" || return 1
352
+ expected_cli="$snapshot_parent/tailscale-identity-snapshot/Tailscale.app/Contents/MacOS/Tailscale"
353
+ if [ "$snapshot_cli" != "$expected_cli" ]; then
354
+ echo "Tailscale status executable is not bound to the private authenticated snapshot." >&2
355
+ return 1
356
+ fi
357
+ if [ "$real_host_kernel" = "Darwin" ]; then
358
+ codesign_executable='/usr/bin/codesign'
359
+ else
360
+ # Test-only override; unreachable on a real Darwin host.
361
+ codesign_executable="${RECORDINGS_TEST_TAILSCALE_CODESIGN_EXECUTABLE:-/usr/bin/codesign}"
362
+ fi
363
+ recordings_validate_private_tailscale_snapshot_parent "$snapshot_parent" "$real_host_kernel" || return 1
364
+ recordings_require_tailscale_snapshot_tool "Tailscale codesign verifier" "$codesign_executable" || return 1
365
+ snapshot_app="$snapshot_parent/tailscale-identity-snapshot/Tailscale.app"
366
+ recordings_verify_official_tailscale_app_and_cli \
367
+ "$snapshot_app" "$snapshot_cli" "$real_host_kernel" "$snapshot_parent" "$codesign_executable" || return 1
368
+
369
+ if [ "$real_host_kernel" = "Darwin" ]; then
370
+ /usr/bin/env -i \
371
+ HOME=/tmp \
372
+ PATH=/usr/bin:/bin:/usr/sbin:/sbin \
373
+ TMPDIR="$snapshot_parent" \
374
+ "$snapshot_cli" status --json
375
+ else
376
+ # Test-only fixture controls. No caller-provided status environment reaches
377
+ # the official CLI on Darwin.
378
+ /usr/bin/env -i \
379
+ HOME=/tmp \
380
+ PATH=/usr/bin:/bin:/usr/sbin:/sbin \
381
+ TMPDIR="$snapshot_parent" \
382
+ MARKER_DIRECTORY="${MARKER_DIRECTORY:-}" \
383
+ FAIL_TAILSCALE_STATUS="${FAIL_TAILSCALE_STATUS:-0}" \
384
+ TAILSCALE_STATUS_JSON="${TAILSCALE_STATUS_JSON:-}" \
385
+ FAIL_BUILDER_TAILSCALE_STATUS="${FAIL_BUILDER_TAILSCALE_STATUS:-0}" \
386
+ BUILDER_TAILSCALE_STATUS_JSON="${BUILDER_TAILSCALE_STATUS_JSON:-}" \
387
+ "$snapshot_cli" status --json
388
+ fi
389
+ }