@skrr-ai/cli 0.1.79 → 0.1.81
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/base-command.d.ts +4 -0
- package/dist/base-command.js +40 -4
- package/dist/commands/agents/visibility.d.ts +14 -0
- package/dist/commands/agents/visibility.js +52 -0
- package/dist/commands/create-token.d.ts +0 -1
- package/dist/commands/create-token.js +3 -1
- package/dist/commands/inbox/index.js +4 -2
- package/dist/commands/list-tokens.d.ts +0 -1
- package/dist/commands/list-tokens.js +1 -1
- package/dist/commands/login.d.ts +8 -3
- package/dist/commands/login.js +14 -4
- package/dist/commands/machines/dedicated/ssh-proxy.js +1 -0
- package/dist/commands/revoke-token.d.ts +0 -1
- package/dist/commands/revoke-token.js +1 -1
- package/dist/commands/spaces/workflows/events.js +9 -2
- package/dist/commands/store/follow.d.ts +1 -0
- package/dist/commands/store/follow.js +7 -1
- package/dist/commands/store/follows/list.d.ts +11 -0
- package/dist/commands/store/follows/list.js +29 -0
- package/dist/commands/store/follows/remove.d.ts +16 -0
- package/dist/commands/store/follows/remove.js +37 -0
- package/dist/commands/store/publishers/profile/draft.d.ts +17 -0
- package/dist/commands/store/publishers/profile/draft.js +54 -0
- package/dist/commands/store/publishers/profile/handle.d.ts +14 -0
- package/dist/commands/store/publishers/profile/handle.js +39 -0
- package/dist/commands/store/publishers/profile/hide.d.ts +12 -0
- package/dist/commands/store/publishers/profile/hide.js +39 -0
- package/dist/commands/store/publishers/profile/preview.d.ts +11 -0
- package/dist/commands/store/publishers/profile/preview.js +39 -0
- package/dist/commands/store/publishers/profile/publish.d.ts +12 -0
- package/dist/commands/store/publishers/profile/publish.js +41 -0
- package/dist/commands/store/publishers/profile/restore.d.ts +15 -0
- package/dist/commands/store/publishers/profile/restore.js +49 -0
- package/dist/commands/store/publishers/profile/revisions.d.ts +11 -0
- package/dist/commands/store/publishers/profile/revisions.js +30 -0
- package/dist/commands/store/publishers/profile/show.d.ts +11 -0
- package/dist/commands/store/publishers/profile/show.js +39 -0
- package/dist/commands/store/review.d.ts +26 -0
- package/dist/commands/store/review.js +93 -0
- package/dist/commands/store/reviews.d.ts +29 -0
- package/dist/commands/store/reviews.js +170 -0
- package/dist/commands/store/tip.d.ts +20 -0
- package/dist/commands/store/tip.js +57 -0
- package/dist/commands/tokens/create.d.ts +28 -0
- package/dist/commands/tokens/create.js +136 -0
- package/dist/commands/tokens/list.d.ts +11 -0
- package/dist/commands/tokens/list.js +53 -0
- package/dist/commands/tokens/revoke.d.ts +14 -0
- package/dist/commands/tokens/revoke.js +42 -0
- package/dist/lib/access-tokens.d.ts +59 -0
- package/dist/lib/access-tokens.js +50 -0
- package/dist/lib/agentic-stream.js +13 -4
- package/dist/lib/api-fetch.js +6 -2
- package/dist/lib/auth-core-init.d.ts +6 -0
- package/dist/lib/auth-core-init.js +28 -6
- package/dist/lib/brokered-dpop.d.ts +34 -0
- package/dist/lib/brokered-dpop.js +89 -0
- package/dist/lib/credential-resolver.d.ts +1 -1
- package/dist/lib/credential-resolver.js +8 -2
- package/dist/lib/daemonBroker.d.ts +84 -4
- package/dist/lib/daemonBroker.js +209 -40
- package/dist/lib/daemonBrokerRefusal.d.ts +26 -0
- package/dist/lib/daemonBrokerRefusal.js +83 -4
- package/dist/lib/daemonRestartWait.d.ts +39 -0
- package/dist/lib/daemonRestartWait.js +181 -0
- package/dist/lib/dedicated-service.js +10 -5
- package/dist/lib/dedicated-ssh.d.ts +9 -0
- package/dist/lib/dedicated-ssh.js +235 -63
- package/dist/lib/dedicated-terminal.js +6 -3
- package/dist/lib/dpop-auth.d.ts +35 -0
- package/dist/lib/dpop-auth.js +57 -0
- package/dist/lib/login.d.ts +6 -0
- package/dist/lib/login.js +13 -4
- package/dist/lib/node-adapter.js +6 -2
- package/dist/lib/publisher-profile-flags.d.ts +19 -0
- package/dist/lib/publisher-profile-flags.js +48 -0
- package/dist/lib/refresh.d.ts +1 -1
- package/dist/lib/refresh.js +6 -1
- package/dist/lib/ssh-ordered-bytes.d.ts +8 -0
- package/dist/lib/ssh-ordered-bytes.js +31 -0
- package/dist/lib/usd-micros.d.ts +9 -0
- package/dist/lib/usd-micros.js +21 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +42 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +35 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.d.ts +22 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.js +29 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +49 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +47 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +56 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +42 -20
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.d.ts +101 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +179 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +23 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +42 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +32 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.d.ts +22 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.js +27 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +49 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +47 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +56 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +39 -20
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.d.ts +101 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +138 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3895 -3882
- package/oclif.manifest.json +34985 -33643
- package/package.json +28 -4
|
@@ -16,7 +16,7 @@ export { classifyRefreshResponse, type RefreshResponseShape, type ClassifiedRefr
|
|
|
16
16
|
export { refreshDaemonToken, revokeDaemonRefreshSession, withAuthLock, persistReauthState, needsReauthState, clearReauthState, installLockExitHandlers, __refreshDiagnostics, __resetUnknown401StreaksForTest, UNKNOWN_401_BUDGET, } from './refresh.js';
|
|
17
17
|
export { createRefreshScheduler, type RefreshScheduler, type RefreshSchedulerOptions, } from './refreshScheduler.js';
|
|
18
18
|
export { readAuthFromFd, __setFdReaderForTest, type FdAuthPayload } from './fdToken.js';
|
|
19
|
-
export { CredentialResolver, CREDENTIAL_PRECEDENCE, __resetCredentialResolverForTest, invalidateAuthHelperCache, classifyTokenKind, CI_TOKEN_PREFIX, AuthHelperUntrustedError, AuthHelperUnavailableError, type CredentialSource, type CredentialStoreMode, type CredentialResolverOptions, type ResolvedCredential, type TokenKind, type HelperTrustDecision, type AuthHelperBlockedInfo, type AuthHelperSentinelInfo, } from './credentialResolver.js';
|
|
19
|
+
export { CredentialResolver, CREDENTIAL_PRECEDENCE, __resetCredentialResolverForTest, invalidateAuthHelperCache, classifyTokenKind, CI_TOKEN_PREFIX, PERSONAL_ACCESS_TOKEN_PREFIX, isStaticTokenKind, AuthHelperUntrustedError, AuthHelperUnavailableError, type CredentialSource, type CredentialStoreMode, type CredentialResolverOptions, type ResolvedCredential, type TokenKind, type HelperTrustDecision, type AuthHelperBlockedInfo, type AuthHelperSentinelInfo, } from './credentialResolver.js';
|
|
20
20
|
export { invokeAuthHelper, getDefaultHelperTtlMs, getLastAuthHelperFailureReason, __resetAuthHelperFailureReasonForTest, SENTINEL_TRIGGERING_FAILURES, type HelperResult, type InvokeAuthHelperOptions, type AuthHelperFailureReason, } from './authHelper.js';
|
|
21
21
|
export { runHeadersHelper, invalidateHeadersHelperCache, type RunHeadersHelperOptions, } from './headers-helper.js';
|
|
22
22
|
export { generateVerifier, challenge, generateState, safeEqual, verifyChallenge, type PkceChallengeMethod, } from './pkce.js';
|
|
@@ -27,6 +27,7 @@ export { formatRecoveryCode, parseRecoveryCode, normalizeRecoveryCode, isValidRe
|
|
|
27
27
|
export { buildCredentialAad, decrypt as credEnvelopeDecrypt, deserialize as credEnvelopeDeserialize, encrypt as credEnvelopeEncrypt, ENVELOPE_MAGIC as CRED_ENVELOPE_MAGIC, generateDek, isEnvelopeString as isCredEnvelopeString, KEY_LEN as CRED_KEY_LEN, openBuffer as credOpenBuffer, sealBuffer as credSealBuffer, serialize as credEnvelopeSerialize, unwrap as credEnvelopeUnwrap, wrap as credEnvelopeWrap, type SealedBlob as CredSealedBlob, } from './credentialEnvelope.js';
|
|
28
28
|
export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, KekUnavailableError, LinuxKek, MacosKek, registerZeroizeHook, WindowsKek, zeroizeKekCaches, type KekStrategy, } from './kek/index.js';
|
|
29
29
|
export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, normalizeHtu, publicJwkFromX, tokenHash, verifyProof as verifyDeviceProof, type DeviceKeyPair, type DeviceProofClaims, type DeviceProofHeader, type Ed25519PrivateJwk, type Ed25519PublicJwk, type VerifyFailureReason as DeviceProofVerifyFailureReason, type VerifyFailure as DeviceProofVerifyFailure, type VerifyOptions as DeviceProofVerifyOptions, type VerifySuccess as DeviceProofVerifySuccess, } from './deviceKey.js';
|
|
30
|
+
export { DPOP_HEADER, DPOP_MAX_AGE_SECONDS, DPOP_PROOF_INVALID, DPOP_PROOF_REQUIRED, DPOP_PROOF_TYP, DPOP_PROOF_VERIFY_UNAVAILABLE, accessTokenHash, createDpopKey, isAth, isJwkThumbprint, normalizeDpopHtu, verifyDpopProof, type DpopKey, type DpopProofClaims, type DpopProofRequest, type DpopVerifyFailureReason, type DpopVerifyOptions, type DpopVerifyResult, } from './dpop.js';
|
|
30
31
|
export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
|
|
31
32
|
export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, describeDeviceKeySplit, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
|
|
32
33
|
export { getMachineUuid, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
|
|
@@ -22,11 +22,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
22
22
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.
|
|
26
|
-
exports.
|
|
27
|
-
exports.
|
|
28
|
-
exports.
|
|
29
|
-
exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.isTrustedArtifactOrigin = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.TRUSTED_FEED_ORIGINS = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = void 0;
|
|
25
|
+
exports.runHeadersHelper = exports.SENTINEL_TRIGGERING_FAILURES = exports.__resetAuthHelperFailureReasonForTest = exports.getLastAuthHelperFailureReason = exports.getDefaultHelperTtlMs = exports.invokeAuthHelper = exports.AuthHelperUnavailableError = exports.AuthHelperUntrustedError = exports.isStaticTokenKind = exports.PERSONAL_ACCESS_TOKEN_PREFIX = 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.reauthCommand = 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;
|
|
26
|
+
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 = exports.challenge = exports.generateVerifier = exports.invalidateHeadersHelperCache = void 0;
|
|
27
|
+
exports.ensureMachineDaemonId = exports.__resetMachineUuidCacheForTest = exports.HOSTED_RUNTIME_MACHINE_SOURCES = exports.hostedRuntimeIdOverride = exports.getMachineUuid = exports.__getDeviceIdentityStateForTest = exports.__setDeviceIdentityStateForTest = exports.__enrollmentBackoffPathForTest = exports.__enrollmentMarkerPathForTest = exports.__privateKeyPathForTest = exports.__publicKeyPathForTest = exports.describeDeviceKeySplit = 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.DEFAULT_UNREADABLE_CREDENTIAL_HINT = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.splitKeyConvergencePolicy = exports.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDpopProof = exports.normalizeDpopHtu = exports.isJwkThumbprint = exports.isAth = exports.createDpopKey = exports.accessTokenHash = exports.DPOP_PROOF_VERIFY_UNAVAILABLE = exports.DPOP_PROOF_TYP = exports.DPOP_PROOF_REQUIRED = exports.DPOP_PROOF_INVALID = exports.DPOP_MAX_AGE_SECONDS = exports.DPOP_HEADER = exports.verifyDeviceProof = exports.tokenHash = exports.publicJwkFromX = exports.normalizeHtu = void 0;
|
|
28
|
+
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.LOOPBACK_DEFAULT_MAX_BODY_BYTES = exports.LoopbackTargetRefusedError = exports.isLoopbackAddress = exports.isLoopbackHostname = exports.assertLoopbackHttpUrl = exports.loopbackFetch = exports.withLoopbackNoProxy = exports.envHasProxy = exports.LOOPBACK_NO_PROXY_HOSTS = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_NAME_TOKENS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = exports.sanitizeSpawnEnv = exports.harnessLoadsClaudeSkillsLayout = exports.claudeSkillsLayoutEvidence = exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT = exports.tierPermitsConfiguredAutoMode = exports.credentialPolicyForTier = 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 = void 0;
|
|
29
|
+
exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.isTrustedArtifactOrigin = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.TRUSTED_FEED_ORIGINS = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = exports.resolveChannelManifestUrl = exports.isFirstPartyHarnessChannel = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.firstPartyHarnessChannelPrefix = exports.FIRST_PARTY_HARNESS_CHANNELS = exports.DEFAULT_FIRST_PARTY_HARNESS_CHANNEL = exports.migrateFirstPartyHarnessHome = exports.linkFirstPartyHarnessAliases = exports.firstPartyHarnessInstalledBinaryPath = exports.firstPartyHarnessHomeDir = exports.firstPartyHarnessBinDir = exports.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = void 0;
|
|
30
30
|
// Daemon scope vocabulary — single source of truth for non-Mongoose tiers
|
|
31
31
|
// (daemon, desktop, cli). Schema-bound canonical lives in
|
|
32
32
|
// `@skrr-ai/data-schemas/common/daemonScopes`; the two are kept byte-equal
|
|
@@ -109,6 +109,8 @@ Object.defineProperty(exports, "__resetCredentialResolverForTest", { enumerable:
|
|
|
109
109
|
Object.defineProperty(exports, "invalidateAuthHelperCache", { enumerable: true, get: function () { return credentialResolver_js_1.invalidateAuthHelperCache; } });
|
|
110
110
|
Object.defineProperty(exports, "classifyTokenKind", { enumerable: true, get: function () { return credentialResolver_js_1.classifyTokenKind; } });
|
|
111
111
|
Object.defineProperty(exports, "CI_TOKEN_PREFIX", { enumerable: true, get: function () { return credentialResolver_js_1.CI_TOKEN_PREFIX; } });
|
|
112
|
+
Object.defineProperty(exports, "PERSONAL_ACCESS_TOKEN_PREFIX", { enumerable: true, get: function () { return credentialResolver_js_1.PERSONAL_ACCESS_TOKEN_PREFIX; } });
|
|
113
|
+
Object.defineProperty(exports, "isStaticTokenKind", { enumerable: true, get: function () { return credentialResolver_js_1.isStaticTokenKind; } });
|
|
112
114
|
// L6.1 / L6.3 — fail-closed errors thrown by the auth-helper slot.
|
|
113
115
|
Object.defineProperty(exports, "AuthHelperUntrustedError", { enumerable: true, get: function () { return credentialResolver_js_1.AuthHelperUntrustedError; } });
|
|
114
116
|
Object.defineProperty(exports, "AuthHelperUnavailableError", { enumerable: true, get: function () { return credentialResolver_js_1.AuthHelperUnavailableError; } });
|
|
@@ -222,6 +224,22 @@ Object.defineProperty(exports, "normalizeHtu", { enumerable: true, get: function
|
|
|
222
224
|
Object.defineProperty(exports, "publicJwkFromX", { enumerable: true, get: function () { return deviceKey_js_1.publicJwkFromX; } });
|
|
223
225
|
Object.defineProperty(exports, "tokenHash", { enumerable: true, get: function () { return deviceKey_js_1.tokenHash; } });
|
|
224
226
|
Object.defineProperty(exports, "verifyDeviceProof", { enumerable: true, get: function () { return deviceKey_js_1.verifyProof; } });
|
|
227
|
+
// OSK-12020 — RFC 9449 access-token binding (`cnf.jkt` + `DPoP` proof). A
|
|
228
|
+
// different protocol from the device proof above (`typ: dpop+jwt`, header
|
|
229
|
+
// `DPoP`), signed by a dedicated in-memory key the daemon holds.
|
|
230
|
+
var dpop_js_1 = require("./dpop.js");
|
|
231
|
+
Object.defineProperty(exports, "DPOP_HEADER", { enumerable: true, get: function () { return dpop_js_1.DPOP_HEADER; } });
|
|
232
|
+
Object.defineProperty(exports, "DPOP_MAX_AGE_SECONDS", { enumerable: true, get: function () { return dpop_js_1.DPOP_MAX_AGE_SECONDS; } });
|
|
233
|
+
Object.defineProperty(exports, "DPOP_PROOF_INVALID", { enumerable: true, get: function () { return dpop_js_1.DPOP_PROOF_INVALID; } });
|
|
234
|
+
Object.defineProperty(exports, "DPOP_PROOF_REQUIRED", { enumerable: true, get: function () { return dpop_js_1.DPOP_PROOF_REQUIRED; } });
|
|
235
|
+
Object.defineProperty(exports, "DPOP_PROOF_TYP", { enumerable: true, get: function () { return dpop_js_1.DPOP_PROOF_TYP; } });
|
|
236
|
+
Object.defineProperty(exports, "DPOP_PROOF_VERIFY_UNAVAILABLE", { enumerable: true, get: function () { return dpop_js_1.DPOP_PROOF_VERIFY_UNAVAILABLE; } });
|
|
237
|
+
Object.defineProperty(exports, "accessTokenHash", { enumerable: true, get: function () { return dpop_js_1.accessTokenHash; } });
|
|
238
|
+
Object.defineProperty(exports, "createDpopKey", { enumerable: true, get: function () { return dpop_js_1.createDpopKey; } });
|
|
239
|
+
Object.defineProperty(exports, "isAth", { enumerable: true, get: function () { return dpop_js_1.isAth; } });
|
|
240
|
+
Object.defineProperty(exports, "isJwkThumbprint", { enumerable: true, get: function () { return dpop_js_1.isJwkThumbprint; } });
|
|
241
|
+
Object.defineProperty(exports, "normalizeDpopHtu", { enumerable: true, get: function () { return dpop_js_1.normalizeDpopHtu; } });
|
|
242
|
+
Object.defineProperty(exports, "verifyDpopProof", { enumerable: true, get: function () { return dpop_js_1.verifyDpopProof; } });
|
|
225
243
|
// L11/L12 — composed runtime: init, transforms, sign, enroll, locks.
|
|
226
244
|
// Daemon and CLI consume these; the underlying primitives live above.
|
|
227
245
|
var credentialEnvelopeBridge_js_1 = require("./credentialEnvelopeBridge.js");
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* - `access_token`: broker mode — a short-lived access token from a
|
|
23
23
|
* daemon-owned family; nothing durable leaves the daemon.
|
|
24
24
|
*/
|
|
25
|
-
export declare const CLI_HANDOFF_MODES: readonly ["refresh_family", "access_token"];
|
|
25
|
+
export declare const CLI_HANDOFF_MODES: readonly ["refresh_family", "access_token", "access_token_dpop"];
|
|
26
26
|
export type CliHandoffMode = (typeof CLI_HANDOFF_MODES)[number];
|
|
27
27
|
/** Broker-mode redeem route on the daemon's loopback server. */
|
|
28
28
|
export declare const CLI_HANDOFF_TOKEN_PATH = "/v1/auth/cli-handoff-token";
|
|
@@ -74,10 +74,26 @@ export declare function normalizeHandoffModes(value: unknown): CliHandoffMode[];
|
|
|
74
74
|
*/
|
|
75
75
|
export declare function advertisedHandoffModes(d: CliHandoffDescriptor): CliHandoffMode[];
|
|
76
76
|
export declare function descriptorOffersAccessToken(d: CliHandoffDescriptor): boolean;
|
|
77
|
+
/** True when the token route can serve DPoP-bound tokens (OSK-12020). */
|
|
78
|
+
export declare function descriptorOffersDpop(d: CliHandoffDescriptor): boolean;
|
|
77
79
|
export interface CliHandoffTokenRequest {
|
|
78
80
|
/** Bypass the daemon broker's cached token (the 401-recovery path). */
|
|
79
81
|
forceRefresh?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* The caller can fetch per-request proofs from `CLI_HANDOFF_DPOP_PROOF_PATH`
|
|
84
|
+
* and wants a DPoP-bound token. Sent only to a descriptor that advertises
|
|
85
|
+
* `access_token_dpop`; a daemon that does not know the field ignores it and
|
|
86
|
+
* answers an unbound token, which `tokenType` then says.
|
|
87
|
+
*/
|
|
88
|
+
dpop?: boolean;
|
|
80
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* How the token must be presented. `DPoP` means the token carries `cnf.jkt`
|
|
92
|
+
* and every request needs a fresh proof; absent or `Bearer` means an ordinary
|
|
93
|
+
* bearer token. Read from the RESPONSE, never assumed from the request: an
|
|
94
|
+
* older server ignores the binding request and mints an unbound token.
|
|
95
|
+
*/
|
|
96
|
+
export type CliHandoffTokenType = 'Bearer' | 'DPoP';
|
|
81
97
|
export interface CliHandoffTokenResponse {
|
|
82
98
|
accessToken: string;
|
|
83
99
|
/** Absolute expiry in ms since epoch, when the daemon knows it. */
|
|
@@ -85,6 +101,31 @@ export interface CliHandoffTokenResponse {
|
|
|
85
101
|
/** Server the minted credential belongs to — lets the caller verify it is
|
|
86
102
|
* about to use a token for the API it thinks it is talking to. */
|
|
87
103
|
serverUrl?: string;
|
|
104
|
+
tokenType?: CliHandoffTokenType;
|
|
88
105
|
}
|
|
89
106
|
/** Parse the redeem response; null when the token itself is absent. */
|
|
90
107
|
export declare function parseCliHandoffTokenResponse(value: unknown): CliHandoffTokenResponse | null;
|
|
108
|
+
/** The daemon refuses an UNBOUND token because its posture requires binding. */
|
|
109
|
+
export declare const CLI_HANDOFF_ERROR_DPOP_REQUIRED = "HANDOFF_DPOP_REQUIRED";
|
|
110
|
+
/**
|
|
111
|
+
* The daemon signs RFC 9449 proofs for a bound token with a key that never
|
|
112
|
+
* leaves it. Same descriptor secret as the token route: using a stolen bound
|
|
113
|
+
* token therefore needs live loopback access to THIS daemon, which is the
|
|
114
|
+
* boundary the capability already has.
|
|
115
|
+
*/
|
|
116
|
+
export declare const CLI_HANDOFF_DPOP_PROOF_PATH = "/v1/auth/cli-handoff-dpop-proof";
|
|
117
|
+
/** The oracle refused to sign (bad htm/htu, or `ath` names no token it issued). */
|
|
118
|
+
export declare const CLI_HANDOFF_ERROR_DPOP_PROOF_REFUSED = "HANDOFF_DPOP_PROOF_REFUSED";
|
|
119
|
+
/** The daemon holds no DPoP key (binding is off on this daemon). */
|
|
120
|
+
export declare const CLI_HANDOFF_ERROR_DPOP_UNAVAILABLE = "HANDOFF_DPOP_UNAVAILABLE";
|
|
121
|
+
export interface CliHandoffDpopProofRequest {
|
|
122
|
+
htm: string;
|
|
123
|
+
htu: string;
|
|
124
|
+
/** base64url SHA-256 of the bound access token. The token itself is never
|
|
125
|
+
* sent: the oracle commits to a hash, and only to one it issued. */
|
|
126
|
+
ath: string;
|
|
127
|
+
}
|
|
128
|
+
export interface CliHandoffDpopProofResponse {
|
|
129
|
+
proof: string;
|
|
130
|
+
}
|
|
131
|
+
export declare function parseCliHandoffDpopProofResponse(value: unknown): CliHandoffDpopProofResponse | null;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* - `access_token`: broker mode — a short-lived access token from a
|
|
23
23
|
* daemon-owned family; nothing durable leaves the daemon.
|
|
24
24
|
*/
|
|
25
|
-
export const CLI_HANDOFF_MODES = ['refresh_family', 'access_token'];
|
|
25
|
+
export const CLI_HANDOFF_MODES = ['refresh_family', 'access_token', 'access_token_dpop'];
|
|
26
26
|
/** Broker-mode redeem route on the daemon's loopback server. */
|
|
27
27
|
export const CLI_HANDOFF_TOKEN_PATH = '/v1/auth/cli-handoff-token';
|
|
28
28
|
/** Error codes this contract emits. String literals so both ends compare
|
|
@@ -88,7 +88,12 @@ export function advertisedHandoffModes(d) {
|
|
|
88
88
|
return declared.length > 0 ? declared : ['refresh_family'];
|
|
89
89
|
}
|
|
90
90
|
export function descriptorOffersAccessToken(d) {
|
|
91
|
-
|
|
91
|
+
const modes = advertisedHandoffModes(d);
|
|
92
|
+
return modes.includes('access_token') || modes.includes('access_token_dpop');
|
|
93
|
+
}
|
|
94
|
+
/** True when the token route can serve DPoP-bound tokens (OSK-12020). */
|
|
95
|
+
export function descriptorOffersDpop(d) {
|
|
96
|
+
return advertisedHandoffModes(d).includes('access_token_dpop');
|
|
92
97
|
}
|
|
93
98
|
/** Parse the redeem response; null when the token itself is absent. */
|
|
94
99
|
export function parseCliHandoffTokenResponse(value) {
|
|
@@ -101,5 +106,30 @@ export function parseCliHandoffTokenResponse(value) {
|
|
|
101
106
|
accessToken: v.accessToken,
|
|
102
107
|
...(typeof v.accessExpiresAt === 'number' ? { accessExpiresAt: v.accessExpiresAt } : {}),
|
|
103
108
|
...(typeof v.serverUrl === 'string' ? { serverUrl: v.serverUrl } : {}),
|
|
109
|
+
...(v.tokenType === 'DPoP' || v.tokenType === 'Bearer' ? { tokenType: v.tokenType } : {}),
|
|
104
110
|
};
|
|
105
111
|
}
|
|
112
|
+
/** The daemon refuses an UNBOUND token because its posture requires binding. */
|
|
113
|
+
export const CLI_HANDOFF_ERROR_DPOP_REQUIRED = 'HANDOFF_DPOP_REQUIRED';
|
|
114
|
+
// ---------------------------------------------------------------------
|
|
115
|
+
// Proof oracle — POST CLI_HANDOFF_DPOP_PROOF_PATH (OSK-12020)
|
|
116
|
+
// ---------------------------------------------------------------------
|
|
117
|
+
/**
|
|
118
|
+
* The daemon signs RFC 9449 proofs for a bound token with a key that never
|
|
119
|
+
* leaves it. Same descriptor secret as the token route: using a stolen bound
|
|
120
|
+
* token therefore needs live loopback access to THIS daemon, which is the
|
|
121
|
+
* boundary the capability already has.
|
|
122
|
+
*/
|
|
123
|
+
export const CLI_HANDOFF_DPOP_PROOF_PATH = '/v1/auth/cli-handoff-dpop-proof';
|
|
124
|
+
/** The oracle refused to sign (bad htm/htu, or `ath` names no token it issued). */
|
|
125
|
+
export const CLI_HANDOFF_ERROR_DPOP_PROOF_REFUSED = 'HANDOFF_DPOP_PROOF_REFUSED';
|
|
126
|
+
/** The daemon holds no DPoP key (binding is off on this daemon). */
|
|
127
|
+
export const CLI_HANDOFF_ERROR_DPOP_UNAVAILABLE = 'HANDOFF_DPOP_UNAVAILABLE';
|
|
128
|
+
export function parseCliHandoffDpopProofResponse(value) {
|
|
129
|
+
if (value == null || typeof value !== 'object')
|
|
130
|
+
return null;
|
|
131
|
+
const v = value;
|
|
132
|
+
if (typeof v.proof !== 'string' || v.proof.split('.').length !== 3)
|
|
133
|
+
return null;
|
|
134
|
+
return { proof: v.proof };
|
|
135
|
+
}
|
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* Precedence (high → low):
|
|
14
14
|
*
|
|
15
15
|
* 1. flag — `--token <jwt>` CLI flag (debugging)
|
|
16
|
-
* 2. env-token — `OVERSKY_TOKEN`
|
|
17
|
-
*
|
|
16
|
+
* 2. env-token — `OVERSKY_TOKEN` (CI / static). The CLI also reads
|
|
17
|
+
* `SKRR_TOKEN` via `envTokenNames`; `OVERSKY_TOKEN` wins
|
|
18
|
+
* when both are set, matching the CLI config overlay. A
|
|
19
|
+
* personal access token (`osk_pat_*`) is the credential
|
|
20
|
+
* CI should put here.
|
|
18
21
|
* 3. auth-helper — `OVERSKY_AUTH_HELPER` script output (rotating
|
|
19
22
|
* creds — Vault, AWS SM, internal SSO proxy)
|
|
20
23
|
* 4. fd-handoff — `OVERSKY_AUTH_FD` parent→child IPC (Electron
|
|
@@ -64,6 +67,12 @@ export interface CredentialResolverOptions {
|
|
|
64
67
|
* string to use that value directly.
|
|
65
68
|
*/
|
|
66
69
|
envTokenOverride?: string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Environment variables read by the env-token slot, in precedence order.
|
|
72
|
+
* Defaults to `['OVERSKY_TOKEN']`. The CLI passes
|
|
73
|
+
* `['OVERSKY_TOKEN', 'SKRR_TOKEN']` (OSK-13208).
|
|
74
|
+
*/
|
|
75
|
+
envTokenNames?: readonly string[];
|
|
67
76
|
/** Test/explicit override for `OVERSKY_AUTH_HELPER` env. Same semantics. */
|
|
68
77
|
authHelperPathOverride?: string | null;
|
|
69
78
|
/**
|
|
@@ -143,6 +152,8 @@ export interface AuthHelperSentinelInfo {
|
|
|
143
152
|
* (POST /api/daemons/token), MUST NOT schedule refresh,
|
|
144
153
|
* and on auth failure exits 75 with a CI-specific error
|
|
145
154
|
* rather than writing needs-reauth.json.
|
|
155
|
+
* - 'pat' — personal access token (`osk_pat_*`, OSK-13208): a static
|
|
156
|
+
* bearer used AS the user by the CLI. Never refreshed.
|
|
146
157
|
* - 'install' — short-lived web-minted JWT with `scope=daemon-install`.
|
|
147
158
|
* The daemon exchanges it through the same /api/daemons/token
|
|
148
159
|
* bootstrap path, persists only the returned daemon
|
|
@@ -157,9 +168,17 @@ export interface AuthHelperSentinelInfo {
|
|
|
157
168
|
* The kill switch `OVERSKY_DISABLE_CI_TOKEN_RESOLVER=1` forces every token to
|
|
158
169
|
* `'standard'` for triage purposes.
|
|
159
170
|
*/
|
|
160
|
-
export type TokenKind = 'standard' | 'ci' | 'install';
|
|
171
|
+
export type TokenKind = 'standard' | 'ci' | 'install' | 'pat';
|
|
161
172
|
/** CI-token plaintext prefix; mirrors api/server/services/Auth/daemonCiTokens.js. */
|
|
162
173
|
export declare const CI_TOKEN_PREFIX = "osk_ci_";
|
|
174
|
+
/** Personal-access-token prefix; mirrors api/server/services/Auth/personalAccessTokens.js. */
|
|
175
|
+
export declare const PERSONAL_ACCESS_TOKEN_PREFIX = "osk_pat_";
|
|
176
|
+
/**
|
|
177
|
+
* True for a kind whose token is static by construction: never refreshed,
|
|
178
|
+
* never rotated into a store. Like `TokenKind` itself this selects a local
|
|
179
|
+
* code path and grants nothing.
|
|
180
|
+
*/
|
|
181
|
+
export declare function isStaticTokenKind(kind: TokenKind): boolean;
|
|
163
182
|
/**
|
|
164
183
|
* Classify a token by local shape. Pure function; safe to call on any string
|
|
165
184
|
* (including `null`-coerced empty). Returns `'standard'` for empty input
|
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* Precedence (high → low):
|
|
14
14
|
*
|
|
15
15
|
* 1. flag — `--token <jwt>` CLI flag (debugging)
|
|
16
|
-
* 2. env-token — `OVERSKY_TOKEN`
|
|
17
|
-
*
|
|
16
|
+
* 2. env-token — `OVERSKY_TOKEN` (CI / static). The CLI also reads
|
|
17
|
+
* `SKRR_TOKEN` via `envTokenNames`; `OVERSKY_TOKEN` wins
|
|
18
|
+
* when both are set, matching the CLI config overlay. A
|
|
19
|
+
* personal access token (`osk_pat_*`) is the credential
|
|
20
|
+
* CI should put here.
|
|
18
21
|
* 3. auth-helper — `OVERSKY_AUTH_HELPER` script output (rotating
|
|
19
22
|
* creds — Vault, AWS SM, internal SSO proxy)
|
|
20
23
|
* 4. fd-handoff — `OVERSKY_AUTH_FD` parent→child IPC (Electron
|
|
@@ -62,6 +65,16 @@ export const CREDENTIAL_PRECEDENCE = [
|
|
|
62
65
|
];
|
|
63
66
|
/** CI-token plaintext prefix; mirrors api/server/services/Auth/daemonCiTokens.js. */
|
|
64
67
|
export const CI_TOKEN_PREFIX = 'osk_ci_';
|
|
68
|
+
/** Personal-access-token prefix; mirrors api/server/services/Auth/personalAccessTokens.js. */
|
|
69
|
+
export const PERSONAL_ACCESS_TOKEN_PREFIX = 'osk_pat_';
|
|
70
|
+
/**
|
|
71
|
+
* True for a kind whose token is static by construction: never refreshed,
|
|
72
|
+
* never rotated into a store. Like `TokenKind` itself this selects a local
|
|
73
|
+
* code path and grants nothing.
|
|
74
|
+
*/
|
|
75
|
+
export function isStaticTokenKind(kind) {
|
|
76
|
+
return kind === 'ci' || kind === 'pat';
|
|
77
|
+
}
|
|
65
78
|
/**
|
|
66
79
|
* Classify a token by local shape. Pure function; safe to call on any string
|
|
67
80
|
* (including `null`-coerced empty). Returns `'standard'` for empty input
|
|
@@ -97,6 +110,8 @@ export function classifyTokenKind(token) {
|
|
|
97
110
|
return 'standard';
|
|
98
111
|
if (token.startsWith(CI_TOKEN_PREFIX))
|
|
99
112
|
return 'ci';
|
|
113
|
+
if (token.startsWith(PERSONAL_ACCESS_TOKEN_PREFIX))
|
|
114
|
+
return 'pat';
|
|
100
115
|
const payload = decodeJwtPayload(token);
|
|
101
116
|
if (payload &&
|
|
102
117
|
typeof payload === 'object' &&
|
|
@@ -252,8 +267,16 @@ export class CredentialResolver {
|
|
|
252
267
|
if (typeof this.opts.envTokenOverride === 'string') {
|
|
253
268
|
return this.opts.envTokenOverride.length > 0 ? this.opts.envTokenOverride : null;
|
|
254
269
|
}
|
|
255
|
-
|
|
256
|
-
|
|
270
|
+
// `OVERSKY_TOKEN` first (retained by decision, docs/product/skrr-env-inventory.md),
|
|
271
|
+
// then any renamed spelling the caller opted into (`SKRR_TOKEN` for the CLI,
|
|
272
|
+
// OSK-13208). The daemon does not opt in: a CI job that sets `SKRR_TOKEN`
|
|
273
|
+
// for `skrr` must not have a co-located `skrrd` silently adopt it.
|
|
274
|
+
for (const name of this.opts.envTokenNames ?? ['OVERSKY_TOKEN']) {
|
|
275
|
+
const v = process.env[name];
|
|
276
|
+
if (typeof v === 'string' && v.length > 0)
|
|
277
|
+
return v;
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
257
280
|
}
|
|
258
281
|
async tryResolveAuthHelper() {
|
|
259
282
|
let helperPath;
|
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
* Deliberately thin: no persistence, no retry policy, no logging. Callers
|
|
14
14
|
* own their store and their error surfaces; this owns the cache/single-
|
|
15
15
|
* flight/invalidate invariants so they cannot drift between surfaces.
|
|
16
|
+
*
|
|
17
|
+
* Two policies are options because mechanisms genuinely need different ones,
|
|
18
|
+
* and both default to the original behaviour:
|
|
19
|
+
*
|
|
20
|
+
* - `forcedAcquire: 'join'` — a forced acquire joins whatever is in flight,
|
|
21
|
+
* for a mechanism where a second concurrent renewal is itself the hazard
|
|
22
|
+
* (a refresh-family rotation or a family mint);
|
|
23
|
+
* - `classifyAcquireError` — stale-on-degrade: a TRANSIENT failure serves the
|
|
24
|
+
* still-unexpired cached credential instead of failing, a PERMANENT one
|
|
25
|
+
* drops it.
|
|
26
|
+
*
|
|
27
|
+
* Mechanism-specific limits — how often a new family may be minted, which
|
|
28
|
+
* failures retire it — stay in the mechanism. The daemon's task token broker
|
|
29
|
+
* (`daemon/src/task/token-broker.ts`) is the consumer that uses both options.
|
|
16
30
|
*/
|
|
17
31
|
/** What an acquire returns. `expiresAtMs` absent means "no known expiry" —
|
|
18
32
|
* the token is served until `invalidate()` or an acquire replaces it. */
|
|
@@ -26,13 +40,45 @@ export interface CredentialSessionOptions {
|
|
|
26
40
|
* The mechanism. Called with `forceRefresh: true` when the caller asked to
|
|
27
41
|
* bypass the cache (the 401-recovery path) — implementations should renew
|
|
28
42
|
* rather than serve their own cache. A rejected promise propagates to every
|
|
29
|
-
* waiter and is never cached
|
|
43
|
+
* waiter and is never cached (`classifyAcquireError` is the one way a waiter
|
|
44
|
+
* can be served the previous credential instead).
|
|
30
45
|
*/
|
|
31
46
|
acquire: (forceRefresh: boolean) => Promise<AcquiredCredential>;
|
|
32
47
|
/** Serve the cached token until this many ms before expiry. Default 30s. */
|
|
33
48
|
skewMs?: number;
|
|
34
49
|
/** Injectable clock for tests. */
|
|
35
50
|
now?: () => number;
|
|
51
|
+
/**
|
|
52
|
+
* What a forced acquire does when an acquire is already in flight.
|
|
53
|
+
*
|
|
54
|
+
* - `'supersede'` (default): a forced acquire never joins a NON-forced
|
|
55
|
+
* in-flight one — that could serve the very credential a 401 just
|
|
56
|
+
* invalidated — so it starts a second operation, and the superseded one
|
|
57
|
+
* cannot write its result back. Right for a mechanism whose plain
|
|
58
|
+
* acquire may serve its own cache.
|
|
59
|
+
* - `'join'`: every acquire, forced or not, joins the one in flight. Right
|
|
60
|
+
* — and required — for a mechanism whose acquire ALWAYS renews (it never
|
|
61
|
+
* serves a cache of its own), and for which a second concurrent renewal
|
|
62
|
+
* is itself the hazard: a rotation that burns a refresh family, a mint
|
|
63
|
+
* that creates a new one. Whatever is in flight was started after the
|
|
64
|
+
* credential being replaced was issued, so its result is not that
|
|
65
|
+
* credential.
|
|
66
|
+
*/
|
|
67
|
+
forcedAcquire?: 'supersede' | 'join';
|
|
68
|
+
/**
|
|
69
|
+
* Classify an acquire failure. Without it every failure propagates and the
|
|
70
|
+
* cache is left as it was (the default).
|
|
71
|
+
*
|
|
72
|
+
* - `'transient'` — the renewal failed but the cached credential is still
|
|
73
|
+
* good: a waiter is served the cached token while it is inside its HARD
|
|
74
|
+
* expiry (the skew window only says when to start renewing), instead of
|
|
75
|
+
* the error. Nothing stale is served after `invalidate()`, after a forced
|
|
76
|
+
* acquire discarded it, or once it has actually expired.
|
|
77
|
+
* - `'permanent'` — the credential is dead upstream: the cache is dropped
|
|
78
|
+
* and the error propagates.
|
|
79
|
+
* - `undefined` — not classified: the default behaviour.
|
|
80
|
+
*/
|
|
81
|
+
classifyAcquireError?: (err: unknown) => 'transient' | 'permanent' | undefined;
|
|
36
82
|
}
|
|
37
83
|
export interface CredentialSession {
|
|
38
84
|
/**
|
|
@@ -41,6 +87,8 @@ export interface CredentialSession {
|
|
|
41
87
|
* discards the cache first (use after a resource 401).
|
|
42
88
|
*/
|
|
43
89
|
getToken(forceRefresh?: boolean): Promise<string>;
|
|
90
|
+
/** Same as getToken, but resolves the whole credential (with its expiry). */
|
|
91
|
+
getCredential(forceRefresh?: boolean): Promise<AcquiredCredential>;
|
|
44
92
|
/** Drop the cached token; the next getToken() acquires. */
|
|
45
93
|
invalidate(): void;
|
|
46
94
|
}
|
|
@@ -13,11 +13,27 @@
|
|
|
13
13
|
* Deliberately thin: no persistence, no retry policy, no logging. Callers
|
|
14
14
|
* own their store and their error surfaces; this owns the cache/single-
|
|
15
15
|
* flight/invalidate invariants so they cannot drift between surfaces.
|
|
16
|
+
*
|
|
17
|
+
* Two policies are options because mechanisms genuinely need different ones,
|
|
18
|
+
* and both default to the original behaviour:
|
|
19
|
+
*
|
|
20
|
+
* - `forcedAcquire: 'join'` — a forced acquire joins whatever is in flight,
|
|
21
|
+
* for a mechanism where a second concurrent renewal is itself the hazard
|
|
22
|
+
* (a refresh-family rotation or a family mint);
|
|
23
|
+
* - `classifyAcquireError` — stale-on-degrade: a TRANSIENT failure serves the
|
|
24
|
+
* still-unexpired cached credential instead of failing, a PERMANENT one
|
|
25
|
+
* drops it.
|
|
26
|
+
*
|
|
27
|
+
* Mechanism-specific limits — how often a new family may be minted, which
|
|
28
|
+
* failures retire it — stay in the mechanism. The daemon's task token broker
|
|
29
|
+
* (`daemon/src/task/token-broker.ts`) is the consumer that uses both options.
|
|
16
30
|
*/
|
|
17
31
|
const DEFAULT_SKEW_MS = 30_000;
|
|
18
32
|
export function createCredentialSession(opts) {
|
|
19
33
|
const skewMs = opts.skewMs ?? DEFAULT_SKEW_MS;
|
|
20
34
|
const now = opts.now ?? Date.now;
|
|
35
|
+
const forcedJoins = opts.forcedAcquire === 'join';
|
|
36
|
+
const classify = opts.classifyAcquireError;
|
|
21
37
|
let cached = null;
|
|
22
38
|
let inFlight = null;
|
|
23
39
|
let inFlightForced = false;
|
|
@@ -26,17 +42,26 @@ export function createCredentialSession(opts) {
|
|
|
26
42
|
// result back over the newer one.
|
|
27
43
|
let generation = 0;
|
|
28
44
|
const fresh = (t) => t.expiresAtMs == null || now() < t.expiresAtMs - skewMs;
|
|
45
|
+
/** Not yet actually expired — the bar for serving a stale credential. */
|
|
46
|
+
const unexpired = (t) => t.expiresAtMs == null || now() < t.expiresAtMs;
|
|
29
47
|
const acquireShared = (force) => {
|
|
30
|
-
//
|
|
31
|
-
// serve the very credential a 401 just invalidated. A
|
|
32
|
-
// DOES satisfy non-forced waiters: it is already renewing.
|
|
33
|
-
|
|
48
|
+
// Under 'supersede', a forced acquire must not join a non-forced in-flight
|
|
49
|
+
// one — that would serve the very credential a 401 just invalidated. A
|
|
50
|
+
// forced in-flight DOES satisfy non-forced waiters: it is already renewing.
|
|
51
|
+
// Under 'join', everything joins (see `forcedAcquire`).
|
|
52
|
+
if (inFlight && (forcedJoins || inFlightForced || !force))
|
|
34
53
|
return inFlight;
|
|
35
54
|
const gen = ++generation;
|
|
36
55
|
const p = opts.acquire(force).then((t) => {
|
|
37
56
|
if (gen === generation)
|
|
38
57
|
cached = t;
|
|
39
58
|
return t;
|
|
59
|
+
}, (err) => {
|
|
60
|
+
// Settled before any waiter resumes, so a waiter's stale check below
|
|
61
|
+
// already sees a permanent failure's dropped cache.
|
|
62
|
+
if (gen === generation && classify?.(err) === 'permanent')
|
|
63
|
+
cached = null;
|
|
64
|
+
throw err;
|
|
40
65
|
});
|
|
41
66
|
inFlight = p;
|
|
42
67
|
inFlightForced = force;
|
|
@@ -51,13 +76,26 @@ export function createCredentialSession(opts) {
|
|
|
51
76
|
p.then(cleanup, cleanup);
|
|
52
77
|
return p;
|
|
53
78
|
};
|
|
79
|
+
const getCredential = async (forceRefresh = false) => {
|
|
80
|
+
if (forceRefresh)
|
|
81
|
+
cached = null;
|
|
82
|
+
if (cached && fresh(cached))
|
|
83
|
+
return cached;
|
|
84
|
+
try {
|
|
85
|
+
return await acquireShared(forceRefresh);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
// Stale-on-degrade: read `cached` AFTER the failure, so an invalidate()
|
|
89
|
+
// or a forced discard that happened meanwhile is honoured.
|
|
90
|
+
if (classify?.(err) === 'transient' && cached && unexpired(cached))
|
|
91
|
+
return cached;
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
54
95
|
return {
|
|
96
|
+
getCredential,
|
|
55
97
|
async getToken(forceRefresh = false) {
|
|
56
|
-
|
|
57
|
-
cached = null;
|
|
58
|
-
if (cached && fresh(cached))
|
|
59
|
-
return cached.accessToken;
|
|
60
|
-
return (await acquireShared(forceRefresh)).accessToken;
|
|
98
|
+
return (await getCredential(forceRefresh)).accessToken;
|
|
61
99
|
},
|
|
62
100
|
invalidate() {
|
|
63
101
|
cached = null;
|
|
@@ -38,7 +38,13 @@ export declare function describeDeviceKeySplit(profile: string): {
|
|
|
38
38
|
* Order:
|
|
39
39
|
* 1. Try to load existing keypair from disk.
|
|
40
40
|
* - Success → state = active.
|
|
41
|
-
* -
|
|
41
|
+
* - No key material at all (first install) → step 2.
|
|
42
|
+
* - Key material present but unloadable (corruption, decrypt failure,
|
|
43
|
+
* public/private mismatch) → disabled+unreadable_key and a
|
|
44
|
+
* `recovery_required` event, UNLESS `recoverUnreadable` is set. Only
|
|
45
|
+
* an explicit interactive login sets it, because that login proves and
|
|
46
|
+
* re-pins the new key; replacing it on ordinary boot would sign the
|
|
47
|
+
* next refresh with a key the server never pinned.
|
|
42
48
|
* 2. Generate fresh keypair and persist. Failure to persist is a hard
|
|
43
49
|
* error landing in disabled+persist_failed.
|
|
44
50
|
*/
|
|
@@ -335,7 +335,13 @@ async function consolidateDeviceIdentity(profile) {
|
|
|
335
335
|
* Order:
|
|
336
336
|
* 1. Try to load existing keypair from disk.
|
|
337
337
|
* - Success → state = active.
|
|
338
|
-
* -
|
|
338
|
+
* - No key material at all (first install) → step 2.
|
|
339
|
+
* - Key material present but unloadable (corruption, decrypt failure,
|
|
340
|
+
* public/private mismatch) → disabled+unreadable_key and a
|
|
341
|
+
* `recovery_required` event, UNLESS `recoverUnreadable` is set. Only
|
|
342
|
+
* an explicit interactive login sets it, because that login proves and
|
|
343
|
+
* re-pins the new key; replacing it on ordinary boot would sign the
|
|
344
|
+
* next refresh with a key the server never pinned.
|
|
339
345
|
* 2. Generate fresh keypair and persist. Failure to persist is a hard
|
|
340
346
|
* error landing in disabled+persist_failed.
|
|
341
347
|
*/
|
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deviceKey.ts — L11 Phase 1: Ed25519 device identity + DPoP-style proofs.
|
|
3
|
+
*
|
|
4
|
+
* The load-bearing recommendation in `docs/daemon-auth-cc-deep-comparison-
|
|
5
|
+
* 2026-04-24.md` §L11. Two-axis identity:
|
|
6
|
+
*
|
|
7
|
+
* user identity = OAuth refresh token (server-rotated, scope-pinned, reuse-detected)
|
|
8
|
+
* device identity = Ed25519 keypair (device-generated, server-revocable)
|
|
9
|
+
*
|
|
10
|
+
* Every daemon→server request carries a JWS signature over
|
|
11
|
+
* `{htm, htu, iat, jti, rth?, ath?}` using the device key. Server validates
|
|
12
|
+
* against the public key registered for this daemon.
|
|
13
|
+
*
|
|
14
|
+
* Stolen credentials require BOTH halves:
|
|
15
|
+
* - refresh token alone is useless without the device key
|
|
16
|
+
* - device key alone is useless without the refresh token
|
|
17
|
+
*
|
|
18
|
+
* Why DPoP-shaped (RFC 9449) rather than rolling our own JWS:
|
|
19
|
+
* - The htm/htu/iat/jti/ath claim set is well-studied. Server-side
|
|
20
|
+
* replay defenses, clock-skew handling, and audit-trail formats all
|
|
21
|
+
* have a body of prior art to copy from.
|
|
22
|
+
* - Future interop: if we ever expose OverSky as a third-party OAuth
|
|
23
|
+
* resource server, DPoP-bearer is the way clients will already
|
|
24
|
+
* understand.
|
|
25
|
+
*
|
|
26
|
+
* Why we extend with `rth` (refresh-token-hash):
|
|
27
|
+
* - RFC 9449 only mints `ath` (access-token-hash). The whole point of
|
|
28
|
+
* L11 is to bind the *refresh* token to the device, so the proof
|
|
29
|
+
* accompanying a refresh request must commit to the refresh token.
|
|
30
|
+
* `rth` is structurally identical to `ath`; the name disambiguates.
|
|
31
|
+
*
|
|
32
|
+
* What this module is NOT responsible for:
|
|
33
|
+
* - Persistence (lives in `daemon/src/deviceIdentity.ts`).
|
|
34
|
+
* - Server-side public-key registration (separate API in a later PR).
|
|
35
|
+
* - Wire-up to the daemon's outgoing HTTP path (Phase 2 of L11).
|
|
36
|
+
*/
|
|
37
|
+
import crypto from 'node:crypto';
|
|
1
38
|
/**
|
|
2
39
|
* Ed25519 JSON Web Key — RFC 8037 §2 ("Key Type 'OKP'").
|
|
3
40
|
*
|
|
@@ -123,6 +160,18 @@ export interface BuildProofOptions {
|
|
|
123
160
|
* not a silent fallback.
|
|
124
161
|
*/
|
|
125
162
|
export declare function buildProof(privateJwk: Ed25519PrivateJwk, publicJwk: Ed25519PublicJwk, opts: BuildProofOptions): string;
|
|
163
|
+
/** The header `typ` of an OverSky device-identity proof. */
|
|
164
|
+
export declare const DEVICE_PROOF_TYP = "oversky-dpop+jwt";
|
|
165
|
+
/**
|
|
166
|
+
* Sign a DPoP-shaped proof with an Ed25519 key under the given header `typ`.
|
|
167
|
+
*
|
|
168
|
+
* @internal Shared by the device-identity proof (`oversky-dpop+jwt`, above)
|
|
169
|
+
* and the RFC 9449 access-token proof (`dpop+jwt`, `dpop.ts`). `typ` is the
|
|
170
|
+
* only thing on the wire that separates the two protocols, which is why it is
|
|
171
|
+
* a required argument rather than a default: a proof signed for one must
|
|
172
|
+
* never verify as the other.
|
|
173
|
+
*/
|
|
174
|
+
export declare function signEd25519Proof(privateKey: crypto.KeyObject, publicJwk: Ed25519PublicJwk, typ: string, opts: BuildProofOptions): string;
|
|
126
175
|
export type VerifyFailureReason = 'malformed' | 'bad_alg' | 'bad_typ' | 'bad_jwk' | 'sig_invalid' | 'iat_old' | 'iat_future' | 'htm_mismatch' | 'htu_mismatch' | 'rth_mismatch' | 'ath_mismatch' | 'pin_mismatch';
|
|
127
176
|
export interface VerifySuccess {
|
|
128
177
|
valid: true;
|
|
@@ -165,3 +214,10 @@ export interface VerifyOptions {
|
|
|
165
214
|
* `sig_invalid` to keep the failure surface narrow.
|
|
166
215
|
*/
|
|
167
216
|
export declare function verifyProof(jws: string, opts?: VerifyOptions): VerifySuccess | VerifyFailure;
|
|
217
|
+
/**
|
|
218
|
+
* Verify a DPoP-shaped Ed25519 proof whose header `typ` must equal `typ`.
|
|
219
|
+
*
|
|
220
|
+
* @internal See `signEd25519Proof`. `verifyProof` (device identity) and
|
|
221
|
+
* `verifyDpopProof` (`dpop.ts`, RFC 9449) are the two public doors.
|
|
222
|
+
*/
|
|
223
|
+
export declare function verifyEd25519Proof(jws: string, opts: VerifyOptions, typ: string): VerifySuccess | VerifyFailure;
|