@rightkit/release 0.2.135 → 0.2.137
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 +62 -0
- package/addon-command.mjs +53 -34
- package/apple-profile-renew.mjs +30 -12
- package/artifact-storage.mjs +47 -10
- package/background-process.mjs +18 -0
- package/build-release.mjs +1 -1
- package/cache-command.mjs +4 -3
- package/cargo-contract.mjs +1 -1
- package/cargo-guard.mjs +4 -3
- package/cargo-registry-readback.mjs +51 -0
- package/cargo-target.mjs +3 -1
- package/cli/right-release.mjs +26 -0
- package/codesign-environment.mjs +26 -2
- package/codesign-shim.mjs +2 -1
- package/committed-source.mjs +3 -3
- package/development.mjs +7 -7
- package/direct-bootstrap.mjs +19 -9
- package/dmg-build.mjs +117 -0
- package/dmg-layout.mjs +123 -0
- package/github-release.mjs +74 -37
- package/heavy-command.mjs +3 -2
- package/install-app.mjs +358 -0
- package/inventory-check.mjs +980 -0
- package/macos-signing-session.mjs +3 -0
- package/managed-cargo.mjs +23 -19
- package/managed-host-secrets.mjs +30 -0
- package/managed-roots.mjs +39 -0
- package/model-promote.mjs +31 -37
- package/notary-auth.mjs +4 -0
- package/osascript-guard.mjs +24 -0
- package/ownership-approval-command.mjs +15 -4
- package/ownership-approval.mjs +114 -49
- package/ownership-availability.mjs +25 -0
- package/ownership-catalog-config.json +18 -0
- package/ownership-catalog.mjs +94 -0
- package/ownership-command.mjs +20 -10
- package/ownership-declaration.json +15 -0
- package/ownership-default-policy.json +1 -11
- package/ownership-evidence.mjs +10 -8
- package/ownership-git-inventory.mjs +29 -1
- package/ownership-helper-paths.json +2 -2
- package/ownership-hooks.mjs +107 -0
- package/ownership-internal-owners.json +44 -0
- package/ownership-key-authority.mjs +112 -7
- package/ownership-key-command.mjs +76 -19
- package/ownership-key-trust.mjs +97 -9
- package/ownership-lockfiles.mjs +311 -0
- package/ownership-report-command.mjs +12 -3
- package/ownership-report.mjs +20 -17
- package/ownership-repositories.json +19 -0
- package/ownership-scan-worker.mjs +96 -11
- package/ownership-source.mjs +39 -0
- package/ownership-staged.mjs +193 -0
- package/ownership-verdict.mjs +74 -0
- package/ownership.mjs +215 -107
- package/package.json +1 -1
- package/protected-credentials.mjs +111 -0
- package/prune-r2.mjs +4 -3
- package/pub-upload.mjs +3 -85
- package/publication-artifacts.mjs +108 -0
- package/publication-gate.mjs +28 -7
- package/publish-cargo.mjs +47 -16
- package/publish-swift.mjs +46 -17
- package/publish-update.mjs +61 -39
- package/r2-verified-upload.mjs +41 -0
- package/registry-parity.mjs +5 -3
- package/release-state.mjs +1 -0
- package/release-token.mjs +2 -0
- package/rightkit-drift.mjs +3 -2
- package/rightkit-ownership.json +2028 -139
- package/rightkit-signing-agent.mjs +5 -3
- package/rightkit-sync.mjs +6 -2
- package/rightkit-versions.json +215 -103
- package/runtime-artifact-manifest.mjs +20 -6
- package/sign-updater.mjs +5 -0
- package/signing-agent-client.mjs +2 -1
- package/signing-agent-install.mjs +3 -2
- package/standalone-clone-verify.mjs +3 -2
- package/tauri-bundle-marker.mjs +3 -2
- package/upload-large.mjs +36 -82
- package/verifier.mjs +5 -1
|
@@ -6,6 +6,7 @@ import { spawnSync } from "./background-process.mjs";
|
|
|
6
6
|
import { requestSigningAgentSync, validateNotaryAgentReport } from "./signing-agent-client.mjs";
|
|
7
7
|
import { installSigningAgent, requireMacGuiSession } from "./signing-agent-install.mjs";
|
|
8
8
|
import { signingAgentPaths, defaultSigningRoots, ensurePrivateDirectory, privatePath, within, NOTARIZATION_TIMEOUT_MS, MAX_NOTARIZATION_TIMEOUT_MS } from "./rightkit-signing-agent.mjs";
|
|
9
|
+
import { refuseManagedEnvSecrets } from "./managed-host-secrets.mjs";
|
|
9
10
|
|
|
10
11
|
export class MacSigningSessionError extends Error {
|
|
11
12
|
constructor(code, message) {
|
|
@@ -173,6 +174,8 @@ export function runMacGuiNotarization({ kind = "notarize", file, profile = "appl
|
|
|
173
174
|
|
|
174
175
|
export function signMacUpdaterFiles(files, options = {}) {
|
|
175
176
|
const { cwd = process.cwd(), env = process.env, run = spawnSync, username = os.userInfo().username } = options;
|
|
177
|
+
// Inherited updater key material must not bypass the Keychain or GUI signing agent on managed hosts.
|
|
178
|
+
refuseManagedEnvSecrets(env, ["TAURI_SIGNING_PRIVATE_KEY", "TAURI_SIGNING_PRIVATE_KEY_PASSWORD"]);
|
|
176
179
|
const commands = files.map((file) => ["exec", "tauri", "signer", "sign", file]);
|
|
177
180
|
const signingEnv = { ...env, TAURI_SIGNING_PRIVATE_KEY_PASSWORD: env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD ?? "" };
|
|
178
181
|
if (env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT || (!signingEnv.TAURI_SIGNING_PRIVATE_KEY && detectMacSigningSession({ env, run }).guiRunner)) {
|
package/managed-cargo.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { discoverBrokerSocket, managedControlCandidates, resolveManagedRoots } from './managed-roots.mjs';
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { spawnSync } from "./background-process.mjs";
|
|
5
6
|
|
|
6
7
|
export function managedRuntimeClient(env = process.env, platform = process.platform, exists = existsSync) {
|
|
7
|
-
const roots =
|
|
8
|
-
|
|
9
|
-
.filter((root) => typeof root === "string" && root.length > 0);
|
|
8
|
+
const roots = managedControlCandidates({ env, platform });
|
|
9
|
+
const paths = platform === 'win32' ? path.win32 : path.posix;
|
|
10
10
|
for (const root of roots) {
|
|
11
|
-
const client =
|
|
11
|
+
const client = paths.join(root, "runtime-current", "build-control-clients", "src", "rightkit.mjs");
|
|
12
12
|
if (exists(client)) return client;
|
|
13
13
|
}
|
|
14
14
|
return undefined;
|
|
@@ -19,7 +19,7 @@ export function managedRuntimeClient(env = process.env, platform = process.platf
|
|
|
19
19
|
export function acquireManagedRunLease({ command, env = process.env, platform = process.platform, exists = existsSync, pid = process.pid } = {}) {
|
|
20
20
|
const client = managedRuntimeClient(env, platform, exists);
|
|
21
21
|
if (!client) return { path: null, release() {} };
|
|
22
|
-
const runs = path.join(
|
|
22
|
+
const runs = path.join(resolveManagedRoots({ env, platform }).dataRoot, "rightkit-build-control", "global", "runs");
|
|
23
23
|
const file = path.join(runs, `${pid}-${randomUUID()}.json`);
|
|
24
24
|
try {
|
|
25
25
|
mkdirSync(runs, { recursive: true });
|
|
@@ -31,24 +31,28 @@ export function acquireManagedRunLease({ command, env = process.env, platform =
|
|
|
31
31
|
return { path: file, release() { rmSync(file, { force: true }); } };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function managedHost(env) {
|
|
34
|
+
export function managedHost(env) {
|
|
35
35
|
return Boolean(env.RIGHTKIT_BUILD_BROKER_SOCKET)
|
|
36
|
-
|| /(?:^|[;:])[^;:]*[/\\](?:\.rightkit-managed|rightkitmanagedagent)[/\\]agent-bin(?:[;:]|$)/i.test(String(env.PATH ?? ""));
|
|
36
|
+
|| /(?:^|[;:])[^;:]*[/\\](?:\.rightkit-managed|rightkitmanagedagent|rightkit[/\\]build-control)[/\\]agent-bin(?:[;:]|$)/i.test(String(env.PATH ?? ""));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function runCargoSync(args, options = {}) {
|
|
39
|
+
export function runCargoSync(args, options = {}, { platform = process.platform, exists = existsSync, spawn = spawnSync } = {}) {
|
|
40
40
|
const env = options.env ?? process.env;
|
|
41
|
-
const client = managedRuntimeClient(env);
|
|
41
|
+
const client = managedRuntimeClient(env, platform, exists);
|
|
42
42
|
if (client) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return { status: null, stdout: "", stderr: "", error: new Error("installed RightKit runtime client is unavailable for managed Cargo metadata") };
|
|
43
|
+
const socket = discoverBrokerSocket(env, { platform, exists });
|
|
44
|
+
if (platform === 'darwin' && (!socket || !exists(socket))) return unavailable('RightKit broker socket is unavailable');
|
|
45
|
+
const childEnv = socket ? { ...env, RIGHTKIT_BUILD_BROKER_SOCKET: socket } : env;
|
|
46
|
+
const result = spawn(process.execPath, [client, "cargo", ...args], { ...options, env: childEnv });
|
|
47
|
+
if (platform === 'darwin' && result.status !== 0 && /RK_BROKER_UNAVAILABLE|RightKit broker (?:unavailable|endpoint is not configured)/i.test(String(result.stderr ?? ''))) {
|
|
48
|
+
return { ...result, error: unavailable(String(result.stderr).trim()).error };
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
52
51
|
}
|
|
53
|
-
return
|
|
52
|
+
if (platform === 'darwin' || (managedHost(env) && platform === 'win32')) return unavailable('installed RightKit runtime client is unavailable for managed Cargo metadata');
|
|
53
|
+
return spawn("cargo", args, options);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function unavailable(message) {
|
|
57
|
+
return { status: null, stdout: "", stderr: message, error: Object.assign(new Error(message), { code: 'RK_BROKER_UNAVAILABLE' }) };
|
|
54
58
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { managedHost } from "./managed-cargo.mjs";
|
|
2
|
+
|
|
3
|
+
export const MANAGED_HOST_ENV_SECRET_REFUSED = "MANAGED_HOST_ENV_SECRET_REFUSED";
|
|
4
|
+
|
|
5
|
+
export class ManagedHostEnvSecretError extends Error {
|
|
6
|
+
constructor(name) {
|
|
7
|
+
super(`${name} is refused on a managed RightKit host; load the secret from the provisioned OS credential store (macOS Keychain or protected token file) instead`);
|
|
8
|
+
this.name = "ManagedHostEnvSecretError";
|
|
9
|
+
this.code = MANAGED_HOST_ENV_SECRET_REFUSED;
|
|
10
|
+
this.variable = name;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isManagedHost(env = process.env) {
|
|
15
|
+
return managedHost(env);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Throws unconditionally; the message names the variable only, never its value.
|
|
19
|
+
export function refuseManagedEnvSecret(name) {
|
|
20
|
+
throw new ManagedHostEnvSecretError(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// On a managed host, refuse any named environment secret that is set (non-blank).
|
|
24
|
+
// Unmanaged hosts are unaffected.
|
|
25
|
+
export function refuseManagedEnvSecrets(env = process.env, names = []) {
|
|
26
|
+
if (!isManagedHost(env)) return;
|
|
27
|
+
for (const name of names) {
|
|
28
|
+
if (String(env[name] ?? "").trim()) refuseManagedEnvSecret(name);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { posix, win32 } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/** RIGHTKIT_MANAGED_ROOT remains a data-root override for existing release callers. */
|
|
6
|
+
export function resolveManagedRoots({ env = process.env, platform = process.platform, home = homedir(), controlRoot, dataRoot } = {}) {
|
|
7
|
+
const paths = platform === 'win32' ? win32 : posix;
|
|
8
|
+
const legacyRoot = platform === 'win32' ? 'D:\\.rightkit-managed' : '/Volumes/D/.rightkit-managed';
|
|
9
|
+
dataRoot ??= env.RIGHTKIT_DATA_ROOT || env.RIGHTKIT_MANAGED_ROOT || env.RIGHTKIT_EXTERNAL_ROOT || legacyRoot;
|
|
10
|
+
controlRoot ??= env.RIGHTKIT_CONTROL_ROOT || (platform === 'darwin'
|
|
11
|
+
? paths.join(home, 'Library', 'Application Support', 'RightKit', 'build-control') : dataRoot);
|
|
12
|
+
return Object.freeze({ controlRoot, dataRoot, legacyRoot, runtimeCurrent: paths.join(controlRoot, 'runtime-current'),
|
|
13
|
+
configPath: paths.join(controlRoot, 'config.json'), socket: platform === 'win32' ? null : paths.join(controlRoot, 'broker.sock'),
|
|
14
|
+
logs: paths.join(controlRoot, 'logs'), agentBin: paths.join(controlRoot, 'agent-bin'),
|
|
15
|
+
agentPolicy: paths.join(controlRoot, 'agent-policy'), ownershipInstall: paths.join(controlRoot, 'ownership-install'),
|
|
16
|
+
// The Mac signing agent already lives on the internal disk; moving it would force a re-provision.
|
|
17
|
+
signingAgent: platform === 'darwin' ? paths.join(home, 'Library', 'Application Support', 'Orthic Labs', 'shared', 'signing-agent')
|
|
18
|
+
: paths.join(controlRoot, 'signing-agent') });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function managedControlCandidates(options = {}) {
|
|
22
|
+
const platform = options.platform ?? process.platform;
|
|
23
|
+
if (!['darwin', 'win32'].includes(platform)) {
|
|
24
|
+
const env = options.env ?? process.env;
|
|
25
|
+
return [...new Set([options.controlRoot, env.RIGHTKIT_CONTROL_ROOT, options.dataRoot, env.RIGHTKIT_DATA_ROOT, env.RIGHTKIT_MANAGED_ROOT, env.RIGHTKIT_EXTERNAL_ROOT, options.clientRoot].filter(Boolean))];
|
|
26
|
+
}
|
|
27
|
+
const roots = resolveManagedRoots(options);
|
|
28
|
+
return [...new Set([roots.controlRoot, roots.dataRoot, options.clientRoot, roots.legacyRoot].filter(Boolean))];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function discoverBrokerSocket(env = process.env, { exists = existsSync, ...options } = {}) {
|
|
32
|
+
if (typeof env.RIGHTKIT_BUILD_BROKER_SOCKET === 'string' && env.RIGHTKIT_BUILD_BROKER_SOCKET) return env.RIGHTKIT_BUILD_BROKER_SOCKET;
|
|
33
|
+
if ((options.platform ?? process.platform) === 'win32') return undefined;
|
|
34
|
+
for (const root of managedControlCandidates({ ...options, env })) {
|
|
35
|
+
const socket = posix.join(root, 'broker.sock');
|
|
36
|
+
if (exists(socket)) return socket;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
package/model-promote.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { verifyArtifactFile, readVerifiedArtifact, digestBytes } from "./publication-artifacts.mjs";
|
|
3
|
+
import { uploadVerifiedR2File, uploadVerifiedR2Artifact } from "./r2-verified-upload.mjs";
|
|
4
|
+
import { refuseManagedEnvSecrets } from "./managed-host-secrets.mjs";
|
|
5
|
+
import { realpathSync } from "node:fs";
|
|
6
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
7
|
import os from "node:os";
|
|
6
8
|
import path from "node:path";
|
|
7
|
-
import {
|
|
9
|
+
import { spawnSync } from "./background-process.mjs";
|
|
8
10
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
11
|
|
|
10
12
|
import {
|
|
@@ -17,8 +19,6 @@ import { registerRightAppsRelease } from "./rightapps-register.mjs";
|
|
|
17
19
|
import { assertCleanSource } from "./source-gate.mjs";
|
|
18
20
|
|
|
19
21
|
export const RUNTIME_ARTIFACT_TRUSTED_KEY_ID = "rightkit-runtime-artifacts-2026-07";
|
|
20
|
-
const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
-
const UPLOAD = path.join(TOOL_ROOT, "upload-large.mjs");
|
|
22
22
|
|
|
23
23
|
export class RuntimeArtifactPromotionError extends Error {
|
|
24
24
|
constructor(code, message) {
|
|
@@ -52,8 +52,8 @@ export async function promoteRuntimeArtifact(input, deps = {}) {
|
|
|
52
52
|
if (!input.privateKey) throw new Error("runtime artifact signing key is required");
|
|
53
53
|
|
|
54
54
|
const [artifact, evidence] = await Promise.all([
|
|
55
|
-
metadata(input.artifactFile),
|
|
56
|
-
metadata(input.evidenceFile),
|
|
55
|
+
metadata(input.artifactFile, config.artifact),
|
|
56
|
+
metadata(input.evidenceFile, config.evidence),
|
|
57
57
|
]);
|
|
58
58
|
assertMetadata(artifact, config.artifact, "artifact");
|
|
59
59
|
if (evidence.sha256 !== config.evidence.sha256) throw new Error("evidence SHA-256 does not match promotion config");
|
|
@@ -84,6 +84,7 @@ export async function promoteRuntimeArtifact(input, deps = {}) {
|
|
|
84
84
|
file: input.artifactFile,
|
|
85
85
|
key: manifest.object.r2Key,
|
|
86
86
|
lane: manifest.distribution.delivery,
|
|
87
|
+
evidence: { sha256: artifact.sha256, sizeBytes: artifact.sizeBytes },
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
await register(envelope);
|
|
@@ -104,6 +105,7 @@ export function defaultWindowsRuntimeArtifactSigningKeyFile(env = process.env) {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
export async function loadRuntimeArtifactSigningKey({ env = process.env, platform = process.platform, explicitFile } = {}) {
|
|
108
|
+
refuseManagedEnvSecrets(env, ["RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY", "RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY_FILE"]);
|
|
107
109
|
const direct = env.RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY;
|
|
108
110
|
if (direct?.trim()) return direct.replace(/\\n/g, "\n");
|
|
109
111
|
const configuredFile = explicitFile || env.RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY_FILE;
|
|
@@ -140,7 +142,9 @@ async function main() {
|
|
|
140
142
|
sizeBytes: raw.artifact.sizeBytes,
|
|
141
143
|
},
|
|
142
144
|
evidence: { sha256: raw.evidence.sha256 },
|
|
143
|
-
manifest:
|
|
145
|
+
manifest: args.artifactId === undefined
|
|
146
|
+
? raw.manifest
|
|
147
|
+
: { ...raw.manifest, artifactId: args.artifactId },
|
|
144
148
|
};
|
|
145
149
|
assertPromotionConfig(config);
|
|
146
150
|
assertPromotionAuthority(args.authority, config.manifest.entitlement?.appKey);
|
|
@@ -187,6 +191,7 @@ artifact.sizeBytes, and evidence.sha256 must exactly match those files.
|
|
|
187
191
|
|
|
188
192
|
Options:
|
|
189
193
|
--dry-run Validate real files and sign, without R2 or RightApps mutations
|
|
194
|
+
--artifact-id <slug> Override manifest.artifactId for this promotion
|
|
190
195
|
--signing-key-file <pem> Override the protected PEM path
|
|
191
196
|
--key-id <id> Must equal ${RUNTIME_ARTIFACT_TRUSTED_KEY_ID}
|
|
192
197
|
|
|
@@ -206,11 +211,13 @@ function parseArgs(args) {
|
|
|
206
211
|
let config = "";
|
|
207
212
|
let signingKeyFile;
|
|
208
213
|
let keyId = process.env.RIGHTKIT_RUNTIME_ARTIFACT_KEY_ID || RUNTIME_ARTIFACT_TRUSTED_KEY_ID;
|
|
214
|
+
let artifactId;
|
|
209
215
|
let dryRun = false;
|
|
210
216
|
for (let i = 0; i < args.length; i++) {
|
|
211
217
|
const arg = args[i];
|
|
212
218
|
if (arg === "--authority") authority = args[++i] ?? "";
|
|
213
219
|
else if (arg === "--config") config = args[++i] ?? "";
|
|
220
|
+
else if (arg === "--artifact-id") artifactId = args[++i] ?? "";
|
|
214
221
|
else if (arg === "--signing-key-file") signingKeyFile = args[++i] ?? "";
|
|
215
222
|
else if (arg === "--key-id") keyId = args[++i] ?? "";
|
|
216
223
|
else if (arg === "--dry-run") dryRun = true;
|
|
@@ -218,7 +225,7 @@ function parseArgs(args) {
|
|
|
218
225
|
}
|
|
219
226
|
if (!config) throw new Error("model promote requires --config <path>");
|
|
220
227
|
if (!authority) throw new Error("model promote requires --authority <app-key>");
|
|
221
|
-
return { authority, config, signingKeyFile, keyId, dryRun };
|
|
228
|
+
return { authority, config, signingKeyFile, keyId, artifactId, dryRun };
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
function readVerificationEvidence(args, cwd) {
|
|
@@ -234,28 +241,22 @@ function git(cwd, args) {
|
|
|
234
241
|
return result.stdout.trim();
|
|
235
242
|
}
|
|
236
243
|
|
|
237
|
-
async function fileMetadata(file) {
|
|
238
|
-
const
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
await new Promise((resolve, reject) => {
|
|
242
|
-
const stream = createReadStream(file);
|
|
243
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
244
|
-
stream.on("error", reject);
|
|
245
|
-
stream.on("end", resolve);
|
|
246
|
-
});
|
|
247
|
-
return { sha256: hash.digest("hex"), sizeBytes: info.size };
|
|
244
|
+
async function fileMetadata(file, evidence) {
|
|
245
|
+
const artifact = verifyArtifactFile(file, evidence);
|
|
246
|
+
if (artifact.sizeBytes <= 0) throw new Error(`promotion input is not a nonempty file: ${file}`);
|
|
247
|
+
return artifact;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
async function defaultUpload({ file, content, key, lane }) {
|
|
250
|
+
export async function defaultUpload({ file, content, key, lane, evidence }, deps = {}) {
|
|
251
251
|
const bucketAlias = lane === "private-r2" ? "private" : "public";
|
|
252
252
|
const env = runtimeArtifactUploadEnv();
|
|
253
|
-
if (file) return
|
|
253
|
+
if (file) return uploadVerifiedR2File(file, evidence, key, bucketAlias, { ...deps, env });
|
|
254
254
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "rightkit-runtime-pointer-"));
|
|
255
255
|
const pointerFile = path.join(tempRoot, "manifest.json");
|
|
256
256
|
try {
|
|
257
|
-
|
|
258
|
-
await
|
|
257
|
+
const bytes = Buffer.from(content);
|
|
258
|
+
await writeFile(pointerFile, bytes, { mode: 0o600 });
|
|
259
|
+
uploadVerifiedR2Artifact(readVerifiedArtifact(pointerFile, { sha256: digestBytes(bytes) }), key, bucketAlias, { ...deps, env });
|
|
259
260
|
} finally {
|
|
260
261
|
await rm(tempRoot, { recursive: true, force: true });
|
|
261
262
|
}
|
|
@@ -265,14 +266,6 @@ async function registerRuntimeArtifact(envelope, releaseToken) {
|
|
|
265
266
|
return registerRightAppsRelease("/v1/admin/apps/runtime-artifacts", envelope, { token: releaseToken });
|
|
266
267
|
}
|
|
267
268
|
|
|
268
|
-
function run(command, args, env = process.env) {
|
|
269
|
-
return new Promise((resolve, reject) => {
|
|
270
|
-
const child = spawn(command, args, { cwd: process.cwd(), env, stdio: "inherit", windowsHide: true });
|
|
271
|
-
child.on("error", reject);
|
|
272
|
-
child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`${command} exited ${code}`)));
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
269
|
async function readNonemptySecret(file) {
|
|
277
270
|
const value = await readFile(file, "utf8");
|
|
278
271
|
if (!value.trim()) throw new Error(`runtime artifact signing key file is empty: ${file}`);
|
|
@@ -285,7 +278,7 @@ function assertPromotionConfig(value) {
|
|
|
285
278
|
assertExactKeys(value.evidence, ["sha256"], "evidence");
|
|
286
279
|
assertExactKeys(value.manifest, [
|
|
287
280
|
"artifactKind", "entitlement", "distribution", "target", "versions", "provenance", "promotion",
|
|
288
|
-
], "manifest config");
|
|
281
|
+
], "manifest config", ["artifactId"]);
|
|
289
282
|
assertExactKeys(value.manifest.promotion, ["promotionId", "promotedAt"], "manifest config promotion");
|
|
290
283
|
}
|
|
291
284
|
|
|
@@ -294,14 +287,15 @@ function assertMetadata(actual, expected, label) {
|
|
|
294
287
|
if (actual.sizeBytes !== expected.sizeBytes) throw new Error(`${label} size does not match promotion config`);
|
|
295
288
|
}
|
|
296
289
|
|
|
297
|
-
function assertExactKeys(value, expected, label) {
|
|
290
|
+
function assertExactKeys(value, expected, label, optional = []) {
|
|
298
291
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
299
|
-
const allowed = new Set(expected);
|
|
292
|
+
const allowed = new Set([...expected, ...optional]);
|
|
300
293
|
for (const key of Object.keys(value)) if (!allowed.has(key)) throw new Error(`${label} has unknown field ${key}`);
|
|
301
294
|
for (const key of expected) if (!Object.hasOwn(value, key)) throw new Error(`${label}.${key} is required`);
|
|
302
295
|
}
|
|
303
296
|
|
|
304
|
-
|
|
297
|
+
const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
|
|
298
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
305
299
|
main().catch((error) => {
|
|
306
300
|
console.error(`right-release model promote: ${error.code ? `[${error.code}] ` : ""}${error.message}`);
|
|
307
301
|
process.exitCode = 1;
|
package/notary-auth.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { refuseManagedEnvSecrets } from "./managed-host-secrets.mjs";
|
|
3
4
|
|
|
4
5
|
export function notarytoolAuthArgs({ profile, env = process.env } = {}) {
|
|
5
6
|
// A configured profile explicitly selects Keychain authentication. Inherited
|
|
@@ -10,6 +11,9 @@ export function notarytoolAuthArgs({ profile, env = process.env } = {}) {
|
|
|
10
11
|
}
|
|
11
12
|
return ["--keychain-profile", profile.trim()];
|
|
12
13
|
}
|
|
14
|
+
// Env-selected App Store Connect API auth is refused on managed hosts; the
|
|
15
|
+
// provisioned Keychain profile is the only automatic source there.
|
|
16
|
+
refuseManagedEnvSecrets(env, ["APPLE_API_KEY_PATH", "APPLE_API_KEY", "APPLE_API_ISSUER"]);
|
|
13
17
|
const keyPath = env.APPLE_API_KEY_PATH?.trim();
|
|
14
18
|
const keyId = env.APPLE_API_KEY?.trim();
|
|
15
19
|
const issuer = env.APPLE_API_ISSUER?.trim();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "./background-process.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
export function automationDenied({ parentPid = process.ppid, run = spawnSync, caller: declaredCaller } = {}) {
|
|
7
|
+
const result = declaredCaller ? {} : run("/bin/ps", ["-p", String(parentPid), "-o", "command="], {
|
|
8
|
+
encoding: "utf8", timeout: 1000, shell: false,
|
|
9
|
+
});
|
|
10
|
+
const caller = declaredCaller || result.status === 0 && result.stdout?.trim() || `PID ${parentPid}`;
|
|
11
|
+
const error = new Error(`osascript blocked for caller ${caller}; use right-release dmg build for headless layout`);
|
|
12
|
+
error.code = "MAC_AUTOMATION_DENIED";
|
|
13
|
+
error.caller = caller;
|
|
14
|
+
error.parentPid = parentPid;
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
|
|
19
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
20
|
+
const error = automationDenied({ caller: process.argv[2] === "--caller" ? process.argv[3] : undefined });
|
|
21
|
+
console.error(JSON.stringify({ event: "mac-automation-denied", code: error.code, caller: error.caller,
|
|
22
|
+
parentPid: error.parentPid, message: error.message }));
|
|
23
|
+
process.exitCode = 125;
|
|
24
|
+
}
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Draft/import/verify utilities. Signing is intentionally absent; native prompts own signing.
|
|
3
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { readFileSync, writeFileSync, realpathSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { createDraft, loadTrustedExternal, verifyException } from "./ownership-approval.mjs";
|
|
6
|
+
import { createDraft, loadTrustedExternal, policyEntryDigest, validateRecord, verifyException } from "./ownership-approval.mjs";
|
|
7
|
+
import { SCANNER_VERSION } from "./ownership.mjs";
|
|
8
|
+
import manifest from "./rightkit-ownership.json" with { type: "json" };
|
|
7
9
|
|
|
8
10
|
function arg(args, name) { const i = args.indexOf(name); return i < 0 ? undefined : args[i + 1]; }
|
|
9
11
|
function json(file) { return JSON.parse(readFileSync(path.resolve(file), "utf8")); }
|
|
12
|
+
export function createInstalledDraft(input, { catalog = manifest, scannerVersion = SCANNER_VERSION, now = new Date() } = {}) {
|
|
13
|
+
const draft = createDraft({ ...input, issued: now.toISOString().slice(0, 10), policyDigest: policyEntryDigest(catalog, input.fingerprint), minimumScannerVersion: scannerVersion, minimumCatalogVersion: catalog.version ?? String(catalog.schema) });
|
|
14
|
+
const problems = validateRecord(draft);
|
|
15
|
+
const duration = Date.parse(draft.payload.expires) - Date.parse(draft.payload.issued);
|
|
16
|
+
if (!(duration > 0 && duration <= 90 * 86400000)) problems.push("draft expiry must be after issued and within 90 days");
|
|
17
|
+
if (problems.length) throw new Error(problems.join("; "));
|
|
18
|
+
return draft;
|
|
19
|
+
}
|
|
10
20
|
export function main(args) {
|
|
11
21
|
if (args.includes("--draft")) {
|
|
12
22
|
const input = json(arg(args, "--draft"));
|
|
13
|
-
const draft =
|
|
23
|
+
const draft = createInstalledDraft(input);
|
|
14
24
|
const out = arg(args, "--out");
|
|
15
25
|
const data = `${JSON.stringify(draft, null, 2)}\n`;
|
|
16
26
|
if (out) writeFileSync(path.resolve(out), data, { flag: "wx" }); else process.stdout.write(data);
|
|
@@ -33,7 +43,8 @@ export function main(args) {
|
|
|
33
43
|
}
|
|
34
44
|
throw new Error("usage: node ownership-approval-command.mjs --draft input.json [--out draft.json] | --import signed.json --out record.json | --verify signed.json --context scan.json");
|
|
35
45
|
}
|
|
36
|
-
|
|
46
|
+
const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
|
|
47
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
37
48
|
try { process.exitCode = main(process.argv.slice(2)); }
|
|
38
49
|
catch (error) { console.error(`[right-release] ownership approval: ${error.message}`); process.exitCode = 1; }
|
|
39
50
|
}
|