@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,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
|
+
}
|
package/scripts/release-guard.ts
CHANGED
|
@@ -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(
|
|
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
|
+
}
|