@skrr-ai/cli 0.1.11 → 0.1.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/dist/commands/daemon/index.js +4 -1
- package/dist/commands/daemon/install.d.ts +14 -0
- package/dist/commands/daemon/install.js +37 -1
- package/dist/commands/login.js +24 -1
- package/dist/help.d.ts +20 -1
- package/dist/help.js +26 -1
- package/dist/lib/daemon-installer.d.ts +62 -0
- package/dist/lib/daemon-installer.js +247 -0
- package/dist/lib/daemon-setup.d.ts +49 -0
- package/dist/lib/daemon-setup.js +103 -0
- package/dist/lib/daemonHandoff.d.ts +9 -0
- package/dist/lib/daemonHandoff.js +9 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +15 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseKeys.d.ts +87 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseKeys.js +94 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.d.ts +161 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.js +235 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +7 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseKeys.d.ts +87 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseKeys.js +91 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.d.ts +161 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.js +227 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/oclif.manifest.json +23864 -23864
- package/package.json +2 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.offerDaemonSetup = offerDaemonSetup;
|
|
4
|
+
/**
|
|
5
|
+
* daemon-setup.ts — offer to set this machine up, at the moment we can.
|
|
6
|
+
*
|
|
7
|
+
* ## Why login and not `npm install`
|
|
8
|
+
*
|
|
9
|
+
* The obvious way to remove a step is a `postinstall` that installs the daemon
|
|
10
|
+
* when the CLI is installed. It cannot work, and the reason is specific rather
|
|
11
|
+
* than aesthetic: installing the daemon BINDS it to a deployment and profile,
|
|
12
|
+
* taken from the CLI's session (`bindToCliSession`, OSK-279 — a daemon on a
|
|
13
|
+
* different deployment than its CLI is the failure that binding exists to
|
|
14
|
+
* prevent). At `npm install` time there is no session.
|
|
15
|
+
*
|
|
16
|
+
* It would not even fail loudly. With no config on disk `loadConfig()` returns
|
|
17
|
+
* DEFAULT_CONFIG, whose baseURL is the DEV deployment — so a postinstall would
|
|
18
|
+
* quietly bind the machine to dev, and the first `skrr login` against
|
|
19
|
+
* production would produce exactly the split. That is the default path on a
|
|
20
|
+
* clean machine, not an edge case.
|
|
21
|
+
*
|
|
22
|
+
* Login is the first moment the answer exists. It is also where the credential
|
|
23
|
+
* handoff already runs, so this is not a new path through the program; it is
|
|
24
|
+
* the branch where that path used to give up.
|
|
25
|
+
*
|
|
26
|
+
* ## Why it asks
|
|
27
|
+
*
|
|
28
|
+
* Signing in and "run a process on this machine that can execute bash, read and
|
|
29
|
+
* write files, and drive a browser" are not the same size of decision. Doing
|
|
30
|
+
* the second as a silent consequence of the first is the class of surprise this
|
|
31
|
+
* whole area has been removing. So: asked when there is someone to ask, skipped
|
|
32
|
+
* with an instruction when there is not, and never on the opt-out.
|
|
33
|
+
*/
|
|
34
|
+
const prompt_1 = require("./prompt");
|
|
35
|
+
const daemon_binding_1 = require("./daemon-binding");
|
|
36
|
+
const exec_oversky_1 = require("./exec-oversky");
|
|
37
|
+
const daemon_installer_1 = require("./daemon-installer");
|
|
38
|
+
const ssh_detect_1 = require("./ssh-detect");
|
|
39
|
+
/**
|
|
40
|
+
* Ask, then install. Returns without touching anything when there is nobody to
|
|
41
|
+
* ask or the operator has opted out.
|
|
42
|
+
*/
|
|
43
|
+
async function offerDaemonSetup(opts = {}) {
|
|
44
|
+
const env = opts.env ?? process.env;
|
|
45
|
+
const log = opts.log ?? ((line) => console.log(line));
|
|
46
|
+
// The same switch that turns the credential handoff off. An operator who has
|
|
47
|
+
// said "do not touch my daemon" has not asked a narrower question, and a
|
|
48
|
+
// second variable for the same intent is a second thing to remember.
|
|
49
|
+
if (env.OVERSKY_SKIP_DAEMON_HANDOFF === '1' || env.SKRR_SKIP_DAEMON_HANDOFF === '1') {
|
|
50
|
+
return { status: 'skipped', detail: 'skipped by OVERSKY_SKIP_DAEMON_HANDOFF' };
|
|
51
|
+
}
|
|
52
|
+
const interactive = opts.interactive ?? !(0, ssh_detect_1.isNonInteractive)();
|
|
53
|
+
if (!interactive) {
|
|
54
|
+
// A prompt with no terminal hangs forever, which is worse than the extra
|
|
55
|
+
// step. Say which command does it instead.
|
|
56
|
+
return {
|
|
57
|
+
status: 'skipped',
|
|
58
|
+
detail: 'no terminal to ask — run `skrr daemon install` to set this machine up',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const confirmImpl = opts.confirmImpl ?? prompt_1.confirm;
|
|
62
|
+
log('');
|
|
63
|
+
log(' This machine has no skrr runtime. It is what lets your agents work here —');
|
|
64
|
+
log(' running commands, editing files, and driving a browser on this machine.');
|
|
65
|
+
const yes = await confirmImpl(' Set it up now?', false);
|
|
66
|
+
if (!yes) {
|
|
67
|
+
return {
|
|
68
|
+
status: 'declined',
|
|
69
|
+
detail: 'run `skrr daemon install` when you want it',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Fetch the signed binary only when there is none. When the binary is already
|
|
73
|
+
// present and only the service is missing, this step is skipped entirely —
|
|
74
|
+
// downloading over a working runtime is not what "set it up" asked for.
|
|
75
|
+
const binaryPresent = opts.binaryPresent ?? Boolean((0, exec_oversky_1.findOverskyBinary)());
|
|
76
|
+
if (!binaryPresent) {
|
|
77
|
+
log(' Fetching the signed release...');
|
|
78
|
+
const fetched = await (0, daemon_installer_1.installDaemon)({ env });
|
|
79
|
+
if (!fetched.ok) {
|
|
80
|
+
return { status: 'failed', detail: fetched.error };
|
|
81
|
+
}
|
|
82
|
+
log(` Installed skrrd ${fetched.version} (${fetched.platformKey})`);
|
|
83
|
+
}
|
|
84
|
+
// Register the OS service, bound to the deployment and profile of the session
|
|
85
|
+
// that just authenticated — which is the whole reason this runs here and not
|
|
86
|
+
// at install time.
|
|
87
|
+
try {
|
|
88
|
+
const code = await (0, exec_oversky_1.execOversky)(['install', ...(0, daemon_binding_1.bindToCliSession)([])]);
|
|
89
|
+
if (code !== 0) {
|
|
90
|
+
return { status: 'failed', detail: `\`skrrd install\` exited ${code}` };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err.message === 'oversky-not-installed') {
|
|
95
|
+
return {
|
|
96
|
+
status: 'failed',
|
|
97
|
+
detail: 'the runtime vanished between fetching and installing it',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { status: 'failed', detail: err.message };
|
|
101
|
+
}
|
|
102
|
+
return { status: 'installed', detail: 'this machine is set up' };
|
|
103
|
+
}
|
|
@@ -32,6 +32,15 @@
|
|
|
32
32
|
export interface DaemonHandoffOutcome {
|
|
33
33
|
status: 'delivered' | 'skipped' | 'failed';
|
|
34
34
|
detail: string;
|
|
35
|
+
/**
|
|
36
|
+
* Why it skipped, for a caller that must act on the difference.
|
|
37
|
+
*
|
|
38
|
+
* `detail` is a sentence for a human and is expected to be reworded; the
|
|
39
|
+
* caller deciding whether to OFFER TO INSTALL a daemon cannot key off that.
|
|
40
|
+
* The two absent cases are separated because they need different work — one
|
|
41
|
+
* needs a binary fetched, the other only needs the service registered.
|
|
42
|
+
*/
|
|
43
|
+
reason?: 'opted-out' | 'brokered' | 'no-binary' | 'unsupported-daemon' | 'no-service';
|
|
35
44
|
}
|
|
36
45
|
export declare function handOffToLocalDaemon(env?: NodeJS.ProcessEnv, opts?: {
|
|
37
46
|
loginFlow?: string;
|
|
@@ -173,7 +173,11 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
173
173
|
// deliberately running a daemon against a different deployment than their CLI is
|
|
174
174
|
// doing a legitimate thing, and this must not overrule it.
|
|
175
175
|
if (env.OVERSKY_SKIP_DAEMON_HANDOFF === '1' || env.SKRR_SKIP_DAEMON_HANDOFF === '1') {
|
|
176
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
status: 'skipped',
|
|
178
|
+
reason: 'opted-out',
|
|
179
|
+
detail: 'skipped by OVERSKY_SKIP_DAEMON_HANDOFF',
|
|
180
|
+
};
|
|
177
181
|
}
|
|
178
182
|
// The daemon brokered this login, which means it just used its OWN credential
|
|
179
183
|
// against this server and that credential works. There is nothing to fix.
|
|
@@ -186,12 +190,13 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
186
190
|
if (opts.loginFlow === 'daemon-broker') {
|
|
187
191
|
return {
|
|
188
192
|
status: 'skipped',
|
|
193
|
+
reason: 'brokered',
|
|
189
194
|
detail: 'the local daemon brokered this login, so it already has a working credential',
|
|
190
195
|
};
|
|
191
196
|
}
|
|
192
197
|
const binary = (0, exec_oversky_1.findOverskyBinary)();
|
|
193
198
|
if (!binary) {
|
|
194
|
-
return { status: 'skipped', detail: 'no local daemon installed' };
|
|
199
|
+
return { status: 'skipped', reason: 'no-binary', detail: 'no local daemon installed' };
|
|
195
200
|
}
|
|
196
201
|
// An older daemon has no `accept-handoff`, and spawning it would fail with
|
|
197
202
|
// commander's "unknown command" rather than anything a reader could act on. A
|
|
@@ -201,6 +206,7 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
201
206
|
if (supported === false) {
|
|
202
207
|
return {
|
|
203
208
|
status: 'skipped',
|
|
209
|
+
reason: 'unsupported-daemon',
|
|
204
210
|
detail: 'the installed daemon predates `accept-handoff`; run `skrr daemon login` once',
|
|
205
211
|
};
|
|
206
212
|
}
|
|
@@ -233,6 +239,7 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
233
239
|
if (idSource === 'cwd') {
|
|
234
240
|
return {
|
|
235
241
|
status: 'skipped',
|
|
242
|
+
reason: 'no-service',
|
|
236
243
|
detail: 'no daemon service is installed on this machine',
|
|
237
244
|
};
|
|
238
245
|
}
|
|
@@ -36,4 +36,6 @@ export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE
|
|
|
36
36
|
export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
|
|
37
37
|
export { findLegacyLocalState, describeLegacyState, type LegacyStateFinding, type LegacyStateReport, } from './legacyStatePreflight.js';
|
|
38
38
|
export { DEFAULT_SKY_CODE_CHANNEL, DEFAULT_SKY_CODE_FEED_BASE, SKY_CODE_CHANNELS, SKY_CODE_CHANNEL_ENV, SKY_CODE_MANIFEST_FILE, isSkyCodeChannel, resolveChannelManifestUrl, resolveSkyCodeChannel, skyCodeChannelPrefix, skyCodeFeedBase, SKY_CODE_FEED_BASE_ENV, type SkyCodeChannel, } from './skyCodeChannels.js';
|
|
39
|
+
export { OVERSKY_RELEASE_PUBLIC_KEYS, type ReleaseKey } from './releaseKeys.js';
|
|
40
|
+
export { MAX_SIGS, buildArtifactMessage, buildManifestMessage, getDeltaFor, verifyArtifact, verifyManifest, type DaemonVersionManifest, type DeltaDescriptor, type ManifestSignature, type PlatformArtifact, type VerifyResult, } from './releaseManifest.js';
|
|
39
41
|
export { resolveConfigRoot, configRootPath, isConfigRootOverridden } from './configRoot.js';
|
|
@@ -11,7 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
11
11
|
exports.challenge = exports.generateVerifier = exports.invalidateHeadersHelperCache = exports.runHeadersHelper = exports.SENTINEL_TRIGGERING_FAILURES = exports.__resetAuthHelperFailureReasonForTest = exports.getLastAuthHelperFailureReason = exports.getDefaultHelperTtlMs = exports.invokeAuthHelper = exports.AuthHelperUnavailableError = exports.AuthHelperUntrustedError = exports.CI_TOKEN_PREFIX = exports.classifyTokenKind = exports.invalidateAuthHelperCache = exports.__resetCredentialResolverForTest = exports.CREDENTIAL_PRECEDENCE = exports.CredentialResolver = exports.__setFdReaderForTest = exports.readAuthFromFd = exports.createRefreshScheduler = exports.UNKNOWN_401_BUDGET = exports.__resetUnknown401StreaksForTest = exports.__refreshDiagnostics = exports.installLockExitHandlers = exports.clearReauthState = exports.needsReauthState = exports.persistReauthState = exports.withAuthLock = exports.revokeDaemonRefreshSession = exports.refreshDaemonToken = exports.classifyRefreshResponse = exports.EXIT_NEEDS_REAUTH = exports.formatReauthMessage = exports.handleMidStreamAuthExpiry = exports.isAuthExpiredSseEvent = exports.decodeJwtPayload = exports.decodeJwtExpiry = exports.TransientAuthFailure = exports.PermanentAuthFailure = exports.emitAuthTelemetry = exports.getAuthBinaryName = exports.getAuthKillSwitches = exports.getAuthConfigDir = exports.getAuthLogger = exports.configureAuthCore = exports.OAUTH_CLI_SCOPE = exports.isDaemonScope = exports.VALID_DAEMON_SCOPE_SET = exports.ALL_DAEMON_SCOPES = exports.SCOPES = void 0;
|
|
12
12
|
exports.tokenHash = exports.publicJwkFromX = exports.normalizeHtu = exports.jwkThumbprint = exports.generateDeviceKeyPair = exports.buildDeviceProof = exports.zeroizeKekCaches = exports.WindowsKek = exports.registerZeroizeHook = exports.MacosKek = exports.LinuxKek = exports.KekUnavailableError = exports.UnavailableKek = exports.InMemoryKek = exports.getKekStrategy = exports.__setKekStrategyForTest = exports.credEnvelopeWrap = exports.credEnvelopeUnwrap = exports.credEnvelopeSerialize = exports.credSealBuffer = exports.credOpenBuffer = exports.CRED_KEY_LEN = exports.isCredEnvelopeString = exports.generateDek = exports.CRED_ENVELOPE_MAGIC = exports.credEnvelopeEncrypt = exports.credEnvelopeDeserialize = exports.credEnvelopeDecrypt = exports.buildCredentialAad = exports.isValidRecoveryCode = exports.normalizeRecoveryCode = exports.parseRecoveryCode = exports.formatRecoveryCode = exports.MAX_TTL_DAYS = exports.MIN_TTL_DAYS = exports.parseTtlDays = exports.LoginExchangeError = exports.LoginInitError = exports.loginWithLocalhost = exports.LocalCallbackMalformedError = exports.LocalCallbackDeniedError = exports.LocalCallbackStateMismatchError = exports.LocalCallbackTimeoutError = exports.LocalCallbackBindError = exports.LocalCallbackError = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.startLocalCallback = exports.verifyChallenge = exports.safeEqual = exports.generateState = void 0;
|
|
13
13
|
exports.getHarnessTier = exports.HARNESS_TIERS = exports.pairUint8ArrayToBase64Url = exports.pairUint8ArrayToBase64 = exports.parsePairUrl = exports.parsePairPlaintext = exports.parsePairBundle = exports.openPairBundleBase64 = exports.generatePairKeyPair = exports.buildPairUrl = exports.buildPairAad = exports.pairBase64UrlToUint8Array = exports.pairBase64ToUint8Array = exports.assemblePairBundleBase64 = exports.PairBundleError = exports.PAIR_SECRET_LEN = exports.PAIR_PUBKEY_LEN = exports.PAIR_NONCE_LEN = exports.PAIR_BUNDLE_MAX_BASE64_LEN = exports.PAIR_AAD_MAX_LEN = exports.PAIR_AAD_PREFIX = exports.__resetCachedDaemonIdForTest = exports.legacyDaemonUuids = exports.ensureMachineDaemonId = exports.__getDeviceIdentityStateForTest = exports.__setDeviceIdentityStateForTest = exports.__enrollmentBackoffPathForTest = exports.__enrollmentMarkerPathForTest = exports.__privateKeyPathForTest = exports.__publicKeyPathForTest = exports.describeDeviceIdentityState = exports.getDevicePublicKey = exports.isDeviceIdentityActive = exports.clearEnrollmentMarker = exports.enrollWithServer = exports.signServerRequest = exports.resetDeviceIdentity = exports.initDeviceIdentity = exports.__resetShutdownGuardForTest = exports.__getCredEnvelopeStateForTest = exports.__setCredEnvelopeStateForTest = exports.__wrappedDekFilePathForTest = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.describeCredEnvelopeState = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDeviceProof = void 0;
|
|
14
|
-
exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.SKY_CODE_FEED_BASE_ENV = exports.skyCodeFeedBase = exports.skyCodeChannelPrefix = exports.resolveSkyCodeChannel = exports.resolveChannelManifestUrl = exports.isSkyCodeChannel = exports.SKY_CODE_MANIFEST_FILE = exports.SKY_CODE_CHANNEL_ENV = exports.SKY_CODE_CHANNELS = exports.DEFAULT_SKY_CODE_FEED_BASE = exports.DEFAULT_SKY_CODE_CHANNEL = exports.describeLegacyState = exports.findLegacyLocalState = exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = exports.sanitizeSpawnEnv = exports.tierPermitsConfiguredAutoMode = exports.credentialPolicyForTier = void 0;
|
|
14
|
+
exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.SKY_CODE_FEED_BASE_ENV = exports.skyCodeFeedBase = exports.skyCodeChannelPrefix = exports.resolveSkyCodeChannel = exports.resolveChannelManifestUrl = exports.isSkyCodeChannel = exports.SKY_CODE_MANIFEST_FILE = exports.SKY_CODE_CHANNEL_ENV = exports.SKY_CODE_CHANNELS = exports.DEFAULT_SKY_CODE_FEED_BASE = exports.DEFAULT_SKY_CODE_CHANNEL = exports.describeLegacyState = exports.findLegacyLocalState = exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = exports.sanitizeSpawnEnv = exports.tierPermitsConfiguredAutoMode = exports.credentialPolicyForTier = void 0;
|
|
15
15
|
// Daemon scope vocabulary — single source of truth for non-Mongoose tiers
|
|
16
16
|
// (daemon, desktop, cli). Schema-bound canonical lives in
|
|
17
17
|
// `@skrr-ai/data-schemas/common/daemonScopes`; the two are kept byte-equal
|
|
@@ -315,6 +315,20 @@ Object.defineProperty(exports, "resolveSkyCodeChannel", { enumerable: true, get:
|
|
|
315
315
|
Object.defineProperty(exports, "skyCodeChannelPrefix", { enumerable: true, get: function () { return skyCodeChannels_js_1.skyCodeChannelPrefix; } });
|
|
316
316
|
Object.defineProperty(exports, "skyCodeFeedBase", { enumerable: true, get: function () { return skyCodeChannels_js_1.skyCodeFeedBase; } });
|
|
317
317
|
Object.defineProperty(exports, "SKY_CODE_FEED_BASE_ENV", { enumerable: true, get: function () { return skyCodeChannels_js_1.SKY_CODE_FEED_BASE_ENV; } });
|
|
318
|
+
// Daemon release trust: the pinned ed25519 keys and the canonical signature
|
|
319
|
+
// messages. Shared because BOTH the daemon (self-update) and the CLI
|
|
320
|
+
// (`skrr daemon install`, which runs when there is no daemon to ask) verify the
|
|
321
|
+
// same manifest against the same root. See releaseManifest.ts for why a mirror
|
|
322
|
+
// would be the wrong shape here.
|
|
323
|
+
var releaseKeys_js_1 = require("./releaseKeys.js");
|
|
324
|
+
Object.defineProperty(exports, "OVERSKY_RELEASE_PUBLIC_KEYS", { enumerable: true, get: function () { return releaseKeys_js_1.OVERSKY_RELEASE_PUBLIC_KEYS; } });
|
|
325
|
+
var releaseManifest_js_1 = require("./releaseManifest.js");
|
|
326
|
+
Object.defineProperty(exports, "MAX_SIGS", { enumerable: true, get: function () { return releaseManifest_js_1.MAX_SIGS; } });
|
|
327
|
+
Object.defineProperty(exports, "buildArtifactMessage", { enumerable: true, get: function () { return releaseManifest_js_1.buildArtifactMessage; } });
|
|
328
|
+
Object.defineProperty(exports, "buildManifestMessage", { enumerable: true, get: function () { return releaseManifest_js_1.buildManifestMessage; } });
|
|
329
|
+
Object.defineProperty(exports, "getDeltaFor", { enumerable: true, get: function () { return releaseManifest_js_1.getDeltaFor; } });
|
|
330
|
+
Object.defineProperty(exports, "verifyArtifact", { enumerable: true, get: function () { return releaseManifest_js_1.verifyArtifact; } });
|
|
331
|
+
Object.defineProperty(exports, "verifyManifest", { enumerable: true, get: function () { return releaseManifest_js_1.verifyManifest; } });
|
|
318
332
|
// The one definition of the skrr root, shared by `skrr` and `skrrd`. See the
|
|
319
333
|
// module for why re-deriving it kept producing divergences.
|
|
320
334
|
var configRoot_js_1 = require("./configRoot.js");
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* releaseKeys.ts — ed25519 public keys pinned into the daemon binary AND the
|
|
3
|
+
* CLI that installs it.
|
|
4
|
+
*
|
|
5
|
+
* These are the trust anchors the self-updater (`update.ts`) uses to verify
|
|
6
|
+
* the signed release manifest (`daemon/latest/daemon-version.json`) and the
|
|
7
|
+
* downloaded binary artifact OFFLINE, before swapping its own binary. The
|
|
8
|
+
* matching PRIVATE half lives ONLY in CI as the Secrets Manager secret
|
|
9
|
+
* `OVERSKY_RELEASE_SIGNING_KEY` (ed25519, base64 PKCS8) — it is never checked
|
|
10
|
+
* into the repo and never shipped in the binary.
|
|
11
|
+
*
|
|
12
|
+
* CURRENT STATE: a key is pinned (`daemon-2026-07`, provisioned 2026-07-13), so
|
|
13
|
+
* self-update signature verification is FAIL-CLOSED — an unsigned or
|
|
14
|
+
* badly-signed manifest or binary aborts the update.
|
|
15
|
+
*
|
|
16
|
+
* READ THE NEXT PARAGRAPH BEFORE EMPTYING THIS ARRAY.
|
|
17
|
+
*
|
|
18
|
+
* The empty array is not a safe default; it is a DISABLED SECURITY CONTROL.
|
|
19
|
+
* With no key pinned, `update.ts` logs a warning and proceeds on TLS + SHA256
|
|
20
|
+
* only — and the SHA256 comes from the same manifest fetched over the same
|
|
21
|
+
* channel, so it defends against corruption in transit, not against a
|
|
22
|
+
* compromised or spoofed update feed. That posture (mirrored from the desktop
|
|
23
|
+
* `MANIFEST_PUBLIC_KEY=''` default) existed because the keypair had not been
|
|
24
|
+
* minted yet. It has been minted. Removing the last entry below silently
|
|
25
|
+
* re-disables offline verification on every daemon that ships the change, and
|
|
26
|
+
* nothing in the build fails to tell you.
|
|
27
|
+
*
|
|
28
|
+
* To add a key (rotation, or re-provisioning after a compromise) run
|
|
29
|
+
* `node scripts/generate-release-keypair.mjs`. It mints the ed25519 keypair,
|
|
30
|
+
* prints the ready-to-paste `{ keyId, publicKeySpkiB64 }` line for the array
|
|
31
|
+
* below, and prints the commands to store the PRIVATE half in the GitHub
|
|
32
|
+
* secret `OVERSKY_RELEASE_SIGNING_KEY` + AWS Secrets Manager. Never commit the
|
|
33
|
+
* private half; the CI signer reads `OVERSKY_RELEASE_SIGNING_KEY` and MUST
|
|
34
|
+
* import `buildArtifactMessage` / `buildManifestMessage` from
|
|
35
|
+
* `release-manifest.ts` rather than reimplementing them, so signer and verifier
|
|
36
|
+
* can never drift. Add the *next* key alongside the current one BEFORE
|
|
37
|
+
* rotating, so an in-flight rotation verifies against either — the full
|
|
38
|
+
* procedure is the rotation runbook on the array below.
|
|
39
|
+
*/
|
|
40
|
+
export interface ReleaseKey {
|
|
41
|
+
/** Human-readable key identifier, e.g. `daemon-2026-07`. Mirrors manifest `keyId`. */
|
|
42
|
+
keyId: string;
|
|
43
|
+
/** base64-encoded DER-SPKI ed25519 public key. */
|
|
44
|
+
publicKeySpkiB64: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pinned release-signing public keys, newest first. Verification tries each
|
|
48
|
+
* key in turn (key rotation: keep current + next together during a rotation
|
|
49
|
+
* window). Non-empty → fail-closed, which is the state today. Empty →
|
|
50
|
+
* verification DISABLED with only a logged warning; see the file header before
|
|
51
|
+
* ever letting this array become empty.
|
|
52
|
+
*
|
|
53
|
+
* ZERO-DOWNTIME KEY-ROTATION RUNBOOK (config-only, no code change):
|
|
54
|
+
* 1. Mint the next keypair: `node scripts/generate-release-keypair.mjs --force`.
|
|
55
|
+
* 2. Pin its public half ABOVE the current key in the array below, and ship
|
|
56
|
+
* that daemon build so the fleet trusts BOTH keys.
|
|
57
|
+
* 3. Store the new private half as the GH Actions secret
|
|
58
|
+
* OVERSKY_RELEASE_SIGNING_KEY_NEXT (+ optional OVERSKY_RELEASE_KEY_ID_NEXT
|
|
59
|
+
* label) — the signer then DUAL-SIGNS every release, emitting the additive
|
|
60
|
+
* v2 `sigs[]` envelope (scalar `sig` stays = the current/primary key so
|
|
61
|
+
* daemons that haven't picked up step 2 yet still verify).
|
|
62
|
+
* 4. Cut one release during the overlap window; it verifies for daemons
|
|
63
|
+
* pinning the old key, the new key, or both (see release-manifest.test.ts
|
|
64
|
+
* "ROTATION headroom").
|
|
65
|
+
* 5. Once the new-key build has saturated the fleet: promote NEXT→primary
|
|
66
|
+
* (rename the secret to OVERSKY_RELEASE_SIGNING_KEY), drop the old key from
|
|
67
|
+
* the array, and remove OVERSKY_RELEASE_SIGNING_KEY_NEXT. No downtime, and
|
|
68
|
+
* no further code change — the multi-sig machinery is already shipped.
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* NOT ROTATED FOR THE skrr RENAME. Owner decision, 2026-08-31.
|
|
72
|
+
*
|
|
73
|
+
* A rename is not a compromise. Custody is already established (GH Actions
|
|
74
|
+
* secret `OVERSKY_RELEASE_SIGNING_KEY` + AWS Secrets Manager), the keyId
|
|
75
|
+
* `daemon-2026-07` is a date, not a brand, and no user ever sees any of it.
|
|
76
|
+
*
|
|
77
|
+
* The rotation procedure documented above is shipped and correct — it just has
|
|
78
|
+
* nothing to do here. Running it would cost a fleet-saturation window (step 5)
|
|
79
|
+
* and buy nothing, and the window is exactly when a self-update path is most
|
|
80
|
+
* fragile. Rotate on a compromise or an expiry, not on a rename.
|
|
81
|
+
*
|
|
82
|
+
* The signed message prefix (`oversky-daemon-manifest`) stays for the same
|
|
83
|
+
* reason: it lives inside the signed bytes, so changing it breaks verification
|
|
84
|
+
* between old and new peers with zero user-visible benefit — the same call made
|
|
85
|
+
* for `X-Oversky-Origin`.
|
|
86
|
+
*/
|
|
87
|
+
export declare const OVERSKY_RELEASE_PUBLIC_KEYS: ReleaseKey[];
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* releaseKeys.ts — ed25519 public keys pinned into the daemon binary AND the
|
|
4
|
+
* CLI that installs it.
|
|
5
|
+
*
|
|
6
|
+
* These are the trust anchors the self-updater (`update.ts`) uses to verify
|
|
7
|
+
* the signed release manifest (`daemon/latest/daemon-version.json`) and the
|
|
8
|
+
* downloaded binary artifact OFFLINE, before swapping its own binary. The
|
|
9
|
+
* matching PRIVATE half lives ONLY in CI as the Secrets Manager secret
|
|
10
|
+
* `OVERSKY_RELEASE_SIGNING_KEY` (ed25519, base64 PKCS8) — it is never checked
|
|
11
|
+
* into the repo and never shipped in the binary.
|
|
12
|
+
*
|
|
13
|
+
* CURRENT STATE: a key is pinned (`daemon-2026-07`, provisioned 2026-07-13), so
|
|
14
|
+
* self-update signature verification is FAIL-CLOSED — an unsigned or
|
|
15
|
+
* badly-signed manifest or binary aborts the update.
|
|
16
|
+
*
|
|
17
|
+
* READ THE NEXT PARAGRAPH BEFORE EMPTYING THIS ARRAY.
|
|
18
|
+
*
|
|
19
|
+
* The empty array is not a safe default; it is a DISABLED SECURITY CONTROL.
|
|
20
|
+
* With no key pinned, `update.ts` logs a warning and proceeds on TLS + SHA256
|
|
21
|
+
* only — and the SHA256 comes from the same manifest fetched over the same
|
|
22
|
+
* channel, so it defends against corruption in transit, not against a
|
|
23
|
+
* compromised or spoofed update feed. That posture (mirrored from the desktop
|
|
24
|
+
* `MANIFEST_PUBLIC_KEY=''` default) existed because the keypair had not been
|
|
25
|
+
* minted yet. It has been minted. Removing the last entry below silently
|
|
26
|
+
* re-disables offline verification on every daemon that ships the change, and
|
|
27
|
+
* nothing in the build fails to tell you.
|
|
28
|
+
*
|
|
29
|
+
* To add a key (rotation, or re-provisioning after a compromise) run
|
|
30
|
+
* `node scripts/generate-release-keypair.mjs`. It mints the ed25519 keypair,
|
|
31
|
+
* prints the ready-to-paste `{ keyId, publicKeySpkiB64 }` line for the array
|
|
32
|
+
* below, and prints the commands to store the PRIVATE half in the GitHub
|
|
33
|
+
* secret `OVERSKY_RELEASE_SIGNING_KEY` + AWS Secrets Manager. Never commit the
|
|
34
|
+
* private half; the CI signer reads `OVERSKY_RELEASE_SIGNING_KEY` and MUST
|
|
35
|
+
* import `buildArtifactMessage` / `buildManifestMessage` from
|
|
36
|
+
* `release-manifest.ts` rather than reimplementing them, so signer and verifier
|
|
37
|
+
* can never drift. Add the *next* key alongside the current one BEFORE
|
|
38
|
+
* rotating, so an in-flight rotation verifies against either — the full
|
|
39
|
+
* procedure is the rotation runbook on the array below.
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.OVERSKY_RELEASE_PUBLIC_KEYS = void 0;
|
|
43
|
+
/**
|
|
44
|
+
* Pinned release-signing public keys, newest first. Verification tries each
|
|
45
|
+
* key in turn (key rotation: keep current + next together during a rotation
|
|
46
|
+
* window). Non-empty → fail-closed, which is the state today. Empty →
|
|
47
|
+
* verification DISABLED with only a logged warning; see the file header before
|
|
48
|
+
* ever letting this array become empty.
|
|
49
|
+
*
|
|
50
|
+
* ZERO-DOWNTIME KEY-ROTATION RUNBOOK (config-only, no code change):
|
|
51
|
+
* 1. Mint the next keypair: `node scripts/generate-release-keypair.mjs --force`.
|
|
52
|
+
* 2. Pin its public half ABOVE the current key in the array below, and ship
|
|
53
|
+
* that daemon build so the fleet trusts BOTH keys.
|
|
54
|
+
* 3. Store the new private half as the GH Actions secret
|
|
55
|
+
* OVERSKY_RELEASE_SIGNING_KEY_NEXT (+ optional OVERSKY_RELEASE_KEY_ID_NEXT
|
|
56
|
+
* label) — the signer then DUAL-SIGNS every release, emitting the additive
|
|
57
|
+
* v2 `sigs[]` envelope (scalar `sig` stays = the current/primary key so
|
|
58
|
+
* daemons that haven't picked up step 2 yet still verify).
|
|
59
|
+
* 4. Cut one release during the overlap window; it verifies for daemons
|
|
60
|
+
* pinning the old key, the new key, or both (see release-manifest.test.ts
|
|
61
|
+
* "ROTATION headroom").
|
|
62
|
+
* 5. Once the new-key build has saturated the fleet: promote NEXT→primary
|
|
63
|
+
* (rename the secret to OVERSKY_RELEASE_SIGNING_KEY), drop the old key from
|
|
64
|
+
* the array, and remove OVERSKY_RELEASE_SIGNING_KEY_NEXT. No downtime, and
|
|
65
|
+
* no further code change — the multi-sig machinery is already shipped.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* NOT ROTATED FOR THE skrr RENAME. Owner decision, 2026-08-31.
|
|
69
|
+
*
|
|
70
|
+
* A rename is not a compromise. Custody is already established (GH Actions
|
|
71
|
+
* secret `OVERSKY_RELEASE_SIGNING_KEY` + AWS Secrets Manager), the keyId
|
|
72
|
+
* `daemon-2026-07` is a date, not a brand, and no user ever sees any of it.
|
|
73
|
+
*
|
|
74
|
+
* The rotation procedure documented above is shipped and correct — it just has
|
|
75
|
+
* nothing to do here. Running it would cost a fleet-saturation window (step 5)
|
|
76
|
+
* and buy nothing, and the window is exactly when a self-update path is most
|
|
77
|
+
* fragile. Rotate on a compromise or an expiry, not on a rename.
|
|
78
|
+
*
|
|
79
|
+
* The signed message prefix (`oversky-daemon-manifest`) stays for the same
|
|
80
|
+
* reason: it lives inside the signed bytes, so changing it breaks verification
|
|
81
|
+
* between old and new peers with zero user-visible benefit — the same call made
|
|
82
|
+
* for `X-Oversky-Origin`.
|
|
83
|
+
*/
|
|
84
|
+
exports.OVERSKY_RELEASE_PUBLIC_KEYS = [
|
|
85
|
+
// ── NEXT key goes ABOVE this line during a rotation (newest first). ──
|
|
86
|
+
// Provisioned 2026-07-13. Private half: GH Actions secret
|
|
87
|
+
// OVERSKY_RELEASE_SIGNING_KEY + AWS Secrets Manager
|
|
88
|
+
// (us-east-1: oversky/daemon/release-signing-key). Self-update signature
|
|
89
|
+
// verification is now FAIL-CLOSED.
|
|
90
|
+
{
|
|
91
|
+
keyId: 'daemon-2026-07',
|
|
92
|
+
publicKeySpkiB64: 'MCowBQYDK2VwAyEA7ArbRnkbRW354XZ4MNS1MRLCwrTFauQzsw4GP27mMfI=',
|
|
93
|
+
},
|
|
94
|
+
];
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { type ReleaseKey } from './releaseKeys.js';
|
|
2
|
+
export type { ReleaseKey } from './releaseKeys.js';
|
|
3
|
+
/**
|
|
4
|
+
* One entry in a v2 multi-signature envelope. `sig` is a base64 ed25519
|
|
5
|
+
* signature over the EXACT SAME bytes as the v1 scalar `sig` (buildArtifact/
|
|
6
|
+
* ManifestMessage) — v2 is an envelope change, not a wire-format change. The
|
|
7
|
+
* `keyId` is an ADVISORY label only: verification trial-verifies every sig
|
|
8
|
+
* against every pinned key and NEVER consults keyId, so a mislabeled keyId can
|
|
9
|
+
* neither grant nor withhold trust (this structurally defeats keyId
|
|
10
|
+
* substitution). It exists for operator diagnostics ("which key signed this?").
|
|
11
|
+
*/
|
|
12
|
+
export interface ManifestSignature {
|
|
13
|
+
keyId: string;
|
|
14
|
+
/** base64 ed25519 signature over the same bytes as the v1 `sig`. */
|
|
15
|
+
sig: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* v-delta (optional): a differential-update patch that transforms a specific
|
|
19
|
+
* PRIOR version's binary into this release's binary — a brotli-compressed
|
|
20
|
+
* fossil delta. UNSIGNED, exactly like `keyId`/`size`/`sigs` (NOT part of
|
|
21
|
+
* buildArtifactMessage/buildManifestMessage), so it never changes the signed
|
|
22
|
+
* bytes and #782's envelope invariant holds. Trust comes SOLELY from verifying
|
|
23
|
+
* the RECONSTRUCTED binary against the signed `sha256` + ed25519 gates: a
|
|
24
|
+
* tampered/garbage delta yields a non-matching binary and the daemon falls back
|
|
25
|
+
* to the full signed download. `sha256` here is TRANSPORT integrity of the patch
|
|
26
|
+
* file only — NEVER an install gate.
|
|
27
|
+
*/
|
|
28
|
+
export interface DeltaDescriptor {
|
|
29
|
+
/** Absolute public URL of the brotli-compressed fossil delta patch. */
|
|
30
|
+
url: string;
|
|
31
|
+
/** Lowercase hex sha256 of the patch file — transport integrity, not trust. */
|
|
32
|
+
sha256: string;
|
|
33
|
+
/** Size of the patch file in bytes. */
|
|
34
|
+
size: number;
|
|
35
|
+
}
|
|
36
|
+
/** One downloadable, immutable binary for a single `<platform>-<arch>` target. */
|
|
37
|
+
export interface PlatformArtifact {
|
|
38
|
+
/** Bare filename, e.g. `oversky-darwin-arm64`. */
|
|
39
|
+
file: string;
|
|
40
|
+
/** Absolute public URL under the CloudFront feed. */
|
|
41
|
+
url: string;
|
|
42
|
+
/** Lowercase hex sha256 of the binary bytes. */
|
|
43
|
+
sha256: string;
|
|
44
|
+
/** Size in bytes. */
|
|
45
|
+
size: number;
|
|
46
|
+
/** base64 ed25519 signature over buildArtifactMessage(...) — the PRIMARY key. */
|
|
47
|
+
sig: string;
|
|
48
|
+
/**
|
|
49
|
+
* v2 (optional): EVERY signature (primary + rotation key) over the same
|
|
50
|
+
* bytes as `sig`. Emitted ONLY during a key-rotation window (>1 signing key);
|
|
51
|
+
* absent in steady single-key state, so a normal manifest is byte-identical
|
|
52
|
+
* to v1. When present it is the authoritative set — see verifyWithKeysMulti.
|
|
53
|
+
*/
|
|
54
|
+
sigs?: ManifestSignature[];
|
|
55
|
+
/**
|
|
56
|
+
* v-delta (optional, UNSIGNED): differential-update patches keyed by the
|
|
57
|
+
* from-version they apply to (e.g. `"0.8.0"`). Absent in steady state. See
|
|
58
|
+
* DeltaDescriptor — the reconstructed binary is verified against `sha256`
|
|
59
|
+
* above, so deltas add NO trust surface.
|
|
60
|
+
*/
|
|
61
|
+
deltas?: Record<string, DeltaDescriptor>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The signed release manifest. `platforms` is keyed by `<platform>-<arch>`
|
|
65
|
+
* (e.g. `darwin-arm64`, `linux-x64`). The top-level `sig` covers
|
|
66
|
+
* buildManifestMessage(manifest-without-sig).
|
|
67
|
+
*/
|
|
68
|
+
export interface DaemonVersionManifest<TPolicy = unknown> {
|
|
69
|
+
schemaVersion: number;
|
|
70
|
+
version: string;
|
|
71
|
+
minimum: string;
|
|
72
|
+
forceUpdate: boolean;
|
|
73
|
+
keyId: string;
|
|
74
|
+
platforms: Record<string, PlatformArtifact>;
|
|
75
|
+
/**
|
|
76
|
+
* base64 ed25519 signature over buildManifestMessage(this-without-sig) — the
|
|
77
|
+
* PRIMARY key. Kept populated even in v2 so an OLD (v1-only) daemon still
|
|
78
|
+
* verifies against the key the lagging fleet trusts.
|
|
79
|
+
*/
|
|
80
|
+
sig: string;
|
|
81
|
+
/**
|
|
82
|
+
* v2 (optional): EVERY manifest signature (primary + rotation key). Emitted
|
|
83
|
+
* ONLY during a rotation window; when present it is authoritative — see
|
|
84
|
+
* verifyWithKeysMulti. `schemaVersion` stays a reader hint (unsigned, inert);
|
|
85
|
+
* v2 is detected by `Array.isArray(sigs)`, not by the version number.
|
|
86
|
+
*/
|
|
87
|
+
sigs?: ManifestSignature[];
|
|
88
|
+
/**
|
|
89
|
+
* Independently signed release decision envelope. It is intentionally not
|
|
90
|
+
* part of the v1 manifest message so existing daemons can ignore it; newer
|
|
91
|
+
* daemons verify this nested signature before honouring compatibility or
|
|
92
|
+
* rollout controls.
|
|
93
|
+
*/
|
|
94
|
+
policy?: TPolicy;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Canonical bytes signed for a single platform artifact. Newline-joined, fixed
|
|
98
|
+
* field order, `v1` tag. `sha256` is lowercased so the message is stable
|
|
99
|
+
* regardless of the hex casing the caller supplies.
|
|
100
|
+
*/
|
|
101
|
+
export declare function buildArtifactMessage(a: {
|
|
102
|
+
platform: string;
|
|
103
|
+
version: string;
|
|
104
|
+
sha256: string;
|
|
105
|
+
url: string;
|
|
106
|
+
}): string;
|
|
107
|
+
/**
|
|
108
|
+
* Canonical bytes signed for the whole manifest (excluding the top-level
|
|
109
|
+
* `sig`). Platform keys are sorted so the message is independent of JSON key
|
|
110
|
+
* order; each contributes `<platform>=<sha256-lower>`. `forceUpdate` is
|
|
111
|
+
* serialized strictly (`String(forceUpdate === true)` → `"true"`/`"false"`).
|
|
112
|
+
*/
|
|
113
|
+
export declare function buildManifestMessage(m: {
|
|
114
|
+
version: string;
|
|
115
|
+
minimum: string;
|
|
116
|
+
forceUpdate: boolean;
|
|
117
|
+
platforms: Record<string, {
|
|
118
|
+
sha256: string;
|
|
119
|
+
}>;
|
|
120
|
+
}): string;
|
|
121
|
+
/**
|
|
122
|
+
* Discriminated verification result:
|
|
123
|
+
* - `{ ok: true }` — signature verified against a pinned key.
|
|
124
|
+
* - `{ ok: false, reason }` — keys are present but nothing verified (FAIL-CLOSED).
|
|
125
|
+
* - `{ disabled: true }` — no keys pinned; verification is intentionally off.
|
|
126
|
+
*/
|
|
127
|
+
export type VerifyResult = {
|
|
128
|
+
ok: true;
|
|
129
|
+
} | {
|
|
130
|
+
ok: false;
|
|
131
|
+
reason: string;
|
|
132
|
+
} | {
|
|
133
|
+
disabled: true;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Upper bound on signatures we will trial-verify per artifact/manifest. A
|
|
137
|
+
* rotation window needs at most 2–3 (current + next). `sigs[]` is NOT part of
|
|
138
|
+
* the signed bytes, so an attacker can pad it; this cap bounds the verify work
|
|
139
|
+
* they can force to O(MAX_SIGS × pinnedKeys) — a few hundred microseconds even
|
|
140
|
+
* at the cap. A valid signature placed beyond the cap is treated as absent.
|
|
141
|
+
*/
|
|
142
|
+
export declare const MAX_SIGS = 8;
|
|
143
|
+
/**
|
|
144
|
+
* Verify a single platform artifact's `sig` over buildArtifactMessage(...).
|
|
145
|
+
* `platform` is the `<platform>-<arch>` key; `version` is the manifest version
|
|
146
|
+
* the artifact belongs to. Defaults to the baked-in pinned keys.
|
|
147
|
+
*/
|
|
148
|
+
export declare function verifyArtifact(platform: string, version: string, artifact: PlatformArtifact, keys?: readonly ReleaseKey[]): VerifyResult;
|
|
149
|
+
/**
|
|
150
|
+
* Verify the manifest's top-level `sig` over buildManifestMessage(this).
|
|
151
|
+
* Defaults to the baked-in pinned keys.
|
|
152
|
+
*/
|
|
153
|
+
export declare function verifyManifest(manifest: DaemonVersionManifest, keys?: readonly ReleaseKey[]): VerifyResult;
|
|
154
|
+
/**
|
|
155
|
+
* Look up a delta patch that transforms `fromVersion`'s binary into this
|
|
156
|
+
* artifact's binary. Returns null when none is advertised or the descriptor is
|
|
157
|
+
* malformed — the caller then does a full download. PURE; validates shape only
|
|
158
|
+
* (the delta is UNSIGNED, so trust comes from verifying the RECONSTRUCTED binary
|
|
159
|
+
* against the signed `artifact.sha256`, not from this metadata).
|
|
160
|
+
*/
|
|
161
|
+
export declare function getDeltaFor(artifact: PlatformArtifact, fromVersion: string): DeltaDescriptor | null;
|