@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.
Files changed (114) hide show
  1. package/dist/base-command.d.ts +4 -0
  2. package/dist/base-command.js +40 -4
  3. package/dist/commands/agents/visibility.d.ts +14 -0
  4. package/dist/commands/agents/visibility.js +52 -0
  5. package/dist/commands/create-token.d.ts +0 -1
  6. package/dist/commands/create-token.js +3 -1
  7. package/dist/commands/inbox/index.js +4 -2
  8. package/dist/commands/list-tokens.d.ts +0 -1
  9. package/dist/commands/list-tokens.js +1 -1
  10. package/dist/commands/login.d.ts +8 -3
  11. package/dist/commands/login.js +14 -4
  12. package/dist/commands/machines/dedicated/ssh-proxy.js +1 -0
  13. package/dist/commands/revoke-token.d.ts +0 -1
  14. package/dist/commands/revoke-token.js +1 -1
  15. package/dist/commands/spaces/workflows/events.js +9 -2
  16. package/dist/commands/store/follow.d.ts +1 -0
  17. package/dist/commands/store/follow.js +7 -1
  18. package/dist/commands/store/follows/list.d.ts +11 -0
  19. package/dist/commands/store/follows/list.js +29 -0
  20. package/dist/commands/store/follows/remove.d.ts +16 -0
  21. package/dist/commands/store/follows/remove.js +37 -0
  22. package/dist/commands/store/publishers/profile/draft.d.ts +17 -0
  23. package/dist/commands/store/publishers/profile/draft.js +54 -0
  24. package/dist/commands/store/publishers/profile/handle.d.ts +14 -0
  25. package/dist/commands/store/publishers/profile/handle.js +39 -0
  26. package/dist/commands/store/publishers/profile/hide.d.ts +12 -0
  27. package/dist/commands/store/publishers/profile/hide.js +39 -0
  28. package/dist/commands/store/publishers/profile/preview.d.ts +11 -0
  29. package/dist/commands/store/publishers/profile/preview.js +39 -0
  30. package/dist/commands/store/publishers/profile/publish.d.ts +12 -0
  31. package/dist/commands/store/publishers/profile/publish.js +41 -0
  32. package/dist/commands/store/publishers/profile/restore.d.ts +15 -0
  33. package/dist/commands/store/publishers/profile/restore.js +49 -0
  34. package/dist/commands/store/publishers/profile/revisions.d.ts +11 -0
  35. package/dist/commands/store/publishers/profile/revisions.js +30 -0
  36. package/dist/commands/store/publishers/profile/show.d.ts +11 -0
  37. package/dist/commands/store/publishers/profile/show.js +39 -0
  38. package/dist/commands/store/review.d.ts +26 -0
  39. package/dist/commands/store/review.js +93 -0
  40. package/dist/commands/store/reviews.d.ts +29 -0
  41. package/dist/commands/store/reviews.js +170 -0
  42. package/dist/commands/store/tip.d.ts +20 -0
  43. package/dist/commands/store/tip.js +57 -0
  44. package/dist/commands/tokens/create.d.ts +28 -0
  45. package/dist/commands/tokens/create.js +136 -0
  46. package/dist/commands/tokens/list.d.ts +11 -0
  47. package/dist/commands/tokens/list.js +53 -0
  48. package/dist/commands/tokens/revoke.d.ts +14 -0
  49. package/dist/commands/tokens/revoke.js +42 -0
  50. package/dist/lib/access-tokens.d.ts +59 -0
  51. package/dist/lib/access-tokens.js +50 -0
  52. package/dist/lib/agentic-stream.js +13 -4
  53. package/dist/lib/api-fetch.js +6 -2
  54. package/dist/lib/auth-core-init.d.ts +6 -0
  55. package/dist/lib/auth-core-init.js +28 -6
  56. package/dist/lib/brokered-dpop.d.ts +34 -0
  57. package/dist/lib/brokered-dpop.js +89 -0
  58. package/dist/lib/credential-resolver.d.ts +1 -1
  59. package/dist/lib/credential-resolver.js +8 -2
  60. package/dist/lib/daemonBroker.d.ts +84 -4
  61. package/dist/lib/daemonBroker.js +209 -40
  62. package/dist/lib/daemonBrokerRefusal.d.ts +26 -0
  63. package/dist/lib/daemonBrokerRefusal.js +83 -4
  64. package/dist/lib/daemonRestartWait.d.ts +39 -0
  65. package/dist/lib/daemonRestartWait.js +181 -0
  66. package/dist/lib/dedicated-service.js +10 -5
  67. package/dist/lib/dedicated-ssh.d.ts +9 -0
  68. package/dist/lib/dedicated-ssh.js +235 -63
  69. package/dist/lib/dedicated-terminal.js +6 -3
  70. package/dist/lib/dpop-auth.d.ts +35 -0
  71. package/dist/lib/dpop-auth.js +57 -0
  72. package/dist/lib/login.d.ts +6 -0
  73. package/dist/lib/login.js +13 -4
  74. package/dist/lib/node-adapter.js +6 -2
  75. package/dist/lib/publisher-profile-flags.d.ts +19 -0
  76. package/dist/lib/publisher-profile-flags.js +48 -0
  77. package/dist/lib/refresh.d.ts +1 -1
  78. package/dist/lib/refresh.js +6 -1
  79. package/dist/lib/ssh-ordered-bytes.d.ts +8 -0
  80. package/dist/lib/ssh-ordered-bytes.js +31 -0
  81. package/dist/lib/usd-micros.d.ts +9 -0
  82. package/dist/lib/usd-micros.js +21 -0
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +42 -1
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +35 -3
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.d.ts +22 -3
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.js +29 -5
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +49 -1
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +47 -9
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +7 -1
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +7 -1
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +56 -0
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +42 -20
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.d.ts +101 -0
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +179 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +23 -5
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +42 -1
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +32 -2
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.d.ts +22 -3
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.js +27 -4
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +49 -1
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +47 -9
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +7 -1
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +7 -1
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +56 -0
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +39 -20
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.d.ts +101 -0
  108. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +138 -0
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +5 -1
  111. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  112. package/dist/node_modules/@skrr-ai/data-provider/index.js +3895 -3882
  113. package/oclif.manifest.json +34985 -33643
  114. package/package.json +28 -4
@@ -152,17 +152,40 @@ export function normalizeHtu(url) {
152
152
  * not a silent fallback.
153
153
  */
154
154
  export function buildProof(privateJwk, publicJwk, opts) {
155
+ if (publicJwk.x !== privateJwk.x) {
156
+ // Defensive — public and private must come from the same keypair.
157
+ // A mismatch indicates a corrupted in-memory state.
158
+ throw new Error('buildProof: public/private JWK x mismatch (corrupted keypair)');
159
+ }
160
+ // Cast to crypto's `JsonWebKey` shape — that type requires an index
161
+ // signature for arbitrary string keys; our narrow OKP shape doesn't
162
+ // declare one because the strict shape is more useful at our API
163
+ // boundary. Cast through `unknown` to silence the structural mismatch
164
+ // without losing the rest of the type safety on this surface.
165
+ const privateKey = crypto.createPrivateKey({
166
+ format: 'jwk',
167
+ key: privateJwk,
168
+ });
169
+ return signEd25519Proof(privateKey, publicJwk, DEVICE_PROOF_TYP, opts);
170
+ }
171
+ /** The header `typ` of an OverSky device-identity proof. */
172
+ export const DEVICE_PROOF_TYP = 'oversky-dpop+jwt';
173
+ /**
174
+ * Sign a DPoP-shaped proof with an Ed25519 key under the given header `typ`.
175
+ *
176
+ * @internal Shared by the device-identity proof (`oversky-dpop+jwt`, above)
177
+ * and the RFC 9449 access-token proof (`dpop+jwt`, `dpop.ts`). `typ` is the
178
+ * only thing on the wire that separates the two protocols, which is why it is
179
+ * a required argument rather than a default: a proof signed for one must
180
+ * never verify as the other.
181
+ */
182
+ export function signEd25519Proof(privateKey, publicJwk, typ, opts) {
155
183
  if (typeof opts.method !== 'string' || opts.method.length === 0) {
156
184
  throw new Error('buildProof: method must be a non-empty string');
157
185
  }
158
186
  if (typeof opts.url !== 'string' || opts.url.length === 0) {
159
187
  throw new Error('buildProof: url must be a non-empty string');
160
188
  }
161
- if (publicJwk.x !== privateJwk.x) {
162
- // Defensive — public and private must come from the same keypair.
163
- // A mismatch indicates a corrupted in-memory state.
164
- throw new Error('buildProof: public/private JWK x mismatch (corrupted keypair)');
165
- }
166
189
  const claims = {
167
190
  htm: opts.method.toUpperCase(),
168
191
  htu: normalizeHtu(opts.url),
@@ -173,23 +196,10 @@ export function buildProof(privateJwk, publicJwk, opts) {
173
196
  claims.rth = tokenHash(opts.refreshToken);
174
197
  if (opts.accessToken !== undefined)
175
198
  claims.ath = tokenHash(opts.accessToken);
176
- const header = {
177
- alg: 'EdDSA',
178
- typ: 'oversky-dpop+jwt',
179
- jwk: publicJwk,
180
- };
199
+ const header = { alg: 'EdDSA', typ, jwk: publicJwk };
181
200
  const headerB64 = b64u(Buffer.from(JSON.stringify(header), 'utf-8'));
182
201
  const payloadB64 = b64u(Buffer.from(JSON.stringify(claims), 'utf-8'));
183
202
  const signingInput = `${headerB64}.${payloadB64}`;
184
- // Cast to crypto's `JsonWebKey` shape — that type requires an index
185
- // signature for arbitrary string keys; our narrow OKP shape doesn't
186
- // declare one because the strict shape is more useful at our API
187
- // boundary. Cast through `unknown` to silence the structural mismatch
188
- // without losing the rest of the type safety on this surface.
189
- const privateKey = crypto.createPrivateKey({
190
- format: 'jwk',
191
- key: privateJwk,
192
- });
193
203
  // EdDSA over Ed25519 — Node's crypto.sign expects `null` as the
194
204
  // digest algorithm because Ed25519 hashes the message internally.
195
205
  const signature = crypto.sign(null, Buffer.from(signingInput, 'utf-8'), privateKey);
@@ -205,6 +215,15 @@ export function buildProof(privateJwk, publicJwk, opts) {
205
215
  * `sig_invalid` to keep the failure surface narrow.
206
216
  */
207
217
  export function verifyProof(jws, opts = {}) {
218
+ return verifyEd25519Proof(jws, opts, DEVICE_PROOF_TYP);
219
+ }
220
+ /**
221
+ * Verify a DPoP-shaped Ed25519 proof whose header `typ` must equal `typ`.
222
+ *
223
+ * @internal See `signEd25519Proof`. `verifyProof` (device identity) and
224
+ * `verifyDpopProof` (`dpop.ts`, RFC 9449) are the two public doors.
225
+ */
226
+ export function verifyEd25519Proof(jws, opts, typ) {
208
227
  if (typeof jws !== 'string' || jws.length === 0) {
209
228
  return { valid: false, reason: 'malformed' };
210
229
  }
@@ -225,7 +244,7 @@ export function verifyProof(jws, opts = {}) {
225
244
  if (!header || typeof header !== 'object' || header.alg !== 'EdDSA') {
226
245
  return { valid: false, reason: 'bad_alg' };
227
246
  }
228
- if (header.typ !== 'oversky-dpop+jwt') {
247
+ if (header.typ !== typ) {
229
248
  return { valid: false, reason: 'bad_typ' };
230
249
  }
231
250
  const headerJwk = header.jwk;
@@ -0,0 +1,101 @@
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 { type DeviceProofClaims, type Ed25519PublicJwk, type VerifyFailureReason } from './deviceKey.js';
26
+ /** RFC 9449 §4.2 — the proof's JOSE header `typ`. */
27
+ export declare const DPOP_PROOF_TYP = "dpop+jwt";
28
+ /** RFC 9449 §4.1 — the request header carrying the proof. */
29
+ export declare const DPOP_HEADER = "DPoP";
30
+ /** Default freshness window, matching the device-proof verifier. */
31
+ export declare const DPOP_MAX_AGE_SECONDS = 60;
32
+ /** Server error codes for a bound token presented without a usable proof. */
33
+ export declare const DPOP_PROOF_REQUIRED = "DPOP_PROOF_REQUIRED";
34
+ export declare const DPOP_PROOF_INVALID = "DPOP_PROOF_INVALID";
35
+ export declare const DPOP_PROOF_VERIFY_UNAVAILABLE = "DPOP_PROOF_VERIFY_UNAVAILABLE";
36
+ /** A proof's claims: RFC 9449's `{htm, htu, iat, jti, ath?}`. */
37
+ export type DpopProofClaims = Pick<DeviceProofClaims, 'htm' | 'htu' | 'iat' | 'jti' | 'ath'>;
38
+ export interface DpopProofRequest {
39
+ /** HTTP method of the request the proof will accompany. */
40
+ htm: string;
41
+ /** Target URI; query and fragment are stripped (RFC 9449 §4.2). */
42
+ htu: string;
43
+ /** The access token the proof commits to (hashed into `ath`). Omitted only
44
+ * at the token endpoint, where the token does not exist yet. */
45
+ accessToken?: string;
46
+ /** Pre-computed `ath`, for a signer that never sees the token itself. */
47
+ ath?: string;
48
+ /** Test hooks. */
49
+ iat?: number;
50
+ jti?: string;
51
+ }
52
+ /**
53
+ * An in-memory DPoP key. The private half lives in this closure and nowhere
54
+ * else: there is no accessor, no serializer, and it is never written to disk.
55
+ */
56
+ export interface DpopKey {
57
+ readonly publicJwk: Ed25519PublicJwk;
58
+ /** RFC 7638 thumbprint — the value a bound token carries as `cnf.jkt`. */
59
+ readonly thumbprint: string;
60
+ sign(req: DpopProofRequest): string;
61
+ }
62
+ export declare function createDpopKey(): DpopKey;
63
+ /** RFC 9449 §4.2 htu normalization — identical to the device proof's. */
64
+ export declare function normalizeDpopHtu(url: string): string;
65
+ /** `ath` for an access token: base64url(SHA-256(ascii(token))), RFC 9449 §4.2. */
66
+ export declare function accessTokenHash(accessToken: string): string;
67
+ /** True for a value shaped like an `ath` / `jkt` (43-char base64url SHA-256). */
68
+ export declare function isAth(value: unknown): value is string;
69
+ /** Same shape test, named for the `cnf.jkt` call sites. */
70
+ export declare const isJwkThumbprint: typeof isAth;
71
+ export type DpopVerifyFailureReason = Exclude<VerifyFailureReason, 'rth_mismatch' | 'pin_mismatch'> | 'ath_missing' | 'jkt_mismatch';
72
+ export interface DpopVerifyOptions {
73
+ expectedHtm: string;
74
+ expectedHtu: string;
75
+ /** `ath` the proof must carry. Required for a resource request. */
76
+ expectedAth?: string;
77
+ /** Thumbprint the proof's key must have — the token's `cnf.jkt`. */
78
+ expectedJkt?: string;
79
+ maxAgeSeconds?: number;
80
+ clockSkewSeconds?: number;
81
+ now?: number;
82
+ }
83
+ export type DpopVerifyResult = {
84
+ valid: true;
85
+ claims: DpopProofClaims;
86
+ thumbprint: string;
87
+ publicJwk: Ed25519PublicJwk;
88
+ } | {
89
+ valid: false;
90
+ reason: DpopVerifyFailureReason;
91
+ };
92
+ /**
93
+ * Verify an RFC 9449 proof: `typ` dpop+jwt, `alg` EdDSA, a well-formed inline
94
+ * JWK, the signature, iat window, htm, htu, `ath` (when expected) and the
95
+ * key's thumbprint against `expectedJkt` (when given).
96
+ *
97
+ * Replay (`jti`) is NOT checked here — it needs shared state, and the caller
98
+ * owns that (the API's Redis jti cache). A verifier that claimed to do it in
99
+ * process memory would be wrong on every multi-replica deployment.
100
+ */
101
+ export declare function verifyDpopProof(jws: string, opts: DpopVerifyOptions): DpopVerifyResult;
@@ -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
+ }
@@ -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';
@@ -51,7 +51,7 @@ export { CredentialResolver, CREDENTIAL_PRECEDENCE, __resetCredentialResolverFor
51
51
  // H3-6 — public seam 401 handlers call to bust the helper cache so
52
52
  // the next resolve re-invokes OVERSKY_AUTH_HELPER immediately
53
53
  // instead of serving the stale cached token for up to its TTL.
54
- invalidateAuthHelperCache, classifyTokenKind, CI_TOKEN_PREFIX,
54
+ invalidateAuthHelperCache, classifyTokenKind, CI_TOKEN_PREFIX, PERSONAL_ACCESS_TOKEN_PREFIX, isStaticTokenKind,
55
55
  // L6.1 / L6.3 — fail-closed errors thrown by the auth-helper slot.
56
56
  AuthHelperUntrustedError, AuthHelperUnavailableError, } from './credentialResolver.js';
57
57
  // Auth helper — invocation contract for OVERSKY_AUTH_HELPER
@@ -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 {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skrr-ai/auth-core",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "main": "dist/cjs/index.js",
5
5
  "types": "dist/esm/index.d.ts",
6
6
  "exports": {