@skrr-ai/cli 0.1.50 → 0.1.52

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 (131) hide show
  1. package/dist/base-command.d.ts +7 -0
  2. package/dist/base-command.js +95 -8
  3. package/dist/commands/agents/chat.d.ts +14 -0
  4. package/dist/commands/agents/chat.js +37 -1
  5. package/dist/commands/agents/create.js +8 -4
  6. package/dist/commands/agents/show.d.ts +9 -0
  7. package/dist/commands/agents/show.js +53 -0
  8. package/dist/commands/agents/update.d.ts +32 -0
  9. package/dist/commands/agents/update.js +81 -9
  10. package/dist/commands/balance/show.d.ts +34 -0
  11. package/dist/commands/balance/show.js +92 -2
  12. package/dist/commands/commitments/doctor.d.ts +1 -0
  13. package/dist/commands/commitments/doctor.js +19 -4
  14. package/dist/commands/commitments/effective-policy.js +15 -1
  15. package/dist/commands/commitments/next-tick.js +12 -2
  16. package/dist/commands/commitments/preflight.js +10 -0
  17. package/dist/commands/commitments/show.js +11 -1
  18. package/dist/commands/context.js +2 -1
  19. package/dist/commands/daemon/config.d.ts +15 -0
  20. package/dist/commands/daemon/config.js +53 -0
  21. package/dist/commands/daemon/index.js +1 -0
  22. package/dist/commands/daemon/status.js +15 -1
  23. package/dist/commands/doctor.js +8 -1
  24. package/dist/commands/harnesses/models.d.ts +54 -0
  25. package/dist/commands/harnesses/models.js +100 -7
  26. package/dist/commands/harnesses/show.d.ts +11 -0
  27. package/dist/commands/harnesses/show.js +35 -0
  28. package/dist/commands/harnesses/update.d.ts +28 -1
  29. package/dist/commands/harnesses/update.js +73 -10
  30. package/dist/commands/login.d.ts +2 -1
  31. package/dist/commands/login.js +26 -7
  32. package/dist/commands/logout.js +3 -0
  33. package/dist/commands/machines/dedicated/show.js +25 -2
  34. package/dist/commands/outbox/list.d.ts +12 -1
  35. package/dist/commands/outbox/list.js +31 -2
  36. package/dist/commands/revoke-daemon.js +9 -0
  37. package/dist/commands/spaces/create.d.ts +1 -0
  38. package/dist/commands/spaces/create.js +17 -0
  39. package/dist/commands/spaces/show.js +5 -0
  40. package/dist/commands/spaces/update.d.ts +1 -0
  41. package/dist/commands/spaces/update.js +16 -0
  42. package/dist/commands/tasks/create.d.ts +6 -0
  43. package/dist/commands/tasks/create.js +31 -6
  44. package/dist/commands/tasks/show.js +45 -0
  45. package/dist/commands/tasks/start.d.ts +14 -0
  46. package/dist/commands/tasks/start.js +31 -1
  47. package/dist/commands/tasks/update.d.ts +1 -0
  48. package/dist/commands/tasks/update.js +29 -1
  49. package/dist/commands/tasks/upsert.d.ts +1 -0
  50. package/dist/commands/tasks/upsert.js +16 -0
  51. package/dist/commands/whoami.js +5 -3
  52. package/dist/lib/agent-config.d.ts +2 -0
  53. package/dist/lib/agent-config.js +14 -10
  54. package/dist/lib/agentic-stream.d.ts +88 -1
  55. package/dist/lib/agentic-stream.js +330 -9
  56. package/dist/lib/auth-core-init.d.ts +2 -10
  57. package/dist/lib/auth-core-init.js +20 -67
  58. package/dist/lib/auth-failure.d.ts +33 -0
  59. package/dist/lib/auth-failure.js +68 -0
  60. package/dist/lib/auth-storage.d.ts +24 -0
  61. package/dist/lib/auth-storage.js +342 -11
  62. package/dist/lib/cli-id.d.ts +22 -0
  63. package/dist/lib/cli-id.js +56 -0
  64. package/dist/lib/cli-identity.d.ts +49 -0
  65. package/dist/lib/cli-identity.js +159 -0
  66. package/dist/lib/commitments.d.ts +25 -1
  67. package/dist/lib/commitments.js +46 -1
  68. package/dist/lib/config.d.ts +17 -0
  69. package/dist/lib/config.js +15 -3
  70. package/dist/lib/daemonBroker.d.ts +37 -1
  71. package/dist/lib/daemonBroker.js +146 -22
  72. package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
  73. package/dist/lib/daemonBrokerRefusal.js +127 -0
  74. package/dist/lib/dedicated-machines.d.ts +33 -2
  75. package/dist/lib/dedicated-machines.js +50 -1
  76. package/dist/lib/delegated-cli.js +20 -1
  77. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  78. package/dist/lib/exec-runtime-binary.js +4 -2
  79. package/dist/lib/first-party-harness-broker.js +10 -5
  80. package/dist/lib/harnesses.d.ts +10 -0
  81. package/dist/lib/keychain.d.ts +39 -0
  82. package/dist/lib/keychain.js +228 -39
  83. package/dist/lib/label-ref.d.ts +34 -0
  84. package/dist/lib/label-ref.js +68 -1
  85. package/dist/lib/live-runtime-binary.d.ts +40 -0
  86. package/dist/lib/live-runtime-binary.js +193 -0
  87. package/dist/lib/login.d.ts +23 -0
  88. package/dist/lib/login.js +67 -21
  89. package/dist/lib/message-intent.js +3 -1
  90. package/dist/lib/oauthLogin.js +2 -8
  91. package/dist/lib/refresh.js +6 -2
  92. package/dist/lib/tasks.d.ts +20 -0
  93. package/dist/lib/tasks.js +87 -8
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +28 -4
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +23 -5
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
  108. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -1
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
  111. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
  112. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
  113. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +28 -4
  114. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +22 -4
  115. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
  116. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  117. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
  118. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
  119. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
  120. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -1
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
  128. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  129. package/dist/node_modules/@skrr-ai/data-provider/index.js +3139 -3097
  130. package/oclif.manifest.json +28591 -28438
  131. package/package.json +1 -1
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.__setStateForTest = __setStateForTest;
7
7
  exports.__getStateForTest = __getStateForTest;
8
+ exports.__clearWarnDedupeForTest = __clearWarnDedupeForTest;
8
9
  exports.initCredEnvelope = initCredEnvelope;
9
10
  exports.resetCredEnvelope = resetCredEnvelope;
10
11
  exports.shutdownCredEnvelope = shutdownCredEnvelope;
@@ -141,7 +142,21 @@ function atomicWrite0600(target, value) {
141
142
  * The event name passed in is the FULLY-QUALIFIED `cred_envelope.*` form
142
143
  * because daemon-side telemetry consumers (`event-log.ts`, dashboards,
143
144
  * spec assertions) key off the prefixed name. Do not strip the prefix.
145
+ *
146
+ * Identical failure warnings hit the logger only once per process.
147
+ * Telemetry still records every occurrence — the dedupe exists because a
148
+ * single unreadable envelope produced the same warning once per stored
149
+ * field (token, refresh token, expiry…), which read to users as three
150
+ * crashes rather than one stale credential (OSK-12074). The signature is
151
+ * event + kek identity + message, so a genuinely different failure mode
152
+ * still gets its own line.
144
153
  */
154
+ const warnedSignatures = new Set();
155
+ const MAX_WARNED_SIGNATURES = 64;
156
+ /** @internal Test seam — each spec owns its warn-dedupe horizon. */
157
+ function __clearWarnDedupeForTest() {
158
+ warnedSignatures.clear();
159
+ }
145
160
  function emit(eventName, success, metadata) {
146
161
  (0, runtime_js_1.emitAuthTelemetry)({
147
162
  timestamp: new Date().toISOString(),
@@ -150,7 +165,13 @@ function emit(eventName, success, metadata) {
150
165
  metadata: { event: eventName, ...metadata },
151
166
  });
152
167
  if (!success) {
153
- (0, runtime_js_1.getAuthLogger)().warn(`[credEnvelope] ${eventName}`, metadata);
168
+ const signature = `${eventName}:${metadata.kekId ?? ''}:${metadata.kekKind ?? ''}:${metadata.message ?? ''}`;
169
+ if (!warnedSignatures.has(signature)) {
170
+ if (warnedSignatures.size >= MAX_WARNED_SIGNATURES)
171
+ warnedSignatures.clear();
172
+ warnedSignatures.add(signature);
173
+ (0, runtime_js_1.getAuthLogger)().warn(`[credEnvelope] ${eventName}`, metadata);
174
+ }
154
175
  }
155
176
  }
156
177
  // ---------------------------------------------------------------------------
@@ -763,6 +784,10 @@ function maybeDecryptOnRead(stored) {
763
784
  kekId: _state.kekId,
764
785
  kekKind: _state.kekKind,
765
786
  message: err?.message ?? 'unknown',
787
+ // The crypto error alone reads as a crash; the actionable truth is
788
+ // that this stored credential is unreadable by THIS binary (usually
789
+ // a signing-identity or profile drift) and re-login rebuilds it.
790
+ hint: 'stored credential is unreadable on this machine — signing in again rebuilds it',
766
791
  });
767
792
  return { plaintext: null, needsMigration: false };
768
793
  }
@@ -23,9 +23,14 @@
23
23
  *
24
24
  * What the daemon verifies is the binding, not the server's honesty: the
25
25
  * request already arrives over the daemon's authenticated server channel, so
26
- * the field IS the attestation. What the binding buys is that the model —
27
- * which authors `toolInput` — cannot mint an approval, and an approval for
28
- * one call cannot be replayed onto another.
26
+ * the field IS the attestation — it is unsigned, and `decidedBy` is a literal.
27
+ * What the binding buys is that the model — which authors `toolInput` —
28
+ * cannot mint an approval, and an approval for one call cannot be replayed
29
+ * onto another. The verifier also bounds the validity window itself (never
30
+ * trusting the issuer's `expiresAt` beyond `ATTESTED_TOOL_APPROVAL_TTL_MS`),
31
+ * and the daemon spends each approval once: a second delivery of the same
32
+ * attestation inside its window is refused as `replayed`. That ledger lives
33
+ * in daemon memory, so a daemon restart inside the (≤60s) window forgets it.
29
34
  */
30
35
  /** Announced by a daemon that honours `attestedApproval` on daemon:tool:request. */
31
36
  export declare const DAEMON_TOOL_ATTESTED_APPROVAL_CAPABILITY = "daemon_tool_attested_approval_v1";
@@ -89,12 +94,31 @@ export declare function mintAttestedToolApproval(args: {
89
94
  now?: number;
90
95
  ttlMs?: number;
91
96
  }): AttestedToolApproval;
97
+ /**
98
+ * Why an attestation did not approve a call — logged by the daemon, never
99
+ * shown to the caller beyond "absent" vs "did not verify".
100
+ * - `absent` — no attestation arrived (the server did not ask anyone)
101
+ * - `malformed` — not the v1 shape
102
+ * - `expired` — past `expiresAt`, or issued implausibly far in the future
103
+ * - `window` — the issuer claimed a validity window longer than
104
+ * `ATTESTED_TOOL_APPROVAL_TTL_MS` (or an empty/negative one)
105
+ * - `binding` — minted for a different call (toolUseId, tool, or input)
106
+ * - `replayed` — this exact approval was already spent on this daemon
107
+ * (the ledger is the verifier's caller's, see the daemon's
108
+ * `permissions/attestation-ledger.ts`)
109
+ */
110
+ export type AttestedToolApprovalRefusalReason = 'absent' | 'malformed' | 'binding' | 'expired' | 'window' | 'replayed';
92
111
  export type AttestedToolApprovalVerdict = {
93
112
  ok: true;
94
113
  } | {
95
114
  ok: false;
96
- reason: 'absent' | 'malformed' | 'binding' | 'expired';
115
+ reason: AttestedToolApprovalRefusalReason;
97
116
  };
117
+ /**
118
+ * Clock skew tolerated on `issuedAt`: a server clock slightly ahead of the
119
+ * daemon's must not refuse a fresh approval.
120
+ */
121
+ export declare const ATTESTED_TOOL_APPROVAL_CLOCK_SKEW_MS: number;
98
122
  /**
99
123
  * Daemon side: verify an attestation against the call it claims to approve.
100
124
  * Untrusted wire input — every field is checked, and any mismatch is a plain
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ATTESTED_TOOL_APPROVAL_TTL_MS = exports.DAEMON_TOOL_CATEGORIES = exports.DAEMON_TOOL_READ_ONLY_CAPABILITY = exports.DAEMON_TOOL_ATTESTED_APPROVAL_CAPABILITY = void 0;
3
+ exports.ATTESTED_TOOL_APPROVAL_CLOCK_SKEW_MS = exports.ATTESTED_TOOL_APPROVAL_TTL_MS = exports.DAEMON_TOOL_CATEGORIES = exports.DAEMON_TOOL_READ_ONLY_CAPABILITY = exports.DAEMON_TOOL_ATTESTED_APPROVAL_CAPABILITY = void 0;
4
4
  exports.categoryForDaemonTool = categoryForDaemonTool;
5
5
  exports.canonicalToolInputDigest = canonicalToolInputDigest;
6
6
  exports.mintAttestedToolApproval = mintAttestedToolApproval;
@@ -31,9 +31,14 @@ const node_crypto_1 = require("node:crypto");
31
31
  *
32
32
  * What the daemon verifies is the binding, not the server's honesty: the
33
33
  * request already arrives over the daemon's authenticated server channel, so
34
- * the field IS the attestation. What the binding buys is that the model —
35
- * which authors `toolInput` — cannot mint an approval, and an approval for
36
- * one call cannot be replayed onto another.
34
+ * the field IS the attestation — it is unsigned, and `decidedBy` is a literal.
35
+ * What the binding buys is that the model — which authors `toolInput` —
36
+ * cannot mint an approval, and an approval for one call cannot be replayed
37
+ * onto another. The verifier also bounds the validity window itself (never
38
+ * trusting the issuer's `expiresAt` beyond `ATTESTED_TOOL_APPROVAL_TTL_MS`),
39
+ * and the daemon spends each approval once: a second delivery of the same
40
+ * attestation inside its window is refused as `replayed`. That ledger lives
41
+ * in daemon memory, so a daemon restart inside the (≤60s) window forgets it.
37
42
  */
38
43
  /** Announced by a daemon that honours `attestedApproval` on daemon:tool:request. */
39
44
  exports.DAEMON_TOOL_ATTESTED_APPROVAL_CAPABILITY = 'daemon_tool_attested_approval_v1';
@@ -136,6 +141,11 @@ function mintAttestedToolApproval(args) {
136
141
  expiresAt: now + (args.ttlMs ?? exports.ATTESTED_TOOL_APPROVAL_TTL_MS),
137
142
  };
138
143
  }
144
+ /**
145
+ * Clock skew tolerated on `issuedAt`: a server clock slightly ahead of the
146
+ * daemon's must not refuse a fresh approval.
147
+ */
148
+ exports.ATTESTED_TOOL_APPROVAL_CLOCK_SKEW_MS = 30 * 1000;
139
149
  /**
140
150
  * Daemon side: verify an attestation against the call it claims to approve.
141
151
  * Untrusted wire input — every field is checked, and any mismatch is a plain
@@ -159,9 +169,17 @@ function verifyAttestedToolApproval(approval, call) {
159
169
  return { ok: false, reason: 'malformed' };
160
170
  }
161
171
  const now = call.now ?? Date.now();
162
- if (a.expiresAt <= now || a.issuedAt > now + 30_000) {
172
+ if (a.expiresAt <= now || a.issuedAt > now + exports.ATTESTED_TOOL_APPROVAL_CLOCK_SKEW_MS) {
163
173
  return { ok: false, reason: 'expired' };
164
174
  }
175
+ // The TTL is the VERIFIER's bound, not the issuer's claim (OSK-12108): an
176
+ // attestation whose own window is longer than one approval may stay valid
177
+ // is refused whatever `expiresAt` says, so "answers one person about this
178
+ // call, moments ago" holds even against an issuer that set a year.
179
+ const window = a.expiresAt - a.issuedAt;
180
+ if (!(window > 0) || window > exports.ATTESTED_TOOL_APPROVAL_TTL_MS) {
181
+ return { ok: false, reason: 'window' };
182
+ }
165
183
  if (a.toolUseId !== call.toolUseId ||
166
184
  a.toolName.toLowerCase() !== String(call.toolName).toLowerCase() ||
167
185
  a.inputSha256 !== canonicalToolInputDigest(call.toolInput)) {
@@ -604,11 +604,49 @@ async function enrollWithServer(opts) {
604
604
  // Enrollment is the CLI's (JWT) job; the daemon holding an opaque
605
605
  // token should not enroll. Guarded at the daemon call site, this is
606
606
  // defense-in-depth for any other opaque caller.
607
- // • 409 already-enrolled — a concurrent binary won the enrollment race
608
- // for the shared keypair; idempotent, nothing to do.
607
+ // • 409 ALREADY_ENROLLED with OUR thumbprint — a concurrent binary won
608
+ // the enrollment race for this keypair; idempotent, nothing to do.
609
+ //
610
+ // A 409 is NOT benign when the row is pinned to a DIFFERENT key
611
+ // (OSK-12063). The server keeps one pin per (user, machine) row while
612
+ // every local profile has its own key, so a 409 can mean "every refresh
613
+ // this key signs will be refused with DEVICE_PROOF_THUMBPRINT_MISMATCH".
614
+ // The server names the pinned thumbprint (`enrolledThumbprint`) so the
615
+ // two cases can be told apart. An older server that omits it keeps the
616
+ // old reading, because there is nothing to compare. A 409
617
+ // ENROLLMENT_DUPLICATE_KEY — this key is pinned on another active row —
618
+ // is never benign either.
619
+ let conflictCode = null;
620
+ let enrolledThumbprint = null;
621
+ if (response.status === 409) {
622
+ try {
623
+ const parsed = JSON.parse(responseText);
624
+ conflictCode = typeof parsed.code === 'string' ? parsed.code : null;
625
+ enrolledThumbprint =
626
+ typeof parsed.enrolledThumbprint === 'string' ? parsed.enrolledThumbprint : null;
627
+ }
628
+ catch {
629
+ /* non-JSON 409 — treat as the legacy benign race */
630
+ }
631
+ }
632
+ const pinnedToOtherKey = response.status === 409 &&
633
+ enrolledThumbprint !== null &&
634
+ enrolledThumbprint !== keypair.thumbprint;
635
+ const keyPinnedElsewhere = response.status === 409 && conflictCode === 'ENROLLMENT_DUPLICATE_KEY';
609
636
  const isBenignRejection = (response.status === 404 && responseText.includes('DAEMON_NOT_FOUND')) ||
610
637
  (response.status === 403 && responseText.includes('OPAQUE_AUTH_REFUSED')) ||
611
- response.status === 409;
638
+ (response.status === 409 && !pinnedToOtherKey && !keyPinnedElsewhere);
639
+ if (pinnedToOtherKey) {
640
+ emit('enrollment_key_mismatch', false, {
641
+ profile,
642
+ thumbprint: keypair.thumbprint,
643
+ enrolledThumbprint,
644
+ serverUrl,
645
+ message: 'this machine is registered with a different device key than this profile signs ' +
646
+ 'with; its refreshes will be refused. An interactive daemon login (skrrd login) ' +
647
+ 're-registers this key.',
648
+ }, { logLevel: 'warn' });
649
+ }
612
650
  if (response.status === 404 && responseText.includes('DAEMON_NOT_FOUND')) {
613
651
  try {
614
652
  atomicWrite0600(backoffPath, JSON.stringify({
@@ -29,11 +29,13 @@ export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, K
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
30
  export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, maybeEncryptForWrite, maybeDecryptOnRead, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
31
31
  export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
32
+ export { getMachineUuid, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
32
33
  export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
33
34
  export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, type AssembleBundleOptions as PairAssembleBundleOptions, type OpenBundleOptions as PairOpenBundleOptions, type PairBundle, type PairBundleErrorCode, type PairKeyPair, type PairPlaintextV1, type PairUrlFields, } from './daemonPair.js';
34
35
  export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConfiguredAutoMode, type HarnessTrustTier, type CredentialDeliveryPolicy, type CredentialMode, } from './harnessTrust.js';
35
36
  export { HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT, claudeSkillsLayoutEvidence, harnessLoadsClaudeSkillsLayout, } from './harnessSkillLayout.js';
36
- export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, } from './spawnEnv.js';
37
+ export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
38
+ export { loopbackFetch, assertLoopbackHttpUrl, isLoopbackHostname, isLoopbackAddress, LoopbackTargetRefusedError, LOOPBACK_DEFAULT_MAX_BODY_BYTES, type LoopbackFetch, type LoopbackFetchInit, } from './loopbackHttp.js';
37
39
  export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
38
40
  export { findLegacyLocalState, describeLegacyState, type LegacyStateFinding, type LegacyStateReport, } from './legacyStatePreflight.js';
39
41
  export * from './firstPartyHarness.js';
@@ -24,9 +24,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.generateVerifier = exports.invalidateHeadersHelperCache = exports.runHeadersHelper = exports.SENTINEL_TRIGGERING_FAILURES = exports.__resetAuthHelperFailureReasonForTest = exports.getLastAuthHelperFailureReason = exports.getDefaultHelperTtlMs = exports.invokeAuthHelper = exports.AuthHelperUnavailableError = exports.AuthHelperUntrustedError = exports.CI_TOKEN_PREFIX = exports.classifyTokenKind = exports.invalidateAuthHelperCache = exports.__resetCredentialResolverForTest = exports.CREDENTIAL_PRECEDENCE = exports.CredentialResolver = exports.__setFdReaderForTest = exports.readAuthFromFd = exports.createRefreshScheduler = exports.UNKNOWN_401_BUDGET = exports.__resetUnknown401StreaksForTest = exports.__refreshDiagnostics = exports.installLockExitHandlers = exports.clearReauthState = exports.needsReauthState = exports.persistReauthState = exports.withAuthLock = exports.revokeDaemonRefreshSession = exports.refreshDaemonToken = exports.classifyRefreshResponse = exports.EXIT_NEEDS_REAUTH = exports.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
26
  exports.publicJwkFromX = exports.normalizeHtu = exports.jwkThumbprint = exports.generateDeviceKeyPair = exports.buildDeviceProof = exports.zeroizeKekCaches = exports.WindowsKek = exports.registerZeroizeHook = exports.MacosKek = exports.LinuxKek = exports.KekUnavailableError = exports.UnavailableKek = exports.InMemoryKek = exports.getKekStrategy = exports.__setKekStrategyForTest = exports.credEnvelopeWrap = exports.credEnvelopeUnwrap = exports.credEnvelopeSerialize = exports.credSealBuffer = exports.credOpenBuffer = exports.CRED_KEY_LEN = exports.isCredEnvelopeString = exports.generateDek = exports.CRED_ENVELOPE_MAGIC = exports.credEnvelopeEncrypt = exports.credEnvelopeDeserialize = exports.credEnvelopeDecrypt = exports.buildCredentialAad = exports.isValidRecoveryCode = exports.normalizeRecoveryCode = exports.parseRecoveryCode = exports.formatRecoveryCode = exports.MAX_TTL_DAYS = exports.MIN_TTL_DAYS = exports.parseTtlDays = exports.LoginExchangeError = exports.LoginInitError = exports.loginWithLocalhost = exports.LocalCallbackMalformedError = exports.LocalCallbackDeniedError = exports.LocalCallbackStateMismatchError = exports.LocalCallbackTimeoutError = exports.LocalCallbackBindError = exports.LocalCallbackError = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.startLocalCallback = exports.verifyChallenge = exports.safeEqual = exports.generateState = exports.challenge = void 0;
27
- exports.pairUint8ArrayToBase64Url = exports.pairUint8ArrayToBase64 = exports.parsePairUrl = exports.parsePairPlaintext = exports.parsePairBundle = exports.openPairBundleBase64 = exports.generatePairKeyPair = exports.buildPairUrl = exports.buildPairAad = exports.pairBase64UrlToUint8Array = exports.pairBase64ToUint8Array = exports.assemblePairBundleBase64 = exports.PairBundleError = exports.PAIR_SECRET_LEN = exports.PAIR_PUBKEY_LEN = exports.PAIR_NONCE_LEN = exports.PAIR_BUNDLE_MAX_BASE64_LEN = exports.PAIR_AAD_MAX_LEN = exports.PAIR_AAD_PREFIX = exports.__resetCachedDaemonIdForTest = exports.legacyDaemonUuids = exports.ensureMachineDaemonId = exports.__getDeviceIdentityStateForTest = exports.__setDeviceIdentityStateForTest = exports.__enrollmentBackoffPathForTest = exports.__enrollmentMarkerPathForTest = exports.__privateKeyPathForTest = exports.__publicKeyPathForTest = exports.describeDeviceIdentityState = exports.getDevicePublicKey = exports.isDeviceIdentityActive = exports.clearEnrollmentMarker = exports.enrollWithServer = exports.signServerRequest = exports.resetDeviceIdentity = exports.initDeviceIdentity = exports.__resetShutdownGuardForTest = exports.__getCredEnvelopeStateForTest = exports.__setCredEnvelopeStateForTest = exports.__wrappedDekFilePathForTest = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDeviceProof = exports.tokenHash = void 0;
28
- 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 = exports.describeLegacyState = exports.findLegacyLocalState = exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_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 = void 0;
29
- exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = void 0;
27
+ exports.parsePairBundle = exports.openPairBundleBase64 = exports.generatePairKeyPair = exports.buildPairUrl = exports.buildPairAad = exports.pairBase64UrlToUint8Array = exports.pairBase64ToUint8Array = exports.assemblePairBundleBase64 = exports.PairBundleError = exports.PAIR_SECRET_LEN = exports.PAIR_PUBKEY_LEN = exports.PAIR_NONCE_LEN = exports.PAIR_BUNDLE_MAX_BASE64_LEN = exports.PAIR_AAD_MAX_LEN = exports.PAIR_AAD_PREFIX = exports.__resetCachedDaemonIdForTest = exports.legacyDaemonUuids = exports.ensureMachineDaemonId = exports.__resetMachineUuidCacheForTest = exports.HOSTED_RUNTIME_MACHINE_SOURCES = exports.hostedRuntimeIdOverride = exports.getMachineUuid = exports.__getDeviceIdentityStateForTest = exports.__setDeviceIdentityStateForTest = exports.__enrollmentBackoffPathForTest = exports.__enrollmentMarkerPathForTest = exports.__privateKeyPathForTest = exports.__publicKeyPathForTest = exports.describeDeviceIdentityState = exports.getDevicePublicKey = exports.isDeviceIdentityActive = exports.clearEnrollmentMarker = exports.enrollWithServer = exports.signServerRequest = exports.resetDeviceIdentity = exports.initDeviceIdentity = exports.__resetShutdownGuardForTest = exports.__getCredEnvelopeStateForTest = exports.__setCredEnvelopeStateForTest = exports.__wrappedDekFilePathForTest = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDeviceProof = exports.tokenHash = void 0;
28
+ 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 = 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 = 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 = 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
@@ -258,7 +258,16 @@ Object.defineProperty(exports, "__enrollmentMarkerPathForTest", { enumerable: tr
258
258
  Object.defineProperty(exports, "__enrollmentBackoffPathForTest", { enumerable: true, get: function () { return deviceIdentityBridge_js_1.__enrollmentBackoffPathForTest; } });
259
259
  Object.defineProperty(exports, "__setDeviceIdentityStateForTest", { enumerable: true, get: function () { return deviceIdentityBridge_js_1.__setStateForTest; } });
260
260
  Object.defineProperty(exports, "__getDeviceIdentityStateForTest", { enumerable: true, get: function () { return deviceIdentityBridge_js_1.__getStateForTest; } });
261
+ // Hardware machine identity sent as `machineUuid` (IOPlatformUUID /
262
+ // machine-id / MachineGuid). ONE implementation for daemon and CLI —
263
+ // OSK-12063. Also exposed dependency-free as `@skrr-ai/auth-core/machine-uuid`.
264
+ var machineUuid_js_1 = require("./machineUuid.js");
265
+ Object.defineProperty(exports, "getMachineUuid", { enumerable: true, get: function () { return machineUuid_js_1.getMachineUuid; } });
266
+ Object.defineProperty(exports, "hostedRuntimeIdOverride", { enumerable: true, get: function () { return machineUuid_js_1.hostedRuntimeIdOverride; } });
267
+ Object.defineProperty(exports, "HOSTED_RUNTIME_MACHINE_SOURCES", { enumerable: true, get: function () { return machineUuid_js_1.HOSTED_RUNTIME_MACHINE_SOURCES; } });
268
+ Object.defineProperty(exports, "__resetMachineUuidCacheForTest", { enumerable: true, get: function () { return machineUuid_js_1.__resetMachineUuidCacheForTest; } });
261
269
  // Stable per-machine UUIDv7 identity persisted at <configDir>/daemon.id.
270
+ // NOT a `machineUuid` — see machineUuid.ts.
262
271
  var machineId_js_1 = require("./machineId.js");
263
272
  Object.defineProperty(exports, "ensureMachineDaemonId", { enumerable: true, get: function () { return machineId_js_1.ensureMachineDaemonId; } });
264
273
  Object.defineProperty(exports, "legacyDaemonUuids", { enumerable: true, get: function () { return machineId_js_1.legacyDaemonUuids; } });
@@ -316,6 +325,19 @@ Object.defineProperty(exports, "SENSITIVE_ENV_PREFIXES", { enumerable: true, get
316
325
  Object.defineProperty(exports, "SENSITIVE_ENV_SUFFIXES", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_SUFFIXES; } });
317
326
  Object.defineProperty(exports, "SENSITIVE_ENV_NAME_TOKENS", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_NAME_TOKENS; } });
318
327
  Object.defineProperty(exports, "SENSITIVE_ENV_PREFIX_EXCEPTIONS", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_PREFIX_EXCEPTIONS; } });
328
+ Object.defineProperty(exports, "LOOPBACK_NO_PROXY_HOSTS", { enumerable: true, get: function () { return spawnEnv_js_1.LOOPBACK_NO_PROXY_HOSTS; } });
329
+ Object.defineProperty(exports, "envHasProxy", { enumerable: true, get: function () { return spawnEnv_js_1.envHasProxy; } });
330
+ Object.defineProperty(exports, "withLoopbackNoProxy", { enumerable: true, get: function () { return spawnEnv_js_1.withLoopbackNoProxy; } });
331
+ // OSK-12134 — the one proxy-free client for loopback HTTP. Bun's fetch (and
332
+ // Node's under NODE_USE_ENV_PROXY) sends 127.0.0.1 requests to HTTP_PROXY,
333
+ // credentials and all; every loopback caller goes through this instead.
334
+ var loopbackHttp_js_1 = require("./loopbackHttp.js");
335
+ Object.defineProperty(exports, "loopbackFetch", { enumerable: true, get: function () { return loopbackHttp_js_1.loopbackFetch; } });
336
+ Object.defineProperty(exports, "assertLoopbackHttpUrl", { enumerable: true, get: function () { return loopbackHttp_js_1.assertLoopbackHttpUrl; } });
337
+ Object.defineProperty(exports, "isLoopbackHostname", { enumerable: true, get: function () { return loopbackHttp_js_1.isLoopbackHostname; } });
338
+ Object.defineProperty(exports, "isLoopbackAddress", { enumerable: true, get: function () { return loopbackHttp_js_1.isLoopbackAddress; } });
339
+ Object.defineProperty(exports, "LoopbackTargetRefusedError", { enumerable: true, get: function () { return loopbackHttp_js_1.LoopbackTargetRefusedError; } });
340
+ Object.defineProperty(exports, "LOOPBACK_DEFAULT_MAX_BODY_BYTES", { enumerable: true, get: function () { return loopbackHttp_js_1.LOOPBACK_DEFAULT_MAX_BODY_BYTES; } });
319
341
  /* Local-machine identity — the names this product leaves on a disk. Exported so
320
342
  the CLI, daemon and Desktop share one definition instead of 47 literals. */
321
343
  var localIdentity_js_1 = require("./localIdentity.js");
@@ -0,0 +1,32 @@
1
+ /** Upper bound on a buffered response body. Loopback control-plane bodies are small. */
2
+ export declare const LOOPBACK_DEFAULT_MAX_BODY_BYTES: number;
3
+ /** Thrown (synchronously surfaced as a rejection) for a URL that is not loopback HTTP. */
4
+ export declare class LoopbackTargetRefusedError extends Error {
5
+ readonly code = "LOOPBACK_TARGET_REFUSED";
6
+ constructor(message: string);
7
+ }
8
+ /** True for an IP literal in 127.0.0.0/8, `::1`, or an IPv4-mapped 127/8 address. */
9
+ export declare function isLoopbackAddress(address: string): boolean;
10
+ /** True for a host name or address this client accepts as "this machine". */
11
+ export declare function isLoopbackHostname(hostname: string): boolean;
12
+ /**
13
+ * Parse and validate a loopback HTTP URL. Throws {@link LoopbackTargetRefusedError}
14
+ * for anything that is not `http:` to a loopback host.
15
+ */
16
+ export declare function assertLoopbackHttpUrl(input: string | URL): URL;
17
+ /**
18
+ * `RequestInit`, so a `fetch` call site can switch to {@link loopbackFetch}
19
+ * unchanged. Only `method`, `headers`, `signal` and a string/bytes `body` are
20
+ * honoured; a stream, Blob or FormData body is rejected rather than guessed at.
21
+ */
22
+ export interface LoopbackFetchInit extends RequestInit {
23
+ /** Cap on the buffered response body; exceeding it rejects. */
24
+ maxBodyBytes?: number;
25
+ }
26
+ /** A `fetch`-shaped signature, for callers that keep a `fetchImpl` seam. */
27
+ export type LoopbackFetch = (input: string | URL, init?: LoopbackFetchInit) => Promise<Response>;
28
+ /**
29
+ * `fetch` for a loopback URL, over `node:http`, never through a proxy.
30
+ * Refuses (rejects with {@link LoopbackTargetRefusedError}) any other target.
31
+ */
32
+ export declare const loopbackFetch: LoopbackFetch;
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loopbackFetch = exports.LoopbackTargetRefusedError = exports.LOOPBACK_DEFAULT_MAX_BODY_BYTES = void 0;
7
+ exports.isLoopbackAddress = isLoopbackAddress;
8
+ exports.isLoopbackHostname = isLoopbackHostname;
9
+ exports.assertLoopbackHttpUrl = assertLoopbackHttpUrl;
10
+ /**
11
+ * OSK-12134 — the ONE HTTP client for talking to a process on this machine.
12
+ *
13
+ * ── Why this is not `fetch` ────────────────────────────────────────────────
14
+ *
15
+ * The compiled daemon runs on Bun, and Bun's `fetch` routes EVERY URL through
16
+ * `HTTP_PROXY` / `HTTPS_PROXY` unless `NO_PROXY` names it — a `127.0.0.1`
17
+ * target included. Node's `fetch` does the same once `NODE_USE_ENV_PROXY=1` is
18
+ * set (Node 22.21+/24). On a laptop with a local proxy exported in the shell
19
+ * (Clash, Surge, a corporate agent, …) every loopback call the daemon, a
20
+ * wrapper, or the CLI made with `fetch` was delivered to that proxy instead —
21
+ * WITH its credential: the daemon's local secret, the CLI-handoff descriptor
22
+ * secret, the wrapper fan-out bearer, the browser bridge token, the extension
23
+ * relay token. OSK-12087 found this on the `/health` probe and moved that one
24
+ * caller to `node:http`; this module is that fix made the only way to do it.
25
+ *
26
+ * `node:http` never consults proxy environment variables, on Node or on Bun, so
27
+ * a request made through it cannot be routed off the machine by environment.
28
+ *
29
+ * ── What it refuses ────────────────────────────────────────────────────────
30
+ *
31
+ * Only `http:` URLs whose host is a loopback name or address are accepted
32
+ * (`127.0.0.0/8`, `::1`, `localhost`). Anything else throws BEFORE a socket is
33
+ * opened: a caller that builds its URL from a descriptor or an environment
34
+ * variable must not be turned into an SSRF primitive by a hostile value, and a
35
+ * credential that is only meant for this machine must not be sent anywhere
36
+ * else. `localhost` is resolved through a lookup that rejects any non-loopback
37
+ * answer, so a rewritten hosts file cannot redirect it either.
38
+ *
39
+ * ── Shape ──────────────────────────────────────────────────────────────────
40
+ *
41
+ * `loopbackFetch` is a drop-in for the subset of `fetch` these callers use —
42
+ * method, headers, a string/bytes body, an `AbortSignal` — and returns a real
43
+ * `Response`, so existing callers (and their `fetchImpl` test seams) change one
44
+ * identifier. Transport failures reject with `TypeError('fetch failed')` whose
45
+ * `cause` carries the socket error (`ECONNREFUSED`, …), and aborts reject with
46
+ * the signal's reason, exactly as `fetch` does, so existing error handling keeps
47
+ * working. The response body is buffered (bounded by `maxBodyBytes`); nothing
48
+ * on loopback streams through this client.
49
+ */
50
+ const node_dns_1 = __importDefault(require("node:dns"));
51
+ const node_http_1 = __importDefault(require("node:http"));
52
+ const node_net_1 = __importDefault(require("node:net"));
53
+ /** Loopback host names this client will connect to, besides 127/8 and ::1 literals. */
54
+ const LOOPBACK_NAMES = new Set(['localhost']);
55
+ /** Upper bound on a buffered response body. Loopback control-plane bodies are small. */
56
+ exports.LOOPBACK_DEFAULT_MAX_BODY_BYTES = 16 * 1024 * 1024;
57
+ /** Statuses a `Response` must be constructed with a null body for. */
58
+ const NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
59
+ /** Thrown (synchronously surfaced as a rejection) for a URL that is not loopback HTTP. */
60
+ class LoopbackTargetRefusedError extends Error {
61
+ code = 'LOOPBACK_TARGET_REFUSED';
62
+ constructor(message) {
63
+ super(message);
64
+ this.name = 'LoopbackTargetRefusedError';
65
+ }
66
+ }
67
+ exports.LoopbackTargetRefusedError = LoopbackTargetRefusedError;
68
+ function stripBrackets(hostname) {
69
+ return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
70
+ }
71
+ /** True for an IP literal in 127.0.0.0/8, `::1`, or an IPv4-mapped 127/8 address. */
72
+ function isLoopbackAddress(address) {
73
+ const value = stripBrackets(address.trim().toLowerCase());
74
+ if (node_net_1.default.isIPv4(value))
75
+ return value.split('.')[0] === '127';
76
+ if (node_net_1.default.isIPv6(value)) {
77
+ if (value === '::1' || value === '0:0:0:0:0:0:0:1')
78
+ return true;
79
+ // IPv4-mapped (::ffff:127.0.0.1, which WHATWG URL normalizes to ::ffff:7f00:1).
80
+ if (value.startsWith('::ffff:')) {
81
+ const rest = value.slice('::ffff:'.length);
82
+ if (node_net_1.default.isIPv4(rest))
83
+ return isLoopbackAddress(rest);
84
+ const high = /^([0-9a-f]{1,4}):[0-9a-f]{1,4}$/.exec(rest);
85
+ return high ? parseInt(high[1], 16) >> 8 === 127 : false;
86
+ }
87
+ return false;
88
+ }
89
+ return false;
90
+ }
91
+ /** True for a host name or address this client accepts as "this machine". */
92
+ function isLoopbackHostname(hostname) {
93
+ const value = stripBrackets(hostname.trim().toLowerCase());
94
+ return LOOPBACK_NAMES.has(value) || isLoopbackAddress(value);
95
+ }
96
+ /**
97
+ * Parse and validate a loopback HTTP URL. Throws {@link LoopbackTargetRefusedError}
98
+ * for anything that is not `http:` to a loopback host.
99
+ */
100
+ function assertLoopbackHttpUrl(input) {
101
+ let url;
102
+ try {
103
+ url = new URL(String(input));
104
+ }
105
+ catch {
106
+ throw new LoopbackTargetRefusedError('Loopback request refused: not a valid URL.');
107
+ }
108
+ if (url.protocol !== 'http:') {
109
+ throw new LoopbackTargetRefusedError(`Loopback request refused: only http: is allowed on loopback (got ${url.protocol}).`);
110
+ }
111
+ if (!isLoopbackHostname(url.hostname)) {
112
+ throw new LoopbackTargetRefusedError(`Loopback request refused: ${url.hostname} is not a loopback host.`);
113
+ }
114
+ if (url.username || url.password) {
115
+ throw new LoopbackTargetRefusedError('Loopback request refused: URL must not carry userinfo.');
116
+ }
117
+ return url;
118
+ }
119
+ /** A `dns.lookup` that only ever answers with a loopback address. */
120
+ const loopbackOnlyLookup = (hostname, options, callback) => {
121
+ node_dns_1.default.lookup(hostname, { ...options, all: true }, (err, addresses) => {
122
+ if (err) {
123
+ callback(err, '', 0);
124
+ return;
125
+ }
126
+ const list = (Array.isArray(addresses) ? addresses : []);
127
+ const loopback = list.filter((entry) => isLoopbackAddress(entry.address));
128
+ if (loopback.length === 0) {
129
+ const refused = Object.assign(new Error(`${hostname} did not resolve to a loopback address`), { code: 'LOOPBACK_TARGET_REFUSED' });
130
+ callback(refused, '', 0);
131
+ return;
132
+ }
133
+ if (options?.all) {
134
+ callback(null, loopback);
135
+ return;
136
+ }
137
+ callback(null, loopback[0].address, loopback[0].family);
138
+ });
139
+ };
140
+ function abortReason(signal) {
141
+ return signal.reason !== undefined
142
+ ? signal.reason
143
+ : new DOMException('This operation was aborted', 'AbortError');
144
+ }
145
+ function fetchFailed(cause) {
146
+ return new TypeError('fetch failed', { cause });
147
+ }
148
+ function toBodyBuffer(body) {
149
+ if (body === undefined || body === null)
150
+ return null;
151
+ if (typeof body === 'string')
152
+ return Buffer.from(body, 'utf8');
153
+ if (body instanceof ArrayBuffer)
154
+ return Buffer.from(body);
155
+ if (ArrayBuffer.isView(body))
156
+ return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
157
+ throw new TypeError('loopbackFetch supports only string or byte bodies');
158
+ }
159
+ /**
160
+ * `fetch` for a loopback URL, over `node:http`, never through a proxy.
161
+ * Refuses (rejects with {@link LoopbackTargetRefusedError}) any other target.
162
+ */
163
+ const loopbackFetch = (input, init = {}) => {
164
+ let url;
165
+ let payload;
166
+ try {
167
+ url = assertLoopbackHttpUrl(input);
168
+ payload = toBodyBuffer(init.body);
169
+ }
170
+ catch (err) {
171
+ return Promise.reject(err);
172
+ }
173
+ const signal = init.signal ?? undefined;
174
+ if (signal?.aborted)
175
+ return Promise.reject(abortReason(signal));
176
+ const maxBodyBytes = init.maxBodyBytes ?? exports.LOOPBACK_DEFAULT_MAX_BODY_BYTES;
177
+ const method = (init.method ?? 'GET').toUpperCase();
178
+ const headers = {};
179
+ new Headers(init.headers ?? undefined).forEach((value, name) => {
180
+ headers[name] = value;
181
+ });
182
+ if (payload && headers['content-length'] === undefined) {
183
+ headers['content-length'] = String(payload.length);
184
+ }
185
+ return new Promise((resolve, reject) => {
186
+ let settled = false;
187
+ let req;
188
+ const onAbort = () => {
189
+ if (settled)
190
+ return;
191
+ settled = true;
192
+ req?.destroy();
193
+ reject(abortReason(signal));
194
+ };
195
+ const finish = (fn) => {
196
+ if (settled)
197
+ return;
198
+ settled = true;
199
+ signal?.removeEventListener('abort', onAbort);
200
+ fn();
201
+ };
202
+ signal?.addEventListener('abort', onAbort, { once: true });
203
+ try {
204
+ req = node_http_1.default.request({
205
+ protocol: 'http:',
206
+ host: stripBrackets(url.hostname),
207
+ port: url.port ? Number(url.port) : 80,
208
+ path: `${url.pathname}${url.search}`,
209
+ method,
210
+ headers,
211
+ // No pooled agent: a short-lived command must be able to exit as soon
212
+ // as it has its answer, and no socket outlives its request.
213
+ agent: false,
214
+ lookup: loopbackOnlyLookup,
215
+ }, (res) => {
216
+ const chunks = [];
217
+ let size = 0;
218
+ res.on('data', (chunk) => {
219
+ size += chunk.length;
220
+ if (size > maxBodyBytes) {
221
+ res.destroy();
222
+ finish(() => reject(fetchFailed(new Error(`response larger than ${maxBodyBytes} bytes`))));
223
+ return;
224
+ }
225
+ chunks.push(chunk);
226
+ });
227
+ res.on('error', (err) => finish(() => reject(fetchFailed(err))));
228
+ res.on('aborted', () => finish(() => reject(fetchFailed(new Error('response aborted')))));
229
+ res.on('end', () => {
230
+ finish(() => {
231
+ const status = res.statusCode ?? 0;
232
+ const responseHeaders = new Headers();
233
+ for (const [name, value] of Object.entries(res.headers)) {
234
+ if (value === undefined)
235
+ continue;
236
+ if (Array.isArray(value)) {
237
+ for (const v of value)
238
+ responseHeaders.append(name, v);
239
+ }
240
+ else {
241
+ responseHeaders.set(name, String(value));
242
+ }
243
+ }
244
+ const body = NULL_BODY_STATUSES.has(status) ? null : Buffer.concat(chunks);
245
+ try {
246
+ resolve(new Response(body, {
247
+ status,
248
+ statusText: res.statusMessage ?? '',
249
+ headers: responseHeaders,
250
+ }));
251
+ }
252
+ catch (err) {
253
+ reject(fetchFailed(err));
254
+ }
255
+ });
256
+ });
257
+ });
258
+ req.on('error', (err) => finish(() => reject(fetchFailed(err))));
259
+ if (payload)
260
+ req.write(payload);
261
+ req.end();
262
+ }
263
+ catch (err) {
264
+ finish(() => reject(fetchFailed(err)));
265
+ }
266
+ });
267
+ };
268
+ exports.loopbackFetch = loopbackFetch;
@@ -10,8 +10,8 @@
10
10
  * with a real UUIDv7 downstream.
11
11
  *
12
12
  * The caller may pass `machineUuidFallback` to override the default
13
- * hostname+platform fingerprint with a richer detector (e.g. the
14
- * daemon's `getMachineUuid()` from `registry.ts`).
13
+ * hostname+platform fingerprint with a richer detector (e.g.
14
+ * `getMachineUuid()` from `machineUuid.ts`).
15
15
  */
16
16
  export declare function ensureMachineDaemonId(machineUuidFallback?: () => string): string;
17
17
  /**