@skrr-ai/cli 0.1.79 → 0.1.80
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.js +9 -2
- package/dist/commands/inbox/index.js +4 -2
- package/dist/commands/login.d.ts +8 -3
- package/dist/commands/login.js +14 -4
- package/dist/commands/spaces/workflows/events.js +9 -2
- package/dist/lib/agentic-stream.js +13 -4
- package/dist/lib/api-fetch.js +6 -2
- package/dist/lib/brokered-dpop.d.ts +34 -0
- package/dist/lib/brokered-dpop.js +89 -0
- package/dist/lib/daemonBroker.d.ts +71 -2
- package/dist/lib/daemonBroker.js +203 -36
- 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.js +6 -3
- 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 +4 -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/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/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 +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +19 -3
- 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/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/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 +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3422 -3407
- package/oclif.manifest.json +31930 -31930
- package/package.json +2 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dpop.ts — RFC 9449 proof-of-possession for ACCESS tokens.
|
|
3
|
+
*
|
|
4
|
+
* Sibling of `deviceKey.ts`, and deliberately a different protocol on the
|
|
5
|
+
* wire. A device-identity proof (`typ: oversky-dpop+jwt`, header
|
|
6
|
+
* `OverSky-DPoP`) proves a daemon holds its ENROLLED key and authorizes
|
|
7
|
+
* minting/rotating that daemon's families. An access-token proof
|
|
8
|
+
* (`typ: dpop+jwt`, header `DPoP`) proves the presenter of an access token
|
|
9
|
+
* holds the key the token is bound to (`cnf.jkt`). The `typ` keeps either
|
|
10
|
+
* proof from ever verifying as the other.
|
|
11
|
+
*
|
|
12
|
+
* Why a SEPARATE key, never the device key (OSK-12020): the access-token key
|
|
13
|
+
* signs whatever `{htm, htu, ath}` a local caller asks of the daemon's proof
|
|
14
|
+
* oracle (`/v1/auth/cli-handoff-dpop-proof`). Handing that oracle the device
|
|
15
|
+
* key would turn it into a signer for the credential that mints families —
|
|
16
|
+
* one confused-deputy step from the workload asking for a device proof over
|
|
17
|
+
* `POST /api/daemons/cli-handoff`. A dedicated key carries no authority but
|
|
18
|
+
* the tokens bound to it, lives only in daemon memory (`createDpopKey` never
|
|
19
|
+
* exposes the private half), and dies with the family it binds.
|
|
20
|
+
*
|
|
21
|
+
* Algorithm: EdDSA / Ed25519, the same primitive as the device key. RFC 9449
|
|
22
|
+
* lets the server advertise `dpop_signing_alg_values_supported`; ours is
|
|
23
|
+
* exactly `["EdDSA"]`, and the verifier refuses anything else.
|
|
24
|
+
*/
|
|
25
|
+
import * as crypto from 'node:crypto';
|
|
26
|
+
import { jwkThumbprint, normalizeHtu, tokenHash, verifyEd25519Proof, } from './deviceKey.js';
|
|
27
|
+
/** RFC 9449 §4.2 — the proof's JOSE header `typ`. */
|
|
28
|
+
export const DPOP_PROOF_TYP = 'dpop+jwt';
|
|
29
|
+
/** RFC 9449 §4.1 — the request header carrying the proof. */
|
|
30
|
+
export const DPOP_HEADER = 'DPoP';
|
|
31
|
+
/** Default freshness window, matching the device-proof verifier. */
|
|
32
|
+
export const DPOP_MAX_AGE_SECONDS = 60;
|
|
33
|
+
/** Server error codes for a bound token presented without a usable proof. */
|
|
34
|
+
export const DPOP_PROOF_REQUIRED = 'DPOP_PROOF_REQUIRED';
|
|
35
|
+
export const DPOP_PROOF_INVALID = 'DPOP_PROOF_INVALID';
|
|
36
|
+
export const DPOP_PROOF_VERIFY_UNAVAILABLE = 'DPOP_PROOF_VERIFY_UNAVAILABLE';
|
|
37
|
+
export function createDpopKey() {
|
|
38
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519');
|
|
39
|
+
const exported = publicKey.export({ format: 'jwk' });
|
|
40
|
+
if (exported.kty !== 'OKP' || exported.crv !== 'Ed25519' || typeof exported.x !== 'string') {
|
|
41
|
+
throw new Error('createDpopKey: unexpected public JWK shape');
|
|
42
|
+
}
|
|
43
|
+
const publicJwk = Object.freeze({ kty: 'OKP', crv: 'Ed25519', x: exported.x });
|
|
44
|
+
const thumbprint = jwkThumbprint(publicJwk);
|
|
45
|
+
return Object.freeze({
|
|
46
|
+
publicJwk,
|
|
47
|
+
thumbprint,
|
|
48
|
+
sign(req) {
|
|
49
|
+
return buildDpopProofWithKey(privateKey, publicJwk, req);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function b64u(buf) {
|
|
54
|
+
return buf.toString('base64').replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Sign one proof. `ath` comes from the token when the signer holds it, or is
|
|
58
|
+
* taken as given — the daemon's proof oracle is handed the hash and never the
|
|
59
|
+
* token, which is the point of the oracle.
|
|
60
|
+
*/
|
|
61
|
+
function buildDpopProofWithKey(privateKey, publicJwk, req) {
|
|
62
|
+
if (typeof req.htm !== 'string' || !/^[A-Za-z]{3,10}$/.test(req.htm)) {
|
|
63
|
+
throw new Error('dpop: htm must be an HTTP method');
|
|
64
|
+
}
|
|
65
|
+
if (typeof req.htu !== 'string' || req.htu.length === 0) {
|
|
66
|
+
throw new Error('dpop: htu must be a URL');
|
|
67
|
+
}
|
|
68
|
+
const ath = req.accessToken !== undefined ? tokenHash(req.accessToken) : req.ath;
|
|
69
|
+
if (ath !== undefined && !isAth(ath)) {
|
|
70
|
+
throw new Error('dpop: ath must be a base64url SHA-256 digest');
|
|
71
|
+
}
|
|
72
|
+
const header = { alg: 'EdDSA', typ: DPOP_PROOF_TYP, jwk: publicJwk };
|
|
73
|
+
const claims = {
|
|
74
|
+
htm: req.htm.toUpperCase(),
|
|
75
|
+
htu: normalizeDpopHtu(req.htu),
|
|
76
|
+
iat: req.iat ?? Math.floor(Date.now() / 1000),
|
|
77
|
+
jti: req.jti ?? b64u(crypto.randomBytes(16)),
|
|
78
|
+
...(ath !== undefined ? { ath } : {}),
|
|
79
|
+
};
|
|
80
|
+
const signingInput = `${b64u(Buffer.from(JSON.stringify(header), 'utf-8'))}.${b64u(Buffer.from(JSON.stringify(claims), 'utf-8'))}`;
|
|
81
|
+
const signature = crypto.sign(null, Buffer.from(signingInput, 'utf-8'), privateKey);
|
|
82
|
+
return `${signingInput}.${b64u(signature)}`;
|
|
83
|
+
}
|
|
84
|
+
/** RFC 9449 §4.2 htu normalization — identical to the device proof's. */
|
|
85
|
+
export function normalizeDpopHtu(url) {
|
|
86
|
+
return normalizeHtu(url);
|
|
87
|
+
}
|
|
88
|
+
/** `ath` for an access token: base64url(SHA-256(ascii(token))), RFC 9449 §4.2. */
|
|
89
|
+
export function accessTokenHash(accessToken) {
|
|
90
|
+
return tokenHash(accessToken);
|
|
91
|
+
}
|
|
92
|
+
/** True for a value shaped like an `ath` / `jkt` (43-char base64url SHA-256). */
|
|
93
|
+
export function isAth(value) {
|
|
94
|
+
return typeof value === 'string' && /^[A-Za-z0-9_-]{43}$/.test(value);
|
|
95
|
+
}
|
|
96
|
+
/** Same shape test, named for the `cnf.jkt` call sites. */
|
|
97
|
+
export const isJwkThumbprint = isAth;
|
|
98
|
+
/**
|
|
99
|
+
* Verify an RFC 9449 proof: `typ` dpop+jwt, `alg` EdDSA, a well-formed inline
|
|
100
|
+
* JWK, the signature, iat window, htm, htu, `ath` (when expected) and the
|
|
101
|
+
* key's thumbprint against `expectedJkt` (when given).
|
|
102
|
+
*
|
|
103
|
+
* Replay (`jti`) is NOT checked here — it needs shared state, and the caller
|
|
104
|
+
* owns that (the API's Redis jti cache). A verifier that claimed to do it in
|
|
105
|
+
* process memory would be wrong on every multi-replica deployment.
|
|
106
|
+
*/
|
|
107
|
+
export function verifyDpopProof(jws, opts) {
|
|
108
|
+
const result = verifyEd25519Proof(jws, {
|
|
109
|
+
expectedHtm: opts.expectedHtm,
|
|
110
|
+
expectedHtu: opts.expectedHtu,
|
|
111
|
+
...(opts.maxAgeSeconds !== undefined ? { maxAgeSeconds: opts.maxAgeSeconds } : {}),
|
|
112
|
+
...(opts.clockSkewSeconds !== undefined ? { clockSkewSeconds: opts.clockSkewSeconds } : {}),
|
|
113
|
+
...(opts.now !== undefined ? { now: opts.now } : {}),
|
|
114
|
+
}, DPOP_PROOF_TYP);
|
|
115
|
+
if (!result.valid) {
|
|
116
|
+
// rth/pin are device-proof-only checks the options above never request.
|
|
117
|
+
return { valid: false, reason: result.reason };
|
|
118
|
+
}
|
|
119
|
+
// An empty jti cannot be claimed in a replay cache, so it is not a nonce.
|
|
120
|
+
if (result.claims.jti.length === 0)
|
|
121
|
+
return { valid: false, reason: 'malformed' };
|
|
122
|
+
if (opts.expectedAth !== undefined) {
|
|
123
|
+
if (typeof result.claims.ath !== 'string')
|
|
124
|
+
return { valid: false, reason: 'ath_missing' };
|
|
125
|
+
if (result.claims.ath !== opts.expectedAth)
|
|
126
|
+
return { valid: false, reason: 'ath_mismatch' };
|
|
127
|
+
}
|
|
128
|
+
if (opts.expectedJkt !== undefined && result.thumbprint !== opts.expectedJkt) {
|
|
129
|
+
return { valid: false, reason: 'jkt_mismatch' };
|
|
130
|
+
}
|
|
131
|
+
const { htm, htu, iat, jti, ath } = result.claims;
|
|
132
|
+
return {
|
|
133
|
+
valid: true,
|
|
134
|
+
claims: { htm, htu, iat, jti, ...(typeof ath === 'string' ? { ath } : {}) },
|
|
135
|
+
thumbprint: result.thumbprint,
|
|
136
|
+
publicJwk: result.publicJwk,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -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';
|
|
@@ -104,6 +104,10 @@ export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, K
|
|
|
104
104
|
// persistence + signing lives in `daemon/src/deviceIdentity.ts`.
|
|
105
105
|
// Server-side verification middleware is a later PR.
|
|
106
106
|
export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, normalizeHtu, publicJwkFromX, tokenHash, verifyProof as verifyDeviceProof, } from './deviceKey.js';
|
|
107
|
+
// OSK-12020 — RFC 9449 access-token binding (`cnf.jkt` + `DPoP` proof). A
|
|
108
|
+
// different protocol from the device proof above (`typ: dpop+jwt`, header
|
|
109
|
+
// `DPoP`), signed by a dedicated in-memory key the daemon holds.
|
|
110
|
+
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, } from './dpop.js';
|
|
107
111
|
// L11/L12 — composed runtime: init, transforms, sign, enroll, locks.
|
|
108
112
|
// Daemon and CLI consume these; the underlying primitives live above.
|
|
109
113
|
export {
|